cvs-cvs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Cvs-cvs] ccvs/src ChangeLog admin.c annotate.c commit.c ... [newtags2]


From: Derek Robert Price
Subject: [Cvs-cvs] ccvs/src ChangeLog admin.c annotate.c commit.c ... [newtags2]
Date: Tue, 17 Jan 2006 15:41:25 +0000

CVSROOT:        /cvsroot/cvs
Module name:    ccvs
Branch:         newtags2
Changes by:     Derek Robert Price <address@hidden>     06/01/17 15:41:24

Modified files:
        src            : ChangeLog admin.c annotate.c commit.c cvs.h 
                         import.c log.c patch.c rcs.c rcs.h sanity.sh 
                         status.c tag.c update.c vers_ts.c 

Log message:
        Merge from `newtags' branch.  Also:
        
        * cvs.h: New fn (Version_release_relTag)
        * vers_ts: New fn (Version_release_relTag): Prepend revision number
        to relative tags
        * patch.c: (patch_fileproc): Prepend revision number to relative tags
        * tag.c: (check_fileproc, tag_fileproc): dito
        * annotate.c: (annotate_fileproc): dito
        * log.c: (log_expand_revlist): dito, change params: RCSNode->file_info
        * update.c: (update_fileproc): dito
        (join_file): make jrev1, jrev2 fn params so relative tags can be
        resolved per file.
        * rcs.h: New fns: RCS_is_symbolic, RCS_is_relative
        * rcs.c: (is_symbolic): Comment fixed.
        New fns: RCS_is_symbolic, RCS_is_relative
        (RCS_getprevious): Fixed for branches without revisions.
        (RCS_getroot): dito, Gratious reformatting.
        (RCS_getcommitid): Ignore dead revisions if duplicate commitids
        exist (file added on branch, dead rev. on trunk);
        For '@<' compare timestamp to determine predecessor
        * sanity.sh: More tests
        * cvs.texinfo: Improved; Mention 'commitid' and compatibility issues,
        (Patch from Frank Hemer <address@hidden>.)

CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/ChangeLog.diff?only_with_tag=newtags2&tr1=1.3337&tr2=1.3337.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/admin.c.diff?only_with_tag=newtags2&tr1=1.112&tr2=1.112.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/annotate.c.diff?only_with_tag=newtags2&tr1=1.21&tr2=1.21.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/commit.c.diff?only_with_tag=newtags2&tr1=1.258&tr2=1.258.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/cvs.h.diff?only_with_tag=newtags2&tr1=1.346&tr2=1.346.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/import.c.diff?only_with_tag=newtags2&tr1=1.175&tr2=1.175.8.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/log.c.diff?only_with_tag=newtags2&tr1=1.103&tr2=1.103.8.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/patch.c.diff?only_with_tag=newtags2&tr1=1.106&tr2=1.106.8.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/rcs.c.diff?only_with_tag=newtags2&tr1=1.357&tr2=1.357.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/rcs.h.diff?only_with_tag=newtags2&tr1=1.83&tr2=1.83.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/sanity.sh.diff?only_with_tag=newtags2&tr1=1.1108&tr2=1.1108.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/status.c.diff?only_with_tag=newtags2&tr1=1.68&tr2=1.68.8.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/tag.c.diff?only_with_tag=newtags2&tr1=1.142&tr2=1.142.8.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/update.c.diff?only_with_tag=newtags2&tr1=1.260&tr2=1.260.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/vers_ts.c.diff?only_with_tag=newtags2&tr1=1.65&tr2=1.65.8.1&r1=text&r2=text

Patches:
Index: ccvs/src/ChangeLog
diff -u /dev/null ccvs/src/ChangeLog:1.3337.2.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/ChangeLog  Tue Jan 17 15:41:23 2006
@@ -0,0 +1,14382 @@
+2006-01-16  Derek Price  <address@hidden>
+
+       * cvs.h: New fn (Version_release_relTag)
+       * vers_ts: New fn (Version_release_relTag): Prepend revision number
+       to relative tags
+       * patch.c: (patch_fileproc): Prepend revision number to relative tags 
+       * tag.c: (check_fileproc, tag_fileproc): dito
+       * annotate.c: (annotate_fileproc): dito
+       * log.c: (log_expand_revlist): dito, change params: RCSNode->file_info
+       * update.c: (update_fileproc): dito
+       (join_file): make jrev1, jrev2 fn params so relative tags can be
+       resolved per file.
+       * rcs.h: New fns: RCS_is_symbolic, RCS_is_relative
+       * rcs.c: (is_symbolic): Comment fixed.
+       New fns: RCS_is_symbolic, RCS_is_relative
+       (RCS_getprevious): Fixed for branches without revisions.
+       (RCS_getroot): dito, Gratious reformatting.
+       (RCS_getcommitid): Ignore dead revisions if duplicate commitids
+       exist (file added on branch, dead rev. on trunk);
+       For '@<' compare timestamp to determine predecessor
+       * sanity.sh: More tests
+       * cvs.texinfo: Improved; Mention 'commitid' and compatibility issues,
+       (Patch from Frank Hemer <address@hidden>.)
+
+2005-04-19  Derek Price  <address@hidden>
+
+       * admin.c (admin_fileproc): Undo last commit. TAG_TRUNK test removed.
+       * commit.c (check_fileproc): Imporve comments. TAG_TRUNK test removed.
+       (commit_fileproc): TAG_TRUNK test removed.
+       (remove_file): TAG_TRUNK test removed.
+       * update.c (update_fileproc): TAG_TRUNK test removed.
+       * rcs.c: Improve comments.
+       (RCS_gethead): Remove this fn.
+       (RCS_getversion): TAG_TRUNK test removed.
+       (RCS_gettag): Remove magic conversion.
+       (RCS_nodeisbranch): Properly detect magic for numeric rev.
+       Remove magic conversion.
+       (RCS_whatbranch): Gratious reformatting. Simplify.
+       (translate_tag): Always return non-magic branches for symbolic/extended
+       tags. Add param flag to prevent conversion for non-extended tags.
+       Improve assumption verification.
+       (RCS_extract_tag): Adapt error msg to cvs conventions.
+       (RCS_getprevious): Rootdate comparison fixed.
+       (RCS_getroot): Remove magic conversion.
+       * sanity.sh: More tests added.
+       (Patch from Frank Hemer <address@hidden>.)
+
+2005-04-06  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_getprevious): Remove erroneous FIXME.
+
+2005-03-20  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_nodeisbranch): Improve comments.  Return bool.  Gratuitous
+       reformatting.
+       (RCS_getprevious): NULL return from RCS_branch_head means a tag does
+       not exist.  Factor common code from then/else.  Add FIXME note.
+       Gratuitous reformatting.  Simplify.
+       (RCS_getorigin): Simplify.
+       (translate_tag): Gratuitous reformatting.
+       * rcs.h (RCS_nodeisbranch): Update prototype.
+
+2005-03-19  Derek Price  <address@hidden>
+
+       Begin cleanup.  tag-ext test still broken.
+       * admin.c (admin_fileproc): Remove obfuscations.
+       * rcs.c (RCS_tag2rev): Remove obfuscations.  Gratuitous reformatting.
+       (is_symbolic): New function.
+       (RCS_gettag, RCS_nodeisbranch): s/isrevnumonly/!is_symbolic/.
+       (RCS_whatbranch): s/rev/tag/ where appropriate.  Improve comments.
+       Assert assumptions.  Gratuitous reformatting.  Process symbols first.
+       (RCS_branch_head): Remove obfuscations.  Gratuitous reformatting.
+       Assert assumptions.  Process symbols first.
+       (truncate_revnum_in_place): Declare inline.
+       (RCS_head): Improve comments.  Perform error checking.
+       (RCS_getprevious): Remove obfuscations.
+       (translate_tag): Improve comments.  Simplify.  Return branches when
+       input is a branch name.
+       (truncate_revnum): Simplify.
+       * status.c: Gratuitous reformatting.
+       * subr.c (isrevnumonly): Remove this function.
+       * subr.h (isrevnumonly): Remove this proto.
+       * sanity.sh (tag-ext): Cleanup.
+
+2005-03-19  Derek Price  <address@hidden>
+
+       * admin.c, subr.h, update.c, rcs.h, subr.c, import.c, sanity.sh,
+       cvs.h, vers_ts.c, tag.c, commit.c, rcs.c: Tag extensions added
+       (Patch from Frank Hemer <address@hidden>.)
+
+2006-01-16  Derek Price  <address@hidden>
+
+       * sanity.sh (join-16a): Test "no such tag" error with -j.
+
+       * update.c (update): Correct typo.
+
+2006-01-13  Larry Jones  <address@hidden>
+
+       * mkmodules.c (config_contents): Change SystemAuth to yes to match
+       the default value.
+
+2006-01-10  Derek Price  <address@hidden>
+
+       * cvs.h (valloc): Remove unused proto.
+
+2006-01-09  Larry Jones  <address@hidden>
+
+       * commit.c (remove_file): Record correct revision in history file.
+       (Reported by Chris Reed <address@hidden>.)
+
+2006-01-05  Derek Price  <address@hidden>
+
+       * recurse.c (start_recursion): Expand header comment block.
+
+2005-12-31  Mark D. Baushke  <address@hidden>
+
+       [Add CVSNT compatible [-w width] switch to cvs (r)annotate command.]
+       * rcs.c (RCS_deltas): Provide for annotate_width for author field
+       and use Xasprintf in place of xmalloc/sprintf.
+       * rcs.h (annotate_width): New global.
+       * annotate.c (annotate_width): Default to 8.
+       (annotate_usage): New -w width option.
+       (annotate): Ditto.
+       * sanity.sh (userlen, username1): New variables.
+       (basica-10w1, basica-10wmax, ann-10w1, ann-10wmax): New tests for
+       the annotate -w width option.
+       
+2005-12-20  Derek Price  <address@hidden>
+
+       * sanity.sh (config3): Accept `.' in paths.
+
+       * admin.c (admin): Don't pass the global optind to getopt_long as
+       longindex - it causes infinite loops on some systems.
+
+2005-12-07  Derek Price  <address@hidden>
+
+       * client.c (handle_redirect): Give stack control of string key.
+
+       * client.c (start_server), root.c (method_names), root.h (CVSmethod):
+       Handle :extssh: as a kindness to Eclipse users.
+       (Suggestion from Joseph P. Skudlarek <address@hidden>.)
+
+2005-12-07  Mark D. Baushke  <address@hidden>
+
+       * buffer.c (fd_buffer_shutdown): Use error (0, ...) instead of
+       error (1, ...) to avoid infinite loops. (patch #4678)
+       Patch adapted from "Allan L. Bazinet" <address@hidden>
+
+2005-11-30  Mark D. Baushke  <address@hidden>
+
+       [bug #14900] Add 'cvs server' handling for --allow-root
+       * root.c (root_allow_used): New function.
+       * root.h (root_allow_used): Ditto.
+       * server.c (serve_root): Call new function in server mode.
+       * sanity.sh (server2-3): Fix typo in test name.
+       (server2-5, server2-6): New tests for --allow-root feature.
+       (proxy-init): Fix --allow-root instances for primary-wrapper
+       to include the primary and the secondary in the list.
+       (writeproxy-noredirect): Ditto.
+       (writeproxy-ssh-noredirect): Ditto.
+
+2005-11-22  Derek Price  <address@hidden>
+
+       * checkout.c (checkout_proc): Bury sacrificial chicken.
+       * sanity.sh: Update to compensate.
+
+2005-11-18  Derek Price  <address@hidden>
+
+       * update.c (checkout_file): Add FIXME.
+
+2005-11-17  Derek Price  <address@hidden>
+
+       * client.c (update_entries), update.c (update): Restore refetch
+       notices and add two new ones.
+       * sanity.sh: Update to compensate.
+
+2005-11-15  Derek Price  <address@hidden>
+
+       * client.c (update_entries): Only print "U ..." when patches are
+       applied successfully.  Only print patch failed messages for traces.
+       * update.c (update): Share flags with refetch pass.  Never send
+       contents when refetching.  Only print refetching message for traces.
+       Use "U" instead of "P", except for traces.
+       * sanity.sh: Update to compensate.
+
+2005-11-14  Mark D. Baushke  <address@hidden>
+
+       * admin.c (opt_values): New enum for long option values.
+       (short_options): Now in file scope for validation of
+       UserAdminOptions.
+       (long_options): Use opt_value enums for option.value entries.
+       (make_UserAdminOptions): Rewrite.
+       (admin): Use opt_value enumes for long_option cases.
+       Quote the name of the restricted admin group.
+       * mkmodules.c (UserAdminOptions): Add defaults for execute and
+       no-execute.
+
+2005-11-13  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (modes-execute-*): Use $CVSROOT_DIRNAME rather than
+       default text for expected data.
+
+       * admin.c (admin_usage): Fix spelling in help string.
+       Alphabetize long options.
+       (Patch from Alexandros Manoussakis.)
+
+2005-11-12  Mark D. Baushke  <address@hidden>
+
+       * admin.c: Add long option processing.
+       (make_UserAdminOptions): New option for administrative permission
+       of long options.
+       (admin): Use long option processing. Add support for --execute and
+       --no-execute options.   
+       * parseinfo.c (parse_config): Use new make_UserAdminOptions
+       function to process the UserAdminOptions= configuration line.
+       * sanity.sh (modes): Added tests for admin --execute/--no-execute
+       options.
+       (Based on CVS patch #4446 from Alex Manoussakis.)
+
+2005-11-10  Larry Jones  <address@hidden>
+
+       * commit.c (commit): Complain about obsolete -n option if not in
+       server mode.
+
+2005-11-10  Derek Price  <address@hidden>
+
+       * edit.c (mark_up_to_date): Accept update_dir for error messages.
+       Print correct, quoted file name in error message.
+       * edit.h (mark_up_to_date): Update proto to match.
+       * checkin.c, client.c: Update all callers.
+
+2005-11-09  Derek Price  <address@hidden>
+
+       * edit.h: Remove unneeded `extern' from function decls.
+
+       * sanity.sh (pserver-4.2): Accept a "no such sytem user" message when
+       a root attempt is made.
+
+2005-11-07  Derek Price  <address@hidden>
+
+       * edit.c (watch_onoff): Remove unneeded proto.
+
+2005-11-03  Derek Price  <address@hidden>
+
+       * sanity.sh (compression): Elucidate.
+
+2005-10-28  Derek Price  <address@hidden>
+
+       * commit.c (check_fileproc): Don't print conflict marker warning in
+       really quiet mode.
+
+       * sanity.sh (rcslib): Rename some misnamed tests.
+
+       [bug #14840]
+       * sanity.sh (compression): New test for problem fixed below.
+
+       [bug #14840]
+       * zlib.c (compress_bufer_input): Don't assume the number of bytes the
+       caller requested will be available from the stream underlying the
+       compression buffer - the data is compressed and should be shorter by
+       definition.  Improve comment.
+       (Original report from Rahul Bhargava <address@hidden>.)
+
+2005-10-18  Derek Price  <address@hidden>
+
+       Include "wait.h" only as needed.
+       * cvs.h: Remove #include of "wait.h".
+       * run.c, server.c: Add #include "wait.h".
+
+2005-10-16  Brian Murphy  <address@hidden>
+
+       * server.c (check_pam_password): set PAM_RHOST to remote host ip.
+
+2005-10-11  Derek Price  <address@hidden>
+
+       * client.h (SEND_BUILD_DIRS, SEND_FORCE, SEND_NO_CONTENTS,
+       BACKUP_MODIFIED_FILES): Tidy.
+
+2005-10-04  Derek Price  <address@hidden>
+
+       * sanity.sh (diff_u_test, diff_recursive_test): New functions.
+       (directory_cmp): Use GNU diff -ur when possible.
+       (find_tool): Catch stderr output from tests.  Count MARGINAL test
+       results and prefer tools with more PASSes.
+       (*): Replace use of cmp to $diff_u where possbile.
+
+2005-10-04  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (watch6-0): Avoid use of GNU grep -qE extensions for
+       anchored search.
+
+2005-10-04  Derek Price  <address@hidden>
+
+       * sanity.sh (find_tool): Accept tool name for error messages.  Change
+       all callers.
+
+       * run.c: Assume unistd.h.
+
+2005-10-03  Derek Price  <address@hidden>
+
+       * sanity.sh (sshstdio-6): Use diff -u instead of cmp so that errors
+       show up in the automated nightly testing emails.
+
+       * run.c (piped_child): Close original dup'd descriptors.  Add comments.
+
+       * server.c: #include "setenv.h" to eliminate a Solaris warning.
+
+2005-09-30  Larry Jones  <address@hidden>
+
+       * expand_path.c (expand_path): Fix memory leaks.
+
+2005-09-29  Mark D. Baushke  <address@hidden>
+
+       * main.c (main): Simplify commitid creation.
+
+       * main.c (main): Not all hosts support O_NOCTTY for open.
+       Try to use more random data when time() returns -1.
+       
+2005-09-29  Derek Price  <address@hidden>
+
+       * main.c (main): Check for error return from time().
+
+2005-09-29  Paul Eggert  <address@hidden>
+           Derek Price  <address@hidden>
+
+       * client.c (handle_m, handle_e): Remove incomplete workaround for
+       O_NONBLOCK problem; no longer needed because of the fix below.
+       * cvs.h (piped_child): New bool argument saying whether O_NONBLOCK
+       fix is needed.  All uses changed.
+       * rsh-client.c (start_rsh_server): We need the O_NONBLOCK fix,
+       so pass 'true' to piped_child to enable the workaround.
+       * run.c (work_around_openssh_glitch): New function.
+       (piped_child): Use it if the fix is requested.  Avoid call call to
+       vfork with undefined behavior.
+
+2005-09-29  Mark D. Baushke  <address@hidden>
+
+       * main.c (main): Rename N as COMMITID_RAW_SIZE.
+       Use base62 encoding for non-urandom case.
+
+2005-09-29  Derek Price  <address@hidden>
+
+       * buffer.c (fd_buffer_input), client.c (handle_m, handle_e): Use new
+       fd_select in place of select.
+
+       * subr.c: Remove select includes - the sleep functions are now in
+       lib.
+
+2005-09-28  Mark D. Baushke  <address@hidden>
+
+       * main.c (N, RANDOM_BYTES): New constants for base62 conversion.
+       (divide_by): Used in base62 conversion.
+       (convert): Ditto.
+       (main): Set global_session_id using more random data and the
+       current time in base62 if possible, otherwise fallback to the old
+       method.
+
+2005-09-25  Conrad T. Pino  <address@hidden>
+
+       * rcs.c: Use "#ifdef HAVE_FSYNC" just like every where else.
+
+2005-09-25  Conrad T. Pino  <address@hidden>
+
+       * buffer.c client.h socket-client.h:  Remove HAVE_WINSOCK_H macro &
+       change <winsock.h> to <sys/socket.h> include.
+
+       * server.c:  Remove HAVE_WINSOCK_H and <winsock.h> include.
+
+2005-09-25  Derek Price  <address@hidden>
+
+       * rcs.c (rcs_internal_unlockfile): Fsync files before renaming them.
+       Patch from Rahul Bhargava <address@hidden>.
+
+       * cvs.h, filesubr.c, main.c, server.c:
+       s/push_cvs_tmp_dir/push_cvs_temp_dir/.
+
+       * main.c (get_cvs_temp_dir): Use new get_system_temp_dir function.
+       (push_env_temp_dir): Move to...
+       * filesubr.c (push_env_temp_dir): ...here to split from Windows.
+       (get_system_temp_dir): New function.
+       * cvs.h (get_cvs_temp_dir, push_env_temp_dir): Add protos.
+
+       * buffer.c (fd_buffer_input): Fix spelling in header comment block.
+
+2005-09-24  Derek Price  <address@hidden>
+
+       * socket-client.c: Check HAVE_CONFIG_H, not HAVE_CONFIG.
+
+       * socket-client.c, socket-client.h: Minor cleanup.
+
+       * Makefile.am (cvs_SOURCES): Move socket-client.[ch] to...
+       (EXTRA_cvs_SOURCES): ...here to avoid compiling them.
+       * socket-client.c (init_sockaddr), socket-client.h (init_sockaddr):
+       Move to...
+       * client.c (init_sockaddr), client.c (init_sockaddr): ...here.
+
+       * socket-client.c (socket_buffer_input): Avoid overflow problems.
+
+       * socket-client.c: General cleanup.
+
+2005-09-23  Larry Jones  <address@hidden>
+
+       * checkout.c (export_usage): Note that -r requires a tag.
+
+2005-09-22  Derek Price  <address@hidden>
+
+       * classify.c (Classify_File): Correct comment.
+
+2005-09-22  Larry Jones  <address@hidden>
+
+       * patch.c (patch_usage): Document -k option.
+
+2005-09-22  Derek Price  <address@hidden>
+
+       * classify.c (Classify_File): If a file had a conflict and the
+       timestamp hasn't changed, it still has a conflict.  Add comment about
+       how T_MODIFIED could later turn out to have conflict markers and why
+       it should not be checked in this function.
+       * client.c (send_fileproc): Don't send contents for files known to have
+       conflicts unless this is for `cvs diff'.
+       * commit.c (check_fileproc): T_CONFLICT should be handled like
+       T_MODIFIED, since force could be requested.  Simplify logic since
+       T_CONFLICT can now be trusted.
+       * rcs.c (RCS_Checkout): Comment noexec behavior in header block.
+       * server.c (serve_unchanged, serve_is_modified): Handle conflicts.
+       * status.c (status_fileproc): Trust T_CONFLICT to simplify.
+       * subr.c (file_has_conflict): Removed.
+       * subr.h (file_has_conflict): Remove proto.
+       * update.c (update_fileproc): Trust T_CONFLICT.
+       (RegisterMerge): New function factored from...
+       (merge_file, join_file): ...these two functions.
+       * vers_ts.c (time_stamp_server): Handle = conflict timestamps in server
+       entdata.
+       * sanity.sh (files-12): Account for slight behavior improvement.
+       (status, conflicts, mwrap): Account for corrected behavior.
+       (join-readonly-conflict-10): Correct comment.
+       (binfiles-con1b): New test for correct behavior.
+
+       * classify.c (Classify_File): Consolidate redundant conditionals.
+
+2005-09-21  Derek Price  <address@hidden>
+
+       * entries.c: Remove obsolete comment.
+
+2005-09-20  Derek Price  <address@hidden>
+
+       * myndbm.c: #include getdelim.h for Windows.
+
+       * main.c: #include setenv.h for Windows.
+
+2005-09-19  Derek Price  <address@hidden>
+
+       * sanity.sh (modules5-8): Rename...
+       (modules5-8r): ...to this and comment Mac OS X failure.
+       Comment Solaris 9 failure below with a `FIXME?' tag.
+
+       * sanity.sh: Remove previous hack in favor of setting TESTDIR on
+       Solaris (and Mac OS X) until problem is solved correctly.
+
+2005-09-15  Derek Price  <address@hidden>
+
+       * sanity.sh: Use /bin/pwd to verify current dir since Solaris 9 is
+       sometimes resolving symlinked paths.
+
+2005-09-14  Derek Price  <address@hidden>
+
+       * edit.c (edit_usage, unedit_usage, editors_usage), watch.c
+       (watch_usage, watchers_usage): Add quotes and reword for clarity and
+       consistency.
+
+       * edit.c (edit_usage): Add missing syntax.  Reword description for
+       clarity.  Mention default behavior.
+
+2005-09-13  Derek Price  <address@hidden>
+
+       * sanity.sh ($anyusername): Reduce charset to that allowed on Linux.
+
+       * sanity.sh ($anyusername): Make more robust and expand comments.
+       Reported by Mark D. Baushke <address@hidden>.
+
+       * sanity.sh: Split $username into $username & $username8.  Rename
+       $author as $anyusername.
+
+2005-09-12  Derek Price  <address@hidden>
+
+       * sanity.sh (username): Cut $username down to 8 characters when longer,
+       since that is all that appears in output.
+
+2005-09-11  Derek Price  <address@hidden>
+
+       [bug #14504]
+       * main.c (main): Set server_hostname before it is used by
+       gserver_authenticate_connection.
+       (Report from Serguei E. Leontiev <address@hidden>.)
+
+2005-09-08  Larry Jones  <address@hidden>
+
+       * server.c (parseServerOptions): getopt() returns int, not char.
+
+2005-09-07  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_parse): Minor reformatting.
+
+       Close <http://savannah.nongnu.org/bugs/?func=detailitem&item_id=14462>.
+       * rcs.c (RCS_parse): Free variable *after* using it for the last time.
+
+2005-09-06  Derek Price  <address@hidden>
+
+       * sanity.sh (tempfile): All CVS temp files start with "cvs".
+
+       * sanity.sh (tempfile): New var.
+       (tempname): Set to $TMPDIR/$tempfile after $TMPDIR is set.
+       (info): s#$TMPDIR/##.
+
+       * rcs.c (RCS_putdtree): Remove unused variable.
+
+2005-09-06  Mark D. Baushke  <address@hidden>
+
+       Close <https://savannah.nongnu.org/bugs/?func=detailitem&item_id=14435>.
+       * rcs.c (RCS_putdtree): Avoid stack overflow which may be
+       possible with excessive recursive calls to RCS_putdtree().
+       (Patch from Serg Masyutin.)
+
+2005-09-06  Derek Price  <address@hidden>
+
+       Close <http://savannah.nongnu.org/bugs/?func=detailitem&item_id=14448>.
+       * kerberos-client.c (start_kerberos4_server): Pass new root arg to
+       make_bufs_from_fds.
+       (Patch from <Manuel.Guijarro.AT.cern.ch>.)
+
+       * server.c: Reorganize includes slightly and alphabetize sections.
+
+       * server.c: #include canon-host.h.
+       (gserver_authenticate_connection): Print out canon-host errors.
+       * subr.c: #include canon-host.h.
+       (isThisHost): Print out canon-host errors.
+
+       * parseinfo.c (parse_config): Don't check hostname without client
+       or server support.
+
+       * main.c (main): Set cvs_cmd_name before calling parseServerOptions.
+
+2005-09-05  Derek Price  <address@hidden>
+
+       * cvs.h (Tmpdir): Remove global decl.
+       (get_cvs_tmp_dir, push_env_tmp_dir): New protos.
+       * filesubr.c (cvs_temp_file): Use get_cvs_tmp_dir.
+       * main.c (Tmpdir): Remove global.
+       (tmpdir_cmdline, tmpdir_env): New globals.
+       (get_cvs_tmp_dir, push_env_tmp_dir): New functions.
+       (main): Discard most tmpdir overhead in favor of new globals and
+       functions.
+       * parseinfo.c (parse_config): Parse TmpDir.
+       * sanity.sh (config4): New tests.
+       * server.c (serve_root): Set env TMPDIR after parsing config.  Create
+       tmpdir here instead of...
+       (serve_co, server): ...either of thse locations.
+       (server_cleanup): Use new function in favor of obsolete Tmpdir.
+
+       * sanity.sh (config2): Don't overwrite potentially significant config.
+
+       * sanity.sh (info): Use $tempfile instead of reproducing the regex.
+
+       * sanity.sh (info): Accept `.' in temp file names.
+
+       * main (main): Set the actual PID, not a pointer to a string.
+
+2005-09-04  Derek Price  <address@hidden>
+
+       * sanity.sh (config3): Generalize.
+
+       * main.c (main), server.c (serve_root, switch_to_user): Replace putenv
+       with GNULIB setenv.
+       * sanity.config.sh.in (HAVE_PUTENV): Remove.
+       * sanity.sh (env): Remove reference to $HAVE_PUTENV.
+
+       * sanity.sh (tests): Add config2 & config3.
+
+       * server.c (isSamePath, isThisHost): Move to...
+       * subr.c (isSamePath, isThisHost): ...here.
+       * subr.h (isSamePath, isThisHost): Add protos.
+       * parseinfo.c (parse_config): Handle [rootspec] syntax.
+       * main.c (main): Always set server_hostname.
+       * sanity.sh (config3): New tests for same.
+
+       * server.c (gserver_authenticate_connection): Output hostname in
+       error message.
+
+       * server.c (isThisHost): Fix typo.
+
+       * server.c (isThisHost, gserver_authenticate_connection): Simplify
+       using canon_host().
+
+       * root.c (free_cvsroot_t): Update header comment.
+
+       * root.c (new_cvsroot_t): directory is not client-specific.
+       (free_cvsroot_t): Declare static.
+       * root.h (free_cvsroot_t): Remove proto.
+       * server.c (server_init): Don't free cvsroot_t when finished with it.
+
+2005-09-03  Derek Price  <address@hidden>
+
+       * root.h (cvsroot_t->isremote): Update comment.
+
+       * history.c (read_hrecs_file): Suppress signed/unsigned char warning.
+
+       * root.h (cvsroot_t->isremote): Declare bool.
+       * root.c (new_cvsroot_t): Initialize isremote to false instead of 0.
+
+       * add.c (add_usage): Standardize usage message somewhat.
+
+2005-09-02  Larry Jones  <address@hidden>
+
+       * commit.c (checkaddfile): Improve error messages for lock_RCS failure.
+       * release.c (release): Improve error message for pclose failure.
+
+       * root.h (struct cvsroot_s): Always declare isremote to simplify
+       other code.  Simplify referencing code.
+       * root.c (new_cvsroot_t): Always initialize isremote.
+       * server.h: Always declare server_active to simplify other code.
+       Simplify referencing code.
+       * server.c: Always define server_active.
+
+2005-09-02  Larry Jones  <address@hidden>
+
+       * parseinfo.c (parse_config): Variable declarations must precede
+       executable code for pre-C99 compilers.  Pass correct line number
+       variable to expand_path.
+
+2005-09-01  Derek Price  <address@hidden>
+
+       * recurse.c: Update bug report email address.
+
+2005-08-31  Derek Price  <address@hidden>
+
+       * cvs.h (expand_path): Update proto.
+       * expand_path.c (expand_variable): Accept and use cvsroot arg inplace
+       of global.
+       (expand_path): Accept and pass through cvsroot arg.
+       * main.c (main): Prescan args for config path before config options are
+       used.  Pass config path on as needed.  Update comment. 
+       * modules.c (do_module): Update expand_path call.
+       * parseinfo.ci (Parse_Info): Ditto.
+       (allowed_config_prefixes): New global.
+       (parse_config): Accept configPath arg, update expand_path calls, and
+       expand LockDir path.
+       * parseinfo.h (parse_config): Update proto.
+       * root.c (root_allow_add, get_root_allow_config): Accept new configPath
+       arg and pass through to parse_config.
+       * root.h (root_allow_add, get_root_allow_config): Update protos.
+       * server.c (gConfigPath, server_usage): New globals.
+       (parseServerOptions): New function.
+       (server): Use new usage var.
+       (pserver_authenticate_connection): Update get_root_allow_config call.
+       * server.h (parseServerOptions): New proto.
+       * wrapper.c (wrap_add): Update expand_path calls.
+       * sanity.sh (server): New tests for setting config file path.
+
+2005-08-31  Derek Price  <address@hidden>
+
+       * sanity.sh (close-stdout): Remove archive dir when done.
+
+2005-08-31  Larry Jones  <address@hidden>
+
+       * import.c (import_descend): Lock repository directory during import.
+
+2005-08-31  Derek Price  <address@hidden>
+
+       * server.c (isSamePath): Compare args rather than assuming values.
+
+2005-08-29  Derek Price  <address@hidden>
+
+       Add %{sV} format string to verifymsg script.
+       * logmsg.c (do_verify): Accept change list arg, pass through to...
+       (verifymsg_proc): ...here, to pass through to format_cmdline.
+       * commit.c (commit_fileproc, commit_direntproc), import.c (update):
+       Update all callers of do_verify.
+       * cvs.h (do_verify): Update proto.
+       * sanity.sh (info): Test new verifymsg format strings.
+
+2005-08-09  Derek Price  <address@hidden>
+
+       * sanity.sh: Remove debugging echo.
+       (watch6, watch6-0): Clean up properly.
+
+2005-08-03  Jim Hyslop <address@hidden>
+
+       * edit.c, watch.c, watch.h, sanity.sh: fixed problems with watch not
+       setting default attributes, when directory specified.
+
+2005-07-20  Derek Price  <address@hidden>
+
+       * main.c: s/cvshome.org/nongnu.org.etc.../.
+
+2005-07-12  Derek Price  <address@hidden>
+
+       * buffer.c, buffer.h, client.h, expand_path.c, history.c, myndbm.h,
+       release.c: Add copyright notices.
+
+2005-07-12  Derek Price  <address@hidden>
+
+       * client.c: Update fwrite usage to use size & count in the standard
+       order.
+
+2005-07-11  Derek Price  <address@hidden>
+
+       * buffer.c, buffer.h, client.h, expand_path.c, history.c, myndbm.h,
+       release.c: Update license notices.
+
+2005-06-28  Derek Price  <address@hidden>
+
+       * server.c (serve_co): Remove obsolete support for Repository request.
+
+2005-06-10  Derek Price  <address@hidden>
+
+       * filesubr.c, ignore.c, import.c, vers_ts.c: Include "lstat.h".
+
+2005-06-10  Derek Price  <address@hidden>
+
+       * logmsg.c (logmsg_list_to_args_proc): Add format character for
+       destination tag.
+       (Original patch from Todd Vierling <address@hidden>).
+
+       * tag.c (pretag_list_to_args_proc): Likewise.
+       (check_fileproc): Set destination tag name.
+       (tag_delproc): Delete destination tag name.
+       * sanity.sh (info, taginfo): Test new format strings.
+
+2005-06-08  Derek Price  <address@hidden>
+
+       * parseinfo.c: Restore comparison to NULL in assignment within
+       conditional to placate non-GNU compilers.  Eliminate assignments in
+       conditionals where possible by GNU coding standards.  Eliminate other
+       comparisons to NULL where possible.
+       (Parse_Info): Make int a true bool.
+
+2005-06-03  Derek Price  <address@hidden>
+
+       * client.c (force_gzip): New static global.
+       (handle_force_gzip): New function.
+       (responses): Add `Force-gzip'.
+       (start_server): Turn on encryption and compression before potentially
+       sending other rooted requests.  Turn on compression when requested by
+       the user or the server.
+       * main.c (opt_usage):  Note that -z<level> *requests* compression
+       <level> from the server.
+       * parseinfo.c (new_config):  Initialize MaxCompressionLevel.
+       (parse_config): Parse MinCompressionLevel & MaxCompressionLevel.
+       * parseinfo.h (struct config): Add MinCompressionLevel &
+       MaxCompressionLevel.
+       * server.c (pending_warning_text):  New static global.
+       (print_pending_error): Print pending warnings too.
+       (warning_pending): New macro.
+       (alloc_pending_internal): New function with much content...
+       (alloc_pending): ...previously from here.
+       (alloc_pending_warning): New function.
+       (server_root, serve_gzip_contents, gzip_stream): Force gzip_level into
+       configured restrictions.
+       (serve_command_prep): Print pending errors.
+       (requests): Make `Gzip-stream', `gzip-file-contents',
+       `Kerberos-encrypt', `Gssapi-encrypt', & `Gssapi-authenticate' requests
+       rootless to allow them before compression starts.
+       (serve_valid_requests): Send `Force-gzip' response when needed.
+       (server): Abort if a rootless compression request forced compression
+       outside restricted levels.
+       * zlib.c (struct compress_buffer, compress_buffer_initialize): Store
+       compression level.
+       (compress_buffer_output): Reset compression level when global
+       gzip_level has changed.
+       * sanity.sh (config2): New tests for compression restrictions.
+
+2005-06-03  Derek Price  <address@hidden>
+
+       * zlib.c (compress_buffer_input): Update comment.
+
+2005-06-03  Derek Price  <address@hidden>
+
+       * error.c (error): Correct spelling and grammar in comment.
+
+2005-06-03  Derek Price  <address@hidden>
+
+       * modules.c (my_module), wrappers.c (wrap_add): Use new expand_path
+       API.
+
+2005-06-03  Derek Price  <address@hidden>
+
+       * cvs.h (expand_path): Rearrange args and use bool for formatsafe flag.
+       * expand_path.c: Globally: Remove init of globals to NULL by C89,
+       reformat to CVS conventions, remove unnecessary comparisons to NULL and
+       0, & remove unnecessary typecasts.
+       (expand_variable): Remove proto and move function above first use.
+       Make return value const.
+       (expand_path): Don't refer to var when contents are known.  Rearrange
+       args per cvs.h changes.  Improve header comment block.
+       * parseinfo.c (Parse_Info): Use new expand_path API.
+
+2005-06-02  Derek Price  <address@hidden>
+
+       * client.c: Don't set NULL for globals by C89.  Globally remove
+       comparisons to NULL or replace with !.  Similarly remove or replace
+       comparisons of strcmp and strncmp return value to 0.  Remove some
+       unneeded braces around single-element blocks.
+       (handle_*): Remove unecessary protos.
+       (handle_notified, notified_a_file): Move up before first use.
+
+2005-06-02  Derek Price  <address@hidden>
+
+       * sanity.sh (config): Simplify cleanup.
+
+2005-06-02  Derek Price  <address@hidden>
+
+       * zlib.c (compress_buffer_input): Don't request more bytes from the
+       underlying buffer than asked for.
+       (compress_buffer_shutdown_input): Don't attempt to read EOF from the
+       client during shutdown.  It might never be sent.
+       * sanity.sh (abspath2): Test for this.
+
+2005-05-31  Derek Price  <address@hidden>
+
+       * rcscmds.c (call_diff_argc_allocated): Rename to...
+       (call_diff_arg_allocated): ...to match similar usage in other files.
+
+2005-05-31  Derek Price  <address@hidden>
+       for Alexander Taler <address@hidden>
+
+       * rcscmds.c: Change type of call_diff_argc_allocated from int to
+       size_t, to match the prototype of run_add_arg_p().  This fixes a
+       bus error in OpenBSD 3.6 sparc64.
+
+2005-05-27  Derek Price  <address@hidden>
+
+       * client.c (send_arg): Make arg const.  Remove unnecessary copy to
+       buffer.
+       (send_option_string): Rename to...
+       (send_options): ...this and accept argc/argv in place of string.
+       * client.h: Update protos to match the changes to client.c.
+       * cvs.h (RCS_exec_rcsdiff, diff_exec): Update protos.
+       (run_add_arg_p, run_arg_free_p): New protos.
+       * diff.c (opts, opts_allocated): Replace with...
+       (diff_argv, diff_argc, diff_arg_allocated): ...these.
+       (add_diff_args): New convenience function.
+       (diff): Use new constructs and APIs.
+       * patch.c (patch_fileproc, RCS_checkin, RCS_delete_revs), rcscmds.c
+       (call_diff_add_arg, call_diff_setup, RCS_merge, RCS_exec_rcsdiff,
+       diff_exec, RCS_output_diff_options), update.c (patch_file): Use new
+       APIs.
+       * run.c (run_add_arg_p, run_arg_free_p): New functions.
+       (run_argc_allocated): Make size_t.
+       (run_setup, run_add_arg): Use new functions.
+       * sanity.sh: Accomodate above changes.
+       (rcslib-diffrgx-3): Slip in test for space splitting.
+
+2005-05-26  Derek Price  <address@hidden>
+
+       * subr.c (isabsolute), subr.h (isabsolute): Remove this function.
+       * root.c: Likewise, plus some reformatting.
+       * checkout.c, client.c, find_names.c, import.c, modules.c, parseinfo.c,
+       repos.c, root.c, server.c, subr.c: s/isabsolute/ISABSOLUTE/.
+
+2005-05-26  Derek Price  <address@hidden>
+
+       * cvs.h: Move "system.h" include before GNULIB includes.  Move some
+       GNULIB includes from "system.h".
+
+2005-05-26  Conrad T. Pino <address@hidden>
+
+       * buffer.c, buffer.h: Add & use typedefs for function pointer arguments
+       and struct buffer function pointers.  New typedefs are useful in casts.
+       * socket-client.c: Function pointers passed to buf_initialize use size_t
+       not int arguments.  buf_initialize warnings are gone from Windows build.
+
+2005-05-24  Derek Price  <address@hidden>
+
+       * client.c, entries.c, filesubr.c, hardlink.c, ignore.c, import.c,
+       lock.c, logmsg.c, mkmodules.c, rcs.c, rcscmds.c, server.c, subr.c,
+       update.c, vers_ts.c: s/CVS_STAT/stat/ & s/CVS_LSTAT/lstat/.
+
+2005-05-23  Derek Price  <address@hidden>
+
+       * filesubr.c (xresolvepath): Move to...
+       * subr.c (xcanonicalize_file_name): ...here and rename.  Use new
+       GNULIB canonicalize module.
+       * cvs.h (xresolvepath): Move proto...
+       * subr.h (xcanonicalize_file_name): ...here.
+       * checkout.c (safe_location), server.c (isSamePath): Use new function
+       name.
+
+2005-05-23  Derek Price  <address@hidden>
+
+       * sanity.sh (rcslib-symlink-10): Accept empty result due to broken glob
+       in glibc 2.3.5.
+
+2005-05-18  Derek Price  <address@hidden>
+
+       * sanity.sh (config-9): Split to local/remote tests to avoid unportable
+       expr characters.
+
+2005-05-17  Derek Price  <address@hidden>
+
+       * sanity.sh: Use a predictable umask.
+
+2005-05-13  Derek Price  <address@hidden>
+
+       * login.c (password_entry_parseline): Placate gcc -Wall.
+
+2005-05-11  Derek Price  <address@hidden>
+
+       * cvs.h (find_files): New proto.
+       * find_names.c (find_files, strip_rcsext): New functions.
+       (find_rcs): Make arg const.  Use new find_files.  Improve header
+       comment block.
+       * history.c (histfile): Remove global.
+       (get_history_log_name): New function.
+       (history, history_write): Use new functions.
+       (read_hrecs_file): New function containing most content from...
+       (read_hrecs): ...this function, which now accepts a file list and calls
+       read_hrecs_file once for each file.
+       * parseinfo.c (parse_config): Parse HistoryLogPath & HistorySearchPath.
+       * parseinfo.h (struct config): Add HistoryLogPath & HistorySearchPath.
+       * sanity.sh (basic2-64): Remove obsolete comment.
+       (config): Test new history options.
+       (crerepos): Cleanup.
+
+2005-05-09  Derek Price  <address@hidden>
+
+       * error.c (error): Avoid unportable calls to vsyslog.
+
+2005-05-09  Derek Price  <address@hidden>
+
+       * history.c (history_write): Add FIXME.
+
+2005-05-09  Derek Price  <address@hidden>
+
+       * hash.c (removenode, mergelists): New function.
+       (delnode): Use removenode.
+       * hash.h (mergelists): New proto.
+
+2005-05-04  Derek Price  <address@hidden>
+
+       * error.c (error): Avoid recursion and syslog the problem.
+
+2005-05-03  Derek Price  <address@hidden>
+
+       * tag.c (is_in_val_tags): Remove unnecessary existance checking for the
+       val-tags file and just rely on open() to create it when necessary.
+
+2005-05-03  Derek Price  <address@hidden>
+
+       * tag.c (tag_check_valid): Don't verify the_val_args.found when it is
+       not initialized.
+
+2005-05-03  Derek Price  <address@hidden>
+
+       * add.c: Update comment to include the -k option.  This resolves issue
+       #226 on cvshome.org.
+
+2005-05-02  Derek Price  <address@hidden>
+
+       Remove unnecessary level of indirection.
+       * lock.c (L_HISTORY_LOCK, L_VAL_TAGS_LOCK): Remove macros.
+       (internal_lock, internal_clear_lock): Accept lock as argument.
+       (history_lock, clear_history_lock, val_tags_lock, clear_val_tags_lock):
+       Replace old macro arg with an actual lock pointer.
+
+2005-05-02  Derek Price  <address@hidden>
+
+       * lock.c (internal_lock, internal_clear_lock): Add protos.
+       (history_lock, val_tags_lock): Return the chartered true/false status.
+
+2005-05-02  Derek Price  <address@hidden>
+
+       * cvs.h (CVSHISTLCK): Rename macro to...
+       (CVSHISTORYLCK): ...this.
+       (CVSVALTAGSLCK): New macro.
+       (val_tags_lock, clear_val_tags_lock): New functions.
+       * lock.c (global_val_tags_lock): New global.
+       (Lock_Cleanup): Clean up after val-tags lock if necessary.
+       (L_HISTORY_LOCK, L_VAL_TAGS_LOCK): New local macros.
+       (internal_lock, val_tags_lock, clear_val_tags_lock): New functions.
+       (history_lock): Use new internal function.
+       * tag.c (is_in_val_tags, add_to_val_tags): New functions using the
+       write lock for val-tags and factored from...
+       (tag_check_valid): ...this function.
+       * sanity.sh (lockfiles-22): Add val-tags lock test.
+
+2005-04-30  Mark D. Baushke  <address@hidden>
+
+       * lock.c (global_readlock, global_writelock, global_history_lock):
+       Add missing alternatives for non-LOCK_COMPATIBILITY initialization.
+
+2005-04-28  Derek Price  <address@hidden>
+
+       * cvs.h (history_lock, clear_history_lock): New protos.
+       * lock.c (struct lock): Add lockdirname.
+       (global_history_lock): New global.
+       (global_read_lock): Initialize.
+       (lock_name): Handle const args.
+       (lock_simple_remove): Factor out code in favor of clear_lock call.
+       (set_lock): Handle variable lockdirname.
+       (lock_filesdoneproc): Set new lockdirname.
+       (history_lock, clear_history_lock): New functions.
+       (clear_lock): Avoid segfault on missing lock.
+       (Lock_Cleanup): Clean up history locks when necessary.
+       * history.c (history_write): Use new lock.
+       * sanity.sh (lockfiles-20): Test new lock.
+
+2005-04-28  Derek Price  <address@hidden>
+
+       * sanity.sh (lockfiles): Port some locking tests over from 1.12.x.
+
+2005-04-28  Derek Price  <address@hidden>
+
+       * lock.c (clear_lock): Improve comment.
+
+2005-04-28  Derek Price  <address@hidden>
+
+       * lock.c (struct lock): Store lockdir name.
+       (masterlock): Remove global.
+       (remove_lock_files, clear_lock, set_lock): Update to compensate.
+
+2005-04-25  Mark D. Baushke  <address@hidden>
+
+       * server.c: Add support for <pam/pam_appl.h> to allow
+       --enable-pam to work on MacOSX 10.2 and newer.
+       (Pach from Moriyoshi Koizumi <address@hidden>.)
+
+2005-04-25  Derek Price  <address@hidden>
+
+       * mkmodules.c (mkmodules): Remove `#if 0' and reformat comment.
+
+2005-04-22  Mark D. Baushke  <address@hidden>
+
+       * expand_path.c (expand_variable): Add SESSIONID and COMMITID
+       internal variables. Both return the unique global session id of
+       the CVS process. Passing this information to administrative
+       triggers seems reasonable. (The same feature exists in CVSNT and
+       the names were chosen to be the same as the CVSNT names.)
+
+       * sanity.sh (info): Add a test for $COMMITID and $SESSIONID.
+
+2005-04-20  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (rcs4): Fix a typo.
+
+2005-04-20  Derek Price  <address@hidden>
+
+       * sanity.sh (rcs5): Minor cosmetic change.
+
+2005-04-20  Derek Price  <address@hidden>
+
+       * sanity.sh (tests): Add rcs4.
+       (rcs5): Add comments.
+
+2005-04-20  Derek Price  <address@hidden>
+
+       * rcs.c (expand_keywords): Avoid buffer overflow.
+       (Original patch from Stewart Brodie <address@hidden>.)
+
+       * sanity.sh (rcs5): New tests for the above.
+
+2005-04-08  Derek Price  <address@hidden>
+
+       * edit.c (edit_fileproc, unedit_fileproc): s/GMT/-0000/.
+       * rcs.c (RCS_getrevtime): Ditto, & replace a sprintf into a static
+       buffer with an Xasprintf which creates a dynamic one.
+       * sanity.sh: Update tests to compensate.
+       (Original bug report from Ian Abbott <address@hidden>.)
+
+2005-03-26  Mark D. Baushke  <address@hidden>
+
+       * checkout.c (checkout_proc): Use Xasprintf/xstrdup instead of
+       xmalloc/strcpy+strcat.
+
+2005-03-22  Mark D. Baushke  <address@hidden>
+
+       * Makefile.in: Regenerated.
+
+2005-03-22  Mark D. Baushke  <address@hidden>
+
+       * rcs.c (linevector_add): Use xnrealloc.
+       * server.c (serve_argument): Ditto.
+
+2005-03-22  Mark D. Baushke  <address@hidden>
+
+       * rcs.c (linevector_copy): Use xtimes in argument.
+
+       * patch.c (patch): Remove dead code.
+
+       * lock.c (set_readlock_name): Use Xasprintf instead of
+       xmalloc/sprintf.
+       (lock_exists, set_promotable_lock, lock_wait): Ditto.
+       (lock_obtained, lock_dir_for_write): Ditto.
+       * log.c (rlog_proc): Ditto.
+
+       * ignore.c (ign_dir_add): Use xnrealloc.
+       * modules.c (save_d): Ditto.
+       * rcs.c (linevector_copy): Ditto.
+
+       * add.c (add): Use xstrdup instead of xmalloc/strcpy.
+       * client.c (client_process_import_file): Ditto.
+       * kerberos4-client.c (start_kerberos4_server): Ditto.
+       * logmsg.c (verifymsg_proc): Ditto.
+       * log.c (log_expand_revlist): Ditto.
+       * patch.c (patch_fileproc): Ditto.
+       * rcs.c (RCS_tag2rev, RCS_nodeisbranch, RCS_getbranch): Ditto.
+       (RCS_getdatebranch, expand_keywords, RCS_addbranch): Ditto.
+       (RCS_checkin): Ditto.
+       * remove.c (remove_fileproc): Ditto.
+
+2005-03-18  Derek Price  <address@hidden>
+
+       * server.c: Reindent pragmas.
+       (become_proxy): Add parentheses for -Wall.
+
+2005-03-18  Derek Price  <address@hidden>
+
+       * error.c (error): Simplify using vasnprintf.
+
+2005-03-17  Mark D. Baushke  <address@hidden>
+
+       * admin.c (postadmin_proc): Cast NULL when it is an argument to
+       stdarg function to ensure it is the correct type.
+       * commit.c (precommit_proc): Ditto.
+       * edit.c (notify_proc): Ditto.
+       * fileattr.c (postwatch_proc): Ditto.
+       * logmsg.c (logfile_write, verifymsg_proc): Ditto.
+       * server.c (prepost_proxy_proc): Ditto.
+       * subr.c (cmdlineescape): Ditto.
+       * tag.c (posttag_proc): Ditto.
+       (Thanks to a report from Derek Price <address@hidden>.)
+       
+2005-03-17  Derek Price  <address@hidden>
+
+       * rcs.h (RCSNode): Improve comment.
+       * rcs.c (RCS_head): Ditto, plus gratuitous reformatting.
+
+2005-03-17  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_deltas): Use rcs->print_path.
+
+2005-03-17  Derek Price  <address@hidden>
+
+       * login.c (password_entry_parseline): Avoid using uninitialized
+       variable.
+       * rcs.c (RCS_deltas): Avoid buffer overflow.
+       (RCS_checkout): Avoid using uninitialized loglen.
+       * patch.c (patch_fileproc): Free original pointer, not one that may
+       have been incremented.
+       (Thanks to report from Alen Zukich <address@hidden>.)
+
+2005-03-17  Derek Price  <address@hidden>
+
+       * commit.c (checkaddfile): Avoid dereferencing a NULL pointer in
+       response to a rare error.
+       * admin.c (admin_fileproc), log.c (log_expand_revlist), mkmodules.c
+       (checkout_file), rcs.c (RCS_getdate, RCS_deltas, RCS_findlock_or_tip,
+       RCS_tag2rev): Avoid dereferencing NULL pointer.
+       (Thanks to report from Alen Zukich <address@hidden>.)
+
+2005-03-17  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_reparsercsfile): Avoid memory leak.
+       (Thanks to report from Alen Zukich <address@hidden>.)
+
+2005-03-17  Derek Price  <address@hidden>
+
+       * log.c (log_expand_revlist): Suppress message and not error handling
+       when really_quiet.
+
+2005-03-16  Mark D. Baushke  <address@hidden>
+
+       * buffer.c (fd_buffer_shutdown): Replace (int *) 0 with NULL.
+       * server.c (do_cvs_command): Ditto.
+
+       * client.c (update_entries): Use xnmalloc.
+
+       * checkin.c (Checkin): Replace (char *) 0 with NULL.
+       * patch.c (patch_fileproc): Ditto.
+       * update.c (update_fileproc): Ditto.
+       * no_diff.c (No_Difference): Ditto.
+       * patch.c (patch_fileproc): Ditto.
+       * rcscmds.c (call_diff_setup, call_diff_add_arg): Ditto.
+
+       * update.c (join_file): Replace (RCSCHECKOUTPROC)0 with NULL.
+       * rcs.c (RCS_checkin, RCS_cmp_file, RCS_delete_revs) 
+       (RCS_delete_revs): Ditto.
+       * rcscmds.c (RCS_merge, RCS_exec_rcsdiff): Ditto.
+       
+       * annotate.c, checkin.c, classify.c, fileattr.c, find_names.c,
+       hash.c, lock.c, login.c, logmsg.c, main.c, modules.c, myndbm.c,
+       no_diff.c, patch.c, rcs.c, rcscmds.c, remove.c, server.c,
+       status.c, subr.c, tag.c, update.c, vers_ts.c: Avoid casting NULL.
+
+2005-03-17  Derek Price  <address@hidden>
+
+       * client.c (call_in_directory): Put function call after var decls.
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * client.c (call_in_directory), commit.c (commit_filesdoneproc), log.c
+       (log_expand_revlist, log_version), logmsg.c (logfile_write), modules
+       (my_module), no_diff.c (No_Difference), parseinfo.c (Parse_Info), rcs.c
+       (RCS_deltas, RCS_checkin, RCS_addbranch, do_locks, do_symbols),
+       rcscmds.c (RCS_merge), root.c (parse_cvsroot, normalize_cvsroot),
+       update.c (merge_file): Verify assumptions via assertions.
+       (Thanks to (probably) incorrect reports from Alen Zukich
+       <address@hidden>.)
+
+2005-03-16  Mark D. Baushke  <address@hidden>
+
+       * rcs.c (RCS_check_kflag): Use Xasprintf instead of
+       sprintf/xstrdup.
+
+       * mkmodules.c (checkout_file): Use Xasprintf instead of
+       xmalloc/strcpy+strcat.
+       * wrapper.c (wrap_unparse_rcs_options): Ditto.
+       (wrap_rcsoption): Ditto.
+
+       * subr.c (getcaller): Use Xasprintf instead of sprintf/xstrdup.
+
+       * history.c (history): Use Xasprintf instead of xmalloc/sprintf.
+       * lock.c (lock_name, set_lockers_name): Ditto.
+       * main.c (cmd_synonyms, main): Ditto.
+       * mkmodules.c (rename_rcsfile, init): Ditto
+       * modules.c (cat_module): Ditto.
+       * parseinfo.c (Parse_Info): Ditto.
+       * rcscmds.c (diff_exec, RCS_output_diff_options): Ditto.
+       * recurse.c (start_recursion, do_dir_proc): Ditto.
+       * remove.c (remove_fileproc): Ditto.
+       * repos.c (Name_Repository): Ditto.
+       * root.c (Name_Root, Create_Root): Ditto.
+       * status.c (status_fileproc, tag_list_proc): Ditto. 
+       * wrapper.c (wrap_setup, wrap_tocvs_process_file): Ditto.
+
+       * hash.c (sortlist): Use xnmalloc.
+       * main.c (cmd_synonyms): Ditto.
+       * server.c (cvs_pam_conv): Ditto.
+
+       * create_adm.c (Create_Admin): Clean up use of Xasprintf/xstrdup.
+       * entries.c (WriteTag, base_walk): Ditto.
+       * modules.c (my_module): Ditto.
+
+       * wrapper.c (wrap_fromcvs_process_file): Use Xasprintf instead of
+       xmalloc/sprintf and clean up control flow.
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * history.c (history_write): Remove test that always evaluates to
+       false.
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * server.c (become_proxy): Close pipe to primary when pipe from it
+       closes.
+
+2005-03-16  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (debug-log-nonfatal): Only set CVS_CLIENT_LOG to the
+       old value if it was previously set.
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * server.c (create_adm_p, serve_entry), tag.c (rtag_proc): Avoid memory
+       leaks.
+       (Thanks to report from Alen Zukich <address@hidden>.)
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * logmsg.c (do_verify): Don't check for NULL return from xfopen().
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * sanity.sh (debug-log-nonfatal): New test.
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * sanity.sh (writeproxy-ssh-noredirect): Don't unconditionally create
+       a primary debug log.
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * cvs.h (open_file): Move proto...
+       * subr.h (xfopen): ...here and rename.
+       * filesubr.c (open_file): Move function...
+       * subr.c (xfopen): ...here, with additional commenting and minor
+       cosmetic changes.
+       * add.c, checkout.c, client.c, cvsrc.c, edit.c, entries.c, filesubr.c,
+       logmsg.c, mkmodules.c, modules,c, patch.c, root.c, subr.c:
+       s/open_file/xfopen/.
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * log-buffer.c (setup_logfiles): Failure to open a debug log file
+       should be non-fatal.
+
+2005-03-16  Derek Price  <address@hidden>
+
+       * server.c (cvs_outerr): Quote error text in syslog messages.
+
+2005-03-15  Mark D. Baushke  <address@hidden>
+
+       * history.c (select_hrec): Avoid possible memory leak.
+
+2005-03-15  Derek Price  <address@hidden>
+
+       * patch.c (patch_proc): Avoid memory leak.
+       (Thanks to report from Alen Zukich <address@hidden>.)
+
+2005-03-12  Mark D. Baushke  <address@hidden>
+
+       * server.c (receive_partial_file): Use ssize_t to deal with < 0
+       return value from write().
+
+2005-03-12  Mark D. Baushke  <address@hidden>
+
+       * history.c (save_file): Bug fix from last change.
+
+2005-03-11  Mark D. Baushke  <address@hidden>
+
+       * update.c (get_linkinfo_proc): Use Xasprintf instead of
+       xmalloc/sprintf.  Gratuitous reformatting.
+       (update_ignproc, update_dirent_proc, checkout_file): Ditto.
+       (patch_file, merge_file, join_file): Ditto.
+       * modules.c (open_module): Ditto.
+
+       * modules (my_module): Use Xasprintf instead of xmalloc/sprintf
+       and avoid possibility of trying to free(NULL).
+
+       * subr.c (backup_file): Use Xasprintf instead of xmalloc/sprintf.
+       (cmdlinequote): Use Xasprintf instead of xmalloc/strcat+strlen.
+       * modules.c (my_module): Ditto.
+
+       * client.c (handle_module_expansion): Use xnmalloc, xnrealloc,
+       and xstrdup in place of xmalloc, xrealloc, and xmalloc/strcpy.
+       (start_server): Do not assume gzip_level precision encoding.
+       (send_modified): Do not assume 1024 bytes is sufficient for
+       a temporary filename.
+
+       * modules.c (my_module): Use xnmalloc and xnrealloc. Gratuitous
+       reformatting.
+       * subr.c (line2argv): Ditto.
+
+2005-03-11  Mark D. Baushke  <address@hidden>
+
+       * modules.c (my_module): Protect against free (NULL) code path.
+
+2005-03-11  Derek Price  <address@hidden>
+
+       * annotate.c (rannotate_proc), fileattr.c (fileattr_write), rcs.c
+       (RCS_deltas), server.c (check_repository_password), update.c (update):
+       Avoid memory leaks.
+       (Thanks to report from Alen Zukich <address@hidden>.)
+
+2005-03-10  Derek Price  <address@hidden>
+
+       * diff.c (diff_fileproc): Remove unnecessary check for NULL.
+
+2005-03-10  Mark D. Baushke  <address@hidden>
+
+       * commit.c: Gratuitous reformatting.
+       * entries.c: Ditto.
+       * import.c (import, import_descend): Ditto.
+
+       * commit.c (commit): Use xnmalloc. Gratuitous reformatting.
+       * history.c (save_user, save_file, save_module, read_hrecs): Ditto.
+       * ignore.c (ign_add): Ditto.
+
+       * hardlink.c (lookup_file_by_inode): Use Xasprintf instead of
+       xmalloc/sprintf.  Gratuitous reformatting.
+       (update_hardlink_info, list_linked_files_on_disk): Ditto.
+       (find_checkedout_proc): Ditto.
+       * history.c (history, history_write, save_file): Ditto.
+       (select_hrec): Ditto. 
+       * ignore.c (ign_setup, ignore_files): Ditto.
+       * import.c (process_import_file, import_descend_dir): Ditto.
+
+       * import.c (import_descend_dir): Use Xasprintf instead of
+       xmalloc/strcpy+strcat+strcat.
+
+       * history.c (read_hrecs): Make function argument a const.
+       
+2005-03-10  Derek Price  <address@hidden>
+
+       * fileattr.c (fileattr_read): Only eat a newline when it really is a
+       newline.
+
+2005-03-10  Mark D. Baushke  <address@hidden>
+
+       * zlib.c: Include "pagealign_alloc.h".
+
+2005-03-09  Derek Price  <address@hidden>
+
+       * add.c (add, add_directory), buffer.c (allocate_buffer_datas),
+       client.c (update_entries), commit.c (checkaddfile), entries.c
+       (Entries_Open), fileattr.c (fileattr_read), ignore.c (ign_add),
+       import.c (import), main.c (main), parseinfo.c (parse_config), rcs.c
+       (RCS_reparsercsfile, RCS_getbranchpoint, RCS_checkout,
+       RCS_delete_revs, apply_rcs_changes): Avoid memory leaks.
+       (Thanks to report from Alen Zukich <address@hidden>.)
+
+       * hardlink.c, hardlink.h: Avoid compiling entire contents of these
+       files w/o preserve permissions support.
+
+2005-03-09  Mark D. Baushke  <address@hidden>
+
+       * history.c (history, save_file): Cleanup the API to match the
+       comments.
+
+2005-03-08  Derek Price  <address@hidden>
+
+       * rcs.c: Define MAP_FILE & MAP_FAILED when necessary.
+
+2005-03-08  Derek Price  <address@hidden>
+
+       * zlib.c (compress_buffer_input): Use pagealign_xalloc when allocating
+       buffer datas.
+       (compress_buffer_output, compress_buffer_flush,
+       compress_buffer_shutdown_output): Don't assume that BUFFER_DATA_SIZE is
+       a constant.
+       (Thanks to report from Larry Jones <address@hidden>.)
+
+2005-03-08  Larry Jones  <address@hidden>
+
+       * release.c (release): Remove unneeded code.
+
+2005-03-07  Conrad T. Pino  <address@hidden>
+
+       * buffer.h (buf_free_data): Compile with proxy disabled.
+       (Thanks to report from Larry Jones <address@hidden>.)
+
+2005-03-07  Mark D. Baushke  <address@hidden>
+
+       * add.c (add_directory): Xasprintf instead of xmalloc/sprintf+strcat.
+       * admin.c (arg_add): Ditto.
+       (admin_fileproc, postadmin_proc, admin_fileproc): Gratuitous
+       reformatting.
+
+       * client.c (mode_to_string): Use Xasprintf.
+       
+       * checkout.c (checkout): Xasprintf instead of xmalloc/sprintf.
+       Gratuitous reformatting.
+       * entries.c (WriteTag): Ditto
+       (subdir_record, base_walk): Ditto.
+       * fileattr.c (fileattr_read, fileattr_set, fileattr_write): Ditto.
+       * filesubr.c (deep_remove_dir, cvs_temp_file): Ditto.
+       (strcat_filename_onto_homedir): Ditto.
+       * find_names.c (Find_Names): Ditto.
+
+       * filesubr.c (expand_wild): Use xnmalloc instead of xmalloc.
+
+2005-03-07  Derek Price  <address@hidden>
+
+       * buffer.c (buf_free_data): Compile with proxy disabled.
+       (Thanks to report from Larry Jones <address@hidden>.)
+
+2005-03-07  Derek Price  <address@hidden>
+
+       * buffer.c (packetizing_buffer_input, packetizing_buffer_output): Don't
+       assume BUFFER_DATA_SIZE is a constant.
+       (Thanks to report from Larry Jones <address@hidden>.)
+
+2005-03-03  Derek Price  <address@hidden>
+
+       Use new pagealign_alloc() and pagealign_free() functions in
+       lieu of maintaining pointers for reuse.  On most systems this should be
+       faster.
+       * buffer.c: Include "pagealign_alloc.h".
+       (free_buffer_data): Remove this global.
+       (allocate_buffer_datas): Remove this function.
+       (buf_free, buf_send_output): Call buf_free_data().
+       (get_buffer_data): Use pagealign_alloc().
+       (buf_free_datas): New function.
+       (buf_send_output, buf_free_data, buf_read_file, buf_read_file_to_eof,
+       buf_read_short_line, buf_read_data, buf_copy_counted): Call
+       buf_free_datas().
+       * buffer.h: Include "getpagesize.h".
+       (BUFFER_DATA_SIZE): Default to getpagesize ();
+       * rcs.c: Lean on m4/mmap-anon.m4 to simplify mmap setup.
+
+2005-03-02  Jim Meyering  <address@hidden>
+
+       Detect and report write failure for e.g., cvs up -p FILE > /dev/full
+       * main.c: Include "closeout.h".
+       (main): Arrange to close standard output upon exit.
+       * sanity.sh (close-stdout): New test for this fix.
+
+2005-03-01  Mark D. Baushke  <address@hidden>
+
+       * checkout.c (emptydir_name): Xasprintf instead of
+       xmalloc/sprintf.
+
+       * add.c (add): Xasprintf instead of xmalloc/sprintf.
+       Gratuitous reformatting.
+       (add_directory, build_entry): Ditto.
+       * annotate.c (rannotate_proc): Ditto.
+       * checkout.c (checkout_proc): Ditto.
+       * client.c (call_in_directory): Ditto.
+       (template, send_dirent_proc): Ditto.
+       (client_process_import_file): Ditto.
+       * commit.c (check_fileproc, precommit_proc): Ditto.
+       (commit_fileproc, finaladd): Ditto.
+       (checkaddfile): Ditto.
+       * create_adm.c (Create_Admin): Ditto.
+       * edit.c (notify_do): Ditto.
+
+2005-03-01  Derek Price  <address@hidden>
+
+       * subr.c (get_date): Minor reformatting.
+
+2005-03-01  Derek Price  <address@hidden>
+
+       * subr.c (get_date): Replace obsolete timeb cruft in this stub.
+
+2005-02-28  Mark D. Baushke  <address@hidden>
+
+       * admin.c (arg_add): Use xnmalloc and xnrealloc.
+       (admin): Use xnmalloc. Minor reformatting.
+       Xasprintf instead of xmalloc/strcpy+strcat.
+       * client.c (client_expand_modules): Use xnmalloc.
+       Minor reformatting.
+       * cvsrc.c (read_cvsrc): Use xnmalloc and xnrealloc.
+       Minor reformatting.
+       * history.c (read_hrecs): Use xnrealloc. Minor reformatting.
+       * ignore.c (ign_add): Use xnrealloc. Minor reformatting.
+       * rcscmds.c (call_diff_add_arg): Use xnrealloc.
+       * wrapper.c (wrap_add_entry): Use xnrealloc. Gratuitous
+       reformatting.
+
+2005-02-28  Derek Price  <address@hidden>
+
+       * edit.c (notify_proc), logmsg.c (logfile_write): Minor reformatting.
+
+2005-02-28  Derek Price  <address@hidden>
+
+       * root.c (parse_root): Trigger a later error message rather than
+       maintaining two copies.
+
+2005-02-27  Mark D. Baushke  <address@hidden>
+
+       * client.c (connect_to_pserver): Use TRACE_FUNCTION, not 1 in
+       TRACE() calls.
+       (connect_to_pserver,send_modified): Ditto.
+       * create_adm.c (Create_Admin): Ditto.
+       * entries.c (Register, Scratch_Entry, WriteTemplate): Ditto.
+       * filesubr.c (copy_file, xchmod, rename_file): Ditto.
+       * history.c (history_write): Ditto.
+       * kerberos4-client.c (start_kerberos4_server): Ditto.
+       * no_diff.c (No_Difference): Ditto.
+       * parseinfo.c (Parse_Info): Ditto.
+       * rcs.c (RCS_checkout): Ditto.
+       * server.c (server_register): Ditto.
+       * update.c (join_file): Ditto.
+
+       * no_diff.c (No_Difference): Gratuitous reformatting.
+       * kerberos4-client.c (start_kerberos4_server): Ditto.
+
+2005-02-27  Jim Meyering  <address@hidden>
+
+       * login.c (password_entry_operation): Exit nonzero when
+       failing to close a just-appended-to .cvspass file.
+
+2005-02-26  Derek Price  <address@hidden>
+
+       * root.c: Gratuitous reformatting.
+
+2005-02-26  Mark D. Baushke  <address@hidden>
+
+       * root.c (parse_cvsroot): Handle another Bad CVSROOT.
+       * sanity.sh (parseroot-8r): Test for it.
+       (Problem report from Hiroyuki Ikezoe <address@hidden>.)
+
+2005-02-26  Derek Price  <address@hidden>
+
+       * server.c: Include netdb.h with server support.  Other reformatting.
+
+2005-02-25  Derek Price  <address@hidden>
+
+       * sanity.sh (multiroot2-9a): Correct for new TRACE message.
+
+2005-02-25  Derek Price  <address@hidden>
+
+       * cvs.h (tag_check_valid): Declare NAME arg const.
+       * tag.c (tag_check_valid): Ditto, update to account for this.
+       Xasprintf instead of xmalloc/sprintf.
+
+2005-02-25  Derek Price  <address@hidden>
+
+       * client.c (send_modified): Suppress a -Wall warning.
+
+2005-02-25  Derek Price  <address@hidden>
+
+       * cvs.h (global_session_id): New global declaration.
+       * import.c (add_rcs_file), rcs.c (RCS_checkin): Save commitid.
+       * log.c (log_version), status.c (status_fileproc): Output commitid.
+       * main.c (global_session_id): Define new global.
+       (main): Create session ID.
+       * sanity.sh: Update to compensate.
+       (Original patch from Frank Hemer <address@hidden>.)
+
+2005-02-24 Derek Price <address@hidden>
+
+       * subr.h (cvs_trace, TRACE*): Move to...
+       * server.h: ...here.
+       * subr.c: Ditto, but to...
+       * server.c: ...here, and print out 'P' instead of 'S' for traces from
+       secondary (proxy) servers.
+
+2005-02-24 Derek Price <address@hidden>
+
+       * admin.c (admin): Suppress warning with -Wall and --disable-client.
+       * server.c: Don't declare functions that won't be defined when
+       --disable-server.
+
+2005-02-24 Derek Price <address@hidden>
+
+       * sanity.sh (primary-wrapper): Rename CVS_SERVER_LOG for the primary in
+       writeproxy mode to avoid overwriting.
+
+2005-02-24 Derek Price <address@hidden>
+
+       * client.c (open_connection_to_server): Fail with an expressive error
+       message when connection is attempted via an unsupported protocol since
+       this is no longer caught in parse_root().
+       * edit.c (edit_fileproc), import.c (import): Don't verify
+       current_parsed_root->isremote without client support.
+       * parseinfo.c (parse_config): Parse PrimaryServer without proxy
+       support.  Postpone method verification until the connect phase.
+       * parseinfo.h (struct config): Always include PrimaryServer.
+       * root.c (primary_root_translate, primary_root_inverse_translate):
+       Don't declare vars without PROXY_SUPPORT when they won't be used.
+       (parse_cvsroot): Parse remote connection methods with server support
+       for PrimaryServer/Redirects.  Delay method support verification until
+       the connect phase.
+       * root.h (cvsroot_t): Include remote elements with SERVER_SUPPORT.
+       * server.c (isProxyServer): Delay method verification until the connect
+       phase.
+
+2005-02-23 Derek Price <address@hidden>
+
+       * tag.c (tag): Handle -r<tag>:<date>.
+       * sanity.sh (tagdate-13.*): New tests.
+
+2005-02-23 Derek Price <address@hidden>
+
+       * annotate.c (annotate), ls.c (ls): Handle -r<tag>:<date>.
+
+2005-02-23 Derek Price <address@hidden>
+
+       * diff.c: Some reformatting.
+       (diff): Handle -r<tag>:<date>.
+       * sanity.sh (tagdate-13b): New test.
+
+2005-02-23 Derek Price <address@hidden>
+
+       * checkout.c (checkout): Use parse_tagdate.
+       * tag.c (tag_check_valid_join): Remove this function.
+       * cvs.h (tag_check_valid_join): Ditto for the proto.
+       (parse_tagdate): New proto.
+       (Make_Date): Make arg const.
+       * main.c (Make_Date): Ditto.
+       (parse_tagdate): New function.
+       * update.c (date_rev1, date_rev2): Rename these globals...
+       (join_date1, join_date2): ...to this.
+       (update): Use parse_tagdate.
+       (do_update): Use new API.
+       * update.h (do_update): Update proto.
+       * sanity.sh: Misc reformatting.
+       (tagdate-12): This test now passes.
+       (tagdate-12b): New test.
+       (multiroot-9a): Handle new TRACE.
+
+2005-02-23 Derek Price <address@hidden>
+
+       * rsh-client.c (start_rsh_server): Update comment.  Replace
+       malloc/sprintf combo with a call to Xasprintf.
+
+2005-02-22 Derek Price <address@hidden>
+
+       * error.c (error): Handle unsigned int format char.
+       * parseinfo.c (parse_error): New function.
+       (parse_config): Keep track of line number and output it in error
+       messages.
+       * rcs.c (RCS_setlocalid): Accept file path and line number for error
+       messages.  Add header comment block.
+       * rcs.h (RCS_setlocalid): Update prototype to match.
+       * parseinfo.h (parse_error): Declare new function.
+       * sanity.sh: Accept new --noredirect argument.
+       (checklongoptarg): Accept longoptmode as an argument
+       rather than via an environment variable.
+       (notnoredirect): New function.
+       (newroot): Handle root options.
+       (keywordexpand, config): Skip in noredirect mode.  Update for new
+       config error messages.
+       (multiroot): Some reformatting.
+       (writeproxy, writeproxy-noredirect): Skip in noredirect mode.
+       (commit, writeproxy-noredirect): Quote CVSROOT arguments since they
+       might contain semicolons.
+
+2005-02-21  Mark D. Baushke  <address@hidden>
+
+       * import.c (import): Avoid using assert with side effects it may
+       be configured away using NDEBUG.
+       (Patch from Frank Hemer <address@hidden>.)
+
+2005-02-20  Mark D. Baushke  <address@hidden>
+
+       * main.c (main): Check the results from xgethostname(). Print a
+       message with the errno and use "localhost" if NULL is returned.
+
+2005-02-20 Derek Price <address@hidden>
+
+       * cvs.h (run_arg): Rename to...
+       (run_add_arg): New function.
+       * run.c (run_arg): Remove.
+       (run_add_arg): Remove static declaration.
+       (run_piped): New function.
+       * logmsg.c, modules.c: s/run_arg/run_add_arg/.
+       * release.c (release): Replace use of piped_child with a call
+       to run_piped to avoid quoting issues.
+       * sanity.sh (info-cleanup-0): Expect success.
+
+2005-02-19 Derek Price <address@hidden>
+
+       * edit.c (unedit_fileproc, mark_up_to_date): Replace xmallc/strcat
+       sequence with single call to Xasprintf.
+
+2005-02-08 Derek Price <address@hidden>
+
+       * rsh-client.c: Some reformatting.
+
+2005-02-04 Derek Price <address@hidden>
+
+       * zlib.c (compress_buffer_input): Don't return EOF when there is data
+       pending.
+
+2005-02-01 Derek Price <address@hidden>
+
+       * main.c: Update year in copyright notice to match GNU standards.
+       * sanity.sh (version-1): Update to match.
+
+2005-02-01  Larry Jones  <address@hidden>
+
+       * log.c (log_fileproc, log_expand_revlist): Add support for BASE tag.
+       * sanity.sh (log): New tests for above.
+
+2005-01-31 Derek Price <address@hidden>
+
+       * main.c: Rephrase --version message.
+       * sanity.sh (version-1): Update to match.
+
+2005-01-31 Derek Price <address@hidden>
+
+       * Makefile.am, add.c, admin.c, annotate.c, checkin.c, checkout.c,
+       classify.c, commit.c, create_adm.c, cvs.h, cvsrc.c, diff.c, entries.c,
+       find_names.c, hash.c, hash.h, history.h, 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, rcs.h, rcscmds.c, recurse.c, remove.c,
+       repos.c, root.c, root.h, server.h, stack.c, stack.h, status.c, subr.c,
+       tag.c, update.c, vers_ts.c, version.c: Update copyright notices.
+
+2005-01-29 Derek Price <address@hidden>
+
+       * log.c (log_usage): Add note about using -S with revision info
+       supression and selection.
+       (Suggestion from Dan Peterson <address@hidden>.)
+
+2005-01-29 Derek Price <address@hidden>
+
+       * sanity.sh (writeproxy-ssh-noredirect): Remove some commented out
+       code.
+
+2005-01-25  Larry Jones  <address@hidden>
+
+       * expand_path.c (expand_path): Rewrite using offsets instead of
+       pointers to simplify and avoid reallocation bugs.
+       (Inspired by Jeremy Bopp <address@hidden>.)
+
+2005-01-20 Brian Murphy <address@hidden>
+
+       * server.c fixing the style of the pam function calls and if
+       statements
+
+2005-01-19 Brian Murphy <address@hidden>
+
+       * server.c (pam_username, pam_password) new global static
+       variables to hold the username and pasword for cvs_pam_conv.
+       (cvs_pam_conv) using pam_username and pam_password.
+       (check_pam_password) set pam_username, pam_password before
+       authentication and clear them when authentication is finished.
+       (server, switch_to_user) Check for pamh being set before using
+       pam functionality, NULL indicating that this user was authenticated
+       using the repository password file.
+
+2004-12-09 Derek Price <address@hidden>
+
+       * log-buffer.c (buf_count_mem): Compile this for PROXY_SUPPORT.
+       (Report from Brad L. Chisholm <address@hidden>.)
+
+2004-12-09 Derek Price <address@hidden>
+
+       * sanity.sh (modules7): New test group.
+       (Based on a patch from Mark D. Baushke <address@hidden>, based on a
+       report from Richard Verhoeven <address@hidden>.)
+
+2004-12-09  Derek Price  <address@hidden>
+
+       * client.c (start_server): Avoid advertising the Redirect response when
+       the user asked us not to.
+       * root.h (cvsroot_t): Add redirect field.
+       * root.c (new_cvsroot_t): Init redirect field.
+       (parse_cvsroot_t): Parse Redirect method option.
+       * server.c (serve_command_prep): Don't throw proxy_log away when
+       Redirect isn't supported and it might be needed later.
+       * sanity.sh (parseroot): Improve comment, add a few new tests.
+       (writeproxy-ssh): Specify Redirect=yes explicitly.
+       (writeproxy-ssh-noredirect): New test group.
+
+2004-12-09  Mark D. Baushke  <address@hidden>
+
+       * main.c (usg): Remove Dr. Pascal Molli's CVS URL from the
+       documentation.
+
+2004-12-08  Derek Price  <address@hidden>
+
+       * root.c (Name_Root): s/TRACE_FUNCTION/TRACE_FLOW/.
+
+2004-12-08  Derek Price  <address@hidden>
+
+       * root.c (parse_cvsroot): Suppress -Wall warning.
+
+2004-12-08  Derek Price  <address@hidden>
+
+       * root.c (parse_cvsroot): findnode() compares LIST to NULL w/o help.
+
+2004-12-08  Derek Price  <address@hidden>
+
+       * root.c (Name_root), server.c (serve_referrer): Don't free cvsroot_t.
+       * server.sh (reposmv): Reaccount for multiple potential warnings and
+       comment.
+
+2004-12-07  Derek Price  <address@hidden>
+
+       * root.c (parse_cvsroot): Cache parsed values for efficiency and to
+       avoid printing warnings about non-fatal parsing errors multiple times.
+       * client.c,, login.c, main.c, recurse.c: Don't dispose of parsed roots,
+       parse_cvsroot() has control.
+       * sanity.sh (reposmv): Stop accounting for multiple warnings.
+
+2004-12-03  Mark D. Baushke  <address@hidden>
+
+       * root.h (cvsroot_t): Add cvs_rsh and cvs_server for bookkeeping
+       purposes.
+       * root.c (new_cvsroot_t, free_cvsroot_t): Add support for case
+       insensitive options CVS_RSH and CVS_SERVER.
+       (parse_cvsroot): Ditto and make all keywords case insensitive.
+       * client.c (connect_to_forked_server): Add support for the new
+       "CVS_SERVER" option to CVSROOT.
+       * rsh-client.c (connect_to_forked_server): Ditto
+       (start_rsh_server): Ditto and add support for the new "CVS_RSH"
+       option to CVSROOT.
+       * sanity.sh (parseroot3): New tests for the new options to
+       CVSROOT.
+
+2004-11-30  Larry Jones  <address@hidden>
+
+       * mkmodules.c (config_contents): Add LocalKeyword and KeywordExpand,
+       misc. cleanup.
+
+2004-11-30  Derek Price  <address@hidden>
+
+       * parseinfo.c (readBool): Update quotes in error message for
+       consistency.  Move function to...
+       * subr.c (readBool): ...here.
+       (*): Gratuitous reformatting.
+       * cvs.h: Move all subr.c function prototypes to...
+       * subr.h: ...this new file.
+       * Makefile.am (cvs_SOURCES): Add subr.h.
+
+2004-11-30  Derek Price  <address@hidden>
+
+       * parseinfo.c (readBool): Reorder arguments to error() and improve
+       header comment.
+
+2004-11-30  Derek Price  <address@hidden>
+
+       * client.c (handle_referrer): New function.
+       (handle_redirect): Handle possibility that CLIENT_REFERRER was set via
+       a response.
+       * server.c (serve_command_prep): Send a normalized Referrer response
+       before a Redirect when the client supports it.
+
+2004-11-29  Mark D. Baushke  <address@hidden>
+
+       * rcs.c (RCS_setlocalid): Do more configuration validation.
+       Include some gratuitous reformatting.
+       * sanity.sh (keywordexpand): Add tests for new validation code.
+
+2004-11-24  Derek Price  <address@hidden>
+
+       * server.c (become_proxy): Note assumptions about syncronized primary
+       and secondary versions with `FIXME?' note.
+
+2004-11-22  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (info): Ignore comments in verifymsg test.
+
+2004-11-22  Mark D. Baushke  <address@hidden>
+
+       * mkmodules.c (loginfo_contents, verifymsg_contents,
+       commitinfo_contents, taginfo_contents, preproxy_contents,
+       postadmin_contents, postproxy_contents, posttag_contents,
+       postwatch_contents, notify_contents): Add comments regarding the
+       additional format strings that are available. Include some
+       gratuitous reformatting.
+
+2004-11-19  Derek Price  <address@hidden>
+
+       * subr.c (Xasnprintf): Improve header comment block.
+
+2004-11-19  Derek Price  <address@hidden>
+
+       * root.c (normalize_cvsroot): Improve header comment block.
+
+2004-11-19  Derek Price  <address@hidden>
+
+       * server.c: Misc reformatting and comment corrections.
+
+2004-11-19  Derek Price  <address@hidden>
+
+       * cvs.h (hostname): Redeclare as pointer rather than array.
+       * main.c: Include xgethostname.h.  Declare server_hostname.
+       (main): Use xgethostname().  Set server_hostname.
+       * server.c (MAXHOSTNAMELEN): Remove this macro.
+       (isThisHost): Reference global HOSTNAME rather than looking it up.
+       Improve header comment block.
+       (gserver_authenticate_connection): Likewise.
+       (serve_command_prep): Correct comment.
+       (serve_hostname): Don't bother to validate hostname length.
+
+2004-11-19  Derek Price  <address@hidden>
+
+       * server.c (isThisHost): strcasecmp before consulting the DNS as an
+       optimization.
+
+2004-11-18  Mark D. Baushke  <address@hidden>
+
+       * checkout.c (checkout_proc): Passing the repository to
+       tag_check_valid seems to stop the assertion failure in recurse.c
+       do_recursion.
+       * sanity.sh (basic2-21a): Removed.
+       (basic2-21c): Fixed.
+
+2004-11-18  Derek Price  <address@hidden>
+
+       * sanity.sh (skip_always, notproxy): New functions.
+       (skip, remoteonly, sshstdio, client): Use new functions.
+
+2004-11-17  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (basic2-21a): The val-tags file should have
+       at least 'rtagged-by-head y' in it. Test from cvs 1.11.18 that
+       shows fixed result.
+       (basic2-21b, basic2-21c): New tests showing a cvs bug when
+       val-tags is not present.
+       (Report from "John Elgin" <address@hidden>.)
+
+       * sanity.sh (basicb-21): POSIX 1003.2 specifies 'illegal option'
+       while lots of getopt implementations still use 'invalid option'.
+       Allow either form for this test.
+
+2004-11-17  Mark D. Baushke  <address@hidden>
+
+       * buffer.c (fd_buffer_block): Deal with BSD and BSDI problems to
+       set block/nonblock on /dev/null.
+
+2004-11-17  Derek Price  <address@hidden>
+
+       * Makefile.am (distclean-local): Clean check.plog~.
+
+2004-11-11  Derek Price  <address@hidden>
+
+       * sanity.sh: s/cp -r/cp -R/ to meet POSIX specification.
+       (Thanks to report from Paul Eggert <address@hidden>.)
+
+2004-11-10  Mark D. Baushke  <address@hidden>
+
+       * ms-buffer.c (ms_buffer_input): Avoid UNICOS cc error where
+       'Both sides of the assignment operator are not compatible.'
+
+       * sanity.sh (importc, rcs, rcs4, tagdate): Use TZ=UTC0 not TZ=UTC
+       to get proper POSIX behavior on MacOS X.
+
+2004-11-10  Derek Price  <address@hidden>
+
+       * sanity.sh: Actually parse -e option like we claim to.
+
+2004-11-10  Derek Price  <address@hidden>
+
+       * sanity.sh: Maintain pass/skip/warn status and output at end.
+       (usage): Note new functionality of -e.
+       (warn): New function.
+       (verify_tmp_empty): Warn instead of failing.  Delete turds if warn()
+       doesn't exit.
+
+2004-11-10  Derek Price  <address@hidden>
+
+       * sanity.sh (verify_tmp_empty): New function.
+       (dotest_internal_*): Call verify_tmp_empty as needed.
+
+2004-11-09  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (expr_tooltest3): Bugfix. Use $expr rather than $EXPR.
+
+2004-11-08  Derek Price  <address@hidden>
+
+       * sanity.sh (version_test): Echo good version data to the log, even
+       when it went to stderr.  Don't echo bad version data.
+
+2004-11-08  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (tool_find): Rewrite. API changed to allow a list of
+       tests to be used against a list of possible command names found on
+       the SEARCHPATH.
+       (version_test): Obtain the version of tools under test if
+       possible.
+       (id_tool_test): Check that 'id -u' and 'id -un' work.
+       (expr_tooltest1): Check for NextStep 3.3 expr bug.
+       (expr_tooltest2): Check for SunOS expr multi-line pattern bug.
+       (expr_create_bar): Create a test file for expr testing.
+       (expr_tooltest3): Use it and test for big multi-line identity
+       matches.
+       (expr_set_ENDANCHOR): Find and set the right value for ENDANCHOR.
+       (expr_set_DOTSTAR): Find and set the right value for DOTSTAR.
+       (expr_tooltest_DOTSTAR): Ensure that DOTSTAR works with big
+       matches.
+       (tr_tooltest1): Verify that tr handles NUL bytes.
+       (ls_tooltest): See if /bin/ls returns true even if wildcard does
+       not match any files.
+       (awk_tooltest1): Verify that awk the BEGIN clause works properly.
+       (awk_tooltest2): Verify that print %c format item works properly.
+
+2004-11-08  Derek Price  <address@hidden>
+
+       * sanity.sh (verify_tmp_empty): New function.
+       (dotest_internal_*): Call verify_tmp_empty as needed.
+
+2004-11-08  Derek Price  <address@hidden>
+
+       * sanity.sh (run_filter): Add function header comment block.
+
+2004-11-07  Larry Jones  <address@hidden>
+
+       * sanity.sh: Remove trailing / from cp -r commands.
+
+2004-11-04  Derek Price  <address@hidden>
+
+       * gssapi-client.c (connect_to_gserver): Silence gcc -Wall.
+
+2004-11-04  Derek Price  <address@hidden>
+
+       * sanity.sh (set_bad_tool): Remove unnecessary quotes.
+
+2004-11-04  Derek Price  <address@hidden>
+
+       * sanity.sh: s/depends_on_/require/.
+
+2004-11-04  Derek Price  <address@hidden>
+
+       * sanity.sh (find_tool): Eliminate variable with single reference.
+
+2004-11-04  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (SEARCHPATH): Unify the PATHs that are to be searched.
+       (Which,find_tool): Use SEARCHPATH.
+       (LS): Use default $SEARCHPATH for Which.
+       (depend_on_rsync): Use default $SEARCHPATH for Which.
+
+2004-11-04  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (depends_on_rsync): Do not echo rsync information to
+       stdout. Look for rsync in more directories.
+
+2004-11-04  Derek Price  <address@hidden>
+
+       * sanity.sh (depends_on_rsync): Minor simplifications.  Make sure that
+       an rsync that doesn't understand `--version' sends its error message to
+       the log file too.
+
+2004-11-04  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (set_bad_tool, is_bad_tool): Avoid printing errors
+       about the same tool multiple times.
+
+2004-11-03  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (depends_on_rsync): Deal with missing rsync.
+
+2004-11-03  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (depends_on_rsync): Include rsync version information
+       in output.
+
+2004-11-03  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (depends_on_rsync): More rigorous tests for rsync
+       2.3.1 problems with --delete --include dir --exclude '*/.
+
+2004-11-03  Derek Price  <address@hidden>
+
+       * sanity.sh (depends_on_rsync): Verify that `rsync --delete' actually
+       does what it is supposed ot do.
+
+2004-11-03  Derek Price  <address@hidden>
+
+       * subr.c (Xreadlink): Remove MAXSIZE macro.  Minor reformatting.
+
+2004-11-03  Mark D. Baushke  <address@hidden>
+
+       * filesubr.c (xreadlink): Remove and use GNULIB version.
+       (islink): Return ssize_t instead of bool.
+       (copy_file): Use Xreadlink().
+       * cvs.h: Add include "xreadlink.h"
+       (islink): Return ssize_t instead of bool.
+       * import.c (preserve_initial_permissions): Use Xreadlink.
+       * rcs.c (RCS_checkin): Ditto.
+       * update.c (special_file_mismatch): Ditto.
+       * subr.c (get_file): Ditto.
+       (resolve_symlink): Ditto.
+       (Xreadlink): New interface to xreadlink(), never return NULL.
+
+2004-11-02  Mark D. Baushke  <address@hidden>
+
+       * filesubr.c (MAXSIZE): New macro.
+       (xreadlink): Ensure initial buffer size does not exceed MAXSIZE.
+       Avoid cast. If readlink fails with buffer size just under MAXSIZE,
+       try again with MAXSIZE.
+
+2004-11-02  Mark D. Baushke  <address@hidden>
+
+       * filesubr.c (xreadlink): AIX and HP-UX readlink() returns ERANGE
+       when there is not enough room in the buffer.
+
+2004-11-01  Derek Price  <address@hidden>
+
+       * cvs.h: Remove getdate proto in favor of including getdate.h.
+       * client.c (handle_mod_time), history.c (history), main.c (Make_Date,
+       format_date_alloc), rcs.c (RCS_getrevtime), server.c
+       (serve_checkin_time): Use new get_date API.
+
+2004-11-01  Derek Price  <address@hidden>
+
+       * sanity.sh (rcslib): Fix typo in path.
+
+2004-11-01  Derek Price  <address@hidden>
+
+       * sanity.sh (rcslib): Test a link to a path longer than 128
+       characters.
+
+2004-10-30  Mark D. Baushke  <address@hidden>
+
+       * patch.c (patch_cleanup): Add signal argument and use it.
+
+2004-10-30  Mark D. Baushke  <address@hidden>
+
+       * client.c: Adjust include files to avoid problems with incomplete
+       types under --disable-client.
+       * msg-buffer.c: Deal with --disable-client --disable-server
+       implicitly meaning --disable-proxy.
+       * server.c: Adjust include files to avoid problems with incomplete
+       types under --disable-server.
+
+2004-10-30  Mark D. Baushke  <address@hidden>
+
+       * server.c (isThisHost): Deal with possibility of a missing
+       hstrerror() function.
+
+2004-10-29  Derek Price  <address@hidden>
+
+       * server.c (server_root, move_file_offset, replace_file_offset):
+       Remove dead code.
+
+2004-10-29  Derek Price  <address@hidden>
+
+       * filesubr.c (xreadlink): Make sure allocation is tried once at the
+       maximum buffer size.  Protect against overflow.
+
+2004-10-29  Derek Price  <address@hidden>
+
+       * server.c (isSameHost): Handle gethostname & gethostbyname errors.
+
+2004-10-29  Derek Price  <address@hidden>
+
+       * server.c (isSameHost): Use strcasecmp to compare host names.
+
+2004-10-29  Mark D. Baushke  <address@hidden>
+
+       * server.c: Need to #include <netdb.h> for either PROXY_SUPPORT or
+       HAVE_GSSAPI to get gethostbyname() declarations for
+
+2004-10-29  Mark D. Baushke  <address@hidden>
+
+       * filesubr.c (SIZE_MAX, SSIZE_MAX): Use #include "xsize.h" instead.
+       (xreadlink): Use xrealloc instead of xmalloc/free.
+
+2004-10-29  Mark D. Baushke  <address@hidden>
+
+       * filesubr.c (SIZE_MAX, SSIZE_MAX): New constants.
+       (xreadlink): Deal with symlinks longer than 127 bytes.
+       (Problem reported as issue 190 by Gottfried Ganssauge
+       <address@hidden>.)
+
+2004-10-29  Derek Price  <address@hidden>
+
+       * server.c (isSameHost): New fuction.
+       (same_path): Rename to...
+       (isSamePath): ...this.
+       (isProxyServer): Use new functions/names.
+
+2004-10-29  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (writeproxy): Use DOTSTAR to avoid problems with
+       a missing Gssapi-authenticate in Valid-requests.
+
+2004-10-28  Derek Price  <address@hidden>
+
+       * server.c (same_path): New function.
+       (isProxyServer): Use new function.
+       * sanity.sh (writeproxy): Test that server resolves symlinks when
+       deciding whether it is a primary server.
+
+2004-10-28  Derek Price  <address@hidden>
+
+       * cvs.h (isdir, isfile, islink, isdevice, isreadable, iswritable,
+       isaccessible, isabsolute): Return boolean rather than int.
+       * filesubr.c (isdir, isfile, islink, isdevice, isreadable, iswritable,
+       isaccessible), subr.c (isabsolute): Ditto.  Some reformatting.
+       * filesubr.c (xresolvepath): Use save_cwd in place of xgetwd.  Some
+       reformatting.
+
+2004-10-28  Derek Price  <address@hidden>
+
+       * client.c (handle_redirect): Detect redirect loops.
+       * sanity.sh (client-20): Test that client detects redirect loops.
+
+2004-10-28  Mark D. Baushke  <address@hidden>
+
+       * release.c (release): Allow builds of cvs with --disable-server
+       --disable-client both used for local installation configuration.
+       * root.c (Name_Root): Ditto.
+       * update.c (checkout_file): Ditto.
+       * edit.c (edit_fileproc): Ditto.
+       * import.c (import): Ditto.
+       (Problem reported by Jean Olivier Caron <address@hidden>.)
+
+2004-10-27  Mark D. Baushke  <address@hidden>
+
+       * cvs.h (RCS_FLAGS_USETIME): New flag.
+       * rcs.c (RCS_checkin): Add citime argument.
+       * rcs.h (RCS_checkin): Ditto.
+       * checkin.c (Checkin): Pass new RCS_checkin argument.
+       * commit.c (remove_file, checkaddfile): Ditto.
+       * import.c (add_rev): Ditto.
+
+       * sanity.sh (tagdate): Delete tagdate-19b as an incorrect test.
+
+2004-10-27  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (tagdate): Provide more output.
+
+2004-10-26  Mark D. Baushke  <address@hidden>
+
+       * commit.c (checkaddfile): Create a dead version for a new file
+       added to a branch. Fixes FIXCVS for tagdate tests.
+       * sanity.sh (tagdate): Update to expect correct results.
+       (death2, branch-after-import, join, ignore-on-branch): Ditto.
+
+2004-10-26  Mark D. Baushke  <address@hidden>
+
+       * server.c (isProxyServer): Fix hostname setup.
+
+2004-10-26  Derek Price  <address@hidden>
+
+       Call all exit handlers via atexit() & exit().  Signal handlers exit().
+       Eliminates a compiler warning.
+
+       * exithandle.c (cleanup_register): Don't register a signal handler.
+       Register a function to block signals before the exit handler is called.
+       * lock.c (Lock_Cleanup): Remove never_run_again cruft and assoc. cmts.
+       * rcs.c (rcs_cleanup), server.c (server_cleanup): Ditto, plus assume
+       signals are blocked.
+       * main.c (main_cleanup): Declare noreturn attribute.
+
+2004-10-26  Derek Price  <address@hidden>
+
+       * gssapi-client.c (connect_to_gserver): Avoid truncating error messages
+       from the GSSAPI server.
+       (Report from Dan Peterson <address@hidden>.)
+
+2004-10-26  Derek Price  <address@hidden>
+
+       * sanity.sh (import-quirks): Test an even branch number.
+
+2004-10-25  Derek Price  <address@hidden>
+
+       * import.c (import): Repair regex for regressions introduced in last
+       commit.
+       * sanity.sh (import-quirks): Test a few branch numbers import shouldn't
+       have a problem with.
+
+2004-10-25  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (modes3): Quiet rsync messages in proxy mode when
+       permissions are removed. They are expected and not a problem here.
+
+2004-10-25  Derek Price  <address@hidden>
+
+       * import.c (import): Anchor and simplify branch verification regex.
+       * sanity.sh (import-quirks): Test another pattern that should fail.
+
+2004-10-25  Derek Price  <address@hidden>
+
+       * cvs.h (yesno): Remove prototype.
+       * edit.c, release.c: Include yesno.h.  Flush output before calling
+       yesno().
+
+2004-10-25  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (tagdate): Added some additional tests and FIXCVS
+       comments for dealing properly with a 'cvs add' of a file to
+       a branch that already exists on the mainline.
+       (Problem reported by Renny Barrett <address@hidden>.)
+
+       * sanity.sh (getrlogdate): New shell function.
+       (tagdate-{13,14,16}): Use it to avoid 'sleep 60' by using
+       the exact 1.1.4.1 timestamp for tagdate-14 and tagdate-16.
+
+2004-10-25  Derek Price  <address@hidden>
+
+       * sanity.sh (depends_on_rsync): Redirect rsync output to /dev/null when
+       just testing.
+
+2004-10-23  Mark D. Baushke  <address@hidden>
+
+       * socket-client.c (socket_buffer_initialize): Drop obsolete
+       arguments to buf_initialize().
+
+2004-10-22  Mark D. Baushke  <address@hidden>
+
+       * client.c (handle_m, handle_e): Winsock is returning
+       SOCK_ERRNO == WSAENOTSOCK for select() problems and not
+       setting errno. Do not bother with printing an error from a
+       select() that is not returning an non-zero errno.
+       (Report from Conrad T. Pino <address@hidden>.)
+
+2004-10-22  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (proxy): proxycheck depends on rsync, but skip all if
+       no rsync is found rather than generate an error return.
+
+2004-10-22  Mark D. Baushke  <address@hidden>
+
+       * Makefile.am (proxycheck): New test target.
+       * sanity.sh: Keep one more level of check.log backup.
+       * Makefile.in: Regenerated.
+
+2004-10-22  Derek Price  <address@hidden>
+
+       * client.c, update.c: Use new MD5 interface.
+
+2004-10-22  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (find_tool): Search /usr/pkg/bin for NetBSD tools
+       like rsync.
+
+2004-10-22  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (tagdate): Fix typo.
+
+2004-10-21  Derek Price  <address@hidden>
+
+       * subr.c: #include vasnprintf.h to avoid compiler warning.
+
+2004-10-21  Derek Price  <address@hidden>
+
+       * cvs.h (error_use_protocol): Move decl here from lib/error.h so that
+       we may use an unforked error.h from GNULIB.
+
+2004-10-21  Derek Price  <address@hidden>
+
+       * main.c: #include strftime.h.
+       (format_time_t, gmformat_time_t): Supply missing args to my_strftime.
+
+2004-10-21  Derek Price  <address@hidden>
+
+       * buffer.c: Don't maintain last_index & last_count for buffers.
+       * buffer.h (struct buffer): Update struct.
+       (buf_initialize): Update proto.
+       * log-buffer.c, ms-buffer.c, zlib.c: Update all callers.
+
+2004-10-21  Derek Price  <address@hidden>
+
+       * sanity.sh: Fail gracefully in proxy mode when rsync is defective.
+       (depends_on_rsync): New function.
+       (writeproxy, writeproxy-noredirect, writeproxy-ssh): Skip these tests
+       gracefully when rsync is missing.
+
+2004-10-21  Mark D. Baushke  <address@hidden>
+
+       * add.c (add): Pay attention to cvswrite mode when resurrecting a
+       file that was not yet committed.
+       (Report from Frank Hemer <address@hidden>.)
+       * sanity.sh (resurrection): Add new tests to deal with read-only
+       mode on a cvs add durring a resurrection. Verify that -r is not
+       honored when the resurrected file must be committed to be seen.
+
+2004-10-21  Derek Price  <address@hidden>
+
+       * ls.c (ls_fileproc): Deal with files specified on the command line.
+       (ls_delproc): Move to a more accessible location.
+
+2004-10-20  Derek Price  <address@hidden>
+
+       * add.c (add): Avoid unnecessary typecast.
+
+2004-10-20  Mark D. Baushke  <address@hidden>
+
+       * Makefile.in: Regenerate for new configure.in.
+
+2004-10-19  Mark D. Baushke  <address@hidden>
+
+       * add.c (add): Backout last typecasts cleanup.
+
+2004-10-19  Derek Price  <address@hidden>
+
+       * add.c (add): Avoid attempting to resurrect a dead rev 1.1.
+       * sanity.sh (resurrection): Add test for the above.
+       (Report from Dan Peterson <address@hidden>.)
+
+2004-10-19  Derek Price  <address@hidden>
+
+       * add.c (add): Avoid unnecessary typecasts.
+
+2004-10-19  Derek Price  <address@hidden>
+
+       * cvs.h: Prototype new function.
+       * subr.c (Xasnprintf): New function.
+       * root.c (primary_root_translate, primary_root_inverse_translate):
+       s/asnprintf/Xasnprintf/.
+       * client.c (connect_to_pserver): Store line length for efficiency.
+
+2004-10-19  Derek Price  <address@hidden>
+
+       * history.c: Remove unecessary typecasts.  Some reformatting.
+
+2004-10-18  Derek Price  <address@hidden>
+
+       * server.c (serve_modified): Eliminate >= 0 check since size_t may not
+       be negative.
+       (Originally reported by Martin Neitzel <address@hidden>.)
+
+2004-10-18  Derek Price  <address@hidden>
+
+       * cvs.h (DEVNULL): This is system dependant.  Move it to lib/system.h.
+       * client.c (copy_a_file): Consolidate USE_VMS_FILENAMES stuff under a
+       single #ifdef.
+
+2004-10-15  Derek Price  <address@hidden>
+
+       * cvs.h: Don't include vasnprintf.h.
+       (Xasprintf): New prototype.
+       * client.c, edit.c, history.c, import.c, ls.c, main.c, parseinfo.c,
+       recurse.c, release.c, repos.c, root.c, server.c, status.c, subr.c,
+       vers_ts.c: s/asnprintf/Xasprintf/.
+       * root.c: Include vasnprintf.h.
+       (primary_root_translate, primary_root_inverse_translate): Use
+       asnprintf() properly.
+       * subr.c: Include vasprintf.h.
+       (Xasprintf): New function.
+
+2004-10-14  Derek Price  <address@hidden>
+
+       * import.c (import): Remove an unecessary level of nesting.  Simplify
+       xmalloc/sprintf with asnprintf.  Remove useless comment.
+
+2004-10-14  Derek Price  <address@hidden>
+
+       * import.c (import): Verify branch specifications more thoroughly.
+       * sanity.sh (importb): Adapt to new error message.
+       (import-quirks): New test.
+
+2004-10-14  Mark D. Baushke  <address@hidden>
+
+       * server.c (server_pause_check, do_cvs_command, server_cleanup):
+       Avoid typecasts.
+
+2004-10-14  Derek Price  <address@hidden>
+
+       * gssapi-client.c: Use new size_t buffer APIs.
+
+2004-10-11  Mark D. Baushke  <address@hidden>
+
+       * buffer.h, buffer.c (buf_output, buf_input_data, buf_read_line,
+       buf_read_data, struct packetizing_buffer,
+       packetizing_buffer_initialize) Use size_t instead of int.
+       Silences warnings in buffer.c, server.c, and zlib.c on OpenBSD
+       sparc64 where sizeof(int) is not the same as sizeof(size_t).
+       * client.c (read_line_via, read_line, try_read_from_server,
+       get_server_response, handle_ok, handle_error,
+       handle_valid_requests, handle_checked_in, handle_new_entry,
+       handle_checksum, handle_copy_file, handle_updated, handle_merged,
+       handle_patched, handle_rcs_diff, handle_removed,
+       handle_remove_entry, handle_set_static_directory,
+       handle_clear_static_directory, handle_set_sticky,
+       handle_clear_sticky, handle_clear_template,
+       handle_module_expansion, handle_wrapper_rcs_option, handle_m,
+       handle_e, handle_f, handle_notified): Ditto
+       * client.h (struct response): Ditto.
+       * server.c (receive_partial_file, receive_file, serve_modified,
+       do_cvs_command): Ditto.
+       * zlib.c (compress_buffer_input, compress_buffer_shutdown_input):
+       Ditto.
+       (Patch from Alexander Taler <address@hidden>.)
+
+2004-10-08  Derek Price  <address@hidden>
+
+       * client.c (send_args): Carry through a const to silence gcc -Wall.
+       * hardlink.c (delhardlist): New function.
+       (lookup_file_by_inode): Use new function as delproc since it should
+       work.  Carry through a const to silence gcc -Wall.
+       * ms-buffer.c (ms_buffer_initialize): Use a working delproc.
+
+2004-10-08  Derek Price  <address@hidden>
+
+       * client.c (open_connection_to_server, close_connection_to_server,
+       handle_redirect): Add traces.
+
+2004-10-07  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Add parseinfo.h.
+
+2004-10-07  Mark D. Baushke  <address@hidden>
+
+       * vers_ts.c (entries_time): Use size_t pointers. sizeof(int)
+       may not be the same as sizeof(size_t) on OpenBSD sparc64.
+       (Patch from Alexander Taler <address@hidden>.)
+
+       * wrapper.c (wrap_clean_fmt_str): Make static.
+
+2004-10-07  Mark D. Baushke  <address@hidden>
+
+       * client.h (struct response): Make name a const.
+       * cvs.h (RETSIGTYPE): Use a full prototype.
+       * hash.h (struct node): Use a full prototype for delproc.
+
+       * client.c (send_arg, send_option_string, option_with_arg): Make
+       args of function const.
+       * client.h (send_arg, send_option_string, option_with_arg): Ditto.
+
+       * checkout.c (checkout): Make valid_options const.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * release.h: Silence gcc -Wall.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * cvs.h: Move include of getopt.h to system.h with similar headers.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * login.c: Include getpass.h.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * cvs.h: Include strcase.h.
+       (cvs_casecmp): remove proto.
+       * subr.c (cvs_casecmp): Remove function.
+       * parseinfo.c: s/cvs_casecmp/strcasecmp/.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * cvs.h: Move getopt.h and regex.h into the GNULIB include section.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * main.c (cmds): Release calls unedit, to it modifies the repository.
+       (struct cmd): Add const and full prototype where needed.
+       * sanity.sh (edit-check): Use modify_repo where needed.
+       (release): No more special case for release in proxy mode.
+       (Patch from Mark D. Baushke  <address@hidden>.)
+
+       * release.c (release): Simplify login in if statement.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * client.c (send_file_names): Back out broken portion of previous
+       change.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * client.c (call_in_directory): Make args of function arg const.
+       * client.h, client.c, edit.h, edit.c, server.h, zlib.c: Carry change
+       through to called functions and functions passed in as args.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * checkout.c, commit.c, edit.c, rcs.c: Avoid typecasts.  Some
+       reformatting.
+
+2004-10-06  Derek Price  <address@hidden>
+
+       * client.c: Avoid more typecasts.  Some reformatting.
+
+2004-10-06  Mark D. Baushke  <address@hidden>
+
+       * client.c (send_file_names): Use new save-cwd API.
+       (connect_to_pserver): Use a union to avoid incompatible pointer
+       type warnings.
+
+2004-10-05  Derek Price  <address@hidden>
+
+       * cvs.h (Xstrdup): New proto mapped via define to xstrdup.
+       * subr.c (Xstrdup): New function.
+
+2004-10-05  Derek Price  <address@hidden>
+
+       * add.c, client.c, history.c, import.c, mkmodules.c, modules.c,
+       recurse.c, release.c, tag.c, update.c: Use new save-cwd API.
+
+2004-10-05  Derek Price  <address@hidden>
+
+       * checkout.c, client.c, commit.c, create_adm.c, cvs.h, filesubr.c,
+       hardlink.c, history.c, logmsg.c, main.c, recurse.c, update.c:
+       s/xgetwd/xgetcwd/.
+
+2004-10-04  Derek Price  <address@hidden>
+
+       * client.c (responses): Add "Edit-file".
+       (handle_edit_file): New function.
+       * commit.c (usage): Add -c option.
+       (check_fileproc): Check for edit when requested.
+       (commit_fileproc): Use new notify_do API.
+       * edit.c (check_edited): New global.
+       (setting_tedit, setting_tunedit, setting_tcommit): s/int/bool/.
+       (ncheck_fileproc): Use new notify_do API.
+       (send_notifications): Remove redundant proto.  Remove unnecessary
+       repository lock.
+       (editors_output, find_editors_and_output, edit_file): New functions.
+       edit_file() factored from...
+       (edit_fileproc): ...here.  Skip files with existing editors when
+       requested.
+       (usage): Add -c and -f.
+       (edit): Handle new -c and -f options.
+       (notify_do): Accept update_dir as an argument for user messages.
+       (editors_fileproc): Factor most content to find_editors_and_output()
+       and edit_file().
+       * edit.h (notify_do): Proto new API.
+       (editors_output, edit_file): New functions.
+       * rcs.c (RCS_unlock): Use new notify_do() API.
+       * sanity.sh: Update assorted tests to compensate for new output.
+       (edit-check): New tests.
+       * server.c (gupdate_dir): New global.
+       (struct notify_note): Keep track of update_dir.
+       (serve_notify): Use update_dir.
+       (serve_hostname, serve_localdir, serve_edit, server_edit_file): New
+       functions.
+       (server_notify): Use new notify_do() API.
+       (requests): Add Hostname, LocalDir, and edit.
+       * server.h (server_edit_file): New proto.
+       (Note: Original design of new advisory lock behavior came from Noel Yap
+       <address@hidden>'s original advisory locks patch, originally ported
+       forward and enhanced by Matthew Ogilvie <address@hidden>.)
+
+2004-10-04 Derek Price <address@hidden>
+
+       * admin.c (postadmin_proc), edit.c (notify_proc), fileattr.c
+       (postwatch_proc), logmsg.c (logfile_write, verifymsg_proc), server.c
+       (prepost_proxy_proc), tag.c (pretag_proc, posttag_proc): Pass referrer
+       to called scripts when possible.
+       * client.c (handle_redirect): Save the original server.
+       (start_server): Send referrer to the new server if possible.
+       * sever.c (referrer): New global.
+       (serve_referrer): Save referrer.
+       (requests): Add "Referrer" response.
+       * server.h (referrer): Add extern decl.
+       * sanity.sh (ssh-wrapper): Use in remote mode too.
+       (writeproxy-ssh): New tests.
+
+2004-10-04 Derek Price <address@hidden>
+
+       * cvs.h (CVSROOT_DFLT): Undef rather than defining to NULL.
+       * main.c (main): Untangle parsing of CVSROOT, eliminating several
+       variables in the process.  Simplify xmalloc/sprintf with asnprintf.
+
+2004-10-04 Derek Price <address@hidden>
+
+       * root.c (primary_root_translate, primary_root_inverse_translate,
+       get_local_root_dir, local_cvsroot): Simplify logic without proxy
+       support.
+
+2004-10-02  Mark D. Baushke  <address@hidden>
+
+       * root.c (primary_root_translate, primary_root_inverse_translate,
+       get_local_root_dir): Protect PrimaryServer with #ifdef
+       PROXY_SUPPORT
+
+2004-10-01  Mark D. Baushke  <address@hidden>
+
+       * main.c (main): Initialize CVSroot before it is used.
+       (Report and patch by Martin Neitzel <address@hidden>.)
+       * sanity.sh (status): Test it.
+
+2004-10-01  Derek Price <address@hidden>
+
+       * checkout.c (checkout_proc), fileattr.c (fileattr_read), find_names.c
+       (Find_Names), myndbm.c (mydbm_open, mydbm_load_file), parseinfo.c
+       (Parse_Info), rcs.c (RCS_parsercsfile_i, rcsbuf_getkey,
+       rcsbuf_getrevnum, rcsbuf_valword): Root translation functions no longer
+       allocate.
+       * commit.c (commit): Use send_a_repository rather than reimplementing.
+       * main.c (main): Remove --primary-root option.
+       * root.c (primary_root_add): Remove this function.
+       (primary_root_in, primary_root_out): Remove globals.
+       (primary_root_translate, primary_root_inverse_translate): Return const.
+       Don't allocate return value.  Rely on parsed root and confg's
+       PrimarServer rather than separately maintained globals.
+       (get_local_root_dir): New function.
+       (local_cvsroot): Translate requests for primary roots when necessary.
+       * root.h (primary_root_add, primary_root_translate,
+       primary_root_inverse_translate): Update protos to match.
+       * sanity.sh (top level, writeproxy): Simplify $proxy setup.
+       (client): Don't execute $proxy.  Simplify skips and reindent.
+       (writeproxy-noredirect): Use --allow-root in place of --primary-root.
+       Save and restore $PRIMARY_CVSROOT*.
+       * server.c (serve_root): Rely on local_cvsroot() to do any necessary
+       root translation.
+       (serve_directory): Root translation functions no longer allocate.
+
+2004-10-01  Derek Price <address@hidden>
+
+       * client.c (arg_should_not_be_sent_to_server), create_adm.c
+       (Create_Admin), recurse.c (start_recursion, do_recursion, do_dir_proc),
+       release.c (release), repos.c (Name_Repository, Short_Repository),
+       update.c (update_filesdone_proc):
+       s/original_root/original_parsed_root->MEMBER/.
+       * main.c (set_root_directory): Set original_parsed_root when setting
+       current_parsed_root.
+       (main): s/original_root/original_parsed_root->MEMBER/.
+       * root.c (original_root): Make cvsroot_t and rename to...
+       (original_parsed_root): ...this.
+       * root.h: Update extern decl to match.
+       * server.c (serve_root): Set original_parsed_root.
+
+2004-10-01  Derek Price <address@hidden>
+
+       * server.c (serve_questionable): Use pending errors per API.
+
+2004-10-01  Derek Price <address@hidden>
+
+       * repos.c (Name_Repository): asnprintf, not sprintf.
+
+2004-10-01  Derek Price <address@hidden>
+
+       * sanity.sh (ignore-11r): Rename second occurance to...
+       (ignore-11ar): ...this.
+
+2004-10-01  Derek Price <address@hidden>
+
+       * sanity.sh (1): Rename to...
+       (init-1): ...this.
+
+2004-10-01  Derek Price <address@hidden>
+
+       * repos.c (Name_Repository): Simplify string construction with
+       asnprintf().  Improve comment.  Some reformatting.
+
+2004-10-01  Derek Price <address@hidden>
+
+       * release.c (release): Simplify string construction with asnprintf().
+
+2004-10-01  Derek Price <address@hidden>
+
+       * recurse.c (do_file_proc): Improve header comment.  Replace
+       xmalloc()/strcat() combination with asnprintf().
+
+2004-09-29  Derek Price <address@hidden>
+
+       * sanity.sh (config): Handle $SECONDRY_ROOT_DIRNAME output in $proxy
+       mode.
+
+2004-09-29  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh: Workaround MacOS X '/bin/ls' is not returning false
+       when no files are listed.  Label some old tests.
+
+2004-09-29  Derek Price <address@hidden>
+
+       * cvs.h: Include parseinfo.h.
+       (top_level_admin, UseNewInfoFmtStrings,
+       ImportNewFilesToVendorBranchOnly, PrimaryServer, MaxProxyBufferSize,
+       MaxCommentLeaderLength, UseArchiveCommentLeader, RereadLogAfterVerify,
+       lock_dir, UserAdminOptions):  Move extern decls for global config opts
+       to config struct in parseinfo.h.
+       (config): New global.
+       (parse_config): Move proto to...
+       * parseinfo.h: This new file.
+       * parseinfo.c (parse_config): Return struct config.
+       (new_config): New function.
+       * admin.c: Remove global UserAdminOptions.
+       (admin): Don't check config on client.
+       * checkin.c (Checkin), checkout.c (checkout_proc), filesubr.c (xchmod),
+       import.c (import, add_rcs_file, expand_and_copy_contents):
+       Use config instead
+       of globals.  Some reformatting.
+       * history.c (logHistory): Move to struct config.
+       (history_write): Use config instead of globals.
+       * lock.c (lock_dir): Move global to struct config.
+       (lock_name): Use config instead of globals.
+       * logmsg.c (RereadLogAfterVerify): Move global to struct config.
+       (do_verify, logfile_write, verifymsg_proc): Prefer config to globals.
+       * main.c (top_level_admin, UseNewInfoFmtStrings, PrimaryServer,
+       MaxProxyBufferSize, MaxCommentLeaderLength, UseArchiveCommentLeader,
+       ImportNewFilesToVendorBranchOnly): Move globals to struct config.
+       (config): New global.
+       (main): Use new parse_config API.
+       * rcs.c (preserve_perms, keywords): Move globals to struct config.
+       (keyword_local): Move to struct rcs_keyword.
+       (new_keywords, free_keywords): New functions.
+       (expand_keywords, RCS_setlocalid, RCS_setincexc): Prefer config to
+       globals.
+       * rcs.h (free_keywords): New proto.
+       (RCS_setincexc, RCS_setlocalid): Accept opaque keywords element.
+       * root.c (delconfig, get_root_allow_config): New functions.
+       * root.h (get_root_allow_config): New proto.
+       * server.c (system_auth): Move global to struct config.
+       (isProxyServer, become_proxy, serve_command_prep): Prefer config to
+       globals.
+       (serve_root): Ditto.  Set config.
+       * server.h (system_auth): Move extern decl for global moved to config.
+
+2004-09-29  Derek Price <address@hidden>
+
+       * history.h: Protect against multiple include.
+
+2004-09-29  Derek Price <address@hidden>
+
+       * root.c (root_allow_count, root_allow_vector, root_allow_size):
+       Replace with...
+       (root_allow): ...this single List *.
+       (root_allow_add, root_allow_free, root_allow_ok): Use new List API.
+       Make args const.  Return bool rather than int when necessary.
+       * root.h (root_allow_add, root_allow_ok): Update protos to match.
+
+2004-09-29  Derek Price <address@hidden>
+
+       * admin.c (admin): s/int/bool/ as appropriate.  Some reformatting.
+
+2004-09-29  Derek Price <address@hidden>
+
+       * history.c (history_write): Use asnprintf().  Some reformatting.
+
+2004-09-29  Derek Price <address@hidden>
+
+       * import.c (killnew): s/int/bool/.
+       (add_rcs_file): Ditto for do_killnew.
+       * rcs.h (add_rcs_file): Change proto to match.
+
+2004-09-29  Derek Price <address@hidden>
+
+       * client.c (start_server): Use bool in place of int.
+
+2004-09-29  Derek Price <address@hidden>
+
+       * subr.c (cvs_trace): Correct header comment.  Some reformatting.
+
+2004-09-28  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (ssh-wrapper-env): New script to avoid the assumption
+       that the remote end of a $CVS_RSH is running a bourne shell.
+       (ssh-wrapper): Use it.
+
+2004-09-28  Derek Price <address@hidden>
+
+       * sanity.sh (writeproxy): Remove some setup obsoleted by redirects.
+
+2004-09-27  Derek Price <address@hidden>
+
+       Use original_root for client comparisons with CVS/Root since
+       current_parsed_root may be the product of a redirect.
+
+       * client.c (handle_redirect): Don't free current_parsed_root.  Validate
+       new roots.
+       (args_should_not_be_sent_to_server):
+       s/current_parsed_root->original/original_root/.
+       * create_adm.c (Create_Admin): Likewise.
+       * cvs.h (current_parsed_root): Move extern decl...
+       * root.h: ...here.  Decl original_root extern.
+       * main.c (set_root_directory): Store original_root.
+       (main): s/current_parsed_root->original/original_root/.
+       * recurse.c, release.c, update.c: Likewise.
+       * root.c: Declare original_root.
+
+2004-09-24  Derek Price <address@hidden>
+
+       * sanity.sh (parseroot2): Correct two test names.  Restore CVSROOT.
+
+2004-09-24  Derek Price <address@hidden>
+
+       * root.c (parse_cvsroot): Use TRACE_FLOW, not TRACE_FUNCTION since this
+       routine is called repeatedly by the recursion routines.
+       * sanity.sh (multiroot2): Adjust to compensate.
+
+2004-09-24  Derek Price <address@hidden>
+
+       * sanity.sh (parseroot2): Use remoteonly.
+
+2004-09-24  Derek Price <address@hidden>
+
+       * sanity.sh (tests): Add parseroot2.
+
+2004-09-24  Derek Price <address@hidden>
+
+       * recurse.c (do_recursion, do_dir_proc): Make process_this_directory a
+       boolean.
+
+2004-09-24  Derek Price <address@hidden>
+
+       * sanity.sh (parseroot2): New test for root parsing consistency.
+       (Original patch from Alexander Taler <address@hidden>.)
+
+       * cvs.h (Name_Root, free_cvsroot_t, parse_cvsroot, local_cvsroot,
+       Create_Root, root_allow_add, root_allow_free, root_allow_ok): Move
+       these protos to...
+       * root.h: ...here.
+       * client.c (arg_should_not_be_sent_to_server), recurse.c
+       (start_recusrion, do_recursion): Use new Name_Root API.
+       * main.c (current_root): Remove global.
+       (set_root_directory): Set current_parsed_root directly.
+       (main): Use new Name_Root API.  Restore deletion of root directories
+       list.
+       * root.c (Name_Root): Return a parsed cvsroot_t rather than a string.
+
+2004-09-24  Mark D. Baushke  <address@hidden>
+
+       * buffer.c (buf_append_buffer): Fix typo in comment.
+
+2004-09-23  Mark D. Baushke  <address@hidden>
+
+       * buffer.c (buf_read_file): Fix typo in comment.
+       (buf_read_file_to_eof): Ditto.
+       * server.c (serve_command_prep): Ditto.
+
+2004-09-23  Derek Price <address@hidden>
+
+       * sanity.sh (depends_on_ssh, sshstdio): Don't use skip() to skip
+       remote-only tests.
+
+2004-09-23  Derek Price <address@hidden>
+
+       * sanity.sh (crerepos, sshstdio): Minor modifications to make use of
+       the new depends_on_?sh API.
+
+2004-09-23  Derek Price <address@hidden>
+
+       * sanity.sh: Accept new -e option to interpret non-fatal calls to skip
+       as errors.
+       (skip, depends_on_rsh, depends_on_ssh): New functions.
+
+2004-09-23  Mark D. Baushke  <address@hidden>
+
+       * server.c (cvs_output, cvs_output_binary): fflush (stderr)
+       here to avoid problems with 'cvs status 2>&1'.
+       (Report by Frank Hemer <address@hidden>.)
+
+2004-09-17  Derek Price  <address@hidden>
+
+       * buffer.c, buffer.h, log-buffer.c, log-buffer.h, main.c, mkmodules.c,
+       parseinfo.c, server.c: Remove TRUST_OS_FILE_CACHE.
+
+2004-09-17  Derek Price  <address@hidden>
+
+       * buffer.c (fd_set_block): Ignore FreeBSD /dev/null problem.
+
+2004-09-17  Derek Price  <address@hidden>
+
+       * cvs.h (strip_trailing_slashes): Remove proto in favor of including
+       dirname.h from GNULIB.
+       * sever.c (dir_name): Rename to...
+       (gDirname): ...this to avoid conflicts with the GNULIB function.
+
+2004-09-16  Derek Price  <address@hidden>
+
+       * expand_path.c (expand_path): Silence `gcc -Wall'.  Reformat some
+       comments to fit in 80 characters.
+
+2004-09-15  Derek Price  <address@hidden>
+
+       * sanity.sh (sync-secondary, writeproxy, writeproxy-noredirect): Remove
+       redundant checks for $RSYNC readability.
+
+2004-09-15  Derek Price  <address@hidden>
+
+       * sanity.sh: Only find $RSYNC once.
+
+2004-09-15  Derek Price  <address@hidden>
+
+       * sanity.sh (writeproxy, writeproxy-noredirect): Find $RSYNC in a
+       portable manner.
+
+2004-09-15  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh: Find $RSYNC in a portable manner.
+
+2004-09-15  Derek Price  <address@hidden>
+
+       * parseinfo.c (parse_config): Complete parsing of the remainder of the
+       config file when errors are encountered.  Accept and ignore
+       UseNewInfoFmtStrings=yes when !SUPPORT_OLD_INFO_FMT_STRINGS.
+
+2004-09-15  Derek Price  <address@hidden>
+
+       * buffer.c (buf_copy_data): Pass args to buf_append_data correctly.
+
+2004-09-15  Derek Price  <address@hidden>
+
+       * sanity.sh: Set $RSYNC in proxy mode.
+
+2004-09-15  Derek Price  <address@hidden>
+
+       * main.c: Fix typo in comment.
+
+2004-09-15  Derek Price  <address@hidden>
+
+       * rcs.c: s/abort/assert/.  Reformat function headers.  Remove
+       unnecessary typecasts & prototypes.
+
+2004-09-14  Mark D. Baushke  <address@hidden>
+
+       * parseinfo.c (readBool): Remove dead code.
+
+       * filesubr.c (cvs_casecmp): Move to...
+       * subr.c (cvs_casecmp): ...here.
+       * cvs.h (cvs_casecmp): No longer ifdef under SERVER_SUPPORT
+
+       * parseinfo.c (readBool): Return false when there was no boolean
+       found.
+
+2004-09-14  Derek Price  <address@hidden>
+
+       * cvs.h (top_level_admin, ImportNewFilesToVendorBranchOnly):
+       s/int/bool/.
+       (MaxCommentLeaderLength, UseArchiveCommentLeader): New vars.
+       * main.c: Ditto, for all four vars above.
+       * mkmodules.c (config_contents): Add default info for
+       MaxCommentLeaderLength & UseArchiveCommentLeader.
+       * parseinfo.c (readBool, readSizeT): New functions.
+       (parse_info): Use new functions.  Parse MaxCommentLeaderLength &
+       UseArchiveCommentLeader.
+       * rcs.c (expand_keywords): Limit the size of the comment leader to
+       MaxCommentLeaderLength & fall back to the comment leader specified in
+       the RCS archive when requested.
+       (preserve_perms): s/int/bool/.
+       * rcs.h (preserve_perms), server.c (system_auth), server.h
+       (system_auth): Likewise.
+       * sanity.sh (keywordlog): Add new tests for the above.
+
+2004-09-12  Mark D. Baushke  <address@hidden>
+
+       * rcs.c (RCS_checkout): Allow noexec to do checkouts when
+       server_active is true.
+       * sanity.sh (join7): Test above change (fixes a FIXCVS).
+
+2004-09-09  Derek Price  <address@hidden>
+
+       * buffer.c (stuct packetizing_buffer): Use size_t & bool as appropriate
+       in preference to int.
+       (packetizing_buffer_output): s/int/size_t/ as appropriate.
+
+2004-09-09  Derek Price  <address@hidden>
+
+       * buffer.c (packetizing_buffer_input): s/int/size_t/ as appropriate.
+
+2004-09-09  Mark D. Baushke  <address@hidden>
+
+       * root.c (primary_root_inverse_translate): No longer inline.
+
+2004-09-09  Conrad T. Pino  <address@hidden>
+
+       * server.c: Add comment before #if at line 5580 to move it further
+       into the file which seems to work around an apparent buffer managment
+       bug in Microsoft Visual C++ 6.0 compiler.
+
+2004-09-08  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (join7): Fix if-then-else conditional.
+
+       * sanity.sh (join7): Re-order join7-5 and join7-6 tests.
+
+2004-09-08  Conrad T. Pino  <address@hidden>
+
+       * server.c: Remove extra token in conditional compile line 5580
+       causing error in Windows Visual C++ 6.0 compile.
+
+2004-09-08  Mark D. Baushke  <address@hidden>
+
+       * buffer.c (fd_buffer_block): Protect fcntl calls when F_GETFL,
+       O_NONBLOCK and F_SETFL are not available.
+       * server.c (move_file_offset): Ditto.
+       (set_nonblock_fd): Ditto.
+
+       * buffer.c (packetizing_buffer_input): Use size_t rather than int.
+       (struct packetizing_buffer): Ditto.
+
+2004-09-07  Mark D. Baushke  <address@hidden>
+
+       * server.c (server_updated): Deal with cvs -n update -jt1 -jt2
+       "protocol error: uncounted data discarded" problem.
+       * sanity.sh (join7): New test for this case.
+
+2004-09-04  Derek Price  <address@hidden>
+
+       * socket-client.c (socket_client_initialize): Pass new args to
+       buf_initialize.
+
+2004-09-04  Derek Price  <address@hidden>
+
+       Silence `gcc -Wall'.
+
+       * buffer.c (buf_initialize): Remove unnecessary typecasts by using
+       size_t instead of int or bool as args.
+       * buffer.c, import.c, log-buffer.c, ms-buffer.c, zlib.c: Change all
+       callers.
+       * buffer.h, cvs.h: Update protos.
+       * client.c, ls.c, main.c, rcs.c, root.c, server.c: Remove unused vars,
+       add parens, as requested by `gcc -Wall'.
+
+2004-09-04  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (writeproxy-noredirect): Between "Set" and
+       "expand-modules" are the optional Kerberos-encrypt, Gssapi-encrypt
+       and Gssapi-authenticate entries. Use ${DOTSTAR} to deal with
+       these optionally configured requests.
+
+2004-09-04  Mark D. Baushke  <address@hidden>
+
+       * import.c (expand_at_signs): Typecasting for fwrite results.
+       * log-buffer.c (log_buffer_input, log_buffer_output,
+       log_buffer_initialize): Ditto.
+
+       * log-buffer.c (log_buffer_initialize): Protect reference to
+       fatal_errors.
+
+       * parseinfo.c (parse_config): Protect reference to PrimaryServer
+       using #ifdef PROXY_SUPPORT.
+
+       * ls.c (ls_fileproc): Remove unused variables.
+       * subr.c (increment_revnum): Ditto.
+       * vers_ts.c (time_stamp): Ditto.
+
+2004-09-03  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_checkin), commit.c (remove_file): Accept UPDATE_DIR
+       argument and use it to output full relative path on commit.
+       * rcs.h (RCS_checkin): Update prototype.
+       * checkin.c, commit.c, import.c: Change all callers.
+       * sanity.sh: Adjust to compensate.
+
+2004-09-03  Derek Price  <address@hidden>
+
+       * client.c (call_in_directory): Change passed in function to accept
+       void * to avoid typecasting.  Change all functions using this API.
+
+2004-09-03  Derek Price  <address@hidden>
+
+       * sanity.sh (secondary-wrapper, writeproxy-secondary-wrapper): Improve
+       comments.  Use exec to launch server.
+       (writeproxy-noredirect): New tests for writeproxy functionality in
+       conjunction with clients that cannot handle the `Redirect' response.
+
+2004-09-03  Derek Price  <address@hidden>
+
+       * log-buffer.c, rsh-client.c, client.c: Reformat function headers.
+       Remove unnecessary typecasts and prototypes.
+       * client.h: Remove unnecessary extern declarations on protos.
+
+2004-09-03  Derek Price  <address@hidden>
+
+       * sanity.sh (skip): New function.
+       (sshstdio): Use new function.
+       (writeproxy): Skip test when rsync isn't found.
+
+2004-09-02  Mark D. Baushke  <address@hidden>
+
+       * server.c (serve_directory): C89 compilers do not like mixed
+       declarations and code.
+
+2004-08-19  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (sync-secondary): 'dirname -b' fails during the
+       spacefiles-5 test on FreeBSD, so use 'dirname -- "\$dir"' for now
+       and look to AS_DIRNAME at some future date.
+
+2004-08-19  Derek Price  <address@hidden>
+
+       * sanity.sh (ssh-wrapper): Create for $proxy mode too & forward CVS_PID
+       for crerepos.
+
+2004-08-18  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (writeproxy): Use CVS_PID instead of PPID as the
+       former environment variable is set by cvs and the latter is
+       NOT set by all bourne shells.
+
+2004-08-18  Mark D. Baushke  <address@hidden>
+
+       * log-buffer.c (log_buffer_rewind): Avoid FreeBSD compilation
+       error for dereferencing a void * pointer tmp as well as using it
+       as a buffer pointer under some conditions.
+
+2004-08-17  Derek Price  <address@hidden>
+
+       * server.c (isProxyServer): Always compile.  Cache hostname lookup.
+       (serve_notify): Ignore notifications in conjunction with redirects.
+       (do_cvs_command): Send an error when !PROXY_SUPPORT and a client does
+       not support redirects.  Only close proxy logs when they exist.
+       (serve_command_prep): New function.
+       * sanity.sh (secondary-wrapper, writeproxy): Dynamically switch the
+       servers proxy/primary status for testing.
+       (basica): Gratuitous reformatting.
+       (devcom3, config, release): Handle some new proxy/redirect error
+       messages.
+
+2004-08-17  Derek Price  <address@hidden>
+
+       * main.c (PrimaryServer): Include without PROXY_SUPPORT to support
+       redirects.
+       (main): Handle --primary-root without PROXY_SUPPORT.
+       * mkmodules.c (PrimaryServer), parseinfo.c (parse_config), root.c
+       (primary_root_add, primary_root_translate,
+       primary_root_inverse_translate): Likewise.
+
+2004-08-17  Derek Price  <address@hidden>
+
+       * client.c: Misc reformatting.
+       (handle_redirect, close_connection_to_server): New functions.
+       (failure_exit,*): s/int/bool/.
+       (responses): Add `Redirect'.
+       (get_server_responses): Handle response_type_redirect.
+       (get_responses_and_close): Use close_connection_to_server().
+       (supported_request): Change API to use bool & const.
+       (start_server): Handle response_type_redirect.
+       * client.h (supported_request): Update proto.
+       (type): Add response_type_redirect.
+       * main.c (lookup_command_attribute): Declare arg const.
+       * cvs.h (lookup_command_attribute): Ditto.
+       * sever.c (requests): Create dummy `Command-prep' request.
+
+2004-08-12  Derek Price  <address@hidden>
+
+       * main.c (main): Don't process --primary-root without PROXY_SUPPORT.
+       * root.c (primary_root_translate, primary_root_inverse_translate):
+       Declare inline.
+
+2004-08-11  Derek Price  <address@hidden>
+
+       * ms-buffer.h, ms-buffer.c: Disable contents without PROXY_SUPPORT.
+
+2004-08-11  Derek Price  <address@hidden>
+
+       * server.c (isProxyServer): Declare inline.
+       (reprocess_proxy_log): Rename to...
+       (rewind_buf_from_net): ...this and change all callers.
+
+2004-08-11  Derek Price  <address@hidden>
+
+       * sanity.sh (sync-secondary): Don't bother to log sync activity.
+
+2004-08-11  Derek Price  <address@hidden>
+
+       * buffer.c (buf_copy_data), buffer.h (buf_copy_data), log-buffer.c
+       (log_buffer_initialize, log_buffer_input, log_buffer_output,
+       log_buffer_rewind, log_buffer_closelog), log-buffer.h
+       (log_buffer_initialize), main.c (MaxProxyBufferSize), mkmodules.c
+       (config_contents), parseinfo.c (parse_config), server.c (server):
+       Switch eariler support for logs in memory buffers on
+       TRUST_OS_FILE_CACHE.
+
+2004-08-11  Derek Price  <address@hidden>
+
+       * buffer.c (buf_free_data, buf_copy_data): Only compile with proxy
+       support.
+       * buffer.h: Ditto for including the protos.
+
+2004-08-11  Derek Price  <address@hidden>
+
+       * buffer.c (fd_buffer_input): Bracket misguided attempt at improved
+       I/O efficiency with TRUST_OS_FILE_CACHE pragmas.
+
+2004-08-10  Derek Price  <address@hidden>
+
+       * cvs.h, main.c, mkmodules.c, parseinfo.c, server.c:
+       s/MaxSecondaryBufferSize/MaxProxyBufferSize/.
+
+2004-08-10  Derek Price  <address@hidden>
+
+       * sever.c (secondary_log, secondary_log_out): Rename globals everywhere
+       to...
+       (proxy_log, proxy_log_out): ...these.
+       (isSecondaryServer): Rename function...
+       (isProxyServer): ...to this.
+
+2004-08-10  Derek Price  <address@hidden>
+
+       * log-buffer.c: #ifdef PROXY_SUPPORT in appropriate places.
+
+2004-08-10  Derek Price  <address@hidden>
+
+       * cvs.h, log-buffer.h, main.c, mkmodules.c, parseinfo.c, server.c:
+       s/SECONDARY_SUPPORT/PROXY_SUPPORT/.
+
+2004-08-10  Derek Price  <address@hidden>
+
+       * server.c (isSecondaryServer): Declare static.
+       * server.h (isSecondaryServer): Remove proto.
+
+2004-08-10  Derek Price  <address@hidden>
+
+       * log-buffer.h (log_buffer_rewind, log_buffer_closelog): Don't define
+       protos without SECONDARY_SUPPORT.
+       * server.c (*): #ifdef correctly for !SECONDARY_SUPPORT.
+       * version.c (version): Remove unused code.
+
+2004-08-10  Derek Price  <address@hidden>
+
+       * server.c (server_cleanup): Remove unused variable.
+
+2004-08-10  Derek Price  <address@hidden>
+
+       * server.c (buf_from_net_save): Remove obsolete variable.
+       (server_cleanup): Don't close BUF_FROM_NET_SAVE.
+
+2004-08-09  Derek Price  <address@hidden>
+
+       * sanity.sh (sync-secondary, *info): Sync only the updated directories
+       rather than the entire repository after a write for a minor efficiency
+       improvement.
+       (info, taginfo): Hack *info to sync all dirs for these tests rather
+       than rewriting the sync-secondary script to handle old-style *info
+       format strings.
+
+2004-08-05  Derek Price  <address@hidden>
+
+       Beginnings of support for turning off writeproxy support (still broke.)
+       * cvs.h (PrimaryServer, MaxSecondaryBufferSize), main.c (PrimaryServer,
+       MaxSecondaryBufferSize), mkmodules.c (config_contents), parseinfo.c
+       (parse_config): Switch off when writeproxy support disabled.
+       * server.c (replace_file_offset, move_file_offset): Comment out
+       temporarily.
+
+2004-08-05  Derek Price  <address@hidden>
+
+       * filesubr.c (copy_file): Don't fsync.  It's slow.
+
+2004-08-05  Derek Price  <address@hidden>
+
+       * buffer.c (buf_copy_data): New function.
+       * buffer.h: Proto new function.
+       * cvs.h (MaxSecondaryBufferSize): Declare new config global.
+       * log-buffer.c: Allow file-backed memory buffers for "speed".
+       (struct log_buffer): Add new fields.
+       (log_buffer_force_file): New function.
+       (log_buffer_initialize): Initialize new fields.
+       (log_buffer_input, log_buffer_output): Handle logging to memory when
+       asked.
+       (log_buffer_disable): Remove function, moving much functionality...
+       (log_buffer_rewind): ...to this new function and expanding.
+       (log_buffer_closelog): Handle new fields and structs.
+       (log_buffer_get_fd): Remove function.
+       (setup_logfiles): Use new _initialize API.
+       * log-buffer.h: Update protos to match.
+       * main.c (MaxSecondaryBufferSize): Init new config global.
+       * mkmodules.c (config_contents): Add comments 4 MaxSecondaryBufferSize.
+       * parseinfo.c (parse_config): Parse MaxSecondaryBufferSize..
+       * server.c (secondary_log_name, secondary_log_out_name): Remove unused
+       globals.
+       (read_secondary_log): Remove function.
+       (reprocess_secondary_log): Use log_buffer_rewind() instead of the above.
+       (become_proxy): Ditto.
+       (server_cleanup): No need to clean up logfiles any longer.
+       (server): Use new log_buffer_initialize API.
+
+2004-08-04  Derek Price  <address@hidden>
+
+       * buffer.c (buf_read_data): s/abort/assert/.
+
+2004-08-04  Derek Price  <address@hidden>
+
+       * server.c (loop_over_inputs): Remove function, moving contents back...
+       (server): ...to here.
+
+2004-08-03  Derek Price  <address@hidden>
+
+       Checking in IO changes intended to improve speed for posterity since
+       they actually increase CPU usage by about .2% in remote mode and 5% in
+       writeproxy mode.
+       * Makefile.am (cvs_SOURCES): Add ms-buffer.c & ms-buffer.h.
+       * buffer.c (buf_free_data): New function.
+       (buf_read_data): s/abort/assert/.
+       (fd_buffer_input): Try to improve efficiency of blocking read.
+       * buffer.h (buf_free_data): New proto.
+       * server.c (reprocess_secondary_log): Only reopen log and attach to
+       BUF_FROM_NET - don't actually loop over inputs.
+       (become_proxy, serve_co, do_cvs_command): Use new log from above.
+       * ms-buffer.c, ms-buffer.h: New files.
+
+2004-08-03  Derek Price  <address@hidden>
+
+       * sanity.sh (TIMING): Make this work when not in $remotehost mode.
+       (reserved-13b): Use sorted output.
+
+2004-08-03  Derek Price  <address@hidden>
+
+       * log-buffer.c: Improve comments.
+
+2004-08-02  Derek Price  <address@hidden>
+
+       * sanity.sh (ssh-wrapper): Export CVSUMASK to remote host.
+       (*): Remove some hacks that were needed because CVSUMASK was not
+       exported to the remote host.  Misc cleanup.
+
+2004-07-28  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (pserver-auth-no-dos): ENOMEM on Solaris 7, 8, 9, and
+       AIX 4.3 all use the text "Not enough space" instead of the text
+       "Cannot allocate memory" as is printed on GNU/Linux, NetBSD, and
+       FreeBSD systems.
+
+2004-07-28  Derek Price  <address@hidden>
+
+       * sanity.sh (ssh-wrapper): Export CVS_RSH on remote host.
+
+2004-07-28  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (secondary-wrapper): Not all $TESTSHELL shells
+       are able to set a variable and export it at the same time.
+       Separate the value assignment from the export statement.
+       (writeproxy-secondary-wrapper): Ditto.
+
+2004-07-28  Derek Price  <address@hidden>
+
+       * server.c (isSecondaryServer): Fix array out of bounds problem.
+       * sanity.sh (ssh-wrapper): Wrap any rsh implementation we are handed to
+       forward variables relevant to testing.  Add new $TIMING variable to
+       allow timing of processes on a remote host.
+       (*): Gratuitous reformatting.
+
+2004-07-26  Derek Price  <address@hidden>
+
+       * server.c (read_secondary_log): Minor comment corrections.
+
+2004-07-23  Derek Price  <address@hidden>
+
+       * buffer.c (fd_buffer_output): Don't fsync.  It is unneeded and slow.
+       * server.c (move_file_offset, replace_file_offset): Ditto.
+       * log-buffer.c (log_buffer_flush_log): Remove function.
+
+2004-07-19  Derek Price  <address@hidden>
+
+       * log-buffer.c (log_buffer_disable): Return log FILE *.  Don't close
+       log.  Fix header comment.
+       (log_buffer_closelog): New function.
+       * log-buffer.h: Update protos to match.
+       * server.c (read_secondary_log): Rewind file pointer rather than
+       closing and reopening file for speed.
+       (serve_root): Close secondary log rather than just disabling when not
+       running in secondary mode.
+
+2004-07-19  Derek Price  <address@hidden>
+
+       * buffer.c (buf_flush): Replace abort w/assert.
+       * log-buffer.c (log_buffer_flush_log): New function for syncing a log.
+       (log_buffer_flush, log_buffer_flush): Don't sync log for speed.
+       * log-buffer.h (log_buffer_flush_log): New proto.
+       * sanity.sh: Tidy.
+       (run_filter): Accept file name to filter as argument.
+       (dotest_*): pass new arg to run_filter.
+       * server.c (read_secondary_log): Sync log before reopening.
+
+2004-07-16  Derek Price  <address@hidden>
+
+       * buffer.c (fd_buffer_input): Back out previous two changes due to
+       incompatibility with current state of write proxies.
+       * log-buffer.c (log_buffer_initialize): Handle new buffers which
+       already have some data in them.
+       (log_buffer_input): Don't fsync here.  It is slow.
+       * server.c (serve_root): Disable the secondary output log too.
+       (serve_noop, pserver_authenticate_connection): Misc cleanup.
+       * sanity.sh: Misc gratuitous cleanup.
+
+2004-07-16  Derek Price  <address@hidden>
+
+       * buffer.c (fd_buffer_input): Don't overwrite the input buffer the
+       second time through the blocking read loop.
+
+2004-07-15  Derek Price  <address@hidden>
+
+       * buffer.c (fd_buffer_input): Improve efficiency.
+       * sanity.sh (modify_repo): Move timestamp race avoidance to...
+       (sync-secondary): This script.
+       (big): Misc cleanup.
+
+2004-07-15  Derek Price  <address@hidden>
+
+       * sanity.sh: Misc gratuitous cleanup.
+       (modify_repo): Sleep 1 before rsync to avoid timestamp comparison
+       issues.
+
+2004-07-15  Derek Price  <address@hidden>
+
+       * log-buffer.c (log_buffer_output): Remove extremely slow fsync call.
+       (log_buffer_disable): Move to here so log files are sync'd before
+       close.
+       * sanity.sh (run_filter): New function to allow for filtering of cruft
+       output by Rational Quantify or other profilers.
+       (dotest_*): Call new function.
+
+2004-07-13  Derek Price  <address@hidden>
+
+       * server.c (prepost_proxy_proc): Add the CVSROOT string for the primary
+       server, as documented.
+
+2004-07-13  Derek Price  <address@hidden>
+
+       * tag.c (tag_filesdoneproc): Don't track posttag errors.
+       (cvstag): Move addition of successful tags to val-tags to...
+       (tag_fileproc): ...here and...
+       (rtag_fileproc): ...here.  Consolidate returns at single location.
+       (*): Misc reformatting.
+       * sanity.sh (sync-secondary): Include more data in the update-log.
+
+2004-07-13  Derek Price  <address@hidden>
+
+       * .cvsignore: Ignore coverage data generated by GCC.
+
+2004-07-12  Derek Price  <address@hidden>
+
+       * sanity.sh: Watch $servercvs and other minor fixes.
+
+2004-07-12  Derek Price  <address@hidden>
+
+       * server.c: Gratuitous reformatting.
+       * sanity.sh: Misc write proxy accommodations.
+
+2004-07-11  Derek Price  <address@hidden>
+
+       * log.c (log_fileproc, log_expand_revlist, log_fix_singledate,
+       log_count_print, log_tree, log_abranch, log_version), parseinfo.c
+       (Parse_Info, parse_config), rcs.c (RCS_fully_parse, rcsbuf_getkey,
+       rcsbuf_getrevnum, rcsbuf_valword, RCS_getbranchpoint, RCS_getdate,
+       RCS_getrevtime, RCS_checkout, RCS_findlock_or_tip, RCS_addbranch,
+       RCS_cmp_file, RCS_lock, RCS_unlock, RCS_delete_revs, RCS_deltas,
+       RCS_getdeltatext, RCS_putdtree): Print primary path.
+       * server.c (serve_kopt): Handle secondary log.
+       * sanity.sh: Misc accommodations.
+
+2004-07-11  Derek Price  <address@hidden>
+
+       * checkin.c (checkout_proc): Correct vi induced typo.
+
+2004-07-11  Derek Price  <address@hidden>
+
+       * admin.c (postadmin_proc), commit.c (precommit_proc), edit.c
+       (notify_proc), fileattr.c (postwatch_proc), logmsg.c (logfile_write),
+       server.c (prepost_proxy_proc), tag.c (posttag_proc, pretag_proc): Add
+       default %c format string.
+       * client.c, edit.c, lock.c, fileattr.c, mkmodules.c, myndbm.c,
+       parseinfo.c, recurse.c: Misc gratuitous cleanup.
+       * commit.c (commit_filesdoneproc): Move loginfo call to after CVSROOT
+       sync.
+       * checkout.c (checkout_proc), fileattr.c (fileattr_read), myndbm.c
+       (mydbm_open, mydbm_load_file): Print primary path.
+       * server.c (serve_checkin_time): Handle secondary log.
+       (prepost_proxy_proc): Move before first call.
+       (become_proxy): Move before first call.
+       (serve_notify): Become proxy for this request.
+       * sanity.sh: Misc accommodations.
+
+2004-07-10  Derek Price  <address@hidden>
+
+       * server.c (serve_notify): Handle secondary_log.
+
+2004-07-08  Derek Price  <address@hidden>
+
+       Intermediate checkin.  Adds some new server requests and test fixes.
+       * sanity.sh: Update tests to handle proxies and new hooks.
+       * cvs.h (CVSROOTADM_PREPROXY): Alphebetize & sub correct name.
+       * mkmodules.c (filelist): Alphabetize.
+       * server.c (serve_max_dotdot, serve_static_directory, serve_argumentx):
+       Handle secondary_log.
+       * tag.c (*): Misc gratuitous cleanup.
+
+2004-07-08  Derek Price  <address@hidden>
+
+       Intermediate checkin.  Adds preproxy, & postproxy hooks and some test
+       cruft to test everything.
+
+       * cvs.h (CVSROOTADM_*): Add new hook config files.
+       * log.c (log_fileproc): Print primary path.
+       * mkmodules.c (*_content): Add init content for new files.
+       (filelist): Add new files.
+       * rcs.c (RCS_parsercsfile_i): Set print_path.  Misc gratuitous cleanup.
+       (freercsnode): Free print_path.
+       * rcs.h (struct rcsnode): Add print_path.
+       * rcscmds.c (RCS_merge, RCS_exec_rcsdiff): Print primary path.
+       * server.c (isSecondaryServer): No longer static.
+       (serve_sticky, serve_argumentx): Handle secondary logging.
+       (prepost_proxy_proc): New function.
+       (become_proxy): Call pre & post proxy hooks.  Handle IO closing better.
+       * server.h (isSecondaryServer): No longer static.
+       * status.c (status_fileproc): Print primary path.
+       * sanity.sh: Accept --proxy argument and run in write proxy mode when
+       seen.  Misc fixes to account for other changes.  Misc gratuitous
+       cleanup.
+
+2004-07-02  Derek Price  <address@hidden>
+
+       Woo-hoo!  Write proxies work!
+       * client.c (open_connection_to_server): Set up log files...
+       (start_server): ...here.
+       * server.c (secondary_log_out_name, secondary_log_out): New globals.
+       (argument_cound, argument_vector, argument_vector_size): Move before...
+       (reprocess_secondary_log): ...referencing here.  Assume secondary_log.
+       (read_secondary_log): Accept buf & name args.
+       (serve_modified, serve_unchanged, serve_is_modified, serve_entry):
+       Handle logging pass.
+       (become_proxy): Use new output-to-client-log.  Verify buffers still
+       exist before using.
+       (output_dir): Translate paths to the client on the secondary.
+       (serve_co): Only reprocess the secondary log when one exists.
+       (server_cleanup): Free buf after shutdown.  Dispose of client output
+       log.
+       (server): Create client output log.
+       (*): Misc reformatting & detypecasting.
+       * log-buffer.c (log_buffer_output): Handle fatal_errors.
+       (log_buffer_flush): Don't operate on NULL streams.
+       (log_buffer_disable): Reformat so as not to confuse the optimizer.
+       * root.c (primary_root_translate): Remove unused variable and redundant
+       slash.
+       (primary_root_inverse_translate): New function.
+       * root.h: Add new proto.
+       * sanity.sh (writeproxy): Wrap server executables to set args and
+       server variables properly.  Fill in some test gaps.  Correct `cd' args.
+       Clean up wrappers.
+
+2004-06-30  Derek Price  <address@hidden>
+
+       * root.h (primary_root_add, primary_root_translate): New protos.
+       * root.c (primary_root_add, primary_root_translate): New functions.
+       * main.c (long_options): Add --primary-root.  Handle --primary-root
+       and --allow-root only with SERVER_SUPPORT.
+       (main): Ditto.
+       * server.c (move_file_offset): Set block before fsync.  Report
+       ftruncate errors.  Force sync after rearranging data.
+       (replace_file_offset): Force sync after replacing data.
+       (serve_directory): Translate roots based on --primary-root arg.
+       (serve_root): Likewise & don't rewrite the log file.
+       (become_proxy): Increment select's N arg because it is required.
+       (do_cvs_command): Use MAX macro appropriately.
+       * sanity.sh (writeproxy): Wrap the secondary server in such a way that
+       it gets the --primary-root option and the primary does not.  Move the
+       primary root out of the way for the read operations to prove only the
+       secondary was accessed.
+
+2004-06-30  Derek Price  <address@hidden>
+
+       * log-buffer.c (log_buffer_input, log_buffer_output): Flush logs as
+       soon as they are written to better diagnose hangs.
+
+2004-06-30  Derek Price  <address@hidden>
+
+       * client.c (connect_to_forked_server): Compile for SERVER_SUPPORT.
+
+2004-06-30  Derek Price  <address@hidden>
+
+       * buffer.c (buf_append_buffer): Handle NULL from->data.
+
+2004-06-28  Derek Price  <address@hidden>
+
+       * log-buffer.c (log_buffer_flush): Sync all, not just data.
+       * buffer.c (fd_buffer_flush): Ditto.  Ignore problems synchronizing
+       unsynchronizable descriptors.
+
+2004-06-28  Derek Price  <address@hidden>
+
+       Intermediate checkin on the way to enabling the write proxy.
+
+       * server.c (isSecondaryServer): Handle forked primary.
+       (read_secondary_log, move_file_offset, replace_file_offset,
+       become_proxy): New functions.
+       (reprocess_secondary_log): Use new read_secondary_log().
+       (serve_root): Replace `Root' request with new version for primary.
+       (do_cvs_command): Use new become_proxy() function.
+       (*): Gratuitous reformatting.
+       (server): Open new logs and avoid opening pipes to pserver twice.
+       * buffer.c (buf_initialize): Handle new LAST_INDEX & LAST_COUNT
+       initializers.
+       (*): Remove unnecessary typecasts.  Gratuitous reformatting.  Use
+       assert() rather than if()/abort().
+       (buf_append_buffer, buf_read_data, buf_copy_lines, buf_copy_counted):
+       Track LAST_INDEX & LAST_COUNT.
+       (buf_read_short_line): Track LAST_INDEX & LAST_COUNT.
+       * buffer.h (struct buffer): Add LAST_INDEX & LAST_COUNT.
+       * cvs.h: Include minmax.h.
+       * root.h (enum CVSmethod): Force null_method to 0.
+       * zlib.c: Remove unnecessary typecasts.  Gratuitous reformatting.  Use
+       assert() rather than if()/abort().
+
+2004-06-23  Derek Price  <address@hidden>
+
+       Checkout and probably other read-only commands now work.
+
+       * server.c (serve_expand_modules): Discard arguments even when
+       reprocessing.
+       (serve_argument): Always process arguments.
+       (serve_wrapper_sendme_rcs_options): Process in first pass.
+
+2004-06-23  Derek Price  <address@hidden>
+
+       Operate correctly in non-write proxy mode, delaying processing of most
+       commands until after the `Root' request is received.
+
+       * server.c (buf_from_net_save): New global variable to store the input
+       buffer from the client while the secondary log is being reprocessed.
+       (reprocessing): Global to track whether we are reprocessing.
+       (various redundant prototypes): Removed.
+       (fd_buffer_*): Remove unneeded typecasts.
+       (serve_valid_responses, serve_global_option, serve_set,
+       serve_valid_requests): Avoid processing twice.
+       (command_pid, outbuf_memory_error, input_memory_error): Moved above new
+       references.
+       (server): Factor loop over the client inputs to...
+       (loop_over_inputs): ...this new function.
+       (serve_root): Loop over secondary log of client inputs when we
+       discover we are not the secondary.
+       (*): Add !secondary_log assertions to verify that certain code paths
+       are not yet taken.
+       (do_cvs_command, serve_init): Add switch and pseudo-code comments about
+       what will need to be done on secondary servers.
+       (seve_noop): Print errors & "ok" in the first pass, skip "ok" but
+       restore handling of entries and notification in the second.
+       (serve_co): Note what will need to be done on secondaries.
+       (server_cleanup): Deal with buf_from_net_save when necessary.
+
+2004-06-22  Derek Price  <address@hidden>
+
+       * server.c (server): Move previously fatal error on failure to open a
+       secondary log to...
+       (serve_root): ...here, and only when we discover we are actually a
+       secondary server.
+       (server): Ensure an interrupt cannot dump core.
+
+2004-06-22  Derek Price  <address@hidden>
+
+       Enable the writeproxy log and turn it off when we determine we are not
+       running as a secondary server.
+
+       * log-buffer.c: Compile log buffer routines in server mode for write
+       proxy.
+       (struct log_buffer): Add fatal_error member.
+       (log_buffer_initialize, log_buffer_input): Allow for fatal write errors
+       for writeproxy.
+       (log_buffer_disable): Turn off the log when asked.
+       (log_buffer_shutdown): Close log via log_buffer_disable.
+       (log_buffer_input, log_buffer_output, log_buffer_flush): Don't operate
+       on the log when it doesn't exist.
+       (*): Misc gratuitous cleanup.
+       (setup_logfiles): Use new log_buffer_initialize API.
+       * log-buffer.h (log_buffer_initialize, log_buffer_disable): New
+       prototypes.
+       (log_buffer_initialize): Update prototype.
+       * server.c: Include log-buffer.h.  Gratuitous reformatting of pragmas.
+       (secondary_log_name, secondary_log): New globals.
+       (server): Set up recording for writeproxy.
+       (serve_root): Turn off recording when we determine that we are not a
+       secondary.
+
+2004-06-21  Derek Price  <address@hidden>
+
+       * sanity.sh (writeproxy): Verify that secondary is updated after a
+       commit.  Comment test that verifies that commit took place on primary.
+
+2004-06-10  Derek Price  <address@hidden>
+
+       * sanity.sh (writeproxy): Test response to a failing rsync.
+
+2004-06-09  Derek Price  <address@hidden>
+
+       * server.c (isSecondaryServer): New function.
+       (MAXHOSTNAMELEN): Move to top of file and improve comment.
+
+2004-06-09  Derek Price  <address@hidden>
+
+       * parseinfo.c (parse_config): Get my enum references correct.
+
+2004-06-09  Derek Price  <address@hidden>
+
+       * parseinfo.c (parse_config): Verify that the ProxyServer connection
+       method is valid.
+
+2004-09-02  Derek Price <address@hidden>
+
+       * server.c (do_cvs_command): Pass new args to fd_buffer_initialize().
+       (server): Don't initialize BUF_TO_NET & BUF_FROM_NET when
+       pserver_authenticate already did.
+       (pserver_read_line): New function to access pserver auth dialogue via
+       buffers.
+       (pserver_authenticate_connection): Init buffers to/from net and access
+       via pserver_read_line() and the buffer output functions.
+       (fd_buffer_*): Move to...
+       * buffer.c (fd_buffer_*): ...here.  Handle blocking input more
+       efficiently.
+       (buf_initialize): Handle get_fd() argument.
+       (buf_nonio_initialize, packetizing_buffer_initialize): Pass new
+       get_fd() argument.
+       (buf_copy_data, buf_free_data, buf_read_short_line, buf_get_fd,
+       packetizing_buffer_get_fd): New functions.
+       (bufread_line): Wrap buf_read_short_line().
+       (stdio_buffer_*): Remove these functions.
+       (*): Some reformatting of function headers.
+       * buffer.h (struct buffer, buf_initialize): Add get_fd().
+       (buf_read_short_line, buf_get_fd, buf_copy_data, buf_free_data,
+       fd_buffer_initialize): New prototypes.
+       * client.c (get_port_number, get_cvs_port_number,
+       get_proxy_port_number): Compile with SERVER_SUPPORT.
+       (make_bufs_from_fds): Likewise, and accept new ROOT arg and pass on to
+       fd_buffer_initialize().
+       (connect_to_pserver, connect_to_forked_server): Pass ROOT to
+       make_bufs_from_fds().
+       (start_server): Factor much functionality into...
+       (open_connection_to_server): ...this new function.
+       * client.h (make_bufs_from_fds): Update proto.
+       (open_connection_to_server): New proto.
+       * log-buffer.c (log_buffer_initialize): Handle get_gd().
+       (log_buffer_get_fd): New function.
+       * zlib.c (compress_buffer_initialize, compress_buffer_get_fd): Ditto
+       twice.
+       * rsh-client.c (start_rsh_server): Pass ROOT to make_bufs_from_fds().
+       * sanity.sh (pserver): Expect new error messages.
+
+2004-09-01  Derek Price <address@hidden>
+
+       * run.c: Remove unneeded typecasts.  Reformat function headers.  Fix
+       trace.
+
+2004-08-31  Derek Price <address@hidden>
+
+       * subr.c (format_cmdline), cvs.h (format_cmdline): Accept bool rather
+       than int.
+       * admin.c, commit.c, edit.c, fileattr.c, logmsg.c, tag.c: Change all
+       callers.
+       * main.c (UseNewInfoFmtStrings), cvs.h (UseNewInfoFmtStrings):
+       s/int/bool/.
+       * parseinfo.c: Change all references.
+
+2004-08-31  Derek Price <address@hidden>
+
+       * checkout.c: Reformat function headers.  Remove unnecessary typecasts
+       and prototypes.  Some other reformatting for legibility.
+
+2004-08-31  Derek Price <address@hidden>
+
+       * buffer.c: Gratuitous reformatting of header comments.
+       s/abort/assert/.  Remove unnecessary typecasts.
+       * buffer.h: Remove unnecessary "extern" decls.  Some reformatting.
+
+2004-08-24  Derek Price <address@hidden>
+
+       * recurse.c (start_recursion): Don't shorten //. to / (use //).
+
+2004-08-24  Derek Price <address@hidden>
+
+       * recurse.c (start_recursion): Strip trailing CWD indirections on
+       repository.
+       * sanity.sh (rstar-toplevel): Update to account for new behavior.
+       (Report from Dan Peterson <address@hidden>.)
+
+2004-08-24  Mark D. Baushke  <address@hidden>
+
+       * recurse.c (do_recursion): Correct test for calling
+       server_pause_check to occur when locktype != CVS_LOCK_WRITE.
+       (Patch suggested by Ian Lance Taylor <address@hidden>
+       in bug#198).
+
+2004-08-24  Derek Price <address@hidden>
+
+       * sanity.sh: Update a few tests to account for the recent error message
+       changes.
+
+2004-08-24  Derek Price <address@hidden>
+
+       * rcs.c (RCS_valid_rev): Declare arg const.
+       * rcs.h: Likewise.
+
+2004-08-24  Derek Price <address@hidden>
+
+       * rcs.c (translate_symtag): Prevent infinite loop.
+       * tag.c (tag_check_valid): Check tag syntax before searching for tags.
+       * sanity.sh (tag-space): Some tests for the above.
+       (Report from Dan Peterson <address@hidden>.)
+
+2004-08-24  Derek Price <address@hidden>
+
+       * tag.c (tag_check_valid): Use RCS_valid_rev() rather than duplicating
+       code.  Misc error message improvements.
+
+2004-08-24  Mark D. Baushke  <address@hidden>
+
+       * ignore.c (ignore_directory): Include the terminating NUL
+       character in the directory name comparison to avoid matching
+       substrings of directories by accident.
+       (Report and suggested fix from James E Wilson
+       <address@hidden>.)
+       * sanity.sh (modules4): Add some more tests testing the above
+       change.
+
+2004-08-20  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (binfiles): Cleanup the 2a temporary directory.
+
+2004-08-20  Derek Price <address@hidden>
+
+       Cache tags in val-tags on successful creation to avoid problems with
+       write proxies.  Merged from `writeproxy2' branch.
+
+       * tag.c (tag_filesdoneproc): Don't track posttag errors.
+       (cvstag): Move addition of successful tags to val-tags to...
+       (tag_fileproc): ...here and...
+       (rtag_fileproc): ...here.  Consolidate returns at single location.
+       (*): Misc reformatting.
+
+       * tag.c (add_val_tag): New function with content factored from...
+       (tag_check_valid): ...here.
+       (cvstag): Call add_val_tag() when needed.
+       * annotate.c, checkout.c, commit.c, diff.c, ls.c, patch.c, recurse.c,
+       tag.c, update.c: Pass new args to tag_check_valid.
+
+       Merge of postadmin, posttag, and postwatch functionality from
+       `writeproxy2' branch.
+
+       * admin.c (postadmin_proc), commit.c (precommit_proc), edit.c
+       (notify_proc), fileattr.c (postwatch_proc), logmsg.c (logfile_write),
+       server.c (prepost_proxy_proc), tag.c (posttag_proc, pretag_proc): Add
+       default %c format string.
+
+       * cvs.h (CVSROOTADM_POSTWATCH): New macro.
+       * fileattr.c (*): Misc cleanup.
+       (postwatch_proc): New function.
+       (fileattr_write): Call watch proc when done writing fileattr.
+       * mkmodules.c (postwatch_contents): New var.
+       (filelist): Add postwatch.
+       * watch.c (addremove_filesdoneproc): Remove function.
+       (watch_addremove): Don't call above function.
+       (*): Misc cleanup.
+       * watch.h: Remove some unnecessary "extern" decls.
+
+       * admin.c (postadmin_proc, admin_filesdoneproc): New functions.
+       (admin): Pass admin_filesdoneproc() to start_recursion().
+       (*): Misc gratuitous cleanup.
+       * cvs.h (CVSROOTADM_*): Alphabetize, add new hook config files.
+       (format_cmdline): Fix proto to match change below.
+       * mkmodules.c (*_content): Add init content for new files.  Misc
+       cleanup.
+       (filelist): Add new files.
+       * tag.c (struct pretag_proc_data): Move before first use.
+       (posttag_proc, tag_filesdoneproc): New functions.
+       (rtag_proc): Pass new procs to start_recursion().
+       (*): Misc gratuitous cleanup.
+       * sanity.sh: Misc accommodations.
+
+2004-08-19  Mark D. Baushke  <address@hidden>
+
+       * log-buffer.c (log_buffer_output): Protect call to fsync()
+       with #ifdef HAVE_FSYNC.
+
+2004-08-18  Mark D. Baushke  <address@hidden>
+
+       * log-buffer.c (log_buffer_input): Protect call to fsync()
+       with #ifdef HAVE_FSYNC.
+
+2004-08-17  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (sshstdio): Fix comment typo plus gratuitous
+       reformatting.
+
+       * client.c (handle_m): Workaround to deal with stdio getting put
+       into non-blocking via redirection of stderr and interaction with
+       ssh on some platforms. On those boxes, stdio can put stdout
+       unexpectedly into non-blocking mode which may lead to fwrite() or
+       fflush() failing with EAGAIN, but cvs not checking for the error.
+       (Patch suggested by Frank Hemer <address@hidden>.)
+
+       * client.c (handle_e): Similar fix for stderr.
+       * sanity.sh (sshstdio): New test for non-blocking stdio via ssh.
+
+2004-08-11  Derek Price <address@hidden>
+
+       * sanity.sh (basicc): Work around a problem in Linux 2.2 & Bash 2.05b
+       which prevents a `cd ..' from a deleted directory from working.
+       (Original patch from Matthew Ogilvie <address@hidden>.)
+
+2004-07-18  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (newb-123j0): Use DOTSTAR at end of response.
+
+2004-07-17  Mark D. Baushke  <address@hidden>
+
+       * main.c (ImportNewFilesToVendorBranchOnly): New variable.
+       * cvs.h (ImportNewFilesToVendorBranchOnly): Declare new variable.
+       * import.c (import): Respect setting of
+       ImportNewFilesToVendorBranchOnly.
+       * mkmodules.c (config_contents): Document the default
+       ImportNewFilesToVendorBranchOnly=no option in newly generated
+       config files.
+       * parseinfo.c (parse_config): Parse
+       ImportNewFilesToVendorBranchOnly option.
+       * sanity.sh (importX2): New test, to test
+       ImportNewFilesToVendorBranchOnly config file option.
+       (New feature from Chris Demetriou <address@hidden>.)
+
+2004-07-17  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (basic2-14): Use DOTSTAR to be more portable.
+
+       * status.c (status_fileproc): Print datetimes using output_cvs_tagged.
+       * sanity.sh (basic2-14): Allow for an extra blank line at the end.
+
+2004-07-16  Derek Price  <address@hidden>
+
+       * server.c (pamh): New global static to hold the PAM handle.
+       (server): Close the PAM session so that logging works properly.
+       (switch_to_user): Opens a PAM session and gets credentials from PAM so
+       that PAM modules can change group permissions.  Get the username from
+       PAM so that PAM modules can modify the final local username.
+       (cvs_pam_conv): Changed the assertions to allow PAM to output text to
+       the user.
+       (check_system_password): Renamed to...
+       (check_pam_password): ...this.  Changed argument type for username so
+       that PAM modules can modify the username under authentication.  Setting
+       a terminal so some PAM modules which expect it to be set work, it is
+       set to the pam servicename which defaults to the binary name.  Set the
+       username from PAM after authentication if a module has changed it.
+       (check_password): Calls check_pam_password if PAM is enabled otherwiseu
+       calls check_system_password.
+       (Patch from Brian Murphy <address@hidden>.)
+
+2004-07-15  Derek Price  <address@hidden>
+
+       * sanity.sh (run_filter): New function to allow for filtering of cruft
+       output by Rational Quantify or other profilers.
+       (dotest_*): Call new function.
+
+2004-07-13  Derek Price  <address@hidden>
+
+       * .cvsignore: Ignore GCC profiling data.
+
+2004-07-12  Derek Price  <address@hidden>
+
+       * client.c: Misc reformatting.
+
+2004-07-12  Derek Price  <address@hidden>
+
+       * main.c: fix format_time_t to call localtime
+       (Patch from Bart Robinson  <address@hidden>.)
+
+2004-07-02  Derek Price  <address@hidden>
+
+       * vers_ts.c: Gratuitous reformatting & detypecasting.
+
+2004-06-30  Derek Price  <address@hidden>
+
+       * log-buffer.c (log_buffer_input, log_buffer_output): Flush logs as
+       soon as they are written to better diagnose hangs.
+
+2004-06-29  Derek R. Price  <address@hidden>
+
+       * sanity.sh (toplevel-12): Handle new error output.
+
+2004-06-29  Derek R. Price  <address@hidden>
+
+       * subr.c (xrealloc_and_strcat): Use bool in place of short.
+
+2004-06-29  Derek R. Price  <address@hidden>
+
+       * client.c: Gratuitous reformatting.
+       (send_repository): Send relative Directory when server reports it is
+       able to handle it.
+       * server.c (serve_directory): Handle relative directories.
+       (output_dir): Send relative directories.
+       (requests): Add `Relative-directory' request.
+
+2004-06-26  Mark D. Baushke  <address@hidden>
+
+       * import.c (import_usage): Add new -X flag.
+       (import): Handle new -X flag.
+       (process_import_file): Handle new -X flag.
+       (killnew): New static flag variable to indicate use of -X flag.
+       (preserve_initial_permissions): New function, extracted from
+       add_rcs_file().
+       (expand_and_copy_contents): Likewise.
+       (add_rcs_file): New argument, do_killnew, to cause "import -X" flag
+       processing.  Implement -X flag, and use newly abstracted functions.
+       * rcs.h (add_rcs_file): Update prototype for do_killnew argument.
+       * commit.c (checkaddfile): Update for add_rcs_file function change.
+       * mkmodules.c (init): Likewise.
+       * client.c (handle_mt): Handle an importmergecmd tag without
+       any conflicts (for 'import -X' support).
+       * sanity.sh (importX): New test.
+       (New feature from Chris Demetriou <address@hidden>.)
+
+2004-06-22  Derek Price  <address@hidden>
+
+       * wrapper.c: Add explicit "void" return type to "wrap_clean_fmt_str"
+       definition.
+       (Patch from Conrad T. Pino <address@hidden>.)
+
+2004-06-09  Derek Price  <address@hidden>
+
+       * server.c (entries, serve_is_modified): Reorder to remove prototypes.
+       (supported_response): Remove prototype.
+
+2004-06-09  Derek Price  <address@hidden>
+
+       * commit.c, filesubr.c, history.c, server.c, wrapper.c: Various
+       security fixes.
+       (Original patch from Stefan Essler <address@hidden> & Sebastian
+       Krahmer <address@hidden>.)
+
+       * cvs.h: Include xsize.h.
+
+2004-06-09  Derek Price  <address@hidden>
+
+       * server.c (serve_entry, serve_is_modified, serve_unchanged): Protect
+       against malformed entries.
+       * sanity.sh (server): Tests for same.
+
+2004-06-07  Larry Jones  <address@hidden>
+
+       * sanity.sh (basica): More tests for string-based revision inc.
+
+2004-06-04  Larry Jones  <address@hidden>
+
+       * subr.c (increment_revnum): Rewrite ala RCS to work directly on
+       the string rather than converting to int to avoid overflow.
+       * sanity.sh (basica): New tests for above, update others to match.
+
+2004-06-04  Derek Price  <address@hidden>
+
+       Preliminary writeproxy functionality.
+       * main.c: Declare PrimaryServer.
+       * cvs.h: Likewise, but extern.
+       * mkmodules.c: Add PrimaryServer to default CVSROOT/config content.
+       * parseinfo.c: Handle PrimaryServer line.
+       * sanity.sh: (Failing) tests for writeproxy functionality.
+
+2004-05-28  Derek Price  <address@hidden>
+
+       * main.c (format_time_t, gm_format_time_t): Use my_strftime from
+       GNULIB rather than the system-dependant strftime.
+
+2004-05-20  Derek Price  <address@hidden>
+
+       * sanity.sh: s/GMT/UTC/ where appropriate.
+
+2004-05-20  Derek Price  <address@hidden>
+
+       * server.c (cvs_output_tagged): Move new server code inside a
+       SERVER_SUPPORT block.
+
+2004-05-19  Derek Price  <address@hidden>
+
+       * cvs.h (gmformat_time_t, entries_time, unix_time_stamp): New protos.
+       * ls.c (struct long_format_data): New structure.
+       (ls_print): Print datetimes using cvs_output_tagged.
+       (long_format_data_delproc): New function.
+       (ls_fileproc, ls_direntproc): Keep track of long_format_data.
+       * main.c (Make_Date): Use standard quotes.
+       (format_time_t, gmformat_time_t): New functions.
+       (format_date_alloc): Use new functions.  Improve comments.
+       * server.c (cvs_output_tagged): Only output in localtime in local mode.
+       * vers_ts.c (entries_time, unix_time_stamp): New functions.
+       (time_stamp): Use new functions.
+       * sanity.sh (ls, branches2): Use $ISO8601DATE where applicable.
+
+2004-05-19  Derek Price  <address@hidden>
+
+       Output `cvs log' times in the local timezone.
+
+       * client.c (handle_mt): Handle the new "date" MT response
+       * server.c (cvs_output_tagged): Likewise
+       * cvs.h: Proto for format_date_alloc
+       * main.c (format_date_alloc, tm_diff): Added.
+       * log.c (log_version): Use MT response to tag date output.
+       (Original patch from Bart Robinson <address@hidden>.)
+
+       * sanity.sh (importc, rcs, rcs4): Use TZ=GMT for the duration of these
+       tests to obtain consistent times in output.
+       (ISO8601DATE, ISO8601DATE1971, ISO8601DATE2034): Use more precise
+       regex.
+
+2004-05-19  Derek Price  <address@hidden>
+
+       * server.c (serve_unchanged, serve_is_modified): Overwrite existing
+       data in timefields.  Fixes CAN-2004-0396.
+
+2004-05-15  Derek Price  <address@hidden>
+
+       * lock.c (Lock_Cleanup), rcs.c (rcs_cleanup), server.c
+       (server_cleanup):  Clean up inchoherent comment.
+
+2004-05-15  Derek Price  <address@hidden>
+
+       * cvs.h, client.c, history.c, main.c, rcs.c, sanity.sh, server.c:
+       Back out get_date() changes from 2004-04-28.
+
+2004-05-14  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (trailingslashes): During cleanup remove topfile,v to
+       avoid problems in later tests (editor-1).
+
+2004-05-13  Derek Price  <address@hidden>
+
+       * sanity.sh (trailingslashes): Note TODO item #205 in the comment.
+
+2004-05-13  Derek Price  <address@hidden>
+
+       * sanity.sh (trailingslashes): New tests to expose a bug in CVS when
+       paths are specified with trailing slashes.  This relates to TODO #205.
+
+2004-05-13  Mark D. Baushke  <address@hidden>
+
+       * ls.c (ls): Use client_senddate() so the server is able to parse
+       the date/time.
+
+2004-05-12  Derek Price  <address@hidden>
+
+       * entries.c, subr.c: Gratuitous reformatting.
+
+2004-05-12  Derek Price  <address@hidden>
+
+       * subr.c (file_has_conflict), vers_ts.c (time_stamp_server): Only
+       special case "=" when it is the only character in a timestamp field.
+       Gratuitous reformatting.
+       * vers_ts.c (time_stamp_server): Check for NULL in a consistent manner.
+       Gratuitous reformatting.
+
+2004-05-12  Derek Price  <address@hidden>
+
+       * sanity.sh (ls): Add some new tests of the ls command with dates
+       specified and show an assertion error when an existing file is
+       specifically requested.
+       (Original patch from Alexander Taler <address@hidden>.)
+
+2004-05-11  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (RCSKEYDATE): New regular expression to match the rcs
+       keyword date format.
+       (keyword,keywordlog): Use it.
+       (RCSDELTADATE): New regular expression to match the internal rcs
+       file format delta.
+       (admin): Use it.
+       (RCSDATE): Deleted.
+       (ISO8601DATE}: A more exact regular expression for cvs log date
+       output than the previous RCSDATE variable.
+       (basica,basic2,branches,branches3,multibranch,import,importb,importc,
+       join,modules,editor,binfiles,log,log2,keyword,multibranch2,admin,
+       reserved,recase,multiroot,trace):
+       Use ${ISO8601DATE} for cvs log output date patterns.
+       (TOUCH1971,ISO8601DATE1971): New variables for test importc.
+       (TOUCH2034,ISO8601DATE2034): Ditto.
+       (importc): Use them. Isolate the touch commands in a sub-shell
+       with TZ=GMT to make the time more predictable.
+       (RAWRCSDATE2000A,RAWRCSDATE1996A,RAWRCSDATE1996B): New date variables.
+       (ISO8601DATE2000A,ISO8601DATE1996A,ISO8601DATE1996B): Regexps to match.
+       (rcs): Use them.
+       (rcs4): Put the touch commands into sub-shells for temporary
+       TZ=GMT use.
+
+2004-05-11  Derek Price  <address@hidden>
+
+       * checkin.c (Checkin), commit.c (commit_filesdoneproc, remove_file,
+       checkaddfile), rcs.c (RCS_checkin): Remove redundant commit messages.
+       Suppress output when really_quiet.
+       * sanity.sh: Update to match.
+
+2004-05-10  Derek Price  <address@hidden>
+
+       * sanity.sh (top-level): Rename to...
+       (rstar-toplevel): ...this for clarity.
+
+2004-05-10  Derek Price  <address@hidden>
+
+       * sanity.sh (dirs2-10ar): Remove unnecessary empty argument.
+
+2004-05-08  Larry Jones  <address@hidden>
+
+       * log.c (log_expand_revlist): Suppress warnings if really_quiet.
+
+2004-05-08  Derek Price  <address@hidden>
+
+       * server.c: Gratuitous reformatting.  Remove unnecessary prototype &
+       unnecessary type cast.
+
+2004-05-07  Derek Price  <address@hidden>
+
+       * sanity.sh (basica): Remove unnecessary empty arguments.
+
+2004-05-07  Derek Price  <address@hidden>
+
+       * cvs.h (fopen_case): Remove obsolescent prototype.
+
+2004-05-05  Derek Price  <address@hidden>
+
+       * sanity.sh: Wait a second and retry if cvs-serv* directories are
+       discovered to avoid race conditions on some systems.
+       (Patch from Pavel Roskin <address@hidden>.)
+
+2004-05-05  Derek Price  <address@hidden>
+
+       * commit.c: Some gratuitous reformatting.
+
+2004-05-04  Derek Price  <address@hidden>
+
+       * update.c: Some gratuitous reformatting.
+
+2004-05-04  Derek Price  <address@hidden>
+
+       * add.c (add): Remove obsolete FIXME comment.
+       (*): Some gratuitous reformatting.
+
+2004-05-03  Derek Price  <address@hidden>
+
+       * src/sanity.sh (branches2-14-ls-4): Change expectations due to new -d
+       flag.
+
+2004-05-02  Derek Price  <address@hidden>
+
+       * sanity.sh (ls): Add some new tests of ls -d flag.
+       (Original patch from Alexander Taler <address@hidden>.)
+
+       * ls.c (ls): Accept -d to show dead files.
+       (ls_proc): Add W_ATTIC to start_recursion flags when user requests dead
+       files.
+       (ls_fileproc): Don't show dead files with -d.  Print "dead" in long
+       listings for dead files.
+
+2004-05-02  Derek Price  <address@hidden>
+
+       * ls.c (ls_dirleaveproc): Return err.
+       (Original patch from Mark D. Baushke <address@hidden>.)
+
+2004-05-02  Derek Price  <address@hidden>
+
+       * ls.c (ls_print): Return 0.
+       (Patch from Mark D. Baushke <address@hidden>.)
+
+2004-04-30  Derek Price  <address@hidden>
+
+       * tag.c (tag_check_valid): Treat a NULL repository the same as an empty
+       one.
+       * sanity.sh (branches2-ls-7): Verify absence of assertion failure.
+
+2004-04-29  Derek Price  <address@hidden>
+
+       * sanity.sh (branches2-rls-1): Reformat comment.
+
+2004-04-28  Derek Price  <address@hidden>
+
+       * sanity.sh (rcs2-5): Update to cope with new getdate.y.
+
+2004-04-28  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_LIBADD): Use libs for nanosleep & clock_gettime when
+       necessary.
+       * cvs.h: Remove get_date() proto and #include getdate.h.
+       * client.c, history.c, main.c, rcs.c, server.c: Use new form of
+       get_date().
+       * Makefile.in: Regenerated.
+
+2004-04-28  Derek Price  <address@hidden>
+
+       * lock.c (set_lock), subr.c (sleep_past): Assume we have nanosleep.
+
+2004-04-27  Derek Price  <address@hidden>
+
+       * root.c (normalize_cvsroot): Use asnprintf in preference to other
+       indirections.
+
+2004-04-27  Derek Price  <address@hidden>
+
+       Add dirname module from GNULIB.
+
+       * add.c, client.c, commit.c, find_names.c, import.c, lock.c, ls.c,
+       repos.c, server.c, subr.c: s/ISDIRSEP/ISSLASH/.
+
+2004-04-27  Derek Price  <address@hidden>
+
+       * commit.c, create_adm.c, entries.c, filesubr.c, hash.c, update.c:
+       Gratuitious reformatting.
+
+2004-04-27  Derek Price  <address@hidden>
+
+       * ls.c (ls_direntproc): Remove unneeded assertion.
+
+2004-04-27  Derek Price  <address@hidden>
+
+       * ls.c (ls): Set client_prune_dirs in order to delete any directories
+       created by the server.
+       (ls_dirleaveproc): Always delete directories created by
+       ls_direntproc().
+       * sanity.sh (ls): Update to match.
+
+2004-04-27  Derek Price  <address@hidden>
+
+       * ls.c: Remove unneeded prototypes.  Add `prune' option.
+       (dircount): Remove static global.
+       (set_tag, created_dir, ls_prune_dirs): New static globals.
+       (ls): Handle new prune option.
+       (ls_print_dir): Don't count directories, just remember not to print a
+       blank line in front of the first one.  Don't list empty directories
+       when prune is specified.
+       (ls_delproc): New function to dispose of dirlist.
+       (ls_direntproc): Reorganize to assume a directory without a parent must
+       be listed.  Create missing directories a la update and checkout so that
+       they may be processed.  Use new delproc when creating new list nodes.
+       (ls_dirleave_proc): New function to remove directories created by
+       ls_direntproc.
+       (ls_proc): Call start_recursion() once for each argument so that
+       ls_direntproc() may assume that any directory without a parent in the
+       dirlist must be listed and others must not unless recursing.
+       * sanity.sh (ls): New tests.
+       (Thanks to a report from Mark D. Baushke <address@hidden>.)
+
+2004-04-26  Derek Price  <address@hidden>
+
+       * rsh-client.c (start_rsh_server): Don't rely on GNU argument
+       processing capabilities in the RSH command.
+       (Report from Mark Andrews <address@hidden>.)
+
+2004-04-26  Derek Price  <address@hidden>
+
+       * ls.c (dircount): s/long long/long/ for Windows.
+
+2004-04-23  Derek Price  <address@hidden>
+
+       * ls.c (usage): Sync with manual.
+
+2004-04-23  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Add ls.c.
+       * client.c, subr.c: Move #include vasnprintf.h to...
+       * cvs.h: ...here.
+       (ls): Add prototype.
+       * ls.c: New file.
+       * main.c (cmds): Add ls & rls entries.
+       * server.c (serve_ls, serve_rls): New functions.
+       (requests): Add list, ls, rlist, & global-list-quiet.
+       * sanity.sh (branches2): Test new cvs ls & rls commands.
+       * Makefile.in: Regenerated.
+       (Thanks for patches from Alexander Taler <address@hidden>
+       and Mark D. Baushke <address@hidden>.)
+
+2004-04-23  Derek Price  <address@hidden>
+
+       * Makefile.am (AM_CPPFLAGS): No, really, $(top_builddir)/lib.
+       * Makefile.in: Regenerated.
+
+2004-04-23  Derek Price  <address@hidden>
+
+       * Makefile.am (AM_CPPFLAGS): Add the builddir/lib directory for
+       generated header files.
+       * Makefile.in: Regenerated.
+
+2004-04-22  Derek Price  <address@hidden>
+
+       * cvs.h: Move include of fnmatch.h into lib/system.h with the other
+        GNULIB headers.
+
+2004-04-22  Derek Price  <address@hidden>
+
+       * tag.c: Use bool where appropriate.  Some gratuitous reformatting.
+
+2004-04-19  Derek Price  <address@hidden>
+
+       * ignore.c: Gratuitous reformatting.
+
+2004-04-16  Derek Price  <address@hidden>
+
+       * tag.c: Gratuitous reformatting.
+
+2004-04-16  Derek Price  <address@hidden>
+
+       * client.c (connect_to_pserver): Use size_t instead of int as argument
+       to asnprintf.  Some gratuitous reformatting.
+       (Report from Mark <address@hidden>.)
+
+2004-04-15  Derek Price  <address@hidden>
+
+       * client.c, commit.c, server.c: Gratuitous reformatting.
+
+2004-04-11  Derek Price  <address@hidden>
+
+       * client.c (call_in_directory): Check paths the server sends us to make
+       sure they are within a sandbox the user requested be updated.
+       (is_valid_client_path, path_list_prefixed): New functions.
+
+2004-04-11  Derek Price  <address@hidden>
+
+       * modules.c (do_module): Don't allow up-level references in paths to
+       step out of the repository.
+       * sanity.sh (multiroot3): Update tests and add a few more.
+
+2004-04-11  Derek Price  <address@hidden>
+
+       * client.c (get_proxy_port_number): Use CVS_PROXY_PORT as the default
+       proxy port rather than CVS_AUTH_PORT.
+
+2004-04-10  Mark D. Baushke  <address@hidden>
+
+       * client.c (get_cvs_port_number): Use CVS_AUTH_PORT as the default
+       for "cvspserver" rather than the CVS_PROXY_PORT.
+       (Fixes parseroot-3r on machines without "cvspserver" in
+       their /etc/services file.)
+
+2004-04-07  Derek Price  <address@hidden>
+
+       * sanity.sh (parseroot): s/oberon/$username/.
+
+2004-04-06  Derek Price  <address@hidden>
+
+       Import Jim Kingdon's old, old patch which allows CVS to work via a
+       web proxy server.
+       * client.c (*): Some gratuitous restyling.
+       (get_proxy_port_number): New function.
+       (connect_to_pserver): Connect via proxy.
+       * client.h (CVS_PROXY_PORT): Define.
+       * root.c (parse_cvsroot): Parse method options.
+       * sanity.sh (parseroot): Add new tests.
+
+2004-04-06  Derek Price  <address@hidden>
+
+       * root.h (cvsroot_t): Move username, password, hostname, port inside
+       CLIENT_SUPPORT ifdefs.
+       * buffer.c, gssapi-client.c, root.c, sever.c: Add #ifdefs as necessary
+       so that this will compile without client support and the root.h change.
+       Some gratuitous restyling.
+
+2004-04-06  Derek Price  <address@hidden>
+
+       * log.c, tag.c: Gratuitous restyling.
+
+2004-04-04  Derek Price  <address@hidden>
+
+       * filesubr.c (isabsolute): Move...
+       * subr.c: ...here and use new ISABSOLUTE macro.
+
+2004-04-04  Derek Price  <address@hidden>
+
+       * client.c (send_file_names): Cast out an unneeded const to avoid a
+       warning.
+
+2004-04-03  Larry Jones  <address@hidden>
+
+       * cvsrc.c: Remove unused declarations.
+       * run.c: Ditto.
+       * server.h (gunzip_and_write): Declare.
+
+       * client.c (send_file_names): Remove unused variables.
+
+2004-04-02  Derek Price  <address@hidden>
+
+       * sanity.sh (client): Honor $keep.
+
+2004-04-02  Derek Price  <address@hidden>
+
+       * log.c, patch.c, rcs.c: Gratuitous restyling.
+
+2004-04-02  Derek Price  <address@hidden>
+
+       * import.c (import): Use ISDIRSEP rather than testing paths against `/'
+       directly.  Some gratuitos reformatting.
+
+2004-04-02  Derek Price  <address@hidden>
+
+       * sanity.sh: Note the effectiveness of `tail -f check.log' in providing
+       running status.
+
+2004-04-02  Derek Price  <address@hidden>
+
+       * client.c (send_file_names): Move code which calculates and sends
+       Max-dotdot...
+       (send_max_dotdot): ...to this new function.
+       (send_files): Call send_max_dotdot.
+       * sanity.sh (files-14): Expect .. in paths to work now.
+       (status): Add a few new tests using `..'.
+
+2004-04-01  Derek Price  <address@hidden>
+
+       * lock.c: Gratuitous restyling.
+
+2004-04-01  Derek Price  <address@hidden>
+
+       * commit.c, cvs.h, server.c: Gratuitous restyling.
+       * run.c (run_exec): Ditto, plus call cvs_flush{out,err}() instead of
+       flushing stderr & stdout directly.
+
+2004-03-29  Derek Price  <address@hidden>
+
+       * login.c, server.c: Gratuitous restyling.
+
+2004-03-25  Derek Price  <address@hidden>
+
+       * client.c (send_file_names): Initialize string to NULL rather than to
+       a single byte string containing only a null byte.
+       * subr.c (xrealloc_and_strcat): If the destination string is NULL,
+       simply allocate a new string.
+       (Original report from Chris Bohn <address@hidden>.)
+
+2004-03-24  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (ISODATE): Fix ISO 8601 format comment.
+
+2004-03-22  Derek Price  <address@hidden>
+
+       * sanity.sh (toplevel): Remove FIXME type comment and unneeded
+       Emtptydir removal.
+
+2004-03-22  Derek Price  <address@hidden>
+
+       * update.c: Some minor style cleanup.
+
+2004-03-22  Derek Price  <address@hidden>
+
+       * update.c: Some minor style cleanup.
+
+2004-03-22  Derek Price  <address@hidden>
+
+       * sanity.sh (top-level): Don't match most of the assertion since this
+       string is often system dependent.
+       (Thanks to Larry Jones <address@hidden>.)
+
+2004-03-22  Derek Price  <address@hidden>
+
+       * sanity.sh (top-level): Don't match the assertion's line number.
+
+2004-03-22  Derek Price  <address@hidden>
+
+       * sanity.sh (top-level): New test to confirm assertion failure.
+
+2004-03-22  Derek Price  <address@hidden>
+
+       * sanity.sh: Only verify argument to -f when -f was passed.  Check for
+       $TMPDIR/cvsXXXXXX temp files after each test.
+
+2004-03-22  Derek Price  <address@hidden>
+
+       * sanity.sh: Verify that the argument to -f is really a test.
+
+2004-03-20  Larry Jones  <address@hidden>
+
+       * cvs.h: Change command_name to cvs_command_name to avoid conflict
+       on HP-UX (incredibly, it declares a global command_name in prot.h,
+       which is included from shadow.h, which we include in server.c).
+       Change all references.
+
+       * subr.c (previous_rev): Fix == vs = typo.
+
+       * buffer.h: Add prototype for buf_empty.
+
+       * add.c (add): Remove unused variable.
+
+2004-03-20  Derek Price  <address@hidden>
+
+       * add.c (add, add_directory, build_entry), admin.c (admin_dirproc),
+       checkin.c (Checkin), checkout.c (safe_location, build_dirs_and_chdir),
+       client.c (add_prune_candidate, send_repository, send_a_repository,
+       send_to_server, start_rsh_server, send_arg, send_modified,
+       send_ignproc, send_filesdone_proc, send_dirent_proc,
+       send_dirleave_proc, client_notify), commit.c (check_direntproc,
+       check_filesdoneproc, checkaddfile, commit_direntproc,
+       commit_dirleaveproc, lock_RCS, precommit_proc, find_data,
+       find_dirent_proc, find_ignproc, find_filesdoneproc), create_adm.c
+       (Create_Admin), cvsrc.c (read_cvsrc), diff.c (diff_dirproc,
+       diff_filesdoneproc, diff_dirleaveproc), edit.c (onoff_filesdoneproc,
+       mark_up_to_date, editor_set, notify_proc_args, notify_proc, notify_do,
+       notify_check), entries.c (Scratch_Entry, Register, WriteTag),
+       expand_path.c (expand_variable, expand_path), fileattr.c
+       (fileattr_startdir), filesubr.c (mkdir_if_needed, xchmod,
+       last_component), history.c (history_write), ignore.c (ignore_directory,
+       ignore_files), import.c (get_comment, add_rcs_file, expand_at_signs),xi
+       lock.c (lock_filesdoneproc), log.c (log_dirproc), logmsg.c
+       (logfile_write, rcsinfo_proc, update_logfile_proc, editinfo_proc,
+       verifymsg_proc, do_editor, do_verify, Update_Logfile), main.c (main
+       program_name, program_path, command_name), parseinfo.c (Parse_Info),
+       patch.c (patch_dirproc), rcs.c (RCS_getdatebranch, rcs_lockfilename,
+       RCS_parse, RCS_setattic, RCS_getversion, RCS_gettag, RCS_getbranch,
+       RCS_getdate, RCS_datecmp, RCS_getrevtime, RCS_setexpand,
+       expand_keywords, RCS_checkout, RCS_addbranch, RCS_checkin, RCS_lock,
+       RCS_cmp_file, RCS_deltas, rcs_lockfilename, make_file_label),
+       rcscmds.c (RCS_output_diff_options, call_diff, RCS_merge,
+       RCS_exec_rcsdiff, diff_exec), recurse.c (start_recursion, do_recursion,
+       do_file_proc), remove.c (remove_dirproc), repos.c (Name_Repository,
+       Short_Repository), root.c (Name_Root, Create_Root), run.c
+       (piped_child), server.c (output_dir, server_register,
+       server_checked_in, server_update_entries, server_copy_file,
+       server_set_entstat, server_clear_entstat, server_set_sticky,
+       server_template, cvs_output_tagged), status.c (status_dirproc), subr.c
+       (make_message_rcslegal), tag.c (pretag_proc, tag_dirproc,
+       check_fileproc, check_filesdoneproc, tag_fileproc, val_direntproc),
+       update.c (update_dirent_proc, update_dirleave_proc, update_ignproc,
+       update_filesdone_proc, isemptydir), vers_ts.c (time_stamp_server,
+       time_stamp), watch.c (watch_modify_watchers, addremove_filesdoneproc),
+       zlib.c (read_and_gzip): Make most string args const, mainly in the
+       interest of preserving repository & updatedir but including some
+       collateral damage.  Update a few functions to comply with new
+       requirement.  Some style fixes.
+       * client.h, cvs.h, edit.h, fileattr.h, rcs.h, server.h, update.h,
+       watch.h: Update prototypes to match.
+
+2004-03-20  Derek Price  <address@hidden>
+
+       * sanity.sh (conflicts2): s/cvs/$testcvs/.
+
+2004-03-20  Derek Price  <address@hidden>
+
+       * add.c (add): Correct longstanding resurrection bugs.  Remove FIXME
+       comment to this effect.  Set mode and Entries timestamps of resurrected
+       files correctly.
+       * sanity.sh (basica, binfiles, conflicts2, recase, resurrection,
+       update-p): Update tests to compensate.  Remove FIXCVS comments.
+
+2004-03-19  Mark D. Baushke  <address@hidden>
+
+       * server.c (gserver_authenticate_connection): Handle large
+       GSSAPI packets dynamically.
+       (Bug report from Douglas Engert <address@hidden>)
+
+2004-03-19  Derek Price  <address@hidden>
+
+       * cvs.h (pathname_levels, previous_rev): Remove leading underscore from
+       prototype arguments to avoid potential conflicts with implementations.
+
+2004-03-18  Derek Price  <address@hidden>
+
+       * cvs.h (pathname_levels): Make string argument const.
+       * subr.c (pathname_levels): Simplify function.
+
+2004-03-17  Derek Price  <address@hidden>
+
+       * commit.c (precommit_list_to_args_proc), logmsg.c (do_editor), subr.c
+       (format_cmdline), tag.c (pretag_list_to_args_proc):
+       s/atribute/attribute/.
+       (Report from Matthew Doar <address@hidden>.)
+
+2004-03-17  Derek Price  <address@hidden>
+
+       * subr.c (pathname_levels): Get it right this time.
+
+2004-03-17  Derek Price  <address@hidden>
+
+       * subr.c (pathname_levels): Remove incorrect assertion and just
+       return 0 when pathname is NULL.
+
+2004-03-17  Derek Price  <address@hidden>
+
+       * subr.c (pathname_levels): Use ISDIRSEP() instead of strchr('/')
+       and remove FIXME comment to that effect.
+
+2004-03-16  Derek Price  <address@hidden>
+
+       * main.c (main): Update the --version Copyright (c) string to
+       include 2004.
+
+2004-03-15  Mark D. Baushke  <address@hidden>
+
+       * release.c (release): Add missing xmalloc of update_cmd.
+
+2004-03-15  Derek Price  <address@hidden>
+
+       * release.c (release): Enable authentication and encryption for a child
+       update process when necessary.
+       (Original patch from Dan Russell <address@hidden> via Hal Mahaffey
+       <address@hidden>.)
+
+2004-03-14  Derek Price  <address@hidden>
+
+       * add.c (add): Only call server_updated() when we actual have a new
+       resurrected file for the client.
+
+2004-03-14  Derek Price  <address@hidden>
+
+       * cvs.h (previous_rev, write_letter): New prototypes.
+       (struct file_info): Move to before the write_letter prototype.
+       * add.c (add): Allow resurrection of files which used to exist on a
+       branch.
+       * subr.c (previous_rev): New function.
+       * update.c: Consolidate like pragmas.
+        (write_letter): Remove prototype.  Remove static declaration.
+       * sanity.sh (resurrection): New tests.
+
+2004-03-14  Derek Price  <address@hidden>
+
+       * commit.c (remove_file): Print the actual previous revision instead of
+       a branch number.
+       * sanity.sh: Update to match.
+
+2004-03-14  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_cmp_file): Print the actual name of the file we failed to
+       open in the error message.
+
+2004-03-14  Derek Price  <address@hidden>
+
+       * diff.c (diff_fileproc): Allow diffing of new files against arbitrary
+       revisions instead of assuming that there is no RCS archive file.
+
+2004-03-14  Derek Price  <address@hidden>
+
+       * sanity.sh: Update serveral tests to use $CPROG & $SPROG correctly.
+
+2004-03-14  Derek Price  <address@hidden>
+
+       * add.c (add): Update file resurrection messages for consistency.
+       * sanity.sh: Update to match.
+
+2004-03-13  Derek Price  <address@hidden>
+
+       * server.c (server_updated): Fix header comment.
+
+2004-03-13  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Remove error.h since it is now included in
+       lib.
+       * Makefile.in: Regenerated.
+
+2004-03-09  Larry Jones  <address@hidden>
+
+       * run.c: Move #includes required for cmdline_escape and friends...
+       * subr.c: ...to here.
+
+2004-03-07  Derek Price  <address@hidden>
+
+       * run.c (struct cmdline_bindings, cmdline_bindings_hash_node_delete,
+       cmdline_escape, cmdline_quote, format_cmdline): Move...
+       * subr.c: ...here so they will compile under Windows.
+
+2004-03-03  Derek Price  <address@hidden>
+
+       * import.c (import): Check that the module name specified by the user
+       does not contain `CVS' as a directory name.
+       * ignore.c (ign_add): Never cease ignoring "CVS" - it is a reserved
+       name.
+       (Original patch from Dan Peterson <address@hidden>.)
+
+       * sanity.sh (import-CVS): New tests for the above.
+
+2004-02-29  Larry Jones  <address@hidden>
+
+       * import.c (expand_at_signs): Change type of len to size_t.
+       * subr.c (resolve_symlink): Move declaration of newname inside
+       #ifdef, clean up coding style.
+       * zlib.c (gunzip_and_write): Fix up potential overlow problems.
+       (read_and_gzip): Add explicit casts to placate paranoid compilers.
+
+2004-02-28  Larry Jones  <address@hidden>
+
+       * sanity.sh (join6): New tests for previous fix.
+
+       * update.c (join_file): One more fix to avoid dereferencing NULL.
+       (Reported by Steve McIntyre <address@hidden>.)
+       * sanity.sh (join6): New tests for above.
+
+2004-02-25  Larry Jones  <address@hidden>
+
+       * update.c (join_file): Fix optimization to avoid dereferencing NULL.
+       (Reported by Steve McIntyre <address@hidden>.)
+
+2004-02-25  Derek Price  <address@hidden>
+
+       No longer require directories specified to `checkout -d' to exist.
+
+       * checkout.c (safe_location): Only confirm that destination is a safe
+       location for directories that already exist since we can assume that
+       creating directories under such a safe directory is acceptable.
+       (dir_to_build): Remove just_chdir member.
+       (checkout_proc): Add trace.  No longer set dir_to_build->just_chdir.
+       Minor reformatting.
+       (build_dirs_and_chdir): Don't create or register directories that are
+       not created.
+       * sanity.sh (*): Update tests to account for new behavior.
+
+2004-02-25  Derek Price  <address@hidden>
+
+       * buffer.c (buf_empty): New function.
+       * server.c (server): Check for unread data in buffer before closing.
+
+2004-02-25  Derek Price  <address@hidden>
+
+       * release.c (release): Restore the initial directory before and after
+       calling various sections of code that expect it to prevent corruption
+       of CVS/Entries files on release of a subdir and tell unedit() what to
+       release.
+       * sanity.sh: Add test case for release.c fix.
+       (Original patch from Matthew Ogilvie  <address@hidden>.)
+
+       * client.c (last_entries): Move global variable...
+       (call_in_directory): ...here (now a local variable).  Remove test that
+       always evaluates to true.
+       (last_dir_name): Remove unused global variable.
+
+2004-02-24  Larry Jones  <address@hidden>
+
+       * filesubr.c (xresolvepath): Fix crash in error case.
+       (Reported by Reinhard Zierke <address@hidden>.)
+
+2004-02-24  Derek Price  <address@hidden>
+
+       * sanity.sh (crerepos): Fix it so that it ignores the user's
+       .cvsrc file (.cvsrc "checkout -r" used to cause the "rm -r 1"
+       command to print warnings and wait for input).
+       (Original patch from Matthew Ogilvie  <address@hidden>.)
+
+       * sanity.sh (reposmv, parseroot, devcom3, binwrap3):
+       s/_SAVED\>/_save/ for consistency.
+
+2004-02-24  Derek Price  <address@hidden>
+
+       * sanity.sh (taginfo-newfmt-examine-2): Correct expected results.
+
+2004-02-23  Larry Jones  <address@hidden>
+
+       * sanity.sh (taginfo-examine-1): Correct expected results.
+
+2004-02-20  Derek Price  <address@hidden>
+
+       * subr.c (expand_string): Use x2realloc() from GNULIB for core
+       functionality.
+
+2004-02-20  Derek Price  <address@hidden>
+
+       * subr.c (set_nonblock_fd): Move back to...
+       * server.c: ...here.
+       * cvs.h: Remove protos for the above two functions.
+       * buffer.c (stdio_buffer_shutdown): Remove unexessary and possibly
+       dangerous check for unread data on a pipe with a nonblock read.
+
+2004-02-20  Larry Jones  <address@hidden>
+
+       * tag.c (check_fileproc): Remove unused variable.
+
+2004-02-20  Derek Price  <address@hidden>
+
+       * ChangeLog, commit.c, filesubr.c, rcs.c, root.c, sanity.sh, subr.c,
+       update.c: Remove VIM editor commands.
+
+2004-02-20  Derek Price  <address@hidden>
+
+       Import xalloc module from GNULIB, as well as its remaining unimported
+       dependency, the exitfail module.
+
+       * cvs.h: #include "xalloc.h".
+       * subr.c (xmalloc, xrealloc, xstrdup): Remove these functions.
+
+2004-02-20  Larry Jones  <address@hidden>
+
+       * hash.h (struct node): Change data from char * to void *, change
+       all callers.
+
+       * run.c (cmdlinequote, cmdlineescape): Use prototype in definition
+       to match cvs.h.
+       (format_cmdline): UNIQUE_*_TYPE_* macros imply HAVE_*, so no need
+       to check for both.  Remove duplicate code for size_t and ptrdiff_t.
+
+       * tag.c (check_fileproc): Remove spurious invalid cast, clean up
+       coding style.
+
+       * commit.c (precommit_list_proc): Remove vestigial prototype.
+
+2004-02-19  Derek Price  <address@hidden>
+
+       * run.c (format_cmdline): Don't accept printf `L' modifier at all when
+       long doubles are not available.  Allow UNIQUE_FLOAT_TYPE_LONG_DOUBLE to
+       imply HAVE_LONG_DOUBLE.
+
+2004-02-19  Derek Price  <address@hidden>
+
+       * run.c: Remove include of stddef.h - it is already included via
+       lib/system.h.
+
+2004-02-19  Larry Jones  <address@hidden>
+
+       * run.c: Include the appropriate headers for wchar_t and wint_t,
+       create a typedef for convproc and use it (required for va_arg),
+       add explicit comparisons to keep gcc -Wall happy, remove unused
+       variables.
+       (format_cmdline): Fix bugs and portability problems.
+       * tag.c: Remove unused global variable.
+       (pretag_proc): Correct call to format_cmdline: %hhc is not a valid
+       printf format (should just be %c) and NULL must be cast to the correct
+       type in a varargs call.
+       (pretag_list_to_args_proc): Initialize arg to keep gcc -Wall happy,
+       remove unused variable, add auxiliary variable to simplify code,
+       clean up coding style.
+
+       * commit.c (precommit_list_to_args_proc): Initialize arg to keep
+       gcc -Wall happy, remove unused variable, clean up coding style.
+       (precommit_proc): Cast NULL to the correct type in varargs call.
+       * edit.c (notify_proc): Remove unused variables.
+       * expand_path.c (expand_path): Initialize inquotes, add explicit
+       comparison to keep gcc -Wall happy.
+       * logmsg.c: Remove unused global variables and function decl.
+       (do_verify): Add explicit comparison to keep gcc -Wall happy,
+       clean up coding style.
+       (logmsg_list_to_args_proc): Initialize arg to keep gcc -Wall happy,
+       remove unused variable.
+       (logfile_write): Cast NULL to the correct type in varargs call.
+       (verifymsg_proc): Cast NULL to the correct type in varargs call.
+
+2004-02-19  Derek Price  <address@hidden>
+
+       * run.c (format_cmdline): Correct some typos and cut/paste errors.
+       Switch on HAVE_WINT_T for Solaris.  Switch on HAVE_INTMAX_T for other
+       pre-C99 platforms.
+
+2004-02-19  Derek Price <address@hidden>
+
+       * sanity.sh (crerepos): Correct comment.
+
+2004-02-19  Derek Price <address@hidden>
+
+       * logmsg.c (verifymsg_proc), run.c (format_cmdline): Plug memory leak.
+       (Report from Mark D. Baushke <address@hidden>.)
+
+       * run.c (format_cmdline): Simplify two expressions.
+
+2004-02-19  Larry Jones  <address@hidden>
+
+       * login.c (password_entry_operation): Initialize line.
+
+2004-02-19  Derek Price  <address@hidden>
+
+       * sanity.sh (tests): Add errmsg3.
+
+2004-02-19  Derek Price  <address@hidden>
+
+       * sanity.sh (errmsg3): Don't create directories named `tmp' in
+       $TESTDIR to avoid conflicts with the default value of $TMPDIR.
+
+2004-02-19  Derek Price  <address@hidden>
+
+       * sanity.sh (crerepos): Don't create directories named `tmp' in
+       $TESTDIR to avoid conflicts with the default value of $TMPDIR.
+
+2004-02-19  Derek Price  <address@hidden>
+
+       * sanity.sh (directory_cmp): Use $TESTDIR for temporary files, like the
+       dotest functions.
+
+2004-02-19  Derek Price  <address@hidden>
+
+       * sanity.sh: No longer allow user override of $tmp.  Set $TMPDIR to a
+       directory under $TESTDIR, as for $HOME, but still allowing for user
+       override.  Check for cvs-serv* directories under $TMPDIR rather than
+       $tmp at the end of the script.
+
+2004-02-19  Derek Price <address@hidden>
+
+       * run.c (cmdline_quote): Plug memory leak.
+       (format_cmdline): Don't bother with freeing memory before a call to
+       error() which will exit.  Plug memory leak.
+       (Report from Mark D. Baushke <address@hidden>.)
+
+2004-02-18  Derek Price <address@hidden>
+
+       * run.c (format_cmdline): Move variable declaration to a position more
+       acceptable to the ANSI C standards.
+
+2004-02-17  Derek Price <address@hidden>
+
+       * commit.c (precommit_proc): Installed new format_cmdline() instead
+       of the old, nonuniform info file interpreter.  Support for new user
+       data field in Parse_Info() callbacks.  Use cmdlinequote() instead of c.
+       10 lines of quote processing.
+       * cvs.h (expand_path):  Update proto.
+       (UseNewInfoFmtStrings): New global variable to keep track of the config
+       option of the same name.
+       (format_cmdline): Added function prototypes and type definitions to
+       support this new function.
+       * edit.c (notify_proc):  Added the formatsafe flag to an
+       expand_path() call.  Installed new format_cmdline() instead of the old,
+       nonuniform info file interpreter.  Support for new user data field in
+       Parse_Info() callbacks.
+       * expand_path.c (expand_path):  Added the formatsafe flag to and some
+       code to double up '%'s in variable subs if formatsafe is set on the
+       presumption that the variables that expand_path() subs into the string
+       are already working paths to files or whatever.  It should be quoting
+       too but I haven't done that yet.
+       * logmsg.c (title_proc, logmsg_list_to_args_proc, update_logfile_proc,
+       rcsinfo_proc, editinfo_proc, verifymsg_proc, logfile_write): Installed
+       new format_cmdline() instead of the old, nonuniform info file
+       interpreter.  Support for new user data field in Parse_Info()
+       callbacks.
+       * main.c:  Added global UseNewInfoFmtStrings variable to keep track
+       of the config file option of the same name (see above).
+       * mkmodules.c (commitinfo_contents, editinfo_contents,
+       taginfo_contents, verifymsg_contents, loginfo_contents: Document new
+       format string features.
+       (config_contents): Added new UseNewInfoFmtStrings option with default
+       of yes.
+       * modules.c (do_module):  Added the formatsafe flag to an
+       expand_path call.
+       * parseinfo.c (Parse_Info, parse_config):  Added the formatsafe flag
+       to an expand_path() call.  Added handling for UseNewInfoFmtStrings
+       option in the config file.  Set to no, modifications needed to set to
+       yes are harmless.  Set to yes, modifications needed for full
+       compatibility cause deprecation warnings.  Eliminating deprecation
+       warnings should allow executable to be compile with
+       --disable-old-info-format-support passed into configure.  Added user
+       data field (closure) which is passed through into callproc to
+       Parse_Info().
+       * run.c (format_cmdline): New function to provide uniform handling of
+       *info file scripting hook lines.
+       (cmdline_bindings_hash_node_delete,
+       (cmdlineescape, cmdlinequote): New functions to "quote" and "escape"
+       strings like you would to get them past a command line parser as an
+       argument.
+       (run_setup): Made quote aware
+       * sanity.sh (info, taginfo, config, pserver, lockfiles, reserved):
+       New tests for the new format string functionality as well as the
+       support of backwards compatibility.  Had to alter a few of the tests to
+       not care which version of some of the (shared) config files they'd just
+       checked in so that the tests can be run in any order.
+       (taginfo): Add tests for file names with spaces in them.
+       * server.c (template_proc): Support for new user data field in
+       Parse_Info() callbacks.
+       * tag.c (pretag_proc): Installed new format_cmdline() instead of the
+       old, nonuniform info file interpreter.  Add support for new user data
+       field in Parse_Info() callbacks.
+       (check_fileproc): Add proper tag_info struct as node data rather than a
+       single revision number in a string and pass tlist properly rather than
+       in a global variable.
+       (check_filesdone_proc): Altered to use non-global tlist.
+       (tag_delproc): Handle new tag_info struct data members.
+       (pretag_list_to_args_proc): Ditto.
+       (pretag_list_proc): Deleted.
+       * wrapper.c (Parse_Info): Added the formatsafe flag to an expand_path()
+       call.
+
+2004-02-17  Derek Price  <address@hidden>
+
+       * sanity.sh: Check for $PWD != $TESTDIR after each set of tests rather
+       than once at the end.  Check that there are no cvs-serv* directories in
+       $tmp after each set of remote tests.
+
+2004-02-17  Derek Price  <address@hidden>
+
+       * sanity.sh: Don't check for an empty $TESTDIR - if $TESTDIR was empty
+       then the preceding call to mkdir would have failed anyhow.
+
+2004-02-17  Larry Jones  <address@hidden>
+
+       * log.c (rlog_proc): Fix (harmless) uninitialized variable.
+
+       * sanity.sh (basicc): Add tests pointing out defective handling
+       of the Entries file.
+
+2004-02-17  Derek Price  <address@hidden>
+
+       * checkout.c (build_dir_and_chdir): Expand header comment.
+
+2004-02-15  Mark D. Baushke  <address@hidden>
+
+       * annotate.c (rannotate_proc): Plug a memory leak.
+       * log.c (log_fileproc): Ditto.
+       * tag.c (tag_fileproc): Ditto.
+       * update.c (checkout_file): Ditto.
+       * server.c (server_updated): Do not buf_free (filebuf) here.
+
+2004-02-15  Derek Price  <address@hidden>
+
+       Make error() accessible to the GNULIB functions.
+
+       * error.h: Move into the ../lib directory.
+
+2004-02-13  Derek Price  <address@hidden>
+
+       * lock.c (Reader_Lock, lock_dir_for_write): Plug memory leaks.
+       (Report from Mark D. Baushke <address@hidden>.)
+
+2004-02-13  Derek Price  <address@hidden>
+
+       * lock.c (remove_lock_files): Minor refactoring.
+
+2004-02-12  Mark D. Baushke  <address@hidden>
+
+       * lock.c (lock_exists): Plug a memory leak.
+
+2004-02-12  Derek Price  <address@hidden>
+
+       * modules.c: Reformat comment and line to fit in 80 chars.
+
+2004-02-12  Mark D. Baushke  <address@hidden>
+
+       * server.c (do_cvs_command): Plug a memory leak.
+       (Use buf_free() rather than free().)
+
+2004-02-12  Derek Price  <address@hidden>
+
+       * sanity.sh (lockfiles): Reformat a comment for 80 characters.  Fix a
+       few minor issues that caused tests to fail in remote mode.  Clean up
+       after test.
+
+2004-02-12  Derek Price  <address@hidden>
+
+       * lock.c (unlock_proc): Don't expect an int in closure as it is not
+       condoned in the C standard.  For now, just assume zero since this
+       function is currently only called from one location.
+       (remove_locks): Pass NULL for the closure argument to unlock_proc().
+
+2004-02-12  Derek Price  <address@hidden>
+
+       * lock.c (unlock_proc): Use closure as true/false free_repository
+       free_repository argument to remove_lock_files.  Move to a position
+       above remove_locks and delete prototype.
+       (remove_locks): Don't free storage, as specified in our header comment,
+       in order to avoid a seg fault that could otherwise occur after waiting
+       on a lock.
+       * sanity.sh (lockfiles): Add tests for all operation (read, read
+       promotably, or write) and lock combinations.
+       (Original report from Mark <address@hidden>.)
+
+2004-02-12  Larry Jones  <address@hidden>
+
+       * modules.c (_do_module): Rename to my_module to avoid reserved name.
+       * stack.c (_push, _pop, _unshift, _shift): Rename to do_*.
+
+2004-02-12  Mark D. Baushke  <address@hidden>
+
+       * commit.c (find_fileproc): Plug a memory leak.
+
+2004-02-12  Larry Jones  <address@hidden>
+
+       * lock.c (_lock_simple_remove): Rename to remove_lock_files to avoid
+       reserved name.  Fix typo (free => free_repository).
+       (lock_simple_remove): Delete; use remove_lock_files directly.
+       (lock_simple_remove_and_free): Ditto.
+       (_lock_exists): Rename to lock_exists.
+
+2004-02-11  Larry Jones  <address@hidden>
+
+       * root.c (parse_cvsroot): Set hostname in fork mode for error messages.
+       * buffer.c (stdio_buffer_shutdown): Undo previous change.
+
+2004-02-11  Mark D. Baushke  <address@hidden>
+
+       * buffer.c (buf_free): Plug a memory leak.
+       * commit.c (checkaddfile): Ditto.
+
+       * server.c (fd_buffer_shutdown): Avoid a double free().
+
+       * parseinfo.c (parse_config): Fix comments.
+
+2004-02-11  Derek Price  <address@hidden>
+
+       * buffer.c (stdio_buffer_shutdown): Add logic to avoid attempting to
+       print current_parsed_root->hostname when using the fork method.
+
+2004-02-11  Derek Price  <address@hidden>
+
+       * server.c (do_cvs_command): Simplify stream & pipe closing.
+       (Suggestion from Eric Siegerman <address@hidden>.)
+
+       * cvs.h, subr.c (set_block_fd): Remove this unnecessary function.
+
+2004-02-11  Derek Price  <address@hidden>
+
+       * checkout.c (checkout_proc): Remove unneeded variable and enclosing
+       block.
+       * modules.c (_do_modules): Minor whitespace change.
+
+2004-02-10  Derek Price  <address@hidden>
+
+       * lock.c (lock_simple_remove): Move core functionality...
+       (_lock_simple_remove): ...here.
+       (lock_simple_remove_and_free): New function.
+       (set_promotable_lock): Use new function to avoid freeing data that will 
be
+       reused.
+
+2004-02-10  Derek Price  <address@hidden>
+
+       * server.c (do_cvs_command): s/FIXCVS/FIXME/ in comment.
+       (set_block_fd, set_nonblock_fd): Move to...
+       * subr.c: ...here.
+       * cvs.h: Add protos for the above two functions.
+       * buffer.c (stdio_buffer_shutdown): Replace fgetc() which checked for
+       unread data on a pipe with a nonblock read.
+
+2004-02-10  Derek Price  <address@hidden>
+
+       * server.c (do_cvs_command): Have the server child close all the pipes
+       but the flow control pipe and wait on an EOF on the flow control pipe
+       from the parent when done to avoid a race condition that could
+       otherwise generate a SIGPIPE for the parent before the SIGCHILD when
+       the other pipes were so full after a child exited that the parent
+       attempted to write a stop byte to the flow control pipe.
+       (Original report from <address@hidden>.)
+
+2004-02-10  Derek Price  <address@hidden>
+
+       * buffer.c (stdio_buffer_shutdown): Add a helpful comment.
+
+2004-02-09  Derek Price  <address@hidden>
+
+       * lock.c (lock_simple_remove): Add comments.  Use critical sections to
+       set some variables that might otherwise cause trouble.
+       (struct lock): Correct comment.
+
+2004-02-09  Derek Price  <address@hidden>
+
+       Attempt to improve readability of code.
+
+       * lock.c (promotable_lock): Rename to...
+       (set_promotable_lock): ...this.
+       (set_promotablelock_proc): Expand header comment.
+       (Promotable_Lock): Rename to...
+       (lock_tree_promotably): ...this.
+
+2004-02-09  Derek Price  <address@hidden>
+
+       * lock.c (set_writelock_proc): Remove unused prototype.
+       (promotable_lock): Remove unneded whitespace.
+       (Promotable_Lock): Correct comments.
+
+2004-02-09  Derek Price  <address@hidden>
+
+       * sanity.sh (co-d): Update comments and tests to reflect the current
+       state of my side of my discussion with Larry Jones on how these
+       commands should behave.
+
+2004-02-09  Derek Price  <address@hidden>
+
+       * sanity.sh (emptydir): Add two new tests for how modules -d behaves
+       when a directory already exists in the user's workspace.
+       (emptydir): Add --keep functionality.
+
+2004-02-09  Derek Price  <address@hidden>
+
+       * sanity.sh (co-d): New test to prove `co -d' failure case.
+
+2004-02-05  Derek Price  <address@hidden>
+
+       * sanity.sh (recase): Fix typo that creeped in somehow between my last
+       test run and my commit.
+
+2004-02-04  Derek Price  <address@hidden>
+
+       * modules.c (do_modules): Move content to and make this function a
+       wrapper for...
+       (_do_modules): ...this new function which can watch for infinite loops
+       in alias modules.
+       * stack.c (_push, _pop, _unshift, _shift, push_string, pop_string,
+       unshift_string, shift_string): New
+       functions.
+       * stack.h (push_string, pop_string, unshift_string, shift_string: New
+       prototypes.
+       * sanity.sh (modules): Add check for nested alias loops.
+
+2004-02-04  Derek Price  <address@hidden>
+
+       * sanity.sh (recase): Update test names and comments for clarity and
+       consistency.
+
+2004-02-04  Derek Price  <address@hidden>
+
+       * sanity.sh (recase): Restore some cruft necessary when clients know
+       they are on case insensitive systems.
+
+2004-02-03  Derek Price  <address@hidden>
+
+       Preserve the case of checked out directories in a path as well as file
+       names for client communication with the server.
+
+       * Makefile.am (cvs_SOURCES): Add stack.c & stack.h.
+       * stack.c, stack.h: New files.
+       * cvs.h: Include stack.h.
+       * client.c (send_file_names): Preserve the case of directories in a
+       path as well as file names for communication with the server.
+
+       * Makefile.in: Regenerated.
+
+2004-02-04  Derek Price  <address@hidden>
+
+       * checkout.c (find_fileproc): Update error message for consistency.
+       * sanity.sh (basica): Update to compensate.
+
+2004-02-04  Derek Price  <address@hidden>
+
+       * classify.c (Classify_File): Update header comment block and reformat
+       prototype for readability in 80 character widths.
+
+2004-02-02  Derek Price  <address@hidden>
+
+       * sanity.sh (*): Update tests for the new status message from update.c.
+
+2004-02-02  Derek Price  <address@hidden>
+
+       * sanity.sh (join-rm): New test for issue #104 & #159.
+
+2004-02-02  Derek Price  <address@hidden>
+
+       * update.c (join_file): Correct status message for consistency.
+
+2004-02-02  Derek Price  <address@hidden>
+
+       Continue removal from server of handling of case insensitive clients.
+
+       * cvs.h: Remove extern declaration of ign_case.
+       * ignore.c (ign_case): Remove declaration.
+       (ign_name): Remove support for ign_case.
+       * server.c (serve_case): Ditto.
+       (requests): No longer support the "Case" request.
+       * rcs.c (locate_rcs): Remove reference to GLOBAL in function header
+       comment.
+
+2004-02-02  Derek Price  <address@hidden>
+
+       * client.c (send_file_names): Restore prescribed client handling of the
+       FILENAMES_CASE_INSENSITIVE switch.
+
+2004-01-25  Derek Price  <address@hidden>
+
+       * server.c (kserver_authenticate_connection): Fix call to
+       switch_to_user().
+       (Original patch from Alexey Mahotkin <address@hidden>.)
+
+2004-01-22  Derek Price  <address@hidden>
+
+       * modules.c (do_module): Strip trailing slashes before checking for
+       infinite alias loops.
+       * sanity.sh (modules): Tests for response to infinite alias loops.
+
+2004-01-17  Mark D. Baushke  <address@hidden>
+
+       * logmsg.c (do_verify): Eliminate double-free bug.
+       (Original patch from Gerald Combs.)
+
+2004-01-12  Mark D. Baushke  <address@hidden>
+
+       * lock.c (lock_name): Deal with potentially NULL string pointers
+       in calls to TRACE.
+       (promotable_lock): Ditto.
+       (set_lock): Ditto.
+       * sanity.sh (trace): Update to latest patterns.
+
+2004-01-07  Larry Jones  <address@hidden>
+
+       * checkout.c (safe_location): Remove unused variable(s).
+       * lock.c (lock_tree_for_write): Ditto.
+       * rcs.c (RCS_checkin): Ditto.
+       * subr.c (compare_revnums): Ditto.
+       * tag.c (tag_check_valid): Ditto.
+       * mkmodules.c (init): Initialize err and return it rather than 0.
+       * server.c (do_cvs_command): Only define and set max_command_fd if
+       we're actually going to use it.
+
+2004-01-06  Mark D. Baushke  <address@hidden>
+
+       * socket-client.c (socket_buffer_initialize): Fix argument
+       declaration for VMS compiler.
+       (Patch submitted from Michael Lemke
+       <address@hidden>.)
+
+2004-01-01  Larry Jones  <address@hidden>
+
+       * zlib.c (read_and_gzip, gunzip_and_write): Fix potential buffer
+       overruns, use names for magic numbers.
+       (Original patch from Jeff Downs <address@hidden>.)
+
+2003-12-17  Larry Jones  <address@hidden>
+
+       * main.c (main): Don't reference isremote without CLIENT_SUPPORT.
+       (Patch from Jim Salter <address@hidden>.)
+
+2003-12-18  Derek Price  <address@hidden>
+
+       * server.c (switch_to_user): SysLog attempts to root from pserver.
+
+2003-12-18  Derek Price  <address@hidden>
+
+       * server.c (switch_to_user): Don't allow CVS to run as root in pserver
+       mode.
+       (Original patch from Wichert Akkerman via Bradley M Kuhn
+         <address@hidden>.)
+       * sanity.sh (pserver): Check for bad root error message.
+
+2003-12-17  Larry Jones  <address@hidden>
+
+       * run.c (close_on_exec): fcntl is not documented to return 0 for
+       success (and QNX doesn't), only -1 for error.
+       (Patch from George Refseth <address@hidden>.)
+
+2003-12-10  Larry Jones  <address@hidden>
+
+       * rcs.c: Cleanup HAVE_MMAP code in preparation for falling back to
+       stdio if mmap fails on large files.
+
+2003-12-10  Mark D. Baushke  <address@hidden>
+
+       * tag.c (tag_check_valid): Fix typo.
+       (Patch from Rob Clevenger <address@hidden>.)
+
+2003-12-09  Derek Price  <address@hidden>
+
+       Rewrite code to use promotable write locks to avoid locking more than a
+       directory at a time for a full write.
+
+       * cvs.h (lock_tree_for_write): Rename to...
+       (lock_tree_promotably): ...this.
+       (Simple_Lock_Cleanup): New prototype.
+       * lock.c: Remove some unneeded prototypes.  Some function
+       reorganization.
+        (struct lock): Make repository const.  Add file1 & file2 to
+       track lock names.  Keep track of when repository needs to be freed.
+       (promotablelock, global_writelock): New globals.
+       (locked_dir, locked_list): Remove unneeded globals.
+       (lock_name): Take const args.
+       (remove_locks): Update comment.  Move readlock cleanup...
+       (Simple_Lock_Cleanup): ...to this new function which also cleans up
+       writelocks.
+       (Lock_Cleanup): No need to clean up after the obsolete locked_dir &
+       locked_list.
+       (lock_simple_remove): Use new lock name cache.  Set lock->repository to
+       NULL to show locks are removed.  Free repository name when necessary.
+       (Reader_Lock): Copy xrepository argument so we do not need to trust the
+       caller not to dispose of it.  Use lock name cache.  Factor code to set
+       the global readlock variable...
+       (set_readlock_name): ...to this new function so it can be used by
+       promotable_lock too.
+       (readers_exist): Factor common code and make wrapper for...
+       (_lock_exists): ...this new function.
+       (promotable_exists): Wrapper for _lock_exists().
+       (write_lock): Rename to...
+       (promotable_lock): ...this and tweak for new behavior.
+       (set_writelock_proc): Rename to...
+       (set_promotablelock_proc): ...this and tweak for new functionality.
+       (Write_Lock): Rename to...
+       (Promotable_Lock): ...this and tweak for new functionality.
+       (set_lock): Add trace.
+       (lock_tree_for_write): Rename to...
+       (lock_tree_promotably): ...this and tweak for new functionality.
+       (lock_dir_for_write): Lock only one directory at a time in a promotable
+       lock aware fashion using new interfaces.
+       * admin.c, commit.c, edit.c, watch.c:
+       s/lock_tree_for_write/lock_tree_promotably/.
+       s/CVS_LOCK_NONE/CVS_LOCK_WRITE/ in calls to start_recursion that used
+       to rely on lock_tree_for_write() to obtain their write locks.  Remove
+       some unnecessary typecasting.
+       * recurse.c (do_recursion): Use Simple_Lock_Cleanup() rather than
+       Lock_Cleanup to avoid removing promotable locks.
+       * server.c (do_cvs_command): Add traces.
+       (server): Add new way to sleep the parent server process for connecting
+       a debugger.
+       * sanity.sh (lockfiles, multiroot2): Update tests to accomodate minor
+       trace inconsistencies.
+
+2003-12-09  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (trace): Remove trace from the set of tests to run.
+       The regexps used take an excessive amount of time and need to
+       be simplified.
+
+2003-12-08  Mark D. Baushke  <address@hidden>
+
+       * rcs.c (rcsbuf_ftell): Rename to...
+       (rcsbuf_ftello): this.
+       (rcsbuf_cache_open): Use off_t rather than long as the pos
+       argument. Use fseeko rather than fseek and ftello rather than
+       ftell.
+       * rcs.h (struct rcsnode): delta_pos is now an off_t type.
+
+2003-12-03  Derek Price  <address@hidden>
+
+       * sanity.sh (recase): Add some clarifying comments.
+
+2003-12-03  Larry Jones  <address@hidden>
+
+       * expand_path.c (expand_variable): Expand ${CVSROOT} to just the
+       directory like it's supposed to be.
+       (Reported by Michael S. Tsirkin <address@hidden>.)
+
+2003-11-26  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (recase-17sscs): Use ${CVSROOT_DIRNAME} in pattern.
+
+2003-11-26  Derek Price  <address@hidden>
+
+       Remove server support for case insensitive clients.  Includes some
+       merges from 1.11.x.
+
+       * add.c, client.c, cvs.h, rcs.c, subr.c: Remove unnecessary support for
+       case insensitive clients.
+       * sanity.sh (tests): Add recase.
+       (recase): New test of cases that might be expected to cause problems
+       with a heterogeneous mix of case sensitive and case insensitive clients
+       and servers.
+
+2003-11-26  Derek Price  <address@hidden>
+
+       * sanity.sh (modules3-2): Simplify syntax that may have given Cygwin
+       intermittent conniptions.
+
+2003-11-25  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (binfiles2): Correct yet another Cygwin difficulty.
+
+2003-11-25  Derek Price  <address@hidden>
+
+       * sanity.sh (release): Perform forgotten cleanup.
+
+2003-11-25  Derek Price  <address@hidden>
+
+       * sanity.sh (env): Enable to work with $remotehost.
+
+2003-11-25  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (trace-19): Separate stdout and stderr to workaround
+       problems on SGI IRIX.
+       (crecrepos): Use 'unset DISPLAY' to avoid problems with ssh
+       X11Forwarding configurations.
+
+2003-11-25  Derek Price  <address@hidden>
+
+       * sanity.sh (pserver, server, server2): Save $servercvs and use the
+       local $testcvs for these tests.
+
+2003-11-25  Derek Price  <address@hidden>
+
+       * commit.c (commit_fileproc): Reword comment.
+
+2003-11-25  Derek Price  <address@hidden>
+
+       * sanity.sh (devcom3-9ar): Ignore the stderr output since it varies
+       considerably between platforms.
+
+2003-11-25  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (CVS_RSH): Read config file sooner to pickup RSH_DFLT
+       for use in setting CVS_RSH variable.
+       * sanity.config.sh.in (RSH_DFLT): Use new substitution variable.
+       * Makefile.am (localcheck, remotecheck): Depend on sanity.config.sh.
+       * Makefile.in: Regenerate for new Makefile.am.
+
+       * update.c (join_file): Deal with rev1 == NULL due to rev1 merge tag
+       being missing from the current file.
+       * sanity.sh (join6): New tests for this case.
+       (trace): Renumber test cases.
+
+2003-11-24  Larry Jones  <address@hidden>
+
+       * diff.c (diff_file_nodiff): use_rev1 does *not* imply that diff_rev1
+       is not null, diff_date1 could be set instead (ditto for use_rev2).
+       (Reported by <address@hidden>.)
+
+2003-11-24  Mark D. Baushke  <address@hidden>
+
+       * client.c (connect_to_forked_server): Avoid passing NULL strings
+       to TRACE. Calling printf("%s",NULL) is not defined and may
+       segfault on some systems.
+       * diff.c (diff_file_nodiff): Ditto.
+       * main.c (main): Ditto.
+       * modules.c (do_module): Ditto.
+       * patch.c (patch_proc): Ditto.
+       * rcs.c (RCS_cmp_file): Ditto.
+       * recurse.c (start_recursion): Ditto.
+       * root.c (parse_cvsroot): Ditto.
+       * server.c (dirswitch, server_pathname_check, dirswitch,
+       serve_directory):  Ditto.
+       * tag.c (rtag_proc, check_fileproc, tag_check_valid): Ditto.
+       * sanity.sh (trace): New testcase to test the -t option.
+       (RCSDATE, ISODATE, PFMT): New variables in aid of the trace tests.
+       (dotest_fail_sort): New function in aid of the trace tests.
+       (template): Fix cleanup.
+
+2003-11-24  Derek Price  <address@hidden>
+
+       * sanity.sh (modes3): Skip modes3-5 entirely under Cygwin since
+       permisions are broken there.  This change removes most of the earlier
+       Cygwin differentiation in this test ($cygwin_hack & $cygwin_hack2) in
+       favor of skipping the test entirely.
+
+2003-11-24  Derek Price  <address@hidden>
+
+       * sanity.sh: Add `-h <hostname>' option to enable testing across a
+       :ext: connection to another host.  Warn when `-h' is specified without
+       $TESTDIR.  Leave $TESTDIR intact when it looks absolute since it may
+       contain symlinks.  Allow $CVS_SERVER to be overridden via the
+       environment for `-h'.  Default $CVS_RSH to `ssh'.
+       (*): Use $CVS_RSH to perform certain commands on the remote host (esp.
+       `ln -s' and `chmod') when `-h' is specified to work around
+       incompatibilities with CygWin & Samba.  Add a few other minor
+       workarounds for Cygwin bugs.
+
+       (newroot): New function.
+       (*): Use newroot when appropriate.
+
+2003-11-21  Larry Jones  <address@hidden>
+
+       * hash.c (printnode, printlist): Cast %p arguments to void * as
+       required by the C standard.
+
+2003-11-21  Larry Jones  <address@hidden>
+
+       * recurse.c (start_recursion, do_recursion): Cast %p arguments to
+       void * as required by the C standard.
+
+2003-11-19  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_getrevtime): Add error checking; cleanup.
+
+2003-11-18  Derek Price  <address@hidden>
+
+       * socket-client.c (socket_buffer_initialize): Rename poorly named `n'
+       to a slightly more descriptive `sbuf'.
+       (Suggestion from Larry Jones  <address@hidden>.)
+
+2003-11-18  Derek Price  <address@hidden>
+
+       * socket-client.c (socket_buffer_initialize): Pass in the socket
+       closure we allocate.
+       (Report from Larry Jones  <address@hidden>.)
+
+2003-11-18  Derek Price  <address@hidden>
+
+       * modules.c (do_module): Reject absolute paths.
+       (Report and suggested fix from Tony Hoyle <address@hidden>.)
+
+       * sanity.sh (abspath2): Check for the above.
+       (spacefiles): Remove tests that expect absolute paths to files in the
+       top level repository directory to work.
+       (tests): Add abspath2.
+
+2003-11-18  Derek Price  <address@hidden>
+
+       * socket-client.c (socket_buffer_initialize): Correct a typo that
+       happened to compile.
+       (Report and suggested fix from Patrick Brown <address@hidden>.)
+
+2003-11-13  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_delete_revs): It's `&&', not `and'.
+
+2003-11-13  Derek Price  <address@hidden>
+
+       * sanity.sh: Create the empty log to make it easier to tail immediately
+       after the script is started.
+
+2003-11-13  Derek Price  <address@hidden>
+
+       * sanity.sh (exit_help): Correct help to specify `-H' and not `-h' as
+       the help option.
+
+2003-11-13  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_delete_revs): Don't use the WOE32 kludge which refuses to
+       delete revisions from bvinary files on Cygwin.  I'm not sure what the
+       kludge was trying to avoid, but commenting it out causes the test suite
+       to pass.
+
+2003-11-12  Derek Price  <address@hidden>
+
+       * main.c (main): Remove a trailing newline from the version string.
+       Replace multiple calls to fputs to a single call reformated to C89
+       specifications.  Remove some typecasts unecessary under C89.
+       * sanity.sh (version): Remove trailing newline from the version string.
+
+2003-11-12  Derek Price  <address@hidden>
+
+       * add.c (add): Allocate more space for the string I added characters
+       to.
+       (Report from Mark D. Baushke <address@hidden>.)
+
+2003-11-11  Derek Price  <address@hidden>
+
+       * add.c (add), classify.c (Classify_File), client.c (update_entries),
+       repos.c (Name_Repository): Use consistent quoting in error messages.
+       Misc reformatting.
+       * sanity.sh: Update to match.
+
+2003-11-10  Derek Price  <address@hidden>
+
+       * commit.c (find_fileproc, check_fileproc):  Refuse to remove files
+       when the file exists in the sandbox.  This used to cause data loss.
+       (Report from Andreas Reifschneider <address@hidden>.)
+
+       * sanity.sh (rmadd3): Update to match.  Expand comments.
+
+2003-11-10  Derek Price  <address@hidden>
+
+       * sanity.sh (rmadd3): Test the behavior of commit after the
+       add/replace.
+       (Report from Andreas Reifschneider <address@hidden>.)
+
+2003-11-10  Derek Price  <address@hidden>
+
+       * sanity.sh (rmadd3): Fix another typo.
+
+2003-11-10  Mark D. Baushke  <address@hidden>
+
+       * recurse.c (do_dir_proc): Set xframe.repository to NULL after a
+       call to free().
+
+2003-11-10  Derek Price  <address@hidden>
+
+       * sanity.sh (rmadd3): Fix typo.
+
+2003-11-10  Derek Price  <address@hidden>
+
+       * sanity.sh (rmadd3): New tests that confirms that CVS refuses to
+       delete a file it thinks was already removed.
+       (Report and test from Andreas Reifschneider
+       <address@hidden>.)
+
+2003-11-03  Derek Price  <address@hidden>
+
+       * sanity.sh (server): Test that the global `-l' option is ignored
+       nonfatally.
+
+2003-11-03  Derek Price  <address@hidden>
+
+       * server.c (serve_global_option): Warn that -l is being ignored rather
+       than exiting fatally due to backwards compatibility complaints from
+       administrators.
+
+2003-11-01  Larry Jones  <address@hidden>
+
+       * filesubr.c (xcmp): Make sure S_ISLNK exists before calling it.
+       (Reported by Paul Edwards <address@hidden>.)
+
+2003-10-31  Derek Price  <address@hidden>
+
+       * sanity.sh: s/${TESTDIR}/cvsroot/${CVSROOT_DIRNAME}/.
+
+2003-10-28  Derek Price  <address@hidden>
+
+       * sanity.sh (devcom): Renumber tests and use dotest function.
+
+2003-10-28  Derek Price  <address@hidden>
+
+       * sever.h: Add the standard copyright notice.
+
+2003-10-28  Derek Price  <address@hidden>
+
+       * lock.c: Remove some suggestions which have already been implemented
+       or which have become obsolete from the header comment.
+
+2003-10-26  Derek Price  <address@hidden>
+
+       * sanity.sh (join6): Fix a few typos in the last test and remove a
+       misplaced test.
+
+2003-10-25  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (parseroot): Use dokeep function.
+
+       * sanity.sh (parseroot): Perform this test in a subdirectory.
+       It should avoid problems on case-insensitive systems where
+       CVSROOT and cvsroot are the same directory (eg, MacOS X).
+
+2003-10-24  Derek Price  <address@hidden>
+
+       * update.c (join_file): Restore the optimization Mark recently removed,
+       but fix it.  Move one other optimization up since it needs to be
+       checked for first.  Add bew status messages like merge_file produces
+       when the requested diff has already been applied to the destination.
+       Expand header comment.
+       * sanity.sh (join6): Add tests for the new error messages.
+       (import-113, join-admin-2, diffmerge1): Fix collateral damage.
+
+2003-10-24  Derek Price  <address@hidden>
+
+       * update.c (merge_file): Optimize & eliminate code.
+
+2003-10-24  Derek Price  <address@hidden>
+
+       * recurse.c (do_recursion): Assert that ignoring the return value of
+       Name_Repository is not a memory leak.
+
+2003-10-24  Derek Price  <address@hidden>
+
+       * repos.c (Name_Repository): Replace a FIXME with the improved error
+       message it requested.
+       * sanity.sh (errmsg3): New test for the above.
+
+2003-10-24  Derek Price  <address@hidden>
+
+       * patch.c (patch_proc): Avoid memory leak.
+       (Patch from Mark D. Baushke  <address@hidden>.)
+       (patch_proc): Reformat a few long lines for readability.
+
+2003-10-24  Derek Price  <address@hidden>
+
+       * vers_ts.c (Version_TS): Move variable declaration inside the only
+       block where it is used and remove uneeded reinitialization.
+
+2003-10-24  Derek Price  <address@hidden>
+
+       * server.h: s/^extern// off of function declarations per HACKING.
+       Reformat protos for readability.
+
+2003-10-24  Derek Price  <address@hidden>
+
+       * vers_ts.c (Version_TS): Reformat declaration and expand header
+       comment.
+
+2003-10-24  Derek Price  <address@hidden>
+
+       * update.c (merge_file): Remove code that hasn't been used since CVS
+       used an external RCS (1.9.something).
+
+2003-10-23  Mark D. Baushke  <address@hidden>
+
+       * update.c (join_file): Do the -jrev1 -jrev2 merge even when
+       the file is already at rev2.
+       * sanity.sh (join6): New testcase for above.
+       (Suggested by Paul Edwards, from somewhere in Australia.)
+       (import): Fix collateral damage.
+
+2003-10-23  Derek Price  <address@hidden>
+
+       * sanity.sh (fail): Refer the user to the `TESTS' and `check.log' files
+       on failure.
+
+2003-10-22  Derek Price  <address@hidden>
+
+       * recurse.c (start_recursion): Reformat function declaration and
+       expand comments.
+       (Original patch from Terrence Enger <address@hidden>.)
+
+2003-10-22  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_LDADD): Add $(LIBINTL) for gettext.
+       * Makefile.in: Regenerated.
+
+2003-10-19  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (admin-31): Fix more typos.
+
+2003-10-18  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (admin): Fix a typo.
+
+       * admin.c (admin_fileproc): Restore the ':' character in the
+       -mtag:message admin argument even if the tag does not exist so
+       that other files with the tag will be found. Also, be more
+       paranoid that a symbolic tag actually points to a version that
+       exists.
+       (Reported by Rodolfo Schulz de Lima <address@hidden>.)
+       * sanity.sh (admin): Test these changes.
+
+2003-10-17  Mark D. Baushke  <address@hidden>
+
+       * admin.c (admin_fileproc): Force tag match on admin
+       -mversion:message rather than altering the wrong log message.
+       (Patch from "Rodolfo Schulz de Lima" <address@hidden>.)
+       * sanity.sh (admin): Test case for it.
+
+2003-10-15  Larry Jones  <address@hidden>
+
+       * commit.c (commit_fileproc, finaladd): Don't call fixaddfile()
+       if the RCS file didn't get created at all.
+       (Reported by David Wood <address@hidden>.)
+
+2003-10-14  Derek Price  <address@hidden>
+
+       Port to pedantic POSIX 1003.1-2001 hosts, such as Debian GNU/Linux
+       testing with _POSIX2_VERSION=200112 in the environment.
+
+       * sanity.sh: Use 'sed 1q', not 'head -1'.
+       (Patch from Paul Eggert <address@hidden>.)
+
+2003-10-10  Derek Price  <address@hidden>
+
+       * lock.c (set_lock): Clarify comment.
+
+2003-10-11  Larry Jones  <address@hidden>
+
+       * server.c (server_cleanup): Replace CVS_CHDIR call: some systems
+       won't allow you to delete a directory tree containg your working
+       directory.
+
+2003-10-10  Derek Price  <address@hidden>
+
+       * server.c (cvs_output, cvs_outerr): Protect calls to syslog()
+       with the usual preprocessor condition: HAVE_SYSLOG_H.
+       (Original patch from Terrence Enger <address@hidden>.)
+
+2003-10-09  Derek Price  <address@hidden>
+
+       * cvs.h: s/^extern// off of function declarations per HACKING.
+
+2003-10-08  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Add history.h.
+       * history.c: Include history.h.  Add the `P' record types to more
+       comments. s/ALL_REC_TYPES/ALL_HISTORY_REC_TYPES/.
+       (usage): Reference ALL_HISTORY_REC_TYPES rather than using a separate
+       string literal.
+       (report_hrecs): Handle `P' record type.
+       (ALL_REC_TYPES): Rename and move...
+       * history.h (ALL_HISTORY_REC_TYPES): ...here.
+       * mkmodules.c: Include history.h.
+       (config_contents): Update contents of and references to LogHistory
+       records to use ALL_HISTORY_REC_TYPES.
+       * sanity.sh (basic2-64): Update to include history records of type `P'.
+
+       * Makefile.in: Regenerated.
+
+2003-10-08  Derek Price  <address@hidden>
+
+       * update.c (patch_file): Correct spelling and punctuation in comment.
+       Update some lines to fit in 80 characters.
+
+2003-10-08  Derek Price  <address@hidden>
+
+       * lock.c (remove_locks): Copy global struct and set status variable to
+       NULL before calling disposal functions that might try to access it
+       during calls to error(1,...).
+
+2003-10-08  Larry Jones  <address@hidden>
+
+       * history.c (history): Don't conflate -e with -x since the client's
+       idea of what -e means may not match the server's.
+       (Reported by Frank Hemer <address@hidden>.)
+
+2003-10-08  Derek Price  <address@hidden>
+
+       * lock.c (Lock_Cleanup), rcs.c (rcs_cleanup), server.c
+       (server_cleanup): Expand comments about the never_run_again variable
+       and its interoperation with critical sections, exit, and interrupts.
+
+2003-10-08  Derek Price  <address@hidden>
+
+       * lock.c (remove_locks): Reduce TRACE level since this function is
+       rarely called and we can usually rely on tracing higher level
+       functions.
+
+2003-10-08  Derek Price  <address@hidden>
+
+       * lock.c (lock_name, lock_simple_remove),
+       server.c (server_pathname_check, dirswitch): Add TRACE.
+
+2003-10-08  Derek Price  <address@hidden>
+
+       * main.c: Reformat header comment to fit in 80 chars.
+
+2003-10-08  Larry Jones  <address@hidden>
+
+       * sanity.sh: Use dotest_fail instead of dotest_status for diff tests
+       since CVS only returns success/fail rather than 0/1/2 like diff does.
+
+2003-10-08  Derek Price  <address@hidden>
+
+       Fix a client/server bug introduced via the data loss fix of 2003-03-17.
+       Basically, the server was reporting ambiguous filename requests when it
+       should have been trusting the user to type the intended case or using
+       the case the client preserved in CVS/Entries before it tried to look
+       anything up in case insensitive mode.
+
+       * rcs.c (locate_rcs): Use the filename exactly as cased before
+       investigating a case insensitive lookup, per the client/server protocol
+       specification.  Expand comments.
+       * subr.c (locate_file_in_dir): This function only needs to locate files
+       case insensitively.  Expand comments.
+       * cvs.h (locate_file_in_dir): Only prototype when servers which need to
+       handle case insensitivity are being compiled.
+
+2003-10-08  Derek Price  <address@hidden>
+
+       * rcs.c (locate_rcs): Declare static.  Move to an earlier location in
+       file to avoid prototyping.
+       * rcs.h (locate_rcs): Remove proto.
+
+2003-10-08  Derek Price  <address@hidden>
+
+       * lock.c (lock_filesdoneproc): Reformat long function prototype.
+
+2003-10-07  Larry Jones  <address@hidden>
+
+       * server.c (server_cleanup): Remove old code that was commented out
+       with //, which isn't valid in C.
+
+2003-10-04  Derek Price  <address@hidden>
+
+       * exithandle.c: New file.
+       * Makefile.am (cvs_SOURCES): Add exithandle.c.
+       * cvs.h (cleanup_register, signals_register): New prototypes.
+       * lock.c (Lock_Cleanup, remove_locks), rcs.c (rcs_cleanup),
+       server.c (server_cleanup): Avoid calling twice when called from a
+       signal handler and then exit.  Avoid being interrupted while globals
+       that the signal handler might touch are in inconsistent states.  Expand
+       comments.
+       * rcs.c (rcs_internal_lockfile): Ditto. Use cleanup_register rather
+       than calling atexit() directly.
+       * main.c (main): Consolidate signal stuff into a call to
+       signals_register().  Call cleanup_register to register cleanup
+       functions rather than calling atexit() directly.
+
+       * Makefile.in: Regenerated.
+
+2003-10-04  Derek Price  <address@hidden>
+
+       * error.c, error.h: Remove error_exit() function.
+       * add.c, client.c, history.c, import.c, main.c, mkmodules.c, modules.c,
+       rcscmds.c, recurse.c, release.c, root.c, server.c, socket-client.c,
+       tag.c, update.c: s/\<error_exit *();$/exit (EXIT_FAILURE);/.  Remove
+       related comments.
+
+2003-10-04  Derek Price  <address@hidden>
+
+       * buffer.c: Reformat a few long function prototypes and lines.
+
+2003-10-04  Derek Price  <address@hidden>
+
+       * hash.c (dellist): Immediately set input pointers to NULL in case they
+       are references to global variables which might be accessed by interrupt
+       handlers.
+
+2003-10-04  Derek Price  <address@hidden>
+
+       * rcs.c (rcs_cleanup): Declare static.
+       * rcs.h (rcs_cleanup): Remove prototype.
+
+2003-10-03  Derek Price  <address@hidden>
+
+       Move calls to Lock_Cleanup to the atexit handler.
+
+       * commit.c (commit): Don't call Lock_Cleanup on error exit.
+       * error.c (error_exit): Don't call Lock_Cleanup.
+       * lock.c (Lock_Cleanup): Don't worry about recursive calls now that we
+       are using atexit for calls on exit.  Dispose locklist storage after the
+       locks are removed.  Expand comments.
+       * main.c (main): Move Lock_Cleanup call into atexit(Lock_Cleanup).
+       * server.c (server_notify): Add TRACE.
+
+2003-10-03  Derek Price  <address@hidden>
+
+       * recurse.c (start_recursion): Remove unnecessary typecasts.
+       (do_recursion): Ditto.  Add TRACE.  Expand comments.  Remove unneeded
+       parens.
+
+2003-10-03  Derek Price  <address@hidden>
+
+       * main.c (main): Dispose of old Tmpdir and Editor when specified
+       multiple times between the command line & the ~/.cvsrc file.
+
+2003-10-03  Derek Price  <address@hidden>
+
+       * lock.c (remove_locks): Eliminate unecessary typecasting.
+
+2003-10-03  Derek Price  <address@hidden>
+
+       Move calls to rcs_cleanup to the atexit handler.
+
+       * error.c (error_exit): Don't call rcs_cleanup.
+       * rcs.c: Initialize global RCS_LOCKFILE to NULL.
+       (rcs_internal_lockfile): Use atexit (rcs_cleanup) rather than
+       setting up signal handlers.
+
+2003-10-03  Derek Price  <address@hidden>
+
+       * modules.c (do_module): Format prototype.
+
+2003-10-03  Derek Price  <address@hidden>
+
+       * server.c (server_cleanup): Skip BUF_TO_NET checks as an optimization
+       when ERROR_USE_PROTOCOL is set.
+
+2003-10-03  Derek Price  <address@hidden>
+
+       * modules.c (do_module): Use TRACE.
+
+2003-10-02  Derek Price  <address@hidden>
+
+       * main.c (main): Don't free globals that might be needed by the cleanup
+       functions.
+       * server.c (server_cleanup): Only clean up when called in the parent
+       process.  Set buffers to NULL before shutting down and freeing in case
+       we are interrupted.  Improve comments.  Add TRACE.
+
+2003-10-01  Derek Price  <address@hidden>
+
+       * main.c (main): Use symbolic name for trace level.
+
+2003-10-01  Derek Price  <address@hidden>
+
+       * client.c (connect_to_forked_server): Use TRACE macro rather than the
+       old style.
+
+2003-10-01  Derek Price  <address@hidden>
+
+       * server.c (protocol): Initialize the protocol buffer to NULL so that
+       use before initialization may be detected.
+       (cvs_output, cvs_outerr): Syslog messages when the appropriate buffers
+       are not available.
+       (server_cleanup): Reorganize for a single exit point and to eliminate
+       duplicated code.  Set buf_to_net to NULL before calling the buffer
+       shutdown functions in order to force error messages into the syslog.
+       * buffer.c (stdio_buffer_close): Remove FIXME comment re syslog since
+       the calls to error should go through the cvs_outerr function anyhow.
+
+2003-09-30  Derek Price  <address@hidden>
+
+       * entries.c (WriteTemplate): TRACE on entrance to a function, not exit.
+       Don't worry about checking noexec without server support since this
+       function will then do nothing.
+
+2003-09-30  Derek Price  <address@hidden>
+
+       * update.c (do_update): Reformat function decl.  Move and merge
+       comment.
+
+2003-09-30  Derek Price  <address@hidden>
+
+       * client.h (buf_output, buf_outerr): Check that our buffers exist
+       before sending them data.
+       (buf_output_binary): Assert that the output buffer is not NULL.
+
+2003-09-30  Derek Price  <address@hidden>
+
+       * client.h, rcs.c, rcs.h, server.h: Assume __STDC__ since it is
+       defined as part of the C89 spec.
+
+2003-09-30  Derek Price  <address@hidden>
+
+       * commit.c (commit): Optimize function towards a single exit point.
+
+2003-09-30  Derek Price  <address@hidden>
+
+       * error.c (error_exit): Remove call to SYSTEM_CLEANUP.
+       * main.c (main): Set up atexit(SYSTEM_CLEANUP) rather than calling it
+       explicitly before exit.
+       * server.c (pserver_authenticate_connection): Don't call SYSTEM_CLEANUP
+       before exiting.
+
+2003-09-30  Derek Price  <address@hidden>
+
+       * error.c (exit_error): Remove call to server_cleanup.
+       * main.c (main): Call atexit(server_cleanup).  Let server_cleanup turn
+       server_active off.
+       * server.c (server_cleanup): Don't require an argument.  Fill out
+       header comment.  Unset server_active when done.
+       (server): Don't call server_cleanup - let it be called via the atexit
+       handler.
+       * server.h (server_cleanup): Update proto.
+
+2003-09-30  Derek Price  <address@hidden>
+
+       * server.c (buf_output): Don't check that the buffers exist before
+       using them since cvs_outerr does this without problems.
+
+2003-09-30  Derek Price  <address@hidden>
+
+       * server.c: Remove some unecessary function prototypes.
+
+2003-09-29  Derek Price  <address@hidden>
+
+       * rcs.c (make_file_label): Make a failure to stat a file a fatal error
+       since it signals that a later read will also fail.
+
+2003-09-29  Derek Price  <address@hidden>
+
+       * diff.c (diff_fileproc): Optimize the check for labels set by the
+       user.
+
+2003-09-26  Derek Price  <address@hidden>
+
+       * diff.c (diff): Add a FIXME re spaces in diff arguments.
+
+2003-09-25  Mark D. Baushke  <address@hidden>
+
+       * rcs.c (make_file_label): Do not return an uninitialized label.
+       (Reported by "Todd C. Miller" <address@hidden>)
+
+2003-09-24  Derek Price  <address@hidden>
+
+       * lock.c (lock_name): Remove useless prototype.
+
+2003-09-12  Derek Price  <address@hidden>
+
+       * sanity.sh (mkmodules): Correct comments.
+
+2003-09-12  Derek Price  <address@hidden>
+
+       * mkmodules.c (mkmodules): Do not pass a string which came from the
+       checkoutlist file directly to error as a format string since we don't
+       want to trust any user with access to checkoutlist with creating printf
+       format strings.  I already claimed I did this in the NEWS file.
+       (Thanks to Larry Jones for spotting my mistake.)
+       * sanity.sh (mkmodules): Test for the above.
+
+2003-09-12  Derek Price  <address@hidden>
+
+       * mkmodules.c (checkoutlist_contents): Document the optional portions
+       of this file format more accurately.
+       (mkmodules): Ditto, in comments.  Fix bug that always failed to ignore
+       whitespace before error messages.
+       * sanity.sh (mkmodules-temp-file-removal): Rename to...
+       (mkmodules): ...this and add a test of the checkoutlist error message.
+       Add cleanup step to restore checkoutlist.
+
+2003-09-08  Derek Price  <address@hidden>
+
+       * filesubr.c (cvs_temp_file): Replace a chmod 0600, which shouldn't
+       really be necessary and which provided a false sense of security, with
+       an informative comment.
+       (Thanks to Paul Eggert <address@hidden> for his educational
+       advice.)
+
+2003-08-29  Derek Price  <address@hidden>
+
+       * cvs.h: Delete reference to CVSADMROOT_EDITINFO.
+       * logmsg.c (editinfo_proc): Delete function and proto.
+       (do_editor): Don't look for editinfo script.
+       * mkmodules.c (editinfo_contents): Delete.
+       * sanity.sh (*): Remove references to editinfo in updates of the
+       CVSROOT module.
+
+2003-08-29  Derek Price  <address@hidden>
+
+       * remove.c (cvsremove): Update quotes for consistency.
+
+2003-08-27  Larry Jones  <address@hidden>
+
+       * history.c: 'P' is a valid record type and has been for a long time.
+       Add it to the comments, usage message, and, most important,
+       ALL_REC_TYPES so it gets recorded by default.
+       * server.c (do_cvs_command): Set global command_name to the real
+       command name rather than leaving it set to "server".
+       * sanity.sh: Update to match.
+       (Reported by Dmitry Ryzhkov <address@hidden>.)
+
+2003-08-19  Derek Price  <address@hidden>
+
+       * filesubr.c (cvs_temp_file): Expand comments.  Check for glibc version
+       before compiling chmod command.  Remove FIXME to this effect.
+
+2003-08-19  Derek Price  <address@hidden>
+
+       * logmsg.c (do_editor): Use cvs_temp_file rather than cvs_temp_name to
+       create and open the temporary file.  Remove FIXME to this effect.
+
+2003-08-19  Derek Price  <address@hidden>
+
+       * logmsg.c (do_editor): Move editinfo processing to before creation of
+       the temp file so that it may be skipped when no editor is defined.
+       Remove related FIXME.  Add comments.  Remove some processing of
+       editinfo_editor rendered obsolete when editinfo_editor ceased to be a
+       global.
+
+2003-08-19  Derek Price  <address@hidden>
+
+       * (*.c): Move some includes to lib/system.h.
+
+2003-08-18  Derek Price  <address@hidden>
+
+       * add.c (add): Use consistent quoting style in user messages.
+       * sanity.sh (*): Ditto.
+
+2003-08-13  Larry Jones  <address@hidden>
+
+       * server.c (server_cleanup): Don't shutdown buf_from_net if it's
+       null.
+       (Reported by Scott Mitchell <address@hidden>.)
+
+2003-08-03  Mark D. Baushke  <address@hidden>
+
+       * lock.c: Do not include xtime.h (already included via system.h).
+       * subr.c: Ditto.
+       (Original patch from Rainer Orth <address@hidden>
+       to fix IRIX 5.3 problem.)
+
+2003-08-01  Derek Price  <address@hidden>
+
+       * sanity.sh (join5): Use $SPROG rather than $PROG.
+
+2003-08-01  Derek Price  <address@hidden>
+
+       * sanity.sh (join5): Use $PROG consistently and escape a `.'.
+
+2003-08-01  Derek Price  <address@hidden>
+
+       * sanity.sh (join5): Use `[a-z]*' as opposed to `update'.
+
+2003-07-31  Derek Price  <address@hidden>
+
+       * rcscmds.c (RCS_merge): Pass `--' before the filename arguments to
+       diff so that filenames starting with `-' can be merged.
+       * sanity.sh (join5): New test for same.
+
+2003-07-31  Derek Price  <address@hidden>
+
+       * add.c (add_directory): Don't print status information in really_quiet
+       mode.
+
+2003-07-29  Derek Price  <address@hidden>
+
+       * commit.c (checkaddfile): Simplify the logic here, using assumptions
+       already made later in the function to remove calls to locate_rcs and
+       some conditionals.  Use same assumptions to remove some variables.
+
+2003-07-29  Derek Price  <address@hidden>
+
+       * login.c: Remove GETPASS & HAVE_GETPASSPHRASE cruft in favor of always
+       using the GNULIB getpass since the system getpass was removed from the
+       POSIX.2 specification.
+
+2003-07-28  Derek Price  <address@hidden>
+
+       * subr.c (strip_trailiing_newlines): Use size_t rather than int to
+       count string length.
+       (Suggestion from Paul Edwards, who provides a broken return email
+       address in Tonga.  I believe he is actually from Australia.)
+
+2003-07-28  Derek Price  <address@hidden>
+
+       * checkout.c (checkout): Remove out-of-date comment about Checkin.prog
+       and Update.prog.
+
+2003-07-25  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_parsercsfile): Declare rcsfile argument as const.
+       * rcs.h (RCS_parsercsfile): Update prototype to match.
+       * commit.c (fixaddfile): Accept a single path to an rcs file as an
+       argument rather than trying to look it up again when it is not
+       necessary.
+
+2003-07-25  Derek Price  <address@hidden>
+
+       * commit.c (finaladd): But don't free variables we no longer allocate.
+
+2003-07-25  Derek Price  <address@hidden>
+
+       * checkin.c (Checkin): The rcs argument is unecessary since we know
+       that the parsed RCS data always exists as part of finfo by the time
+       this function gets called.
+       * commit.c (commit_fileproc, finaladd):  Use new Checkin() API.
+       * cvs.h (Checkin): Update prototype.
+
+2003-07-25  Derek Price  <address@hidden>
+
+       * subr.c (strip_trailing_newlines): Check len b4 str[len] to avoid
+       exceeding the array bounds when the string length == 0.
+       (Report from John Tytgat <address@hidden>.)
+
+2003-07-25  Derek Price  <address@hidden>
+
+       * subr.c (strip_trailing_newlines): Generalize this function to watch
+       len so that it cannot walk past the beginning of the string passed in.
+       (Report from John Tytgat <address@hidden>.)
+
+2003-07-25  Derek Price  <address@hidden>
+
+       * subr.c (strip_trailing_newlines): Leave the K&R function decl on this
+       branch.
+
+2003-07-25  Derek Price  <address@hidden>
+
+       * cvs.h (strip_trailing_newlines): Update prototype.
+       * subr.c (strip_trailing_newlines): Return true when newlines are
+       removed.
+       * server.c (pserver_authenticate_connection): Don't give a DOS attack a
+       chance to authenticate accidentally because I like to be paranoid.
+       * sanity.sh (pserver): New test for same.
+
+2003-07-24  Mark D. Baushke  <address@hidden>
+
+       * server.c (check_system_password): Cleanup pam_* return code
+       checking. (Original patch from Brian Murphy <address@hidden>.)
+
+2003-07-24  Derek Price  <address@hidden>
+
+       * main.c: But the GNULIB gethostname accepts an int not ssize_t.
+
+2003-07-24  Derek Price  <address@hidden>
+
+       * main.c: Don't declare gethostname when we already have it to avoid
+       decl conflicts.
+
+2003-07-24  Derek Price  <address@hidden>
+
+       * server.c (server_directory): Add a TRACE for OS X debugging.
+
+2003-07-23  Derek Price  <address@hidden>
+
+       * client.c: Avoid some warning from gcc -Wall.
+       * lock.c: Ditto.
+       * login.c: Ditto.
+       * modules.c: Ditto.
+       * server.c: Ditto.
+
+2003-07-23  Derek Price  <address@hidden>
+
+       * filesubr.c (isaccessible): Correct some double const warnings from
+       protoize.
+       * login.c (password_entry_parseline): Ditto.
+       * server.c (kserver_authenticate_connection): Remove a multi-line
+       string along with the warning from GCC.
+
+2003-07-23  Derek Price  <address@hidden>
+
+       * *.{c,h}: Back out the indent run.
+
+2003-07-23  Derek Price  <address@hidden>
+
+       * cvs.h: Move some includes into lib/system.h.
+
+2003-07-23  Derek Price  <address@hidden>
+
+       * *.{c,h}: Run these through GNU indent as per the NEWS file to fix
+       some of the long function decls which came out of protoize.
+
+2003-07-23  Derek Price  <address@hidden>
+
+       * *.c: Run these through GCC's protoize to convert the pre-ANSI C
+       function decls to C89 compliance.
+
+2003-07-22  Derek Price  <address@hidden>
+
+       * cvs.h: Remove support for the PTR macro, since we can assume void *
+       under C89.  It also was not being made use of in very many places so
+       even most K&R compilers must have supported it, or nobody was using
+       K&R compilers.  We can also assume <stdarg.h> under C89, but move the
+       include...
+       * error.c: ...here, de-macro VA_START, and...
+       * subr.c: ...put a copy here too, as well as de-macroing VA_START.
+       * history.c: s/PTR /void */g;
+       * modules.c: Ditto.
+
+2003-07-22  Derek Price  <address@hidden>
+
+       * cvs.h: Include GNULIB exit.h.
+
+2003-07-20  Derek Price  <address@hidden>
+
+       * server.c: Add PAM support.
+       (cvs_pam_userinfo): New data type for PAM conversations.
+       (cvs_pam_conv): New function.
+       (check_password): Add PAM support.
+       (Original patch from Brian Murphy <address@hidden>.)
+
+2003-07-20  Derek Price  <address@hidden>
+
+       * wrapper.c: Remove mention of obsolete -f and -t wrapper options from
+       a comment.
+
+2003-07-18  Derek Price  <address@hidden>
+
+       * sanity.sh (release): Add new tests for release with unrecognized
+       recognized directories.
+
+2003-07-18  Derek Price  <address@hidden>
+
+       * vers_ts (Version_TS): Don't allow command line keyword expansion
+       modes to override binary mode.
+       * sanity.sh (): Tests for the above.
+       (Original patch from Dieter Maurer <address@hidden>.)
+
+2003-07-16  Derek Price  <address@hidden>
+
+       * add.c: s/PROTO//.
+       * admin.c: Ditto.
+       * annotate.c: Ditto.
+       * buffer.c: Ditto.
+       * buffer.h: Ditto.
+       * checkout.c: Ditto.
+       * classify.c: Ditto.
+       * client.c: Ditto.
+       * client.h: Ditto.
+       * commit.c: Ditto.
+       * cvs.h: Ditto.
+       * diff.c: Ditto.
+       * edit.c: Ditto.
+       * edit.h: Ditto.
+       * entries.c: Ditto.
+       * error.c: Ditto.
+       * error.h: Ditto.
+       * expand_path.c: Ditto.
+       * fileattr.c: Ditto.
+       * fileattr.h: Ditto.
+       * filesubr.c: Ditto.
+       * find_names.c: Ditto.
+       * gssapi-client.c: Ditto.
+       * gssapi-client.h: Ditto.
+       * hardlink.h: Ditto.
+       * hash.c: Ditto.
+       * hash.h: Ditto.
+       * history.c: Ditto.
+       * import.c: Ditto.
+       * kerberos4-client.h: Ditto.
+       * lock.c: Ditto.
+       * log-buffer.c: Ditto.
+       * log-buffer.h: Ditto.
+       * log.c: Ditto.
+       * login.c: Ditto.
+       * logmsg.c: Ditto.
+       * mkmodules.c: Ditto.
+       * modules.c: Ditto.
+       * myndbm.c: Ditto.
+       * myndbm.h: Ditto.
+       * patch.c: Ditto.
+       * rcs.c: Ditto.
+       * rcs.h: Ditto.
+       * rcscmds.c: Ditto.
+       * recurse.c: Ditto.
+       * release.c: Ditto.
+       * remove.c: Ditto.
+       * root.c: Ditto.
+       * rsh-client.h: Ditto.
+       * run.c: Ditto.
+       * server.c: Ditto.
+       * server.h: Ditto.
+       * socket-client.c: Ditto.
+       * socket-client.h: Ditto.
+       * status.c: Ditto.
+       * subr.c: Ditto.
+       * tag.c: Ditto.
+       * update.c: Ditto.
+       * update.h: Ditto.
+       * vers_ts.c: Ditto.
+       * watch.c: Ditto.
+       * watch.h: Ditto.
+       * wrapper.c: Ditto.
+       * zlib.c: Ditto.
+
+2003-07-16  Derek Price  <address@hidden>
+
+       * cvs.h: Include pathmax.h.
+
+2003-07-16  Derek Price  <address@hidden>
+
+       * myndbm.c: Use the GNU getdelim function rather than our package
+       getstr.
+       * server.c: Use the (hopefully) GNULIB and more appropriately named
+       getnline function rather than our getline_safe function.
+
+2003-07-12  Larry Jones  <address@hidden>
+
+       * sanity.sh (diffnl): New tests for diff on files with no newline
+       at end.
+       (Patch from Andrew Moise <address@hidden>.)
+
+2003-07-11  Larry Jones  <address@hidden>
+
+       * Makefile.am (cvs_DEPENDENCIES): Include the libraries.
+       * Makefile.in: Regenerated.
+
+       * diff.c (diff_file_nodiff): Fix -Wall complaints.
+       * log.c (rlog_proc): Ditto.
+       * rcs.c (RCS_setlocalid): Ditto.
+       * recurse.c (start_recursion): Handle null repository_in in TRACE.
+
+2003-07-09  Larry Jones  <address@hidden>
+
+       * sanity.sh: Use ${CPROG} instead of ${PROG} so that changes merged
+       from cvs1-11-x-branch without updating won't appear to work.
+
+       * sanity.sh (keywordexpand): Use ${SPROG} instead of ${PROG} as
+       required.
+
+       * add.c (add): Update "re-adding" message to have quotes around
+       the file name like all the other similar messages.
+       * sanity.sh: Update to match.
+
+       * update.c (join_file): Handle locally removed but not yet committed
+       files.
+       (Reported by Larry Lords <address@hidden>.)
+       * sanity.sh (join, join4): New tests for above.
+
+2003-06-28  Larry Jones  <address@hidden>
+
+       * commit.c (fixaddfile): Bail out if locate_rcs() fails.  Make
+       parameters const.
+
+       * add.c (add): Fix -Wall complaints.
+       * diff.c (diff_file_nodiff): Ditto.
+       * filesubr.c (cvs_casecmp): Ditto.
+       * patch.c (patch_fileproc): Ditto.
+       * rcs.c (RCS_cmp_file): Ditto.
+       * root.c (parse_cvsroot): Ditto.
+       * subr.c (locate_file_in_dir): Ditto.
+       * cvs.h (cvs_casecmp, locate_file_in_dir): Update prototypes.
+
+2003-06-27  Larry Jones  <address@hidden>
+
+       * lock.c (readers_exist): Use LockDir rather than always looking
+       in the repository.
+       (Original patch from Robert Ambalu <address@hidden>.)
+       Remove vestigial lock promotion code.
+
+2003-06-27  Derek Price  <address@hidden>
+
+       * checkout.c (safe_location): Don't try and print from a NULL pointer.
+       (Report and original patch from Sampo Kellomaki <address@hidden>.)
+
+2003-06-26  Larry Jones  <address@hidden>
+
+       * hash.c (sortlist): Avoid crash when list is null.
+
+2003-06-23  Derek Price  <address@hidden>
+
+       * patch.c (patch_fileproc): Output revision number of the original
+       revision in the removed case.
+       (Idea from Paul Edwards <address@hidden>.)
+
+       * sanity.sh (rdiff-add-remove-nodiff): Rename to...
+       (rdiff-short): ...this.  Test for the above changes.  Add some tests
+       for when rev2 defaults to the trunk.  Expand comments.
+
+2003-06-23  Derek Price  <address@hidden>
+
+       * client.c: Reapply Alexey's changes to client.c from three commits
+       back they were left out of the diff.
+
+2003-06-23  Derek Price  <address@hidden>
+
+       * add.c (add): Fix xmalloc's strlen() of wrong variable.
+       * checkout.c (safe_location): leak: reused where_location without free.
+       * log.c (rlog_proc): leak: free where before exit.
+       * logmsg.c (do_verify): leak: free verifymsg_script before exit.
+       (Original patch from Kenneth Lorber <address@hidden>.)
+
+2003-06-23  Derek Price  <address@hidden>
+
+       * client.c: Remove silly comment.
+       (Patch from Alexey Mahotkin  <address@hidden>.)
+
+2003-06-23  Derek Price  <address@hidden>
+
+       * kerberos4-client.h, kerberos4-client.c, client.c: Rename
+       start_tcp_server() to start_kerberos4_server().
+       (Patch from Alexey Mahotkin  <address@hidden>.)
+
+2003-06-20  Derek Price  <address@hidden>
+
+       * kerberos-client.c, kerberos-client.h, client.c: Split out
+       Kerberos 4 code to separate files.
+
+       * Makefile.am: Mention new files.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+       * Makefile.in: Regenerated.
+
+2003-06-16  Derek Price  <address@hidden>
+
+       * cvs.h: Comment an #endif.
+
+2003-06-13  Derek Price  <address@hidden>
+
+       * filesubr.c (cvs_temp_name): Remove portability cruft obsoleted by the
+       import of GNULIB's mkstemp().
+
+2003-06-13  Derek Price  <address@hidden>
+
+       * subr.c (file_has_conflict): Fix comment.
+       (Patch from Paul Edwards <address@hidden>.)
+
+2003-06-13  Derek Price  <address@hidden>
+
+       * subr.c (xrealloc): Trivial comment fix.
+       (Patch from Kenneth Lorber <address@hidden>.)
+
+2003-06-13  Derek Price  <address@hidden>
+
+       * diff.c (diff_fileproc): Fix memory leak.
+       (Patch from Kenneth Lorber <address@hidden>.)
+
+2003-06-12  Derek Price  <address@hidden>
+
+       * root.c (parse_cvsroot, local_cvsroot): Parse trailing '/'s off the
+       end of cvsroots.  Make arguments const.
+       * cvs.h: Update prototypes to match.
+       (Idea from Miles Zarathustra <address@hidden>.)
+
+2003-06-12  Derek Price  <address@hidden>
+
+       * checkout.c (safe_location): Fix memory leak.
+       (Patch from Kenneth Lorber <address@hidden>.)
+
+2003-06-11  Larry Jones  <address@hidden>
+
+       * filesubr.c (xresolvepath): Fix memory leak.
+       (Original patch from Kenneth Lorber <address@hidden>.)
+
+2003-06-11  Derek Price  <address@hidden>
+
+       * commit.c: Change Parse_Info calling convention to include void *
+       suggested in HACKING file and generalize all argument to opt.
+       * cvs.h: update defs for Parse_Info and its callproc.
+       * edit.c: Change Parse_Info calls for new calling convention.
+       * logmsg.c: Ditto.
+       * parseinfo.c: Change Parse_Info for new calling convention.
+       * server.c: Change Parse_Info calls for new calling convention.
+       * tag.c: Ditto.
+       (Original patch from Ken Lorber <address@hidden>.)
+
+2003-06-11  Derek Price  <address@hidden>
+
+       * Makefile.in: Regenerate for new configure.in.
+
+2003-06-11  Larry Jones  <address@hidden>
+
+       * sanity.sh: Change warning messages to note that defective tools
+       can result in defective results, both pass and fail.  Also change
+       "which" to "that" for errant grammar pedants.
+
+2003-06-10  Derek Price  <address@hidden>
+
+       * recurse.c (start_recursion): Avoid unneeded allocation.
+
+2003-06-10  Mark D. Baushke  <address@hidden>
+
+       * rcs.c (RCS_setlocalid,RCS_setincexc): New functions to support
+       LocalKeyword and KeywordExpand config keywords.
+
+       * rcs.h (RCS_setlocalid,RCS_setincexc): New prototypes.
+
+       * parseinfo.c (parse_config): Added LocalKeyword
+       and KeywordExpand keywords.
+
+       * sanity.sh (keywordexpand): New CVSROOT/config tests for
+       LocalKeyword and KeywordExpand options.
+
+2003-06-09  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_delete_revs): Reference WOE32 rather than WIN32 in
+       accordance with the GNU convention to avoid implying that we consider
+       the Microsoft Windows Operating Environment any sort of "win".
+
+2003-06-09  Derek Price  <address@hidden>
+
+       * filesubr.c (cvs_temp_file): Tidy a comment.
+
+2003-06-09  Derek Price  <address@hidden>
+
+       * patch.c (patch_fileproc): Don't assume the content of files is
+       different just because the revision number is different.
+       * sanity.sh (rdiff-add-remove-nodiff): New tests for the above.
+       (Report & original patches from Paul Edwards <address@hidden>.)
+
+2003-06-04  Derek Price  <address@hidden>
+
+       * cvs.h (locate_file_in_dir): New proto.
+       (locate_rcs): Move proto...
+       * rcs.h: ...here.
+       * filesubr.c (locate_rcs): Move function...
+       * rcs.c: ...here for Windows.
+       * filesubr.c (locate_file_in_dir): Move function...
+       * subr.c: ...here for Windows.
+
+2003-06-02  Derek Price  <address@hidden>
+
+       * sanity.sh: Add comments re portability of test -x & test -e.  Don't
+       bother with quotes in arguments to test when we have laready checked
+       the variables for empty content.
+
+2003-06-02  Derek Price  <address@hidden>
+
+       * sanity.sh: Don't use `test -x' since BSD 4.3 doesn't like it.  Minor
+       reorganization for clarity.  Don't check for $server = false after we
+       set its default.  Use </dev/null with calls to $prog --version when we
+       don't know what $prog does for sure.
+
+2003-06-02  Derek Price  <address@hidden>
+
+       * diff.c (diff_file_nodiff): Don't assume that because two specified
+       revision numbers are different, the contents are different.
+       (Original report & patch from Paul Edwards <address@hidden>.)
+
+       * diff.c (diff_file_nodiff): Pass through rev1_cache to be filled in
+       by RCS_cmp_file when it needs to check out revision 1 into a file.  Add
+       some more informative error messages.  Cleanup for efficiency &
+       readability.
+       (diff_fileproc): Pass the cached revision to RCS_exec_diff().  Clean up
+       the error exit code.  Remove code killed by the changes to
+       diff_file_nodiff().
+       * rcscmds.c (RCS_exec_rcsdiff): Accept and use new cached revision text
+       if present.
+       * rcs.c (RCS_cmp_file): Accept a second revision number and cache the
+       first revision if it needs to be checked out.
+
+       * checkin.c (Checkin): Use new RCS_cmp_file().
+       * import.c (update_rcs_file): Ditto.
+       * no_diff.c (No_Difference): Ditto.
+
+       * cvs.h (RCS_exec_rcsdiff): New proto to match above changes.
+       * rcs.h (RCS_cmp_file): Ditto.
+
+       * sanity.sh: Minor corrections to handle the above changes.
+
+2003-05-31  Derek Price  <address@hidden>
+
+       * root.c (parse_cvsroot): Refuse :fork: only in client mode, not
+       server.
+       * client.c (connect_to_forked_server): Die without SERVER_SUPPORT when
+       CVSSERVER isn't supplied in the environment.
+       * sanity.sh: Default $servercvs to $testcvs.  Add SPROG so that testing
+       a client and server with different names works in order to test the
+       above changes.  s/PROG/SPROG/ almost everywhere.  Misc corrections to
+       tests when ${PROG} is required not to use [a-z]*.  Misc uniqifications
+       of test names.  Misc replacement of CVS_SERVER=${testcvs} with
+       CVS_SERVER=${servercvs}.  Confirm ${testcvs} & ${servercvs} exist and
+       are executable.  Set testcvs_server_support=true if the ${testcvs}
+       executable has server support.  Misc comment corrections.
+       (pserver): s/\$\{testcvs\}/${servercvs}/ for invocations of pserver.
+       (server): Ditto for invocations of `cvs server'.
+       (fork): Accept the `was not compiled with server support' error
+       message.
+
+2003-05-29  Derek Price  <address@hidden>
+
+       * client.c (start_server): Don't send -l to server.
+       * history.c (history_write): Fix comment.
+       * main.c (main): Don't process -l.
+       * server.c (serve_global_option): Ditto.
+       (Suggestion from Rob Lanphier <address@hidden>.)
+
+2003-05-29  Derek Price  <address@hidden>
+
+       * log-buffer.c, rsh-client.c, socket-client.c: Allow compilation
+       with --disable-client.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+2003-05-29  Derek Price  <address@hidden>
+
+       * gssapi-client.h: Move contents of lib/xgssapi.h here.
+       * server.c: xgssapi.h is no more.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+2003-05-28  Derek Price  <address@hidden>
+
+       * server.c: Use standard PROTOTYPES symbol instead of non-standard
+       USE_PROTOTYPES.
+       * error.h, cvs.h: Use PROTO.h.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+2003-05-23  Larry Jones  <address@hidden>
+
+       * sanity.sh (info-cleanup-verifymsg): Avoid race in output.
+
+       * sanity.sh (template): Fix unintended duplicate DEFAULT lines,
+       duplicate test names.
+
+2003-05-22  Larry Jones  <address@hidden>
+
+       * commit.c (commit): Fix leading zero stripping code to not strip
+       unless there's a following digit.
+
+       * parseinfo.c (Parse_Info): Warn if multiple DEFAULT lines found.
+       * sanity.sh (info): New test for above.
+
+2003-05-21  Derek Price  <address@hidden>
+
+       * Makefile.in: Regenerate with Automake version 1.7.5.
+
+2003-05-20  Larry Jones  <address@hidden>
+
+       * parseinfo.c (Parse_Info): Fix stupid memory management error.
+
+       * logmsg.c (do_verify): Treate Parse_Info errors as failure.
+       * parseinfo.c (Parse_Info): Don't call expand_path until executing
+       the command so that errors in unexecuted commands aren't reported.
+       * sanity.sh (info): New tests for above.
+
+2003-05-20  Derek Price  <address@hidden>
+
+       * mkmodules.c (config_contents): Add missing newline.
+       (Patch from Kenneth Lorber <address@hidden>.)
+
+2003-05-20  Derek Price  <address@hidden>
+
+       * Makefile.am: Macro subsitution for zlib include path and library
+       location
+       * zlib.c: #ifdef inclusion of <zlib.h> versus "zlib.h"
+       (Original patch from Anthon Pang <address@hidden>.)
+
+       * Makefile.in: Regenerated
+
+2003-05-20  Derek Price  <address@hidden>
+
+       * cvs.h: Move the standard includes into lib/system.h.
+       * subr.c: s/malloc/CVS_MALLOC/;s/realloc/CVS_REALLOC/.
+
+2003-05-19  Derek Price  <address@hidden>
+
+       * filesubr.c: s/\bstat\b/CVS_STAT/g;s/\blstat\b/CVS_LSTAT/g
+       * hardlink.c: Ditto.
+       * ignore.c: Ditto.
+       * rcs.c: Ditto.
+       * update.c: Ditto.
+
+2003-05-18  Larry Jones  <address@hidden>
+
+       * checkout.c (safe_location): Remove unused variable.
+       * hash.c (walklist, printnode, printlist): Use %p to print pointers
+       if available, convert to unsigned long if not.
+       * recurse.c (start_recursion, do_recursion): Ditto.
+       * tag.c (rtag_proc, tag_check_valid): Ditto.
+
+2003-05-18  Mark D. Baushke  <address@hidden>
+
+       * Makefile.am (localcheck,remotecheck): Use cvs$(EXEEXT) not cvs.
+       * Makefile.in: Regenerated.
+       * sanity.sh (status-init-7): Use ${PROG} not cvs in tests.
+       (branch-after-import-5): Ditto.
+       (keywordname-update-11): Ditto.
+
+2003-05-18  Larry Jones  <address@hidden>
+
+       * server.h (kserver_authenticate_connection,
+       pserver_authenticate_connection): Add prototypes.
+
+       * client.c (update_entries): Set file's access time to the current
+       time rather than the same as the modification time.
+       * vers_ts.c (Version_TS): Ditto.
+
+2003-05-09  Derek Price  <address@hidden>
+
+       * client.c: #ifdef inclusion of gssapi-client.h.
+       * server.c: Ditto.
+       (Reported by Boyd Lynn Gerber <address@hidden>.)
+
+2003-05-09  Derek Price  <address@hidden>
+
+       * client.h: Move some of the GSSAPI stuff...
+       * gssapi-client.h: ...to this new file.
+       * client.c (start_server): Use new initialize_gssapi_buffers().
+       (*): Move most of the GSSAPI stuff from here and...
+       * server.c (*): ...here...
+       * gssapi-client.c: ...to this new file.
+       * Makefile.am (EXTRA_cvs_SOURCES, cvs_DEPENDENCIES, cvs_LDADD): Support
+       $(cvs_client_objects).
+       (Original patch from Alexey Mahotkin <address@hidden>.)
+
+       * Makefile.in: Regenerated.
+
+2003-05-09  Derek Price  <address@hidden>
+
+       * buffer.c: Reindent some compiler directives in order to make nesting
+       clearer.
+
+2003-05-08  Derek Price  <address@hidden>
+
+       * client.c (log_buffer*): Move...
+       * log-buffer.c (log_buffer*): ...to this new file.
+       * log-buffer.h (setup_logfiles): New file to share prototype.
+       * Makefile.am: Add log-buffer.c & log-buffer.h.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+       * Makefile.in: Regenerated.
+
+2003-05-08  Derek Price  <address@hidden>
+
+       * client.c (init_sockaddr): Move...
+       * socket-client.c (init_sockaddr): ...here.
+       * socket-client.h (init_sockaddr): Prototype.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+2003-05-08  Derek Price  <address@hidden>
+
+       * client.c (send_to_server): Move most of the functionality to and
+       wrap...
+       (send_to_server_via): ...this new function which accepts the buffer
+       pointer as an argument.
+       (read_line): Ditto, but to...
+       (read_line_via): ...here.
+       (auth_server): Rename lto_server & lfrom_server to s/^l//.  Remove
+       ugly code which sets the global versions of these variables
+       temporarily for function calls.
+       s/send_to_server(/send_to_server_via(to_server,/g,
+       s/read_line(/read_line(from_server,/g,
+       Remove emotional FIXME comment to the effect that all this is
+       necessary.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+2003-05-07  Derek Price  <address@hidden>
+
+       * client.c (from_server, to_server): Rename these global buffer
+       pointers to...
+       (global_from_server, global_to_server): ...this in order to avoid
+       confusion.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+2003-05-07  Derek Price  <address@hidden>
+
+       * client.c (make_bufs_from_fds, connect_to_forked_server,
+       start_tcp_server): Rename struct buffer ** arguments to s/$/_p/ in an
+       attempt to denote their pointerness more clearly.
+       * rsh-client.c (start_rsh_client): Ditto.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+       * client.h (make_bufs_from_fds): Sanitize prototype so that the
+       argument name change doesn't clash.
+
+2003-05-07  Derek Price  <address@hidden>
+
+       * client.h (make_bufs_from_fd): Prototype in order to make available to
+       rsh-client.c.
+       * client.c (start_rsh_server): Moved most of the RSH (:ext:) client
+       stuff to...
+       * rsh-client.h: ...here...
+       * rsh-client.c: ...and here.
+       * Makefile.am (cvs_SOURCES): Add new source files.
+       (Original patch from Alexey Mahotkin <address@hidden>.)
+
+       * Makefile.in: Regenerated.
+
+2003-05-06  Derek Price  <address@hidden>
+
+       * client.c (socket_buffer_*): Moved most of the socket stuff to...
+       * socket-client.h: ...here...
+       * socket-client.c: ...and here.
+       * Makefile.am (cvs_SOURCES): Add new source files.
+       (Patch from Alexey Mahotkin  <address@hidden>.)
+
+       * Makefile.in: Regenerated.
+
+2003-05-05  Derek Price  <address@hidden>
+
+       * hash.c (findnode): Document behavior of this function when its list
+       argument is NULL and document this behavior.  Remove FIXME comment to
+       the effect that this is necessary.
+
+2003-05-01  Derek Price  <address@hidden>
+
+       * main.c (main): Ignore -z when CLIENT_SUPPORT is not defined.
+       (Report from Jim Salter <address@hidden>.)
+
+2003-05-01  Derek Price  <address@hidden>
+
+       * repos.c (Sanitize_Repository_Name): Remove some old comments about
+       the defunct RELATIVE_REPOS macro.
+       * server.c (outside_root): Ditto.
+
+2003-04-30  Derek Price  <address@hidden>
+
+       * vers_ts.c (Version_TS): Minor optimization.
+
+2003-04-30  Derek Price  <address@hidden>
+
+       * add.c (add): Fix a possible, if unlikely, memory out of bounds error.
+
+2003-04-30  Derek Price  <address@hidden>
+
+       * commit.c: Free vers in single place
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+2003-04-30  Derek Price  <address@hidden>
+
+       * Makefile.am: Get rid of $includeopt, using $CPPFLAGS as intended by
+       the Autoconf folk.
+       (Patch from Alexey Mahotkin <address@hidden>.)
+
+       * Makefile.in: Regenerated.
+
+2003-04-30  Derek Price  <address@hidden>
+
+       * add.c (add): Fix a possible, if unlikely, memory out of bounds error.
+
+2003-04-28  Derek Price  <address@hidden>
+
+       * client.c (save_prog): Remove unneeded struct.
+       (checkin_progs, update_progs): Remove these unneeded globals.
+       (handle_set_checkin_prog, handle_set_update_prog, do_deferred_progs):
+       Remove these functions.
+       (send_repository): Remove checkin and update prog support.
+       (responses): Remove Set-checkin-prog and Set-update-prog.
+       (get_responses_and_close): Don't call do_deferred_prog().
+       * commit.c (commit_usage): Remove reference to -n.
+       (commit): Don't set and send run_module_prog via -n.  Don't run
+       Checkin.prog or Checkout.prog in local mode.
+       * modules.c (CVSMODULE_OPTS): Remove -i and -u.
+       (do_module): Don't process -i and -u options to set checkin and update
+       progs, respectively.
+       * server.c (server_prog, serve_checkin_prog, server_update_prog):
+       Remove unused functions.
+       (requests): Remove Checkin-prog and Update-prog.
+       * update.c (update_dirleave_proc): Remove update prog functionality.
+
+       * cvs.h (CVSADM_CIPROG, CVSADM_UPROG): Remove unneeded defines.
+       * server.h (server_prog): Remove proto.
+       (progs): Remove enum.
+
+       * sanity.sh (modules5): Remove tests for checkin and update programs.
+
+2003-04-15  Derek Price  <address@hidden>
+
+       * sanity.sh (*): Shrink the yucky case statement using sed's transform
+       functionality.
+       (getlongoptarg): Convert this function to only confirm the arg and move
+       it...
+       (checklongoptarg): ...here.
+
+2003-04-10  Larry Jones  <address@hidden>
+
+       * Makefile.in: Regenerated.
+
+2003-04-04  Larry Jones  <address@hidden>
+
+       * sanity.sh (branches4-15): New test.
+
+       * error.h: Avoid __pure__ for GCC versions < 2.96 & __malloc__ for GCC
+       versions < 3.0.
+
+2003-04-03  Derek Price  <address@hidden>
+
+       * Makefile.am (DISTCLEAN_FILES): Move the contents of this variable...
+       (distclean-local): ...to this target now that Automake supports it.
+
+2003-04-03  Derek Price  <address@hidden>
+
+       * cvs.h: Avoid __pure__ for GCC versions < 2.96 & __malloc__ for GCC
+       versions < 3.0.
+
+2003-04-02  Larry Jones  <address@hidden>
+
+       * update.c (update, update_fileproc, update_filesdone_proc,
+       update_dirent_proc, update_dirleave_proc): Keep track of whether
+       a tag is both a revision tag and a branch tag and warn the user.
+       * sanity.sh (branches4): New tests for above.
+
+2003-04-02  Derek Price  <address@hidden>
+
+       * recurse.c (do_recursion): Use strstr("/./") rather than strchr('.')
+       to catch only indirections in paths and not directory names with dots
+       in them.
+       (Report from Pavel Roskin <address@hidden>.)
+
+       * sanity.sh (multiroot): Put a dot in the CVSROOT_DIRNAMEs.
+       (dottedroot): New test.
+       (Based on a script from Pavel Roskin <address@hidden>.)
+
+2003-04-01  Derek Price  <address@hidden>
+
+       * sanity.sh (multiroot2-9): Add newly TRACEd parse_cvsroot() to
+       expected output.
+
+2003-03-31  Derek Price  <address@hidden>
+
+       * rcs.c (freercsnode): Revert an accidental change from the previous
+       commit.
+
+2003-03-31  Derek Price  <address@hidden>
+
+       * recurse.c (start_recursion): Accept new repository argument so that
+       the working directory may be tracked by do_recursion without using
+       xgetwd(), which returned a value different from the one the user
+       requested when symlinks were in use.  Add TRACE.  Pass repository_in
+       to do_recursion() as part of the recursion frame.
+       (do_recursion): Default srepository to NULL and only set when we set
+       repository.  Keep track of repository using xframe.repository for the
+       r* commands rather than xgetwd(), which used to break when CVSROOT was
+       a symlink to a real root.
+
+       * cvs.h (xreadlink): #ifdef HAVE_READLINK proto.
+       (xresolvepath): New proto.
+       (start_recursion): Add repository to proto.
+       (*): Define some more abstract TRACE levels.
+       * update.h (do_update): Add repository argument to proto.
+
+       * checkout.c (safe_location): Add more complete header comment.  Add
+       TRACE.  Use new xresolvepath() function.  Always return true in
+       client mode since checking our destination path against the CVSROOT
+       path is usually meaningless in client/server mode.
+       (checkout_proc): Pass repository to do_update() for later use with
+       start_recursion().
+       (*): s/<chdir>/CVS_CHDIR/.
+       * filesubr.c (xreadlink): #ifdef HAVE_READLINK this function.  Add more
+       complete header comment.
+       (xresolvepath): New function.
+       * hash.c (walklist): Add TRACE.
+       * main.c (main): Don't copy and dispose of CVSRoot_cmdline twice.
+       * patch.c (patch_proc): Add TRACE.  Pass repository to
+       tag_check_valid() for r* commands.
+       * root.c (parse_cvsroot): Add TRACE.
+       * tag.c (rtag_proc): Add TRACE.
+       (check_fileproc): Ditto.
+       (tag_check_valid): Ditto.
+       * update.c (do_update): Accept new repository argument for co.
+       (update): Pass NULL repository to do_update().
+
+       * admin.c (*): Use new definition of start_recursion().
+       * annotate.c (*): Ditto.
+       * client.c (*): Ditto.
+       * commit.c (*): Ditto.
+       * diff.c (*): Ditto.
+       * edit.c (*): Ditto.
+       * lock.c (*): Ditto.
+       * log.c (*): Ditto.
+       * patch.c (*): Ditto.
+       * remove.c (*): Ditto.
+       * status.c (*): Ditto.
+       * tag.c (*): Ditto.
+       * update.c (*): Ditto.
+       * watch.c (*): Ditto.
+
+       * sanity.sh: Add new -l option to test symlinked roots.
+       (abspath-3.2): Use [a-z]* rather than "checkout".
+       (check_repository-1): Add server error messages about absolute paths
+       since the client now skips destination validity checks.
+       (check_repository-2): Process client error messages about CVSROOT
+       files being in the way since the client skips destination validity
+       checks since it should be rare that a client is running in
+       client/server mode on the server and CVS has no current way to check if
+       it is runnning on the server.
+
+2003-03-27  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (rdiff2): Add new test case for SEGV problem reported
+       against cvs 1.11.5.
+       (Report from James Cribb)
+
+2003-03-26  Derek Price  <address@hidden>
+
+       * client.c: Fix, reorganize, and comment ifdefs for AUTH_CLIENT_SUPPORT
+       and HAVE_GSSAPI.
+       * client.h: Ditto.  Remove some unecessary server function prototypes.
+
+2003-03-26  Derek Price  <address@hidden>
+
+       * client.c: Include the net headers for HAVE_GSSAPI.
+       (Report from Jim Salter <address@hidden>.)
+
+2003-03-26  Derek Price  <address@hidden>
+
+       * main.c (main): Verify the argument to -z when running without
+       CLIENT_SUPPORT since Eric Siegerman complained about being bit
+       by a run of `cvs -z -n up' which parsed the -n as the argument to
+       -z.
+       * sanity.sh (opterrmsg): New tests for -z argument checking.
+
+2003-03-26  Larry Jones  <address@hidden>
+
+       * main.c (main): Use strtol() instead of atoi() when parsing -z
+       to detect errors.
+       (Reported by Eric Siegerman <address@hidden>.)
+
+2003-03-25  Derek Price  <address@hidden>
+
+       * cvs.h: Disable GNU attributes as part of PROTO behavior.
+       * error.h: Mirror GNU attribute definitions from cvs.h.
+
+2003-03-25  Derek Price  <address@hidden>
+
+       * subr.c (cvs_trace): #ifdef use of server_active.
+       (Report from Jim Salter <address@hidden>.)
+
+       * cvs.h (xmalloc, xrealloc, xstrdup, parse_cvsroot,
+       local_cvsroot, normalize_cvsroot): Use GNU attributes.
+       * error.h (error_exit): Fix PROTO/__attribute__ specification.
+       * root.c (new_cvsroot_t): Add GNU attribute.
+
+2003-03-24  Derek Price  <address@hidden>
+
+       * Makefile.am: Update copyright notice.
+
+       * Makefile.in: Regenerated.
+
+2003-03-24  Larry Jones  <address@hidden>
+
+       * server.h (server_clear_template): Add declaration.
+       (server_template): Add parameter names to prototype.
+
+2003-03-20  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (env): Try more than one ps command if the first one
+       fails. This may let the test succeed on more platforms. Also,
+       keep the ps output that was processed for the error report.
+
+2003-03-19  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh (env): Use 'ps -el' rather than 'ps -l' so that
+       crontab jobs that might be running these tests without a
+       controlling terminal work properly.
+
+       * client.c (start_rsh_server): Use new definition of RSH_DFLT to
+       allow "rsh" to be configured to default to "ssh" or some other
+       local remote transport program.
+
+       * Makefile.in: Regenerated.
+
+2003-03-19  Larry Jones  <address@hidden>
+
+       * filesubr.c (mkdir_if_needed): Save errno since isdir() can clobber.
+       (Patch from Brian Poole <address@hidden>.)
+       * sanity.sh (abspath-4): Update to match.
+
+       * filesubr.c (locate_rcs): Fix gcc warning.
+
+2003-03-19  Derek Price  <address@hidden>
+
+       * add.c (add_directory): Only call WriteTemplate when the server is
+       active.
+       * create_adm.c (Create_Admin): Don't call WriteTemplate here since
+       Create_Admin is only called from the client.
+       * commit.c (commit_dirleaveproc): Don't call WriteTemplate here.  I'm
+       a little confused as to why since update_direntproc works for update.c,
+       but I can't come up with a test case that fails when this call is
+       missing.  Nor can I come up with a test case that passes when this call
+       is present and the one in commit_filesdoneproc is removed.
+       * sanity.sh (template): Tidy, minimize, and add some extra tests.
+
+2003-03-19  Mark D. Baushke  <address@hidden>
+
+       * .cvsignore: Added sanity.config.sh, a new auto-generated file.
+
+       * cvs.h (CVS_PID_ENV): New environment variable CVS_PID has the
+       pid of the parent cvs process.
+       * main.c (main): Initialize it.
+       * sanity.sh (env): Test it.
+
+2003-03-19  Derek Price  <address@hidden>
+
+       * sanity.config.sh.in: New file.
+       * sanity.sh: Source new config file when available.  Accept alternate
+       config file as an argument to a -c option.  Accept long options with
+       arguments.
+       (getlongoptarg): New function.
+
+       * Makefile.in: Regenerated.
+
+2003-03-18  Derek Price  <address@hidden>
+
+       * root.c (parse_root): Add some more comments and expand
+       #ifdef CLIENT_SUPPORT pragmas.  Rearrange sanity checks slightly.
+
+2003-03-18  Derek Price  <address@hidden>
+
+       * main.c (main): Output -R warning in quiet mode and correct spelling
+       in this warning message.
+       * sanity.sh (commit-readonlyfs-2r4): Correct cascaded spelling mistake
+       in test.
+
+2003-03-17  Derek Price  <address@hidden>
+
+       * add.c: Correct comment.
+       * client.c: Ditto.
+       * checkin.c (Checkin): Pass work file name to RCS_checkin so that this
+       function works properly in the case insensitive mode.
+       * commit.c (checkaddfile): Fix and factor add logic so that the
+       correct files and directories are created in the case insensitive mode.
+       Reuse code in RCS_parse() below.  This avoids a problem that could
+       cause corrupted RCS files to be created on an add from a case
+       insensitive system.  Corrupted RCS files could cause later assertion
+       failures for everyone.
+       (locate_rcs): Move this function...
+       * filesubr.c (locate_rcs): ...here and rewrite it.
+       (fopen_case): Remove this function.
+       (locate_file_in_dir): New function.
+       * cvs.h (locate_rcs): Prototype new function.
+       * rcs.c (RCS_parse): Factor out file location into locate_rcs.
+
+2003-03-17  Mark D. Baushke  <address@hidden>
+
+       * main.c (main): Issue a warning about readonlyfs options unless
+       quiet or really_quiet is set.
+       * lock.c (Writer_Lock): Modify readonlyfs error message text.
+       * sanity.sh (commit-readonlyfs): Adjust test for new messages.
+
+2003-03-17  Larry Jones  <address@hidden>
+
+       * server.c (switch_to_user): Add syslog calls for setgid/setuid
+       failure.
+
+2003-03-16  Mark D. Baushke  <address@hidden>
+
+       * cvs.h (CVSREADONLYFS_ENV): New macro to support new environment
+       variable "CVSREADONLYFS" for read-only file-system repository mode.
+       * lock.c (Reader_Lock, Writer_Lock): Add support for new read-only
+       file-system repository mode.
+       * main.c (main, opt_usage): Ditto.
+       * root.c (parse_cvsroot): Ditto.
+       * sanity.sh (commit-readonlyfs): Test new read-only file-system
+       repository mode.
+
+2003-03-14  Mark D. Baushke  <address@hidden>
+
+       * server.c (template_proc): Fix broken Template protocol code.
+       Must call send buf_send_counted() for Template files to avoid
+       "Protocol error: uncounted data discarded" messages in some
+       circumstances.
+       * sanity.sh (template): Test case to verify fix this fix.
+
+2003-03-10  Mark D. Baushke  <address@hidden>
+
+       * cvs.h (WriteTemplate): Add missing prototype.
+
+2003-03-07  Mark D Baushke <address@hidden>
+
+       * sanity.sh: Fix broken setting of the servercvs variable.
+
+2003-03-07  Derek Price  <address@hidden>
+
+       * sanity.sh (help): Add explanation of CVS-TO-TEST and edit for
+       consistency.
+
+2003-03-07  Derek Price  <address@hidden>
+
+       * sanity.sh (usage): Show users long --help rather than less
+       informative -h.
+
+2003-03-07  Derek Price  <address@hidden>
+
+       * sanity.sh: Add support for long options.
+       (exit_usage): Move the actual generation of usage text to...
+       (usage): ...this new function and improve the usage message.
+       (exit_help): New function.
+
+2003-03-07  Mark D. Baushke  <address@hidden>
+
+       * sanity.sh: Drop the clientcvs option. Add usage info for
+       the -s servercvs option.
+
+2003-03-07  Larry Jones  <address@hidden>
+
+       * commit.c (check_fileproc): Remove unused variables.
+
+       * patch.c (patch): Pass local to do_module so that -l actually works.
+       (Reported by John Coers <address@hidden>.)
+       (patch_fileproc): Fix uninitialized variables.
+       * sanity.sh: Define a DATE pattern for rdiff and use it.
+       (basic2-24a): New test for above.
+
+2003-03-07  Mark D. Baushke  <address@hidden>
+
+       * entries.c (WriteTemplate): New function to control updates to
+       the CVS/Template file or its removal.
+       * create_adm.c (Create_Admin): Use the new WriteTemplate function.
+       * add.c (add_directory): Add a WriteTemplate() call
+       when a new directory is added to the repository.
+       * commit.c (commit_filesdoneproc): Ensure that the CVS/Template is
+       updated at the end of a commit -- mostly to remove it if it is not
+       relevant.
+       (commit_dirleaveproc): Ensure that the CVS/Template gets updated
+       when the directory is left.
+       * update.c (update_dirent_proc): Update CVS/Template file.
+       * server.c (server_clear_template): New protocol response to
+       remove existing CVS/Template files.
+       * client.c (clear_template): New function to remove or truncate a
+       CVS/Template file.
+       (handle_clear_template): New function. Handle Clear-template
+       protocol response message.
+       (save_prog): Add new Clear-template response line.
+       * sanity.sh (template): Test the CVS/Template creation with
+       the remote protocol. Nothing gets created locally.
+       (multiroot2): Fix for minor changes to trace output.
+       (getopts): Allow tests to be run with specified client and server
+       cvs commands to allow for interoperatbility testing.
+       (check_keep): New shell function for --keep processing.
+
+2003-03-06  Derek Price  <address@hidden>
+
+       * subr.c (file_has_conflict): New file.
+       * commit.c (check_fileproc): Factor code into new file_has_conflict()
+       function.
+       * update.c (update_fileproc): Ditto.
+       * status.c (status_fileproc): Use new file_has_conflict() function.
+       (Report from Bernd Kuemmerlen <address@hidden>.)
+
+       * sanity.sh (status): New test for same.
+
+2003-03-06  Larry Jones  <address@hidden>
+
+       * sanity.sh (branches3-4): Set and export CVS_LOCAL_BRANCH_NUM
+       to be sure it really gets into the environment, then unset it
+       when finished (ala CVSWRAPPERS et al).
+
+2003-03-03  Mark D. Baushke  <address@hidden>
+
+       * sanith.sh (branches3): Localize the setting of
+       the CVS_LOCAL_BRANCH_NUM environment variable.
+
+       * rcs.c (RCS_magicrev): CVS_LOCAL_BRANCH_NUM feature.
+       Port of the FreeBSD hack for setting the next magic branch number
+       to be used. The original patch was written by Peter Wemm
+       <address@hidden> and may be found by visiting the URL:
+       
http://www.freebsd.org/cgi/cvsweb.cgi/src/contrib/cvs/src/rcs.c.diff?r1=1.1&r2=1.2
+       Implement a horrible (but simple) hack to allow some control over the
+       branch number that is assigned. This is specifically to support the
+       local commit feature of cvsup. If one sets $CVS_LOCAL_BRANCH_NUM to
+       (say) 1000 then branches the local repository, the revision numbers
+       will look like 1.66.1000.xx. This is almost a dead-set certainty that
+       there will be no conflicts with version numbers.
+       (This needs to be something more than an option to 'cvs tag' or 'cvs
+       rtag' as various parts of cvs "know" how to automatically branch files
+       (eg: cvs add). Trying to remember state is getting "Too Hard (TM)")
+       * sanity.sh (branches3): Test the CVS_LOCAL_BRANCH_NUM feature.
+
+2003-03-04  Derek Price  <address@hidden>
+
+       * history.c (history_write): Remove unneeded O_CREAT in the call to
+       open() since we abort a few lines earlier if the file doesn't exist.
+       Add a comment to the effect that this is not the optimal method of
+       doing things and needs fixed.
+
+2003-02-28  Derek Price  <address@hidden>
+
+       * root.c (parse_cvsroot): Set no_password for :gserver: and :kserver:
+       as tokens should already be obtained via external sources.
+       * update.c (update_fileproc): Remove redundant code.
+
+2003-02-28  Larry Jones  <address@hidden>
+
+       * lock.c (set_lock): If possible, try a short wait with no message
+       before calling lock_wait() to optimize master lock contention.
+
+2003-02-26  Larry Jones  <address@hidden>
+
+       * checkout.c (checkout): Send "--" before file names.
+       * sanity.sh (spacefiles): Remote now works just like local.
+
+2003-02-25  Derek Price  <address@hidden>
+
+       * sanity.sh (rcs4): Use UTC to work across timezones.
+
+2003-02-25  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_getdate): Fix a bug that shows up when checking out
+       files by date with the "-D date" command line option. There is
+       code in the original to handle a special case. If the date search
+       finds revision 1.1 it is supposed to check whether revision
+       1.1.1.1 has the same date stamp, which would indicate that the
+       file was originally brought in with "cvs import". In that case it
+       is supposed to return the vendor branch version 1.1.1.1.
+
+       However, there was a bug in the code. It actually compares the date
+       of revision 1.1 for equality with the date given on the command
+       line -- clearly wrong. This commit fixes the coding bug.
+
+       Note: There is an additional bug which is _not_ fixed in this
+       commit. The date comparison should not be a strict equality test.
+       It should allow a fudge factor of, say, 2-3 seconds. Old versions
+       of CVS created the two revisions with two separate invocations of
+       the RCS "ci" command. We have many old files in the tree in which
+       the dates of revisions 1.1 and 1.1.1.1 differ by 1 second.
+
+       This bug was discovered and fixed for FreeBSD cvs. See v 1.21 of
+       <http://www.freebsd.org/cgi/cvsweb.cgi/src/contrib/cvs/src/rcs.c.diff>
+        for more information.
+
+       * sanity.sh (rcs4): Tests for same.
+       (Patch from Mark D. Baushke <address@hidden>.)
+
+2003-02-25  Derek Price  <address@hidden>
+
+       * logmsg.c (logfile_write): Do not pass a NULL pointer to
+       fprintf() when we have an empty log message.
+       * sanity.sh (editor): Add new tests to verify correct behavior of
+       empty log messages.
+       (Patch from Mark D. Baushke <address@hidden>, original report from
+       Piotr KUCHARSKI <address@hidden>.)
+
+2003-02-25  Derek Price  <address@hidden>
+
+       * cvs.h (user_admin_options): Rename to...
+       (UserAdminOptions): ...this to match the convention set by
+       of RereadLogAfterVerify.
+       * admin.c (admin): Ditto.
+       * parseinfo.c (parse_config): Ditto.
+
+       * mkmodules.c (config_contents): Update with UserAdminOptions
+       information.
+
+2003-02-25  Derek Price  <address@hidden>
+
+       * cvsbug.in: Import use of mktemp function from RedHat 8.0's
+       CVS 1.11.2 RPM.  Use new MKTEMP configure variable.  Use new
+       SENDMAIL from configure.
+
+       * Makefile.in: Regenerated.
+
+2003-02-25  Derek Price  <address@hidden>
+
+       * cvs.h (user_admin_options): New global config option.
+       * admin.c (admin): Handle user_admin_options.
+       * parseinfo.c (parse_config): Handle UserAdminOptions.
+       (Original patch from Dan Peterson <address@hidden>.)
+
+2003-02-25  Derek Price  <address@hidden>
+
+       * watch.c (watch_usage): Use {} rather than () for literals.
+
+2003-02-21  Larry Jones  <address@hidden>
+
+       * server.c (switch_to_user): Update comment, change error message
+       so it's not an exact duplicate of the one in check_password.
+       (check_repository_password): Add syslog call for password mismatches.
+       (check_password): Add syslog call for password mismatches, rearrange
+       code to simplify and eliminate redundancy.
+       (pserver_authenticate_connection): Remove syslog call, now done by
+       lower-level routines.
+
+2003-02-19  Larry Jones  <address@hidden>
+
+       * sanity.sh (admin-10): Add test for repository files not in
+       working directory.
+
+       * admin.c (admin_fileproc): Fix crash when no rcs file, return
+       failure status for bogus files.
+       * sanity.sh (admin-4a): Test for above.
+       (Original patch submitted by Mark D. Baushke <address@hidden>).
+
+2003-02-14  Larry Jones  <address@hidden>
+
+       * log.c (log_expand_revlist): Fix crashes in error cases.
+       (Reported by Bart Santy <address@hidden>.)
+       * sanity.sh (log): New tests for above.
+
+2003-02-14  Derek Price  <address@hidden>
+
+       * rcs.h (RCSNode): Add a field for the original path to print with
+       error messages.
+       * rcs.c (RCS_parsercsfile_i): Keep track of the original path for error
+       messages.
+       (freercsnode): Free the origpath.
+
+2003-02-14  Derek Price  <address@hidden>
+
+       * watch.c (watch_usage): Make the repeatability of -a part of the
+       usage spec.
+
+2003-02-14  Derek Price  <address@hidden>
+
+       * watch.c (watch_usage): Mention default for -a.  Mention multiple
+       invocations of -a.  Mention -R as default.  Use required () rather than
+       optional [] around watch subcommand list in invocation spec.  Use
+       `path' instead of `file'.  Put variable <> around `action' and `path'.
+
+2003-02-12  Derek Price  <address@hidden>
+
+       * main.c (main): Update copyright message to 2003.
+
+2003-02-08  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_checkout): Supply the full function name in the TRACE
+       output.
+       * update.c (checkout_file, join_file): Supply tag properly to
+       RCS_checkout more often.
+       (patch_file): Ditto.  Fill out comments.
+       * sanity.sh (keyword, keywordname): Some changes to accomodate the fact
+       that the above changes cause patches generated by patch_file to fail
+       occassionally.
+
+2003-02-07  Derek Price  <address@hidden>
+
+       * sanity.sh (*): Don't keep running after a test when --keep has been
+       supplied.  That was kind of silly, wasn't it?
+
+2003-02-07  Derek Price  <address@hidden>
+
+       * rcscmds.c (RCS_merge): Add a FIXME.
+
+2003-02-07  Derek Price  <address@hidden>
+
+       * commit.c (checkaddfile): Do not lose the vendor branch when
+       adding files to a new branch. Avoids extranious conflicts for
+       future vendor imports. This was found and fixed in FreeBSD cvs.
+       See http://www.freebsd.org/cgi/query-pr.cgi?pr=4033 for details.
+       * sanity.sh (branch-after-import): New test.
+       (Thanks to Mark D Baushke <address@hidden> for forwarding the
+       patch and writing the test cases!)
+
+       * sanity.sh (branch-after-import): Misc portablility and standard
+       changes.
+
+2003-02-07  Derek Price  <address@hidden>
+
+       * add.c: Exercise the pet peeve Karl Fogel, I think, infected me with
+       about using the word invalid rather than illegal and reserving illegal
+       for use when actually discussing laws and governmentally enforced
+       restrictions:
+       s/illegal/invalid/g;s/legality/validity/g;s/legal/valid/g;
+       * admin.c: Ditto.
+       * cvs.h: Ditto.
+       * expand_path.c: Ditto.
+       * log.c: Ditto.
+       * modules.c: Ditto.
+       * rcs.c: Ditto.
+       * rcs.h: Ditto.
+       * repos.c: Ditto.
+       * root.c: Ditto.
+       * sanity.sh: Ditto.
+       * scramble.c: Ditto.
+       * server.c: Ditto.
+       * subr.c: Ditto.
+
+2003-02-06  Derek Price  <address@hidden>
+
+       * rcs.c (RCS_getdatebranch): Update header comment to reflect the state
+       of the docs and the code's operation.
+
+2003-02-06  Derek Price  <address@hidden>
+
+       * client.c: Use the complete path to the CVSADM_TEMPLATE file in
+       error messages.  Remove related FIXME.
+
+2003-02-04  Derek Price  <address@hidden>
+
+       * status.c (status_fileproc): Add a FIXME comment.
+
+2003-02-04  Derek Price  <address@hidden>
+
+       * sanity.sh (conflicts2- c. 142d): New test for double add and two
+       attempted commits of files with the same name.  Fill out some comments
+       and change one FIXME to a FIXCVS THEN FIXME.
+
+2003-02-03  Derek Price  <address@hidden>
+
+       * client.c (start_Server): Send multiple trace options when
+       necessary.
+       * server.c (server): Update trace option processing to accept multiple
+       -t arguments.
+       * *: Use new TRACE macro.
+
+2003-02-02  Larry Jones  <address@hidden>
+
+       * error.c: Update to match error.h.
+
+       * cvs.h (cvs_trace): Add attribute for GNU printf format checking.
+       * error.h: Use same check for prototypes as cvs.h.  Use PROTO
+       macro rather than #ifdef for error and error_exit.
+
+2003-02-01  Larry Jones  <address@hidden>
+
+       * buffer.c (stdio_buffer_shutdown): Handle EINTR from waitpid.
+       (Patch from Johannes Grødem <address@hidden>.)
+
+2003-02-01  Derek Price  <address@hidden>
+
+       * lock.c: Remove extra line feed on TRACE output.
+
+2003-01-31  Derek Price  <address@hidden>
+
+       * cvs.h: Move header includes in from...
+       * error.c: ...here.  Remove checks for definition of vprintf().
+       Since our error() function was making assumptions about the definition
+       of VA_START, we must not have been compiling on platforms without
+       vprintf for quite awhile and I've heard no complaints.
+       (fperrmsg): Assume vprintf().
+       * subr.c (cvs_trace): Don't assume ANSI C function declarations.
+
+2003-01-31  Derek Price  <address@hidden>
+
+       * main.c (main): Allow multiple -t options.
+       (opt_usage): Correct usage.
+       * cvs.h (TRACE): New macro.
+       * subr.c (cvs_trace): New function.
+       (Thanks to the team at the CVSNT project.)
+
+       * lock.c (*): Use new TRACE macro.
+
+2003-01-31  Derek Price  <address@hidden>
+
+       * sanity.sh (keywordname): Change a "FIXME" comment to "FIXCVS".
+
+2003-01-30  Derek Price  <address@hidden>
+
+       * sanity.sh (keywordname): New test.
+
+2003-01-23  Larry Jones  <address@hidden>
+
+       * diff.c (diff_fileproc): Restructure code to simplify and eliminate
+       redundant tests.
+
+       * server.c (do_cvs_command): Use WCOREDUMP macro rather than hard
+       coding test for core file.
+
+2003-01-21  Larry Jones  <address@hidden>
+
+       * root.c (method_name): Redefine as a 2D array.
+       * root.h (method_name): Ditto.
+
+2003-01-21  Jim Meyering  <address@hidden>
+
+       * add.c (add): Rename local-shadowing `i' to `j'.
+
+       * root.c (method_names): Declare to be a const array of const strings.
+       (Name_Root): Save errno so it doesn't get clobbered
+       by the intervening error call.
+       Use getline's return value, mainly to save a call to strrchr.
+
+2003-01-20  Larry Jones  <address@hidden>
+
+       * myndbm.c (O_ACCMODE): Parenthesize the replacement string so that
+       it parses correctly.
+       (Reported by Andres Bertens <address@hidden>.)
+
+2003-01-15  Karl Fogel  <address@hidden>
+
+       * server.c (dirswitch): Don't free dir_name until right before
+       allocating it again.  This removes a potential double-free
+       problem, whereby this function could free dir_name and then
+       immediately return due to invalid directory syntax (without ever
+       reassigning dir_name), then reenter and free dir_name again.
+
+        Thanks to Stefan Esser <address@hidden> for the fix.
+
+2003-01-08  Larry Jones  <address@hidden>
+
+       * client.c (update_entries): Only "0" is a special version number;
+       other numbers starting with 0 (like 0.1) are normal version numbers.
+       * commit.c (find_fileproc): Ditto.  Also reorganize the code to
+       simplify the conditions.
+       (Reported by Michele Zamparelli <address@hidden>.)
+
+2003-01-02  Larry Jones  <address@hidden>
+
+       * rcs.c (getdelta): Use RCSDEAD rather than literal "dead".
+
+2002-12-27  Derek Price  <address@hidden>
+
+       * admin.c: s/LOCK_(NONE|WRITE|READ)/CVS_$&/g; since the definition of
+       LOCK_WRITE clashes with a definition in objidl.h on Windoze platforms.
+       * annotate.c: Ditto.
+       * client.c: Ditto.
+       * commit.c: Ditto.
+       * cvs.h: Ditto.
+       * diff.c: Ditto.
+       * edit.c: Ditto.
+       * lock.c: Ditto.
+       * log.c: Ditto.
+       * patch.c: Ditto.
+       * recurse.c: Ditto.
+       * remove.c: Ditto.
+       * status.c: Ditto.
+       * tag.c: Ditto.
+       * update.c: Ditto.
+       * watch.c: Ditto.
+       * myndbm.c: Ditto & define O_ACCMODE when it isn't defined, as under
+       Windoze.
+       (Thanks to Stephane Rouleau <address@hidden>,
+        Cristopher Seawood <address@hidden>, and
+        Frederico Costa <address@hidden> for all their hints,
+        tips, and patches for this problem.)
+
+2002-12-20  Derek Price  <address@hidden>
+
+       * client.c (send_a_repository): Suppress a warning under Windoze.
+
+2002-12-19  Derek Price  <address@hidden>
+
+       * Makefile.am: Remove reference to options.h.
+       * cvs.h: Ditto.
+       * options.h: Remove this obsolete file.
+       * sanity.sh: Remove comment about external diffs causing tests to fail
+       since CVS hasn't used external diffs in years.
+
+       * Makefile.in: Regenerated.
+
+2002-12-16  Derek Price  <address@hidden>
+
+       * admin.c: Disable cvsadmin group checking on the client.
+       (Reported by Dan Peterson <address@hidden>.)
+
+2002-12-06  Derek Price  <address@hidden>
+
+       * buffer.c: Replace calls to malloc with calls to xmalloc and calls to
+       realloc with calls to xrealloc.
+       * parseinfo.c: Ditto.
+       * root.c: Ditto.
+       * server.c: Ditto.
+       * zlib.c: Ditto.
+       * scramble.c: Change some comments to refer to xmalloc rather than
+       malloc.
+       (Reported by Dan Peterson <address@hidden>.)
+
+2002-12-04  Derek Price  <address@hidden>
+
+       * options.h: Remove CVS_ADMIN_GROUP.
+
+2002-12-02  Larry Jones  <address@hidden>
+
+       * commit.c (commit): Strip leading zeros from numeric revision
+       in addition to trailing dots.
+       (Reported by Peter Meszaros <address@hidden>.)
+
+2002-11-22  Larry Jones  <address@hidden>
+
+       * sanity.sh: Note that the tests run for a long time.
+
+       * checkout.c (safe_location): Use xstrdup, not strdup.
+       (Reported by Terrence Enger <address@hidden>.)
+
+2002-11-19  Larry Jones  <address@hidden>
+
+       * log.c (log_expand_revlist): Fix cross-branch correction code.
+
+       * sanity.sh: Set $LANG for systems that ignore $LC_ALL.
+       (rcs2-7): Change date offset from 100 months to 96 months to reduce
+       periodic problems with invalid dates.
+
+2002-11-12  Derek Price  <address@hidden>
+
+       * sanity.sh (rcslib-symlink): Use rm -f rather than a simple rm when
+       removing links because under some configurations of RH Linux 8.0 the
+       script pauses to ask for removal approval.
+
+2002-11-08  Derek Price  <address@hidden>
+
+       * sanity.sh (importc): Update the use of the touch command to be
+       compliant with POSIX 1003.1-2001, SUS2, and SUS3 now that GNU touch
+       supports this.  If this breaks any test platforms we should test
+       the behavior of touch like we do for other tools.
+
+2002-11-03  Derek Price  <address@hidden>
+
+       * sanity.sh (rcs2-7): Notate with a wild untested hypothesis.
+
+2002-11-03  Derek Price  <address@hidden>
+
+       * sanity.sh (rcs2-7): Notate with three more failure dates.
+
+2002-10-25  Derek Price  <address@hidden>
+
+       * root.c: Change some calls to SYSTEM_CLEANUP() and then exit() to
+       more appropriate calls to error_exit().
+       * server.c: Ditto.
+       * tag.c: Ditto.
+
+2002-10-24  Derek Price  <address@hidden>
+
+       * buffer.c (stdio_buffer_shutdown): Remove the getc() call used to
+       detect spurious output from clients since getc() would sometimes
+       block and hang indefinately if the client kept the conection open but
+       sent no data.  Bug reports state that this hapened frequently with
+       older clients connecting to 1.11.2 servers, especially when
+       compression is enabled.
+       (Original report from Mark D. Baushke <address@hidden>.
+        Original patch from Ralf S. Engelschall <address@hidden>
+        via Peter Wemm <address@hidden>.)
+
+2002-10-05  Larry Jones  <address@hidden>
+
+       * recurse.c (start_recursion, do_recursion): Allow write locking
+       in addition to read locking.  Change all callers.
+       * cvs.h: Change prototype to match, add lock types.
+       * tag.c (rtag_proc, rtag_fileproc, tag_fileproc): Have start_recursion
+       use write locks rather than calling lock_dir_for_write to avoid deadly
+       embrace.
+
+2002-10-04  Larry Jones  <address@hidden>
+
+       * client.c (get_responses_and_close, connect_to_pserver): Set
+       to_server and from_server to NULL after freeing.
+       * main.c (main): Clear server_active when finished.  Also neaten
+       up the SERVER_SUPPORT ifdef's.
+       * server.c (do_cvs_command): Set protocol_inbuf, stderrbuf, and
+       stdoutbuf to NULL after freeing.
+       (server_cleanup): Free buf_from_net and buf_to_set and set to NULL.
+       Also reset error_use_protocol.
+       (server): Don't SIG_register server_cleanup.  main_cleanup (which
+       is already registered) outputs a fatal error which causes it to
+       be called; registering it directly results in it being called twice.
+       (cvs_output): Don't try to use buf_to_net or protocol if they're NULL.
+
+2002-10-03  Larry Jones  <address@hidden>
+
+       * lock.c (readers_exist): Ignore our own read lock, if any, to
+       allow upgrading an existing read lock to a write lock.
+       * tag.c (rtag_proc, rtag_fileproc, tag_fileproc): Rather than
+       locking the entire tree, have start_recursion establish read
+       locks and then upgrade the read lock to a write lock (so only
+       one directory is locked at a time).
+
+2002-09-27  Larry Jones  <address@hidden>
+
+       * add.c (add): Send "--" before file names.
+       * admin.c (admin): Ditto.
+       * annotate.c (annotate): Ditto.
+       * commit.c (commit): Ditto.
+       * diff.c (diff): Ditto.
+       * edit.c (watch_onoff, editors): Ditto.
+       * log.c (cvslog): Ditto.
+       * remove.c (cvsremove): Ditto.
+       * status.c (cvsstatus): Ditto.
+       * tag.c (cvstag): Ditto.
+       * update.c (update): Ditto.
+       * watch.c (watch_addremove, watchers): Ditto.
+
+       * sanity.sh (client-9): Update to match.
+
+2002-09-24  Derek Price  <address@hidden>
+
+       * options.h: Remove prototype of STDC exit() function.  If this breaks
+       a build, this should be detected in configure.in somehow rather than
+       restoring the line to this file.
+
+2002-09-24  Derek Price  <address@hidden>
+
+       * options.h: Move definition of AUTH_CLIENT_SUPPORT into configure.in.
+
+2002-09-24  Derek Price  <address@hidden>
+
+       * options.h: Move definition of FORCE_USE_EDITOR into configure.in.
+
+2002-09-24  Derek Price  <address@hidden>
+
+       * options.h: Move definition of UMASK_DFLT into configure.in.
+
+2002-09-24  Derek Price  <address@hidden>
+
+       * Makefile.in: Regenerated using Automake 1.6.3.
+
+2002-09-24  Larry Jones  <address@hidden>
+
+       * filesubr.c, history.c, import.c, rcs.c, update.c: Use
+       HAVE_STRUCT_STAT_ST_BLKSIZE and HAVE_STRUCT_STAT_ST_RDEV instead of
+       the obsolete HAVE_ST_BLKSIZE and HAVE_ST_RDEV.
+
+2002-09-24  Derek Price  <address@hidden>
+
+       * options.h: Move definition of TMPDIR_DFLT into configure.in.
+
+2002-09-24  Derek Price  <address@hidden>
+
+       * options.h: Move defininition of EDITOR_DFLT into configure.in.
+
+       * Makefile.in: Regenerated.
+
+2002-09-23  Jim Meyering  <address@hidden>
+
+       If `cvs -d REPO commit ...' was used to override CVS/Root,
+       then modified files in the directory from which cvs is invoked
+       would not be committed.
+       * client.c (arg_should_not_be_sent_to_server): The above would happen
+       because this function would throw out a file name when CVS/Root
+       did not match the current server.  Fix by allowing the command-line-
+       specified repository to take precedence over the value returned
+       by Name_Root.  Patch by Simon Walton <address@hidden>.
+       * sanity.sh (commit-d): New tests for the above.
+       Patch by Simon Walton <address@hidden>.
+
+2002-09-20  Derek Price  <address@hidden>
+
+       * options.h: Move definition of SERVER_FLOWCONTROL, SERVER_HI_WATER,
+       and SERVER_LO_WATER into configure.in.
+
+2002-09-20  Derek Price  <address@hidden>
+
+       * options.h: Move definition of PATCH_PROGRAM to configure.in.
+
+2002-09-18  Larry Jones  <address@hidden>
+
+       * client.c (call_in_directory): Don't create admin directory when
+       exporting into an existing directory.
+       (Reported by Jens Engel <address@hidden>.)
+       * sanity.sh (basic2): New tests for above.
+
+2002-09-16  Jim Meyering  <address@hidden>
+
+       * server.c (do_cvs_command): Move declarations of locals, timeout and
+       timeout_ptr, `up', out of enclosing `#ifdef SERVER_FLOWCONTROL' block.
+       Otherwise, this file would not compile with SERVER_FLOWCONTROL
+       turned off.  Patch by Ed Santiago <address@hidden>.
+
+2002-09-15  Larry Jones  <address@hidden>
+
+       * myndbm.c (mydbm_open): Open the file read/write rather than read-
+       only if that's what the user asked for to ensure that the later open
+       for write will succeed.
+       (Patch submitted by Josh Lehan <address@hidden>.)
+
+2002-08-28  Larry Jones  <address@hidden>
+
+       * logmsg.c (do_editor): Fix bug which prevented reusing log messages.
+       (Reported by Eric Siegerman <address@hidden>.)
+
+2002-08-16  Derek Price  <address@hidden>
+
+       * create_adm.c (Create_Admin): Assume RELATIVE_REPOS is set.
+       * server.c (outside_root): Add comment.
+       * options.h: Remove RELATIVE_REPOS & CVS_BADROOT.
+       * sanity.sh: Remove a lot of !RELATIVE_REPOS cruft from tests.
+
+2002-08-14  Derek Price  <address@hidden>
+
+       * server.c (server): Dispose of the correct pointer.  Tidy comment.
+
+2002-08-13  Derek Price  <address@hidden>
+
+       * client.c (get_cvs_port_number): Fix typo in comment.  Add comments.
+       * server.c (server): Fix a FIXME.  Remove an errant "const" directive.
+       Remove some redundant memory allocation and error handling code.
+
+2002-08-08  Derek Price  <address@hidden>
+
+       * import.c (import): Surrounded `server_active' with
+       #ifdef SERVER_SUPPORT/#endif.
+       * commit.c (commit_fileproc, commit_direntproc): Likewise.
+       (Patch from John Tytgat  <address@hidden>.)
+
+2002-07-31  Derek Price  <address@hidden>
+
+       * filesubr.c: Add a line so VIM can determine tab stops and shift 
widths.
+       * root.c: Ditto.
+       * (parse_cvsroot): Add comments and tidy slightly.
+
+2002-07-31  Derek Price  <address@hidden>
+
+       * sanity.sh: Add another date to the comment about rcs2-7 failing.
+
+2002-07-26  Jim Meyering  <address@hidden>
+
+       * commit.c (find_fileproc): When committing in client mode,
+       arrange to fail if a `cvs add'ed file no longer exists in the
+       working directory.
+       * sanity.sh (commit-add-missing): New test for above.
+
+2002-07-25  Larry Jones  <address@hidden>
+
+       * sanity.sh: Set $TMPDIR if it's not already set and use it rather
+       than /tmp for the expected server temp directory path.
+
+2002-07-09  Larry Jones  <address@hidden>
+
+       * vers_ts.c (time_stamp_server, time_stamp): Eliminate unneeded
+       struct_tm copying.
+
+       * lock.c (lock_wait, lock_obtained): Display time in UTC if possible
+       to reduce confusion in client/server mode.
+       (Original patch from Eduardo Perez Ureta <address@hidden>.)
+
+2002-06-26  Larry Jones  <address@hidden>
+
+       * tag.c (check_fileproc): When checking up-to-date, T_REMOVE_ENTRY
+       is also a valid status.
+       (Reported by David Everly <address@hidden>.)
+       * sanity.sh (tagc): New tests for above.
+
+2002-06-18  Larry Jones  <address@hidden>
+
+       * update.c (patch_file): Don't patch if diff bigger than file.
+       Don't bother adjusting the permission on the diff output if
+       we're not going to use it.
+
+
+2002-06-18  Derek Price  <address@hidden>
+
+       * server.c: Handle HPUX password expiration fields in the passwd
+       string in case we are set up on a server with NIS passwords served
+       from HPUX.
+       (Original patch from John Cavanaugh <address@hidden>.)
+
+2002-06-17  Larry Jones  <address@hidden>
+           and Jonathan Kamens  <address@hidden>
+
+       * commit.c (commit_fileproc, commit_direntproc): Don't try to call
+       an editor to get the log message if running as a server.  Instead,
+       just use an empty log message.
+       * import.c (import): Ditto.
+
+       * import.c (import): In client mode, always send a message to the
+       server, even if it's empty (this parallels a change made by Larry
+       Jones to commit.c on May 7).
+
+2002-05-31  Larry Jones  <address@hidden>
+
+       * rcs.c: Conditionally define MAP_FAILED for old systems that don't
+       have it in <mman.h>.
+       (Reported by jeremy brand <address@hidden>.)
+
+2002-05-24  Larry Jones  <address@hidden>
+
+       * rcscmds.c (diff_exec): Add a -- before the first file name just
+       in case it looks like an option.
+       (Reported by Zooko <address@hidden>.)
+
+       * rcscmds.c (diff_execv): Remove -- same as diff_exec.  Change
+       only caller.
+       * cvs.h: Ditto.
+
+2002-05-23  Larry Jones  <address@hidden>
+
+       * cvs.h (strcat_filename_onto_homedir): Make arguments const.
+       * filesubr.c (strcat_filename_onto_homedir): Make arguments const,
+       move more code here from callers, change all callers.
+
+2002-05-22  Derek Price  <address@hidden>
+
+       * cvs.h: Add prototype for this...
+       * filesubr.c (strcat_filename_onto_homedir): new function.
+       * login.c (): Use new function.
+
+       * cvsrc.c (read_cvsrc): Use new function due to problems on VMS.
+       * ignore.c (ign_setup): Ditto.
+       * wrapper.c (wrap_setup): Ditto.
+       (Original patch from Karsten Spang <address@hidden>.)
+
+2002-05-21  Larry Jones  <address@hidden>
+
+       * rcs.c (rcsbuf_getkey): Correct off-by-one error in ptr assertion
+       and add a similar assertion for ptrend.
+       (Reported by Rebecca Young <address@hidden>.)
+       (rcsbuf_fill): Remove redundant code.
+
+2002-05-20  Derek Price  <address@hidden>
+
+       * buffer.h: New prototype for...
+       * buffer.c (stdio_buffer_get_file): this new function to abstract
+       access to a buffer's file descriptor.
+       * client.c (auth_server): Use the new function.
+       (Original patch from Jonathan Kamens <address@hidden>.)
+
+2002-05-20  Derek Price  <address@hidden>
+
+       * main.c (main): Add 2002 to the copyright years output with the
+       version string.
+
+2002-05-15  Larry Jones  <address@hidden>
+
+       * log.c (log_parse_list): Fix off-by-one error which caused
+       incorrect handling of 'cvs log -wuser1,user2 foo.c' command.
+       (Patch from Alexey Mahotkin <address@hidden>,
+       reported by Alex Morozov <address@hidden>.)
+
+2002-05-09  Larry Jones  <address@hidden>
+
+       * login.c (password_entry_operation): Get cvsroot_canonical before
+       trying to read the user's password file so we have it even if the
+       file doesn't exist.
+       (Reported by Sarah Thompson <address@hidden>.)
+
+2002-05-08  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Add options.h explicitly - since we
+       stopped generating it dynamically, Automake stopped noticing it and
+       including it in dists.  See TODO item #214 for notes.
+
+2002-05-08  Derek Price  <address@hidden>
+
+       * cvs.h: Use the HAVE_CONFIG_H define.
+
+2002-05-07  Larry Jones  <address@hidden>
+
+       * filesubr.c (isaccessible): Set errno before returning failure
+       in the SETXID_SUPPORT code.
+
+       * logmsg (do_verify): Avoid even more work if there's no verifymsg
+       script to run.
+
+       * logmsg: Use fputs/putc rather than fprintf where appropriate.
+       (do_verify): Run the verifymsg script even if there's no log
+       message.  (Reported by Andy Baker <address@hidden>.)
+       Don't reread the log message unless a verifymsg script was run.
+
+       * commit.c (commit): Always send -m to the server, even if there's
+       no message.
+
+       * create_adm.c (Create_Admin): Add dotemplate parameter to trace.
+       Remove unreachable code.
+
+2002-05-03  Larry Jones  <address@hidden>
+
+       * server.c (serve_watch_on, serve_watch_off, serve_watch_add,
+        serve_watch_remove): Just pass "watch" as the command name
+        to do_cvs_command to avoid unknown command errors.
+        (Reported by Gary Hennigan <address@hidden>.)
+
+       * rcs.c (RCS_checkin): Fix bad call to error () in buggy
+       PRESERVE_PERMISSIONS code.
+       (rcs_internal_unlockfile): Include current value of errno in error
+       message even though it may well be irrelevant (it's still better
+       than nothing).
+
+2002-05-02  Derek Price  <address@hidden>
+
+       * .cvsignore: Remove lines for files obsoleted by new autotools.
+
+2002-05-02  Derek Price  <address@hidden>
+
+       * stamp-h2.in: Remove this uneeded file.
+
+2002-05-01  Derek Price  <address@hidden>
+
+       * options.h.in: Move to...
+       * options.h: here.
+
+2002-04-30  Derek Price  <address@hidden>
+
+       * version.h.in: Remove this file.
+       * version.h: Ditto.
+
+       * Makefile.am: Remove references to version.h.
+       * cvs.h: Use <> rather than "" around the config.h #include.  I didn't
+       quite bother to understand why, but autoconf recommends it.
+       * cvsbug.in: Use PACKAGE_BUGREPORT defined by configure for the bug
+       report email address.
+       * version.c (version): Use PACKAGE_STRING defined in config.h instead
+       of the version_string that used to be defined in version.h.
+
+       * Makefile.in: Regenerated with automake 1.6.
+
+2002-04-28  Derek Price  <address@hidden>
+
+       * cvs.h: Use `"'s around includes when we mean a local file.
+
+2002-04-28  Derek Price  <address@hidden>
+
+       * cvs.h: #define new names for functions and variables when they
+       might conflict with system definitions (namely on Mac OS X 10.1 with
+       the most recent dev packages - This should be removable after the Mac
+       dev packages are fixed.).
+
+2002-04-26  Larry Jones  <address@hidden>
+
+       * logmsg.c (do_editor): Fix assertion when CLIENT_SUPPORT not defined.
+       (Reported by Matthias Andree <address@hidden>.)
+
+2002-04-19  Larry Jones  <address@hidden>
+
+       * log.c (log_expand_revlist): First cut at code to allow logging
+       between a revision and *any* ancestor, not just one explicitly on
+       the same branch (e.g., from 1.1 to 4.1.2.3.6.1).
+
+       * subr.c (gca): Simplify and optimize.
+
+2002-04-19  Jim Meyering  <address@hidden>
+       and Ed Santiago <address@hidden>
+
+       * classify.c (Classify_File): Fix it so that `cvs update -p -r...'
+       works, even under some slightly unusual (though perfectly legitimate)
+       circumstances.
+       * sanity.sh (update-p): New tests for this.
+
+2002-04-18  Derek Price  <address@hidden>
+
+       * sanity.sh: Move test for regex metacharacters in username until
+       after we're sure we found the version of expr that we're going to use.
+
+2002-04-18  Larry Jones  <address@hidden>
+
+       * admin.c (admin_fileproc): Allow admin to be used on RCS files with
+       no local version (e.g., removed files) like most other subcommands.
+
+       * wrapper.c (wrap_add): Update URL of -t/-f wrapper discussion.
+
+2002-04-18  Derek Price  <address@hidden>
+
+       * version.h: Regenerated for 1.11.2.1 version update.
+
+2002-04-17  Derek Price  <address@hidden>
+
+       * version.h: Regenerated for 1.11.2.
+
+2002-04-03  Derek Price  <address@hidden>
+
+       * stamp-h2.in: Regenerate with recent version of Autoconf.
+
+2002-04-03  Derek Price  <address@hidden>
+
+       * sanity.sh (TR): Send the stderr of one of the tool setup (tr) tests
+       to /dev/null to avoid spurious output on some operating systems
+       (notably Mac OS X).
+
+2002-03-22  Larry Jones  <address@hidden>
+
+       * sanity.sh (rcslib): Correct new tests to use ${testcvs} instead
+       of cvs.
+
+2002-03-21  Derek Price  <address@hidden>
+
+       * vers_ts.c (time_stamp): Return the timestamp for the newer of the
+       link and the link's source when the file is a link.
+       (Patch from RedHat cvs-1.11.1p1-7 SRPM.)
+
+       * sanity.sh (rcslib): Test for same.
+
+2002-03-17  Larry Jones  <address@hidden>
+
+       * log.c (cvslog, log_fileproc): Add -S option to suppress head or
+       file name if no revisions selected.
+       * sanity.sh (log): New tests for above.
+
+2002-03-13  Derek Price  <address@hidden>
+
+       * main.c (usg): Correct a spelling mistake in a comment.
+       (Thanks to Matt Kraai <address@hidden>.)
+
+2002-03-09  Larry Jones  <address@hidden>
+
+       * import.c (import): Change the suggested merge message to use
+       rev tags instead of the branch tag with a date.
+       * sanity.sh (import, importb): Change to match.
+
+       * remove.c (remove_fileproc): Disallow removing files with sticky
+       dates for the same reason we already disallow sticky numeric tags.
+       * sanity.sh (sticky): New test for above.
+
+2002-02-27  Larry Jones  <address@hidden>
+
+       * diff.c (diff_fileproc): Treat dead revisions as nonexistent.
+
+2002-02-26  Larry Jones  <address@hidden>
+
+       * diff.c (diff): Remove -V and --paginate options: they aren't valid.
+       (diff_usage): Document all the diff options.
+
+2002-02-13  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_gettag): Do not interpret an empty tag as HEAD (nothing
+       else does and I don't see any documentation that says it should).
+       (translate_symtag): Break out of loop at end of symbols to prevent
+       looping forever when tag is "".
+       (Reported by Alain ENOUT <address@hidden>
+       via Eric Gillespie <address@hidden>.)
+
+2002-02-11  Larry Jones  <address@hidden>
+
+       * server.c (server_cleanup): Set buf_to_net back to blocking mode
+       and flush it (in case there are any error messages pending) before
+       shutting down buf_from_net and again right before shutting it down.
+
+2002-02-08  Larry Jones  <address@hidden>
+
+       * main.c (lookup_command_attribute): Throw a fatal error if the
+       command is not found.
+       * server.c (server_tag): Use the correct command name.
+
+2002-01-30  Larry Jones  <address@hidden>
+
+       * error.h (error_exit): Remove unintended prototype.
+
+       * server.c (serve_root): Remove check for impossible condition.
+       (serve_init): Save and restore current_parsed_root.
+
+2002-01-29  Larry Jones  <address@hidden>
+
+       * error.h (error_exit): Declare __noreturn__ to avoid spurious
+       warnings.
+
+       * server.c (serve_root): If the specified root doesn't match the
+       pserver root, return before changing current_parsed_root to prevent
+       subsequent commands from accessing an unchecked root directory.
+       (server_init): Check specified root against the pserver root and
+       complain if they don't match.  Also, if there are pending errors,
+       print them and return before changing current_parsed_root to prevent
+       subsequent commands from accessing an unchecked root directory.
+       * sanity.sh (pserver): New tests for above.
+
+2002-01-10  Larry Jones  <address@hidden>
+
+       * log.c (log_version_requested): Change :: in revision spec to be
+       exclusive just on the low end (so -r tag1::tag2 gives revisions
+       after tag1 but up to and including tag2), which is much more useful
+       than the previous (exclusive at both ends) behavior.
+       (log_usage): Update to match.
+       * sanity.sh (log): Update to match.
+
+2002-01-02  Larry Jones  <address@hidden>
+
+       * server.c (LOG_DAEMON): Define if needed.
+       (Patch from John David Anglin <address@hidden>.)
+
+       * server.c (pserver_authenticate_connection): Add a specific error
+       message for EOF at protocol start and syslog if available.
+       * sanity.sh (pserver-bufinit): Update to match.
+
+2001-12-10  Larry Jones  <address@hidden>
+
+       * log.c (log_usage): Note that -r and -d take lists, not just a
+       single specification.
+       (log_expand_revlist): Don't dereference null pointers when one end
+       of a revision range is a non-existent tag.
+
+2001-12-03  Larry Jones  <address@hidden>
+
+       * annotate.c (annotate, annotate_fileproc): Don't annotate binary
+       files unless new -F option given.
+       * sanity.sh (basica, ann, ann-id, rcs, keywordlog, tagdate): Update
+       to match.
+
+2001-11-30  Larry Jones  <address@hidden>
+
+       * admin.c (admin): Allow unrestricted usage of -q in addition to -k.
+
+2001-10-25  Larry Jones  <address@hidden>
+
+       * log.c (log_expand_revlist): Make erroneous or inconsistent revision
+       specs select no revisions rather than all revisions.
+
+2001-10-23  Larry Jones  <address@hidden>
+
+       * import.c (add_rcs_file): Don't put an expand entry into the file
+       for the default expansion mode (kv).
+       * wrapper.c (wrap_send, wrap_unparse_rcs_options): Process entries
+       with default expansion mode since they may be needed to avoid matching
+       a more general entry later.
+       (wrap_add): Set rcsOption to NULL for default (kv).
+       (wrap_add_entry): Use structure assignment to copy entries rather
+       that copying members by hand.
+       * sanity.sh (binwrap3): Revise to test wrapper entries that don't
+       specify any non-default options but just prevent matching later,
+       more general entries.
+
+2001-10-02  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_fully_parse): Add revision number to more error messages.
+
+2001-09-27  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_fully_parse, RCS_getdeltatext): Add the missing revision
+       number to the "mismatch" error message.
+
+       * sanity.sh (multiroot2-9a): Update to match changes to lock.c.
+
+2001-09-26  Larry Jones  <address@hidden>
+
+       * lock.c (Lock_Cleanup, Reader_Lock, write_lock): Add trace messages.
+
+2001-09-24  Derek Price  <address@hidden>
+
+        * find_names.c (add_entries_proc): Leave closure specified as such in 
the
+       function definition for clarity.
+
+        * find_names.c (Find_Names): Use 'closure' feature of walklist()
+        to eliminate the static variable.
+       (add_entries_proc): Expect closure to be the file list.
+       (Patch from Alexey Mahotkin  <address@hidden>.)
+
+2001-09-19  Derek Price  <address@hidden>
+
+       * rcs.c (rcsbuf_valpolish_internal): Restore one of the
+       "if ( ... ) abort();" sequences since it seems to check the validity of
+       the RCS file rather than for a programming error.  Also added a FIXME
+       comment to the effect that we should explain the RCS file error to the
+       user as such if it is such.
+       (Thanks to Larry Jones  <address@hidden>.)
+
+2001-09-19  Derek Price  <address@hidden>
+
+       * rcs.c (rcsbuf_getkey, rcsbuf_valpolish_internal): Replace some code
+       of the form "if ( ... ) abort();" with equivalent calls to assert().
+
+2001-09-17  Derek Price  <address@hidden>
+
+       * myndbm.c (mydbm_load_file): Fix buffer overflow error and make error
+       messages more informative.
+       * sanity.sh (modules6): New test.
+       (Original report from Taska <address@hidden> and others.)
+
+2001-09-14  Derek Price  <address@hidden>
+
+       * logmsg.c (do_verify): Dispose memory when finished with it.
+
+2001-09-07  Larry Jones  <address@hidden>
+
+       * mkmodules.c (notify_contents): In the example, move the %s to
+       the end since many, if not most, versions of mail insist on
+       options coming before addresses.
+
+2001-09-06  Derek Price  <address@hidden>
+
+       * login.c (login): Deal with NULL return value from getpass.
+
+2001-09-04  Derek Price  <address@hidden>
+
+       * Makefile.in: Regenerated with automake 1.5.
+       * stamp-h2.in: Ditto.
+
+2001-09-04  Derek Price  <address@hidden>
+
+       * main.c (main): Fix empty CVSROOT message to specify `valid' instead
+       of `legal'.
+
+2001-09-04  Derek Price  <address@hidden>
+
+       * server.c (pserver_authenticate_connection): Back out changes from the
+       30th and...
+       * getline.c (getstr): init the buffer instead.
+
+2001-08-31  Derek Price  <address@hidden>
+
+       * Makefile.in: Backed out accidental commit from yesterday.
+
+2001-08-30  Derek Price  <address@hidden>
+
+       * server.c (pserver_authenticate_connection): Don't print from the
+       NULL pointer in the error message string in the case where the client
+       didn't send any data.
+       * sanity.sh (pserver): Test for this case.
+       (Report from Mark Welch <address@hidden>).
+
+2001-08-24  Derek Price  <address@hidden>
+
+       * logmsg.c (do_editor): Add comment and assertion.
+       * import.c (import): Don't call do_editor with a repository argument
+       in client mode.
+       (Report and original patch from darkness <address@hidden>.)
+
+2001-08-24  Larry Jones  <address@hidden>
+
+       * log.c (log_expand_revlist): Arrange for nil revision specs to
+       select nothing instead of everything.
+       * sanity.sh (log): New tests for above.
+
+2001-08-24  Derek Price  <address@hidden>
+
+       * parseinfo.c (Parse_Info): Change the function name in the trace
+       and add the client/server string.
+
+2001-08-24  Derek Price  <address@hidden>
+
+       * Implement RereadLogAfterVerify CVSROOT/config option to control
+       FreeBSD read-write of log messages in the verification script.
+       * logmsg.c: RereadLogAfterVerify defaults to LOGMSG_REREAD_NEVER
+       to preserve the status quo.
+       * parseinfo.c (parse_config): Add parsing for RereadLogAfterVerify
+       option. Possible values are: no | never | yes | always | stat
+       * cvs.h: Add extern for RereadLogAfterVerify and new value macros
+       LOGMSG_REREAD_NEVER, LOGMSG_REREAD_ALWAYS, LOGMSG_REREAD_STAT for
+       its values.
+       (Patch from Mark D. Baushke  <address@hidden>.)
+
+       * Apply changes from FreeBSD cvs sources to implement a read-write
+       user-defined verification script.
+       * logmsg.c (do_verify): Update do_verify to expect a pointer
+       to the saved message. The log file passed to the verifymsg_script
+       should be re-read after the user-defined verification script has
+       been run. The user-defined verification script is allowed to
+       modify the message.  This allows the script to add extra
+       information to the log message or to remove template lines that
+       are not needed.
+       * cvs.h: Update prototype for do_verify prototype to expect a
+       pointer to the saved_message.
+       * commit.c (commit, commit_fileproc, commit_direntproc): Update
+       calls to do_verify as the saved_message arg is now read-write.
+       * import.c (import):  Update calls to do_verify as the
+       saved_message arg is now read-write.
+       * sanity.sh (info-v4-[12]): Rename the old info-v4 test to info-v5
+       and add a new info-v4 test case have the verification script
+       modify the log message to test the above changes.
+       (Patch from Mark D. Baushke  <address@hidden>.)
+
+       * logmsg.c: Change RereadLogAfterVerify default to always.
+       (do_verify): Reformat and make minor fixes to Mark's patch.
+       * mkmodules.c (config_constants): Add comment about
+       RereadLogAfterVerify.
+       * sanity.sh (info-rereadlog): Rename the tests from Mark's patch and
+       reformat them a bit.
+
+2001-08-23  Derek Price  <address@hidden>
+
+       * sanity.sh (info): Demonstrate that the verifymsg scripts can
+       sometimes, but not always, retreive information on which directory is
+       being committed to.
+
+2001-08-22  Derek Price  <address@hidden>
+
+       * logmsg.c: Back out the last change - the repository which is passed
+       in is actually the directory and changes with each call to do_verify.
+       If a verifymsg script is using `pwd`, this could change the operation.
+       * cvs.h: Ditto.
+       * commit.c: Ditto.
+       * import.c: Ditto.
+
+2001-08-22  Derek Price  <address@hidden>
+
+       * logmsg.c (do_editor): Return reused_message.
+       (do_verify): Don't verify the same log message more than once.
+       * cvs.h: Update prototypes for do_verify and do_editor.
+       * commit.c (commit_fileproc, commit_direntproc): Use the new 
functionality.
+       * import.c (import): Ditto.
+
+2001-08-22  Derek Price  <address@hidden>
+
+       * logmsg.c (do_verify): Remove an unecessary "else" clause following an
+       exit and unindent the former contents.
+
+2001-08-22  Derek Price  <address@hidden>
+
+       * commit.c (commit): Don't call do_verify in client mode since we know
+       do_verify will just return anyhow.
+
+2001-08-20  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Add version.c and version.h.
+       (BUILT_SOURCES): Add version.h.
+       (Maintainer Targets): Remove version.h.
+       * version.c: Remove @VERSION@ dependant bits.
+       * version.c.in: Removed.
+       * version.h.in: New file.
+       (Original patch from Alexey Mahotkin <address@hidden>.)
+
+       * Makefile.am: Various modifications to make Automake, make dist, and
+       windows targets work like they are supposed to.
+       * version.h: New (generated) file.
+
+       * Makefile.in: Regenerated.
+
+2001-08-09  Derek Price  <address@hidden>
+
+       * client.c (socket_buffer_shutdown): Use recv instead of read and
+       return 0 on success.
+       (Patch from "Manfred Klug" <address@hidden>.)
+
+2001-08-09  Derek Price  <address@hidden>
+
+       * buffer.c (stdio_buffer_shutdown): Assume the buffer is not a socket
+       when NO_SOCKET_TO_FD is defined.
+       * client.c (make_bufs_from_fds): Add is_sock argument and remove fstat
+       call and reference to S_ISSOCK since these functions aren't available
+       under Windows.
+       (connect_to_forked_server, connect_to_pserver, start_tcp_server,
+       start_server, start_rsh_server): Use new argument.
+       (Patch from "Manfred Klug" <address@hidden>.)
+
+       * buffer.c (stdio_buffer_shutdown): Various reformattings, fix bug
+       where rsh pipes weren't being closed.
+
+2001-08-09  Derek Price  <address@hidden>
+
+       * sanity.sh (rmadd, rm-update-message, join-two-branch,
+       ignore-on-branch): Change a few references to `cvs' to `$PROG'.
+
+2001-08-07  Derek Price  <address@hidden>
+
+       * build_src.com: Add annotate.c/annotate.obj,verify, correct zlib name.
+       * patch.c: VMS time_t appears to be unsigned.   Add a cast when testing
+       for (time_t)-1.
+       * subr.c: #else,#endif for no symlinks should be moved.
+       (Patch from Mike Marciniszyn <address@hidden>.)
+
+2001-08-06  Derek Price  <address@hidden>
+
+       * Makefile.in: Regenerated.
+
+2001-08-01  Derek Price  <address@hidden>
+
+       * diff.c (diff): Send long option for side-by-side diffs to the server
+       rather than '-y', for backwards compatibility with old servers.
+       (Original patch from Peter Mathiasson <address@hidden>.)
+
+2001-07-19  Larry Jones  <address@hidden>
+
+       * mkmodules.c (cvswrappers_contents): Remove -t/-f since they're
+       disabled in wrapper.c.
+
+       * checkout.c (checkout): Don't complain about checking out into the
+       repository when piping output.
+       (Reported by der Mouse <address@hidden>.)
+       * sanity.sh (checkout_repository): New tests for above.
+
+2001-07-10  Larry Jones  <address@hidden>
+
+       * sanity.sh (importc-7): Now works correctly in local mode.
+
+       * commit.c (commit_dirleaveproc): We're still in the directory when
+       this is called, so the first argument to Name_Repository needs to
+       be NULL, not dir.
+       * sanity.sh (rmadd): New tests for above.
+
+       * commit.c (commit): Reword error messages for committing as root.
+
+2001-07-08  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_checkout): Correct scanf format to allow for trailing
+       NUL terminator.
+       * update.c (special_file_mismatch): Ditto.
+       (Reported by Pekka Savola <address@hidden>.)
+
+2001-07-05  Larry Jones  <address@hidden>
+
+       * client.c, root.c: Fix -Wall warnings.
+
+       * buffer.c: #include socket header to declare shutdown().
+
+       * rcs.c (rcsbuf_open): Use getpagesize() instead of sysconf() for
+       portability.
+       (RCS_copydeltas, rcsbuf_fill): Fix -Wall warnings.
+
+2001-07-04  Derek Price  <address@hidden>
+
+       * Makefile.in: Regenerated with new Automake release candidate 1.4h.
+
+2001-07-03  Derek Price  <address@hidden>
+
+       * rcs.c (rcsbuf_open): Reduce memory consumption still further by not
+       mmapping the entire file when pos is specified.
+       (rcsbuf_cache_open): Add FIXME comment wrt read-only mmaps and rcsbuf
+       caching.
+
+2001-07-03  Derek Price  <address@hidden>
+
+       * rcs.c (rcsbuf_open): Use mmap when possible to reduce memory
+       consumption, especially with large (e.g. binary) files.
+       (rcsbuf_close): Call munmap.
+       (rcsbuf_getkey): Remove the buffer fill code when using mmap.
+       (rcsbuf_getrevnum): Ditto.
+       (rcsbuf_fill): Remove this function when using mmap.
+       (rcsbuf_cache_open): Mostly don't use this function with mmap.
+       (RCS_copydeltas): Don't depend on the file pointer with mmap.
+
+       * stamp-h2.in: Regenerated.
+
+2001-07-03  Derek Price  <address@hidden>
+
+       * update.c: Indent compiler directives.
+
+2001-07-02  Larry Jones  <address@hidden>
+
+       * import.c (update_rcs_file): Use -kb instead of -ko when comparing
+       binary files.
+       (Reported by Gyula Faller <address@hidden>.)
+
+2001-06-28  Larry Jones  <address@hidden>
+
+       * checkout.c (checkout): Explicitly initialize all the static options
+       so that multiple calls work right.  Also fix potential memory leaks.
+       (Reported by Dr. Dieter Maurer <address@hidden>.)
+
+2001-06-28  Derek Price  <address@hidden>
+
+       * Makefile.in: Regenerated with new version of Automake.
+
+2001-06-28  Larry Jones  <address@hidden>
+
+       * checkout.c (checkout): Set history_name for export as well as
+       checkout.
+       (checkout_proc): Use it.
+
+       * checkout.c (safe_location): Add missing argument in error message.
+
+2001-06-26  Larry Jones  <address@hidden>
+
+       * recurse.c (start_recursion): Use strip_trailing_slashes instead
+       of doing it by hand.
+
+       * server.c (pserver_authenticate_connection): Don't clear out
+       descrambled_password until *after* it's (potentially) logged.
+       (Reported by Eric Hanchrow <address@hidden>.)
+
+2001-06-25  Larry Jones  <address@hidden>
+
+       * recurse.c (start_recursion): Deal with at least some of the cases
+       where trailing slashes cause confusion.
+       (Reported by Malcolm Fernandes <address@hidden>.)
+       * sanity.sh (basica, basicb): Tweak existing tests to check this.
+
+2001-06-22  Larry Jones  <address@hidden>
+
+       * sanity.sh (modules5): New tests with -d on command line.
+
+2001-06-21  Larry Jones  <address@hidden>
+
+       * modules.c (do_module): Use run_module_prog and server_active to
+       determine when to call server_prog instead of using server_expanding
+       so that we get the right paths in the replies as long as we take
+       mwhere into account in addition to where.
+       (Reported by Pascal Bourguignon <address@hidden>.)
+       * server.c (server_prog): Use protocol pipe instead of buf_to_net.
+       * sanity.sh (modules5): Remove FIXCVS comment and update to match.
+       * server.c, server.h: Remove server_expanding since now unused.
+
+2001-06-21  Larry Jones  <address@hidden>
+        for Stephen Rasku  <address@hidden>
+
+       * admin.c: Corrected spelling mistakes in help.
+
+2001-06-20  Derek Price  <address@hidden>
+
+       * client.c (socket_buffer_shutdown): Fix untested typos.
+       (Reported by "Jerzy Kaczorowski" <address@hidden>.)
+
+       * buffer.c (stdio_buffer_shutdown): Put the call to SHUTDOWN_SERVER in
+       the correct place.
+
+2001-06-20  Derek Price  <address@hidden>
+
+       * logmsg.c (do_editor): Abort in the case that the file has only
+       comment lines.
+       (Original patch from Mark Valentine <address@hidden>.)
+
+       * logmsg.c (do_editor): Fix rare memory leak.
+       * sanity.sh (editor): Add tests for aborted log messages.
+
+2001-06-20  Larry Jones  <address@hidden>
+
+       * server.c (switch_to_user): Only set $CVS_USER if
+       AUTH_SERVER_SUPPORT is defined.
+       (Reported by Nalin Dahyabhai <address@hidden>.)
+
+2001-06-13  Derek Price  <address@hidden>
+
+        * client.c: Fix incorrect fixed-size buffer usage in
+        connect_to_gserver().
+       (Minor changes to a patch from Alexey Mahotkin  <address@hidden>.)
+
+2001-06-11  Derek Price  <address@hidden>
+
+       * main.c (main): Always print $CVSROOT when parse_cvsroot fails.
+       * root.c (parse_cvsroot): Tidy error messages and provide more
+       consistent behavior.
+       * sanity.sh (crerepos): Adapt to new error messages.
+       (Suggested by  Alexey Mahotkin <address@hidden>.)
+
+2001-06-08  Derek Price  <address@hidden>
+
+       * sanity.sh (tagf-28): Use $CVSROOT_DIRNAME.
+
+2001-06-07  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_unlock): Reverse kj's change of 1999-10-18: a bare -u
+       should never break locks, you have to specify a specific revision
+       to do that.  Also add an informative message for a bare -u when
+       the user doesn't hold any locks.
+       * commit.c (unlockrcs): Make RCS_unlock quiet, like RCS_lock.
+       * sanity.sh (rmadd-24): Update to match.
+
+       * sanity.sh (crerepos-6a): Set CVS_RSH for ${testcvs}, not for
+       dotest_fail.  Allow for "broken pipe" rather than "end of file".
+
+2001-06-07  Derek Price  <address@hidden>
+
+       * sanity.sh (tagf): Use $CVSROOT_DIRNAME rather than
+       /tmp/cvs-sanity/cvsroot.
+
+2001-06-06  Derek Price  <address@hidden>
+
+       (Reformatting, bug fixes, tests, and comments to a
+       patch from Stephen Cameron <address@hidden>.)
+
+       * tag.c: (rtag_fileproc, rtag_delete, tag_fileproc)
+         Changed behavior of "cvs tag -F", "cvs tag -d", "cvs rtag -F"
+         and "cvs rtag -d" so that they will not disturb existing
+         branch tags unless a new "-B" option is given.
+       * sanity.sh (tagf-16 - tagf-33): Added tests for new -B option
+         to "cvs tag" and "cvs rtag"
+
+2001-06-06  Derek Price  <address@hidden>
+
+       * sanity.sh (crerepos-6a): Set CVS_RSH=false and only for the actual
+       test call at Larry's suggestion.  Also, test the error message since
+       it's fixed now.
+
+2001-06-05  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_unlock): Note when breaking someone else's lock.
+       (Reported by MURVAI-BUZOGANY Laszlo
+       <address@hidden>.)
+       * sanity.sh (reserved-14): Update to match.
+
+2001-06-05  Derek Price  <address@hidden>
+
+       * sanity.sh (crerepos-6a): Set CVS_RSH=/bin/false...  this is a local
+       mode only test anyhow.
+       (Thanks to Larry Jones and Morgan Burke <address@hidden>.)
+
+2001-05-31  Derek Price  <address@hidden>
+
+       * sanity.sh (rcs2-7): Add today to the list of failure dates for rcs2-7
+       in the hopes that the data will eventually prove useful to someone
+       motivated enough to fix the problem.
+
+2001-05-30  Derek Price  <address@hidden>
+
+       * stamp-h2.in: Regenerated.
+
+2001-05-30  Derek Price  <address@hidden>
+
+       * *: Various bug fixes and comments for the following
+       patch from Donald Sharp <address@hidden>:
+
+       * checkout.c (safe_location): cvs co -d <directory> still had
+       failure modes from the way the -d option works.
+       * sanity.sh: Misc error message resynching.
+
+2001-05-29  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Add root.h.
+
+       * Makefile.in: Regenerated.
+       * stamp-h2.in: Regenerated.
+
+2001-05-29  Derek Price  <address@hidden>
+
+       * checkout.c (safe_location): Correct formatting.
+
+2001-05-29  Derek Price  <address@hidden>
+
+       * root.c (parse_cvsroot): Fix a comment.
+
+2001-05-26  Larry Jones  <address@hidden>
+
+       * checkout.c (safe_location): Use old-style definition to keep
+       non-ANSI compilers happy.
+
+       * sanity.sh (check_respository): Use ${CVSROOT_DIRNAME} instead
+       of /tmp/cvs-sanity/cvsroot.
+
+2001-05-25  Larry Jones  <address@hidden>
+
+       * sanity.sh (modules5): Add sleep to script to help avoid out of
+       order messages.
+
+       * filesubr.c (mkdir_if_needed): Return 1 if the directory exists
+       reguardless of what errno is set to.
+       (Reported by "Robinson, Greg" <address@hidden>.)
+
+2001-05-25  Derek Price  <address@hidden>
+       for Donald Sharp  <address@hidden>
+
+       * checkout.c:  Modified safe_location() to refuse checkout if
+       the -d option to co specifies inside of the repository.
+       * import.c:  New parameter to safe_location needed to be added.
+       * cvs.h:  New parameter to safe_location needed to be added.
+       * sanity.sh:  Test case to test for failure mode.
+
+2001-05-23  Larry Jones  <address@hidden>
+
+       * checkout.c (checkout_proc): Don't build top_level_admin directory
+       when exporting.
+       (Reported by Tony Byrne <address@hidden>.)
+
+2001-05-21  Derek Price  <address@hidden>
+
+       * client.c: Fix a mispelling in a comment.
+       (Patch from Alexey Mahotkin <address@hidden>).
+
+2001-05-05  Larry Jones  <address@hidden>
+
+       * login.c (password_entry_operation): Only warn if unable to open
+       .cvspass for reading: may be initial login and it doesn't exist yet.
+
+2001-05-15  Derek Price  <address@hidden>
+
+       * client.c (start_tcp_server): Use the struct sockaddr_in declared in
+       the function.
+       (Reported by Emil Isberg <address@hidden>.)
+
+2001-05-05  Larry Jones  <address@hidden>
+
+       * annotate.c (annotate): Pass local to do_module and rannotate_proc
+       so that -l actually works.
+       * log.c (cvslog): Ditto.
+       * patch.c (patch): Ditto; make local local instead of global.
+       (patch_proc): Use local_specified parameter instead of global.
+       * tag.c (cvstag, rtag_proc): Ditto.
+
+2001-05-05  Larry Jones  <address@hidden>
+
+       * client.h: Declare "struct buffer" outside prototype for __STDC__
+       compilers.
+
+2001-05-04  Derek Price  <address@hidden>
+
+       * client.c:  General refactoring.  Removed several global variables in
+       favor of passing locals and/or dynamic evaluation.
+       (recv_line): Removed this function.
+       (make_bufs_from_fds): New function with factored code.
+       (connect_to_forked_server): New prototype.  Use new functions.
+       (connect_to_pserver): New prototype.  Use new functions.
+       (connect_to_gserver): New prototype.  Use new API.
+       (auth_server): Factored this portion of the pserver code so it can be
+       shared.  Rewrote to use buffers rather than depending on a socket.
+       (start_rsh_server): New prototype.  Use new API.
+       (start_tcp_server): New prototype.  Use new API.
+       (start_server): Factor some code.  Use new API.
+       * client.h: New prototypes.
+       * cvs.h: Gratuitous reformatting.  Use new root.h.
+       * login.c (login): Use new connect_to_pserver API.
+       * root.h: New file.  Contains some code that used to be in cvs.h.
+
+2001-05-04  Derek Price  <address@hidden>
+
+       * client.c: Gratuitous reformatting.
+       * client.h: Ditto.
+
+2001-05-04  Derek Price  <address@hidden>
+
+       * zlib.c (compress_buffer_shutdown_input): Use new buffer shutdown
+       prototype.
+       (compress_buffer_shutdown_output): Ditto.
+       (Thanks to Pavel Roskin <address@hidden>.)
+
+2001-05-03  Derek Price  <address@hidden>
+
+       * buffer.c (struct stdio_buffer_closure): New structure to hold a
+       FILE * and the child's PID when necessary.
+       (stdio_buffer_initialize): Change proto to accept PID.  Set up new
+       closure.  Pass new stdio_buffer_shutdown to buf_initialize.
+       (stdio_buffer_input): Use new closure.
+       (stdio_buffer_output): Ditto.
+       (stdio_buffer_flush): Ditto.
+       (stdio_buffer_shutdown): New function.  Teach buffer to close itself.
+       (packetizing_buffer_shutdown): Use new buffer shutdown proto.
+       * buffer.h (struct buffer): New buffer shutdown proto.
+       (stdio_buffer_initialize): New proto.
+       * client.c (log_buffer_shutdown): Use new proto.
+       (socket_buffer_initialize): Pass shutdown func.
+       (socket_buffer_shutdown): New function.
+       * server.c (get_responses_and_close): Remove most of the guts.  Rely
+       on the buffer shutdown function from now on.
+       (start_rsh_server): Return child PID.
+
+2001-05-03  Larry Jones  <address@hidden>
+
+       * history.c (history_write): Handle the case where the user's home
+       directory doesn't exist gracefully instead of erroring out.
+       (Reported by David Hoover <address@hidden>.)
+
+2001-05-03  Derek Price  <address@hidden>
+
+       * cvs.h: s/allocate_and_strcat/xrealloc_and_strcat/ since that is what
+       I wrote in the ChangeLog, oh, so long ago.
+       * diff.c (diff): Ditto.
+       * subr.c (allocate_and_strcat, xrealloc_and_strcat): Ditto.
+
+2001-05-02  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_getdate): Handle the (unusual!) case where we
+       can't find any revisions at all.
+       (Reported by Ryan Grow <address@hidden>.)
+
+2001-04-30  Larry Jones  <address@hidden>
+
+       * sanity.sh (multiroot2-9a): Rename (from multiroot2-9) to avoid
+       duplicate names; fix to work without SERVER_SUPPORT defined.
+       (Reported by Pavel Roskin <address@hidden>.)
+
+2001-04-29  Derek Price  <address@hidden>
+
+       * Makefile.am (check-local): Make dependent on localcheck and
+       remotecheck and move old check target...
+       (localcheck): here.
+
+       * Makefile.in: Regenerated.
+
+2001-04-27  Larry Jones  <address@hidden>
+
+       * sanity.sh (pserver): Add tests for readers and writers.
+
+2001-04-27  Derek Price  <address@hidden>
+
+       * sanity.sh (version-2r): Update to handle patch releases in version
+       numbers.
+
+2001-04-27  Derek Price  <address@hidden>
+
+       * version.c: Regenerated.
+
+2001-04-27  Derek Price  <address@hidden>
+
+       * version.c: Regenerated.
+
+2001-04-27  Larry Jones  <address@hidden>
+
+       * main.c (lookup_command_attribute): Lookup specified command, not
+       whatever's in the global command_name.
+
+2001-04-25  Derek Price  <address@hidden>
+
+       * Makefile.in: Regenerated using AM 1.4e as of today at 18:10 -0400.
+       * version.c: Regenerated.
+
+2001-04-22  Larry Jones  <address@hidden>
+
+       * tag.c (tag_check_valid): Make an unwritable val-tags file a
+       warning instead of a fatal error.
+
+2001-04-20  Larry Jones  <address@hidden>
+
+       * annotate.c (annotate_usage): -r and -D are not mutually exclusive.
+       * main.c (cmd_usage): Add missing version subcommand.
+       * update.c (update_usage): Add missing -C option.
+
+       * sanity.sh (death2): New tests for previous change.
+
+       * classify.c (Classify_File): Treat a dead revision like the RCS
+       file doesn't exist.
+       * sanity.sh: Update to match.
+
+2001-04-16  Larry Jones  <address@hidden>
+
+       * checkout.c, update.c: Fix usage messages: -r and -D are not
+       mutually exclusive.
+       (Suggested by David L. Martin <address@hidden>.)
+
+       * logmsg.c (do_editor): Don't add a blank line to the message.
+       * sanity.sh (editor-log-file*): Update to match.
+
+       * checkout.c, update.c: Note in usage message that -k is sticky.
+
+       * server.c: (server_cleanup, wait_sig): Remove ancient SunOS kludge.
+       (Suggested by Rob Saccoccio <address@hidden>.)
+
+2001-04-04  Larry Jones  <address@hidden>
+
+       * sanity.sh (dotest, dotest_lit, dotest_fail, dotest_status,
+       dotest_sort): Don't count on $? being set in then or else clauses.
+
+       * ignore.c (ignore_files): Collect unignored files into a list and
+       sort it before calling PROC to avoid order dependencies.  Rewrite
+       the while loop to allow normal continues instead of goto.
+
+2001-04-04  Derek Price  <address@hidden>
+
+       * sanity.sh (ignore-on-branch-3): Fix in the remote case.
+
+2001-04-03  Larry Jones  <address@hidden>
+
+       * update.c (update_fileproc): Remove unused variable (resurrecting).
+
+2001-04-03  Derek Price  <address@hidden>
+           Larry Jones  <address@hidden>
+           reported by Jakob Bøhm  <address@hidden>
+
+       * update.c (update_fileproc): Don't store a file with T_UNKNOWN status
+       in ignlist if present in the sandbox.
+       * sanity.sh (ignore-on-branch): New test.
+       (ignore): Tidy this test.
+
+2001-04-02  Derek Price  <address@hidden>
+
+       * sanity.sh: Make sure the test for `id' fails when a nonstandard `id'
+       is used and the user is root.  Fix some quoting in error messages.
+       (fork): Take `cvs' out of the PATH.
+       (TODO): Add note about the test suite not working with user names over
+       eight characters in length.
+
+2001-04-02  Derek Price  <address@hidden>
+
+       * sanity.sh (fork): New test for CVS_SERVER default.
+       (TODO): Note about eventually removing most of the references to
+       CVS_SERVER.
+
+2001-04-02  Larry Jones  <address@hidden>
+
+       * client.c (connect_to_forked_server): Use program_path as the default
+       server instead of "cvs".
+
+2001-04-02  Derek Price  <address@hidden>
+
+       * sanity.sh: Use less obfuscated English in my comment about sanity
+       checking sanity.sh.
+
+2001-04-02  Derek Price  <address@hidden>
+
+       * sanity.sh (rm-update-message): Create a test directory again but
+       change back to the correct directory upon completion this time.
+
+2001-04-02  Derek Price  <address@hidden>
+
+       * sanity.sh: Change last two '[.*]'s to 'test's for
+       consistency and remove...
+       (TODO): the note from the TODO list.
+
+2001-04-02  Derek Price  <address@hidden>
+
+       * sanity.sh: Add test for PWD before successful exit.
+
+2001-03-30  Larry Jones  <address@hidden>
+
+       * sanity.sh (rm-update-message): Remove duplicate code.
+
+2001-03-30  Derek Price  <address@hidden>
+
+       * sanity.sh (rm-update-message): New test for local/client-server
+       warning message discrepency.
+
+2001-03-30  Larry Jones  <address@hidden>
+
+       * annotate.c: Move annotate() here from rcs.c, support rannotate.
+       * Makefile.am, Makefile.in: Add annotate.c.
+       * main.c (cmds[], cmd_usage[]): Add rannotate.
+       * rcs.c: Move declarations of rcs_delta_op and RCS_deltas to...
+       * rcs.h:    ... here.
+       * server.c (serve_rannotate): New.
+       (requests[]): Add rannotate.
+       * sanity.sh (ann): New tests for rannotate.
+
+       * log.c (rlog_proc): Remove dead code.
+
+2001-03-30  Derek Price  <address@hidden>
+
+       * sanity.sh (join-readonly-conflict): Run more of this through dotest.
+
+2001-03-30  Larry Jones  <address@hidden>
+
+       * log.c (log_fileproc): Don't output working file for rlog.
+       * sanity.sh (log): New tests for rlog.
+
+       * cvs.h (mtype): Add MISC type.
+       * log.c (cvslog): Support rlog as well as log.
+       (rlog_proc): New.
+       * main.c (cmds[], cmd_usage[]): Add rlog.
+       (main): Remove old rlog warning message.
+       * server.c (serve_rlog): New.
+       (requests[]): Add rlog.
+
+2001-03-29  Derek Price  <address@hidden>
+
+       * sanity.sh: cd to $TESTDIR once after it is normalized.  Make TODO
+       on history and symlinks more specific.  Tested properly this time.
+
+2001-03-29  Larry Jones  <address@hidden>
+
+       * main.c (cmds[], lookup_command_attribute, main): Include the
+       command attributes in the global command table instead of inferring
+       them from the command names.  Change the sense of the
+       CVS_CMD_IGNORE_ADMROOT attribute to match its name.
+
+2001-03-29  Derek Price  <address@hidden>
+
+       * sanity.sh (*, basic2-64): Remove references to TMPPWD.  Fix FIXME
+       at end of script now that $TESTDIR can't be relative.
+
+2001-03-29  Derek Price  <address@hidden>
+
+       * sanity.sh: Normalize TESTDIR even when the user set it.
+
+2001-03-29  Larry Jones  <address@hidden>
+
+       * client.c (connect_to_pserver, start_tcp_server): Add IP address
+       to connect failed message.
+       (connect_to_forked_server, connect_to_pserver, start_tcp_server): Add
+       trace messages ala start_rsh_server.
+       (start_rsh_server): Include entire command in trace message for
+       START_RSH_WITH_POPEN_RW like ! START_RSH_WITH_POPEN_RW does.
+
+2001-03-29  Derek Price  <address@hidden>
+
+       * sanity.sh: Global search & replace ${TESTDIR}/cvsroot with
+       ${CVSROOT_DIRNAME} for consistency.
+
+2001-03-29  Derek Price  <address@hidden>
+
+       * sanity.sh (conflicts-12[68].5): Remove sanity hack which has allowed
+       for a CVS bug since May 96/97.  Not sure when the bug went bye-bye, but
+       the tests broke when $TESTDIR != $TMPPWD.
+
+2001-03-26  Larry Jones  <address@hidden>
+
+       * classify.c (Classify_File): Don't report a conflict for a removed
+       file when piping.  Also simplify the code structure.
+       (Reported by Milos Kleint <address@hidden>.)
+       * sanity.sh (rmadd2-14[abc]): New tests for above.
+
+2001-03-24  Noel Cragg  <address@hidden>
+
+       * diff.c: mods to allow `-T' and `-y' options to be passed through
+       to the diff library.  This wasn't allowed earlier because of a
+       similarly named options that got passed through to the old rcs
+       programs.  We've long since stopped sending `-T' to any rcs
+       utility and have never used `-y'.  Any users of moldly CVS
+       versions which used to support `-T' have (hopefully) upgraded to
+       one where that option isn't supported.  It seems reasonable to
+       enable them again and pass them through.  (sanity.sh still works
+       anyways...)
+       (longopts): add short option equivalents for --initial-tab and
+       --side-by-side.
+       (diff): add new short options to getopt string and switch
+       statement.
+
+2001-03-22  Larry Jones  <address@hidden>
+
+       * sanity.sh: Add check for ${DOTSTAR} with large matches.
+
+2001-03-23  Derek Price  <address@hidden>
+
+       * sanity.sh: Do the same as below for $keep.
+
+2001-03-23  Derek Price  <address@hidden>
+
+       * sanity.sh: Replace 'remote=(yes|no)' with 'remote=(:|false)' since
+       often 'false' and more often ':' are shell builtins.  This makes the
+       succinct, 'if $remote; then' faster than 'if test $remote = yes; then'.
+       Alter tests in the rest of the script to match the new usage.  Added
+       a suffix of 'r' to remote test names when it was appropriate and I
+       remembered.  Some reformatting.
+
+2001-03-22  Larry Jones  <address@hidden>
+
+       * sanity.sh (diffmerge1_yours, diffmerge1_mine): Check for exact
+       output instead of using wildcards to avoid buffer overflows in some
+       versions of expr.
+
+2001-03-21  Derek Price  <address@hidden>
+
+       * sanity.sh: cd to '/tmp' again rather than $HOME since HOME was set to
+       a value inside ${TESTDIR} by the script.
+
+2001-03-20  Derek Price  <address@hidden>
+
+       * sanity.sh (diffmerge1): Minor formatting and syntax changes.
+
+           for Jacob Burckhardt  <address@hidden>
+
+       * sanity.sh (diffmerge1): More merging behavior tests.  Specifically,
+       test some cases which broke before in Karl Tomlinson's diff fix was
+       checked in today.
+
+2001-03-20  Derek Price  <address@hidden>
+
+       * sanity.sh: Don't use unescaped parens in sh outside of quotes.
+
+2001-03-20  Derek Price  <address@hidden>
+
+       * sanity.sh: Don't remove ${TESTDIR} when -k (keep) set.
+
+2001-03-20  Derek Price  <address@hidden>
+
+       * sanity.sh: Change usage to match the new getopts format and comment.
+
+2001-03-16  Derek Price  <address@hidden>
+
+       * sanity.sh (modules2-nestedrename): New test.  Verifies behavior of
+       renames nested under an ampersand module.
+       (modules2-ampertag): New test.  Verifies an error condition which
+       prevents some ampersand modules from being checked out when a tag
+       is specified.
+
+2001-03-16  Derek Price  <address@hidden>
+
+       * sanity.sh (modules2): Additional test for ampersand module behavior
+       with '-d'.
+
+           for Greg Klanderman  <address@hidden>
+
+       * checkout.c (build_one_dir): Fix typo where clauses of two
+       conditionals were reversed in call to Create_Admin.  This caused
+       the CVS/Tag file to be removed in cases where it should have been
+       set, and vice-versa.  It only surfaced in rare cases as this code
+       is only invoked when using the -d option to build the path to
+       check out in.  Further, the bug would only matter when checking
+       out a module containing ampersand modules within it, via
+       client/server CVS.
+
+2001-03-16  Derek Price  <address@hidden>
+
+       * sanity.sh (admin-28-5): Confirm that a missing tag during an
+       'admin -n' operation is not a fatal error.
+
+2001-03-16  Derek Price  <address@hidden>
+
+       * admin.c (admin_data): Remove 'quiet' member.
+       (admin_fileproc): Use global 'really_quiet' rather than
+       admin_data->quiet.
+
+2001-03-16  Derek Price  <address@hidden>
+
+       * sanity.sh (admin): Replace hardcoded testdir path with the variable.
+
+2001-03-15  Derek Price  <address@hidden>
+
+       * sanity.sh (basica, binfiles, head, admin): Adjust for new messages.
+       * admin.c (admin_fileproc): Only print messages when not in
+       really_quiet mode.
+
+           for Stephen Rasku  <address@hidden>
+
+       * rcs.c (RCS_tag2rev): Make a missing tag a survivable error.
+
+2001-03-15  Larry Jones  <address@hidden>
+
+       * subr.c (sleep_past): Fix various bugs that would result in a
+       negative sleep time if it weren't unsigned; since it is, it would
+       result in a very large sleep time.  Ensure that us is always less
+       than 1000000.  Don't try to sleep for more 1 sec with usleep.
+       Cast NULL select arguments to correct type just in case.
+
+2001-03-14  Derek Price  <address@hidden>
+
+       * subr.c (sleep_past): New function.
+       * client.c (get_responses_and_close): Use new function.
+       * commit.c (commit): Ditto.
+       * update.c (do_update): Ditto.
+       * cvs.h: Prototype new function.
+
+       * stamp-h2.in: Regenerated.
+
+2001-03-14  Derek Price  <address@hidden>
+
+       * Makefile.in: Regenerated.
+       * stamp-h2.in: Ditto.
+
+2001-03-14  Larry Jones  <address@hidden>
+
+       * commit.c (check_fileproc): Allow adding on the trunk when there's
+       an existing non-Attic RCS file as long as the head revision is dead.
+       This can happen due to an aborted resurrection.
+       (commit_fileproc): When resurrecting, consider the dead revision
+       along with the other files' revisions.
+       (findmaxrev): Avoid unnecessary work.
+       (checkaddfile): Only warn if file isn't in Attic as expected.
+       (Reported by Ross Burton <address@hidden>.)
+       * sanity.sh (basica-r*): New tests for above.
+       (basica-o4): Update to match.
+
+2001-03-09  Larry Jones  <address@hidden>
+
+       * edit.c (edit_fileproc, unedit_fileproc): Some implementations of
+       asctime/ctime apparently use a leading zero on the date instead
+       of the space required by the C Standard.  Correct for this so that
+       shared working directories work without hassle.
+       (Reported by David L. Martin <address@hidden>.)
+       * entries.c (fgetentent): Ditto.
+       * vers_ts.c (time_stamp_server, time_stamp) Ditto.
+
+2001-03-07  Larry Jones  <address@hidden>
+
+       * sanity.sh (basica, binfiles2, head, admin): Update to match
+       change to admin.c.
+
+2001-03-06  Larry Jones  <address@hidden>
+
+       * client.c (recv_bytes): Handle EOF as in recv_line().
+       (Reported by Pavel Roskin <address@hidden>.)
+
+       * admin.c (admin_fileproc): Change final error message to clarify
+       that CVS refused to modify the RCS file rather than being unable to.
+
+2001-02-28  Jim Meyering  <address@hidden>
+
+       * commit.c (commit_usage): Use `-F logfile' (rather than -F file') in
+       the description of that option, to be consistent with the `-F logfile'
+       in the Usage: line.  Use spaces instead of TAB characters, and realign.
+
+2001-03-02  Derek Price  <address@hidden>
+
+       * sanity.sh (crerepos): Make failed ${CVS_RSH-rsh} attempt print the
+       name of the command it actually used rather than 'rsh'.
+
+2001-02-27  Derek Price  <address@hidden>
+
+       * sanity.sh (modules2-ampermod-*): Added these tests to make sure the
+       top level directory is created in an ampermodule when '-n' is passed to
+       checkout.
+
+       original bug report from
+           Wolfgang Haefelinger <address@hidden>
+
+2001-02-27  Derek Price  <address@hidden>
+
+       * sanity.sh (version-[12]): replace ' (client/server)' with .* in these
+       two tests so that 'make check' works with whatever client/server
+       options the executable was compiled with.
+
+2001-02-23  Derek Price  <address@hidden>
+
+       * main.c (main): Only check a cvsroot_t's isremote member when client
+       support is enabled.
+       * server.c: Include GSSAPI headers with client support as well as
+       server support.
+
+2001-02-21  Larry Jones  <address@hidden>
+
+       * modules.c, cvs.h (do_module): Add build_dirs argument and use it
+       instead of run_module_prog.  Change all callers.
+       * tag.c (cvstag): For rtag, don't build directories.
+       * sanity.sh (modules3): Update to match.
+
+2001-02-20  Derek Price  <address@hidden>
+
+       * client.c: Use xgssapi.h.
+       * server.c: Ditto.
+
+2001-02-15  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Correct error from yesterday.
+       * Makefile.in: Regenerated.
+
+2001-02-14  Derek Price  <address@hidden>
+
+       * server.c: Include xselect.h.
+       * update.c (do_update): Use best available sleep function.
+
+2001-02-14  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Alphabetize and split to one/line.
+       (cvs_LDADD): Alphabetize and split to one/line.
+       * Makefile.in: Regenerated.
+
+2001-02-14  Larry Jones  <address@hidden>
+
+       * build_src.com: Remove references to rtag.c & rtag.obj.
+
+2001-02-13  Derek Price  <address@hidden>
+
+       * main.c (date_to_tm): New function to convert an RCS date string to a
+       struct tm.
+       (tm_to_internet): New function to convert a struct tm to a date string
+       as specified by RFC822 and amended by RFC 1123.
+       (date_to_internet): Use the above two functions and a struct tm
+       intermediary for conversion.
+       * patch.c (patch_fileproc): Answer somebody's comment and use the new
+       diff_exec API.
+       * rcs.c (RCS_checkin): Use new diff_exec API.
+       (RCS_delete_revs): Use new diff_exec API.
+       (make_file_label): If the file name is DEVNULL, date it the Epoch for
+       compatibility with the POSIX.2 spec and Larry Wall's patch
+       implementation.
+       * rcscmds.c (diff_exec): Accept new label arguments.
+       * sanity.sh (death2): Update some diff tests to accept the new format.
+       * update.c (patch_file): Use new diff_exec API.
+       * diff.c (diff_fileproc): Create header labels appropriate for
+       compatibility with the Larry Wall version of patch.
+       (diff): Rename calls to strcat_and_allocate.
+       (strcat_and_allocate): Rename and move...
+       * subr.c (xrealloc_and_strcat): here.
+       * cvs.h: Update prototypes to match.
+
+2001-02-13  Derek Price  <address@hidden>
+
+       * Makefile.am (cvs_SOURCES): Remove rtag.c.
+
+2001-02-07  Larry Jones  <address@hidden>
+
+       * sanity.sh (directory_cmp): Return status rather than setting ISDIFF.
+       (basic2): Rewrite using dotest.
+
+2001-02-06  Larry Jones  <address@hidden>
+
+       * tag.c, rtag.c: Merge with tag.c being the surviving file.
+       * Makefile.in: Update to match.
+       * main.c (cmds): rtag() => cvstag().
+       * server.c (serve_rtag): Ditto, and set command name.
+
+2001-02-06  Derek Price  <address@hidden>
+           Rex Jolliff  <address@hidden>
+           Shawn Smith  <address@hidden>
+
+       * add.c: Replace opendir, closedir, & readdir calls with CVS_OPENDIR,
+       CVS_CLOSEDIR, & CVS_READDIR in support of changes to handle VMS DEC C
+       5.7 {open,read,close}dir problems.  Check today's entry in the vms
+       subdir for more.
+       * filesubr.c: ditto
+       * find_names.c: ditto
+       * ignore.c: ditto
+       * import.c: ditto
+       * lock.c: ditto
+       * update.c: ditto
+
+2001-02-02  Larry Jones  <address@hidden>
+
+       * error.h: Changed include guard macro from _error_h_ to ERROR_H;
+       names beginning with underscore are reserved.
+       * login.c (password_entry_parseline, password_entry_operation,
+       password_entry_operation_e, password_entry_operation_t): Removed
+       leading underscore(s).
+       (password_entry_parseline): Corrected error messages.
+       (password_entry_operation): Fixed uninitialized variable (password).
+       (login): Removed unused variable (found_password).
+
+       * rtag.c (rtag_proc): Call lock_tree_for_write() before calling
+       start_recursion.  This fixes a serious problem where do_recursion
+       was reading and caching RCS files without any locks in place and
+       that information was subsequently being used to rewrite the file
+       causing any intermediate changes to be lost.
+       (rtag_filesdoneproc): Defunct.
+       (Reported by Karl Tomlinson <address@hidden>.)
+       * tag.c (cvstag, tag_filesdoneproc): Ditto.
+       * lock.c (lock_tree_for_write): Add which argument, change all
+       callers to pass W_LOCAL.
+       * rcs.h: Ditto.
+
+2001-01-29  Derek Price  <address@hidden>
+
+       * client.c (get_cvs_port_number): change the prototype to accept a
+       const cvsroot_t * as input and add a FIXME comment
+       * cvs.h: new prototypes for get_cvs_port_number & normalize_cvsroot
+       * login.c (_password_entry_operation): consolidate all the ~/.cvspass
+       access into a single new function which reads ~/.cvspass in a backwards
+       compatible manner
+       (logout): use the new _password_entry_operation function
+       (login): ditto
+       (get_cvs_password): ditto
+       * root.c (normalize_cvsroot): move knowledge of default port & username
+       values inside
+
+2001-01-29  Larry Jones  <address@hidden>
+
+       * subr.c (shell_escape): New function.
+       * cvs.h: Declare it.
+       * logmsg.c (logfile_write): Use it to avoid problems with filenames
+       containing "'".
+       (Reported by Gerhard Ahuis <address@hidden>.)
+
+       * server.c (outbuf_memory_error, pserver_authenticate_connection,
+       kserver_authenticate_connection): If available, use syslog() to
+       record some errors.
+
+2001-01-25  Larry Jones  <address@hidden>
+
+       * server.c (do_cvs_command): If there's a partial output line left
+       over and the client doesn't support MT, go ahead and send it in an
+       M response instead of just dropping it.
+       (Reported by Milos Kleint <address@hidden>.)
+
+       * update.c (update_fileproc): Handle toss_local_changes in the
+       T_NEEDS_MERGE case.
+       (Inspired by Noel L Yap <address@hidden>.)
+       * sanity.sh (clean): New tests for above.
+
+2001-01-23  Derek Price  <address@hidden>
+
+       * run.c (run_exec): flush, if used, stderr and stdout before exit
+       * server.c (cvs_flusherr): flush stderr & send a stderr flush command
+       on the protocol pipe
+       (cvs_flushout): like above, for stdout
+       (do_cvs_command): handle flushes properly
+       * sanity.sh (reserved): make the commitinfo script echo errors to
+       stderr rather than stdin
+
+2001-01-18  Larry Jones  <address@hidden>
+
+       * log.c (option_revlist, revlist, log_usage, cvslog,
+       log_parse_revlist, log_expand_revlist, log_version_requested): Add
+       support for :: for exclusive ranges.
+       * admin.c (admin_usage): Reorder -o to be parallel to log -r.
+       * sanity.sh (log): New tests for above.
+
+2001-01-18  Derek Price  <address@hidden>
+
+       * main.c: Add '2001' to the range of copyright years listed by the
+       --version option
+       * version.c.in (version): check current_parsed_root before its isremote
+       member to avoid a core dump
+       * sanity.sh (version): add a test for the version command
+
+       * version.c: regenerated
+
+2001-01-12  Larry Jones  <address@hidden>
+
+       * rcs.c, rcs.h (RCS_lock, RCS_unlock): Use RCS_gettag to find the
+       correct revision so that symbolic tags work correctly.  (This
+       requires removing the "const" from the rev parameter since it's
+       passed to RCS_gettag which might modify it.)
+       (Reported by irina sturm <address@hidden>.)
+
+2001-01-11  Larry Jones  <address@hidden>
+
+       * run.c (close_on_exec): Remove check for FD_CLOEXEC.  As far as I
+       can see, it's *never* been defined, which defeats the whole point.
+       If F_SETFD is defined, it's probably safe to use it.
+
+       * server.c (do_cvs_command): Call close_on_exec on the protocol and
+       flow control pipes in the child process so they don't get inherited
+       by any subsidiary processes.
+       (Reported by Tristan Gingold <address@hidden>.)
+
+       * cvs.h (free_cvsroot_t): Spell correctly (was free_CVSroot_t).
+
+2001-01-10  Derek Price  <address@hidden>
+           Rex Jolliff  <address@hidden>
+
+       * build_src.com: VMS changes
+       * filesubr.c: replace calls to unlink() with CVS_UNLINK() for VMS
+       * rcs.c: ditto
+
+2001-01-10  Derek Price  <address@hidden>
+
+       * main.c (current_root): explicitly list this as a static global
+
+2001-01-10  Derek Price  <address@hidden>
+
+       * cvs.h (get_cvs_port_number): change name & prototype from
+       get_port_number
+       * client.c (get_cvs_port_number): new function which returns a port
+       number based on a cvsroot_t rather than requiring all possible sources
+       passed in
+       (connect_to_pserver): use new get_cvs_port_number function
+       (connect_to_server): ditto
+       * login.c (get_password): use new get_cvs_port_number function
+       (login): ditto
+       (logout): ditto
+
+2001-01-10  Derek Price  <address@hidden>
+
+       * Makefile.am ($(srcdir)/version.c): specify $(srcdir) for all subparts
+       of the build since some systems don't allow mv's across partitions
+       * Makefile.in: regenerated
+
+2001-01-10  Derek Price  <address@hidden>
+
+       * Makefile.am (version.c): specify $(srcdir) explicitly in target rule
+       so version.c gets built properly for all makes.
+       (version.o): specify $(srcdir)/version.c explicitly so dependency is
+       found and built properly
+       * Makefile.in: regenerated
+
+2001-01-09  Derek Price  <address@hidden>
+
+       * version.c: updated timestamp
+
+2001-01-09  Larry Jones  <address@hidden>
+
+       * server.c (server): Change to server_temp_dir immediately after
+       creating it so that any stray files that happen to be created go
+       there instead of in the server's initial directory, wherever that
+       may be.
+       * sanity.sh (modules5-15): Update to match.
+
+       * version.c.in: Update to match Derek's change to version.c.
+
+2001-01-09  Derek Price  <address@hidden>
+
+       * cvs.h: Remove the various CVSroot_* bits and replace them with a
+       single structure of type cvsroot_t (current_parsed_root)
+
+       * root.c (parse_cvsroot): return pointer to a new cvsroot_t rather than
+       altering global variables
+       (local_cvsroot): return a pointer to a new cvsroot_t rather than
+       setting globals.  changed the name of this function from
+       set_local_cvsroot to better explain new functionality
+       (new_cvsroot_t): new initializer function
+       (free_cvsroot_t): new function
+       (others): use current_parsed_root rather than the old CVSroot_* globals
+
+       * add.c: use current_parsed_root rather than the old CVSroot_* globals
+       * admin.c: ditto
+       * checkout.c: ditto
+       * client.c: ditto
+       * commit.c: ditto
+       * create_adm.c: ditto
+       * diff.c: ditto
+       * edit.c: ditto
+       * expand_path.c: ditto
+       * find_names.c: ditto
+       * history.c: ditto
+       * ignore.c: ditto
+       * import.c: ditto
+       * lock.c: ditto
+       * log.c: ditto
+       * login.c: ditto
+       * logmsg.c: ditto
+       * main.c: ditto
+       * mkmodules.c: ditto
+       * modules.c: ditto
+       * parseinfo.c: ditto
+       * patch.c: ditto
+       * rcs.c: ditto
+       * recurse.c: ditto
+       * release.c: ditto
+       * remove.c: ditto
+       * repos.c: ditto
+       * rtag.c: ditto
+       * server.c: ditto
+       * status.c: ditto
+       * tag.c: ditto
+       * update.c: ditto
+       * version.c: ditto
+       * watch.c: ditto
+       * wrapper.c: ditto
+
+2001-01-05  Derek Price  <address@hidden>
+
+       * cvs.h (enum CVSmethod): add null_method
+       * root.c (method_names): correlate null_method & "undefined"
+       (parse_cvsroot): make two error cases non fatal
+       * sanity.sh (crerepos-6b): account for new error message, re above
+
+2001-01-05  Derek Price  <address@hidden>
+
+       * src/Makefile.am (cvsbug, cvsbug_EXTRA_DIST, EXTRA_DIST): move cvsbug
+       target to configure.in - see ../ChangeLog for more
+       * src/cvsbug.in: Rename from cvsbug.sh
+       * src/cvsbug.sh: Rename to cvsbug.in
+
+2001-01-04  Larry Jones  <address@hidden>
+
+       * Makefile.am (cvsbug): Explicitly list input file ($< is only
+       valid in inference rules).
+       * Makefile.in: Ditto.
+
+2001-01-04  Derek Price  <address@hidden>
+
+       * sanity.sh: use getopts rather than getopt for portability reasons
+
+2001-01-03  Derek Price  <address@hidden>
+
+       * Makefile.am (remotecheck): depend on 'all'
+       * Makefile.in: regenerated
+
+2000-12-29  Derek Price  <address@hidden>
+
+       * sanity.sh: remove explicit "$@" from last checkin and move the 'do'
+       to the line following the 'for'.  Apparently this is more portable.
+
+2000-12-29  Derek Price  <address@hidden>
+
+       * sanity.sh: make "$@" explicit in 'for' statement since Solaris 5.6's
+       implementation of Bourne shell doesn't seem to implement this default
+       behavior.
+
+2000-12-27  Derek Price  <address@hidden>
+
+       * sanity.sh: add a -f option for continuing from a particular test
+       and shorten --keep to -k so we can use the getopt function.
+
+2000-12-27  Derek Price  <address@hidden>
+
+       * Makefile.am (remotecheck): Make remotecheck dependant on all
+       * Makefile.in: regenerated
+
+2000-12-26  Derek Price  <address@hidden>
+
+       * Makefile.in: update timestamp
+       * stamp-h2.in: ditto
+       * version.c: ditto
+
+2000-12-26  Derek Price  <address@hidden>
+
+       * Makefile.am: new target for version.c
+       (EXTRA_DIST): add version.c.in & version.c so builds work when
+       configure doesn't
+       * Makefile.in: Regenerated
+       * stamp-h2.in: update timestamp
+       * version.c: ditto
+
+2000-12-26  Derek Price  <address@hidden>
+
+       * Makefile.am (INCLUDES): add zlib
+       * Makefile.in: Regenerated
+
+2000-12-22  Derek Price  <address@hidden>
+
+       * Makefile.am (DISTCLEANFILES): added a few files
+       (INCLUDES): commented
+        * Makefile.in: Regenerated
+
+2000-12-21  Derek Price  <address@hidden>
+
+       * .cvsignore: Added .deps directory and a new stamp file
+       * Makefile.am: New file needed by Automake
+       * Makefile.in: Regenerated
+       * stamp-h2.in: New stamp file created by Automake
+       * version.c.in: use configure to generate version.c
+
+2000-12-16  Derek Price  <address@hidden>
+
+       * server.c (server_update): Keep the vers structure up to date after
+       sending a Remove or Remove-entry command to the client
+       * update.c (update): remove call to server_updated() after
+       scratch_file()
+       (scratch_file): in server mode, call server_updated(), otherwise keep
+       the vers structure up to date
+       (join_file): add a trace, save the revision to Register() on a remove
+       before calling server_scratch & server_updated
+       * sanity.sh (join): Add test for a remove/add caused by an update
+       to a new branch and a join in the same step.
+
+2000-12-15  Larry Jones  <address@hidden>
+
+       * error.c (error): Add %ld and %lu.
+
+       * history.c: Change hrec.idx from int to long, reformat NEXT_BAR
+       for readability, add hrec_idx.
+       (fill_hrec): Change initialization to be portable and always set
+       idx so it can be used as a line number in error messages; improve
+       parsing and error checking.
+       (read_hrecs): Initialize hrec_idx, handle embedded NULs, warn about
+       no newline at end of file.
+       (select_hrec): Add basic validity checking.
+
+2000-12-07  Larry Jones  <address@hidden>
+
+       * history.c (history): Allow multiple -m options as documented.
+
+2000-11-29  Derek Price  <address@hidden>
+
+       * root.c (parse_cvsroot): back out yesterday's redundant changes
+       * main.c (main): fix CVSROOT trace message to look like other trace
+       messages
+       * sanity.sh (multiroot2-9): expect new trace message
+
+2000-11-28  Derek Price  <address@hidden>
+
+       * root.c (parse_cvsroot): add trace on this function
+       * client.c (get_port_number): make trace print look like others
+
+2000-11-16  Derek Price  <address@hidden>
+
+       * filesubr.c (cvs_temp_file): back out the previous change in the
+       interests of portability, add an assertion, and fix the header comment
+
+2000-11-16  Derek Price  <address@hidden>
+
+       * filesubr.c (cvs_temp_file): refine the exit behavior to notice if
+       the out param was passed in NULL and, if so, avoid setting it and delete
+       the temp file for later
+
+2000-11-16  Derek Price  <address@hidden>
+
+       * filesubr.c (cvs_temp_file): fixed a garble or two, added some
+       additional error checking, and added a comment
+
+2000-11-15  Derek Price  <address@hidden>
+
+       * filesubr.c (cvs_temp_file): added cvs_temp_file
+       function to use mkstemp rather than one of the other temp file
+       generators as gcc keeps complaining I should.
+       (cvs_temp_name): altered this function to simply wrap cvs_temp_file
+       and deprecated it
+       * cvs.h: added prototype for cvs_temp_file
+       * commit.c (commit): use the new function instead of the old and plug
+       an old (though related) memory leak.
+       * import.c (import): use the new function
+       * login.c (login): Ditto
+       * logmsg.c (do_editor, do_verify): Ditto
+       * patch.c (patch_fileproc): Ditto
+
+2000-11-14  Larry Jones  <address@hidden>
+
+       * update.c, update.h (do_update): Add xdotemplate parameter.
+       Change all callers.
+       (update_dirent_proc): Use dotemplate for Create_Admin, not 1.
+       * checkout.c (checkout_proc): Don't create CVS/Template if
+       exporting.
+       (Reported by Andrey Podkolzin <address@hidden>.)
+
+2000-11-08  Larry Jones  <address@hidden>
+
+       * admin.c (admin): Use getgroups() to check for membership in
+       CVS_ADMIN_GROUP if it exists.  In any event, check the user's
+       primary group in addition to any additional groups.
+       (Reported by Thomas Okken <address@hidden>.)
+
+2000-11-06  Jim Meyering  <address@hidden>
+
+       Compile with gcc's -Wformat and fix the exposed problems.
+       * root.c (parse_cvsroot) [! HAVE_KERBEROS]: Provide an argument
+       for the %s error format spec.
+       [! HAVE_GSSAPI]: Likewise.
+       (normalize_cvsroot): Put comment delimiters around token after `#endif'.
+
+2000-11-03  Larry Jones  <address@hidden>
+
+       * sanity.sh: Some versions of sed require a space between -e and
+       the value.
+
+2000-10-27  Larry Jones  <address@hidden>
+
+       * checkout.c (checkout): Don't check for a safe location if just
+       cat'ing the module database.
+       (Reported by Ilya Martynov <address@hidden>.)
+       Have -s set cat as well as status; it simplifies the code.
+
+2000-10-26  Larry Jones  <address@hidden>
+
+       * sanity.sh (join-admin-2): Check output from all commands instead
+       of (mostly) discarding.  (Some of the tests used to produce stray
+       output in remote mode.)
+
+       * sanity.sh (dotest_line_by_line): Handle empty lines in pattern
+       (expr doesn't distingish between successfully matching nothing
+       and failing to match anything).
+
+       * sanity.sh (dotest_internal): Rearrange and use elif to simplify.
+
+2000-10-24  Jim Meyering  <address@hidden>
+
+       Fix a bug, introduced with my fix of 2000-07-10, whereby -kk would
+       sometimes be ignored for some of the files involved in an update.
+
+       * update.c (join_file): Restore the original value of `options'
+       right after calling checkout_file.
+       * sanity.sh (join-admin-2): New test for this.
+
+2000-10-23  Derek Price  <address@hidden>
+            James Youngman  <address@hidden>
+
+       * sanity.sh: it's /gnu/bin, not /gun/bin.  Thanks go to James Youngman
+       <address@hidden> for the bug report and patch.
+
+2000-10-20  Jim Kingdon  <http://sourceforge.net/users/kingdon/>
+
+       * server.c (switch_to_user): Set CVS_USER.  Patch from Sudish
+       Joseph and popularized by dozens (e.g. mozilla.org, also others).
+
+2000-10-20  Derek Price  <address@hidden>
+            KOIE Hidetaka  <address@hidden>
+
+       * root.c (normalize_cvsroot): plug a memory leak.  Thanks to
+       KOIE Hidetaka <address@hidden>
+
+2000-10-18  Derek Price  <address@hidden>
+
+       * client.c (connect_to_pserver): added a close brace the lack of which
+       was preventing compilation when gssapi was enabled.  Removed a
+       redundant check for HAVE_KERBEROS.
+
+2000-10-18  Derek Price  <address@hidden>
+
+       * root.c (normalize_cvsroot): removed references to free_port_s and the
+       now useless call to free now that port_s is on the stack.  Thanks to
+       Jon Miner.
+
+2000-10-18  Derek Price  <address@hidden>
+
+       * root.c (normalize_cvsroot): remove calls to snprintf for
+       compatibility with M$ Windoze.
+
+2000-10-18  Derek Price  <address@hidden>
+
+       * sanity.sh (crerepos-6a, crerepos-6a-r): fix a "?" in a regex & pipe
+       the output of a test to /dev/null since we don't know what error
+       messages specific rsh implementations will output.
+
+2000-10-17  Derek Price  <address@hidden>
+
+       * cvs.h: added CVSroot_password variable. Provided prototypes for
+       get_port_number & normalize_cvsroot.
+       * client.c (get_port_number): Fixed an ANSI prototype I had included
+       for get_port_number.
+       * login.c (login, logout): Removed two checks for a non-null
+       CVSroot_username since parse_cvsroot now supplies a default in pserver
+       mode. allow for a password in CVSROOT
+       (get_cvs_passsword): return CVSroot_password if it was supplied
+       in the CVSROOT.
+       * root.c (parse_cvsroot): Changed CVSROOT spec from
+       :method:address@hidden/port:/cvsroot to
+       :method:[[user][:address@hidden:[port]]/cvsroot
+       Removed the xstrdup function since we'd rather have the error checking
+       from the version in subr.c anyhow.  Moved some error messages which
+       looked like they would print the wrong error message after a failed
+       connect_to_gserver call.
+       (normalize_cvsroot): return a normalized CVSROOT for use in the
+       .cvspass file.
+       * sanity.sh (crerepos-6): fix a test which was expecting an old error
+       message.
+
+       * client.c (connect_to_pserver): Moved some error messages which looked 
like they
+       would print the wrong error message after a failed connect_to_gserver
+       call.
+
+       * login.c (login): Paranoiacly zero a password in memory.
+
+2000-10-12  Derek Price  <address@hidden>
+
+       * client.c (auth_server_port_number -> get_port_number, start_pserver,
+       start_tcp_server): use a port specified in CVSROOT instead of the
+       default port.  Failing that, use the CVS_CLIENT_PORT environment
+       variable.
+       * cvs.h: Added global CVSroot_port & renamed auth_server_port_number.
+       * root.c (parse_cvsroot): Parse the new CVSROOT format properly.
+       Incidentally reformated some error messages for uniformity and
+       readability.
+       * sanity.sh (crerepos): fix two tests which were now expecting the
+       wrong error message.
+
+2000-10-11  Larry Jones  <address@hidden>
+
+       * server.c (pserver_authenticate_connection): Fix stupid mistake
+       in previous change.
+
+2000-10-11  Derek Price  <address@hidden>
+
+       * main.c (main): Dispose old CVSroot when parsing a '-d' option if
+       free_CVSroot is set.
+       * root.c (parse_cvsroot): remove references to 'cvsroot_parsed', a
+       static boolean I expect hasn't been used since CVS learned to handle
+       multiple CVSROOTs.
+
+2000-10-10  Larry Jones  <address@hidden>
+
+       * server.c (print_error): Make up a message if strerror fails.
+
+       * server.c (pserver_authenticate_connection): Give a real error
+       message for an invalid repository.
+
+2000-10-06  Derek Price  <address@hidden>
+
+       * add.c (add): Made quiet mode affect some warning messages as seemed
+       appropriate.  Specifically, some of the messages which a user might
+       want to ignore so they don't have to be quite so specific on the
+       command line: files added twice, files already in the repository and
+       check out properly (i.e. but picked up by 'cvs add *'), & files which
+       are readded in place of a dead revision or onto a branch.  '-q' will
+       not change the non-zero exit code for the cases where at least one
+       passed in file name was already in the Entries file.  There seems to
+       be a precedent in remove.c.
+       * remove.c (cvsremove): switched the "use cvs ci to make these changes
+       permanent message" to only print w/o '-Q' to match the new behavior of
+       add.  This seems appropriate as '-Q' is defined to restrict messages
+       to critical errors.
+       * sanity.sh (adderrmsg): Added some tests for the above behavior.
+
+2000-10-05  Larry Jones  <address@hidden>
+
+       * client.c (call_in_directory): Create CVSADM directory if it doesn't
+       exist in the directory.  This makes client/server work more like
+       standalone when checking out into an existing (non-CVS) directory.
+       * sanity.sh (dirs2, conflicts3, toplevel): Update to match.
+
+2000-10-03  Larry Jones  <address@hidden>
+
+       * filesubr.c (get_homedir): Ignore $HOME when running in server mode.
+
+2000-10-02  Larry Jones  <address@hidden>
+
+       * cvs.h: Define (and use) T_PATCH as a valid file classification
+       even when SERVER_SUPPORT isn't defined -- it simplifies the code.
+       * classify.c (Classify_File): Ditto.
+       * commit.c (check_fileproc): Ditto.
+       * status.c (status_fileproc): Ditto.
+       * update.c (update_fileproc): Ditto.
+       * tag.c (check_fileproc): Accept T_PATCH in addition to T_CHECKOUT.
+       * sanity.sh (tagc-10): Update to match.
+
+2000-09-29  Larry Jones  <address@hidden>
+
+       * client.c (get_responses_and_close): Reset server_fd to -1 after
+       shutting down.
+       (Reported by Joerg Thoennes <address@hidden>.)
+
+2000-09-27  Larry Jones  <address@hidden>
+
+       * commit.c (commit): Don't sleep before returning in server mode,
+       just let the client do it.
+       * update.c (do_update): Ditto.
+
+       * sanity.sh (find_tool): Correct method of checking for GNU tools.
+
+       * checkout.c (checkout_proc): Match up user directories with
+       repository directories instead of using Emptydir.
+       * sanity.sh (cvsadm, emptydir): Update to match.
+
+2000-09-19  Larry Jones  <address@hidden>
+
+       * version.c: Push version number to 1.11.0.1.
+
+       * version.c: Version 1.11.
+
+2000-09-07  Larry Jones  <address@hidden>
+
+       * Makefile.in: Use @bindir@, @libdir@, @infodir@, and @mandir@
+       from autoconf.
+
+2000-08-23  Larry Jones  <address@hidden>
+
+       * mkmodules.c (init): Create an empty val-tags file if it doesn't
+       already exist to avoid problems with users not having sufficient
+       permissions to create it later.
+
+2000-09-06  Jim Kingdon  <address@hidden>
+
+       * main.c (lookup_command_attribute): Add "release" to commands
+       which can be done by a read-only user.
+
+2000-08-23  Larry Jones  <address@hidden>
+
+       * repos.c (Name_Repository): Use pathname_levels to detect attempts
+       to get above the repository instead of checking for leading ..
+       which isn't reliable.
+       * sanity.sh (multiroot3-12 to multiroot3-15): New tests for above.
+
+2000-08-21  Larry Jones  <address@hidden>
+
+       * rcs.c (expand_keywords): Handle the unusual case of log == NULL.
+       (Reported by Craig Metz <address@hidden>.)
+
+2000-08-01  Larry Jones  <address@hidden>
+
+       * subr.c (pathname_levels): Fix bug that miscounts adjacent
+       slashes.
+       (Patch submitted by Tanaka Akira <address@hidden>.)
+
+       * loginc.c (login): If available, use getpassphrase instead of
+       getpass to support long passwords on Solaris.
+
+2000-07-28  Larry Jones  <address@hidden>
+
+       * server.c (server_noop): Avoid do_cvs_command() overhead.
+       (requests): Make noop RQ_ROOTLESS.
+
+2000-07-27  Noel Cragg  <address@hidden>
+
+       * root.c (parse_cvsroot): change fork method to behave like other
+       remote methods -- let the server check that the repository
+       directory is an absolute pathname.
+
+2000-07-27  Larry Jones  <address@hidden>
+
+       * lock.c (set_lock): Include actual lock directory in error message.
+       * sanity.sh (multiroot3-10): Change to match.
+
+       * sanity.sh (client-3): Allow for a potential "broken pipe".
+
+2000-07-26  Larry Jones  <address@hidden>
+
+       * commit.c (commit_filesdoneproc): Flush stdout before running script.
+       * modules.c (do_module): Ditto.
+       * update.c (update_dirleave_proc): Ditto.
+       * server.c (do_cvs_command): Give input from the protocol pipe
+       precedence over input from stdout/stderr.  There's no particularly
+       good justification for this other than helping to avoid out-of-order
+       messages in sanity.sh.
+
+       * admin.c (admin_usage): Add the supported options.
+
+       * sanity.sh (info): Try to avoid out-of-order messages.
+
+       * sanity.sh (info): Fix problems when running twice in a row.
+
+2000-07-17  Larry Jones  <address@hidden>
+
+       * sanity.sh (modules5-7, cvsadm-1e, emptydir-2): Allow for a nil
+       commit (can happen if the test is run twice in a row).
+
+2000-07-19  Pavel Roskin  <address@hidden>
+       and Larry Jones  <address@hidden>
+
+       * mkmodules.c (config_contents): Add a commented out example for
+       LockDir. Don't suggest PreservePermissions unless it's enabled.
+
+2000-07-17  Larry Jones  <address@hidden>
+
+       * login.c (get_cvs_password): Handle malformed ~/.cvspass more
+       gracefully.
+
+2000-07-12  Larry Jones  <address@hidden>
+
+       * sanity.sh (modules5): New tests for module programs.
+
+2000-07-11  Larry Jones  <address@hidden>
+
+       * filesubr.c (copy_file, xcmp): Handle systems (like Plan 9) that
+       don't support mknod() and/or st_rdev.
+       * import.c (add_rcs_file): Ditto.
+       * rcs.c (RCS_checkout, RCS_checkin): Ditto.
+       * update.c (special_file_mismatch): Ditto.
+
+2000-07-10  Larry Jones  <address@hidden>
+
+       * zlib.c (gunzip_and_write): Fix type clashes.
+
+       * main.c (main): Remove unused variables.
+
+2000-07-10  Jim Meyering  <address@hidden>
+
+       When a command like `cvs update -kk -jT1 -jT2' creates a new file
+       (because it had the T2 tag, but not T1), the subsequent commit of
+       that just-added file would effectively set the admin `-kk' option
+       for that file in the repository.
+
+       * update.c (join_file): Rename global-shadowing local `options'
+       to `t_options'.
+       Set file-scoped global `options' to NULL just before
+       check-out.
+       * sanity.sh (join-admin): New test for this.
+
+2000-07-08  Larry Jones  <address@hidden>
+
+       * version.c, cvs.h (version): New function.
+       * main.c (cmds[]): Add version command to invoke it.
+       (main): Also use it in -v.
+       * server.c (serve_version): New function.
+       (requests[]): Add version command to invoke it.
+
+2000-07-06  Karl Fogel  <address@hidden>
+
+       * sanity.sh (pserver-14): remove this test for portability
+       reasons (it was only recently added for the 2000-07-04 change).
+
+2000-07-06  Larry Jones  <address@hidden>
+
+       sanity.sh (modules-148): Don't test for specific revisions.
+
+       * main.c (main): Catch SIGABRT to try to clean up after assertion
+       failures.  Don't bother SIG_register'ing Lock_Cleanup because
+       main_cleanup calls it indirectly anyway.
+       * patch.c (patch): Catch SIGABRT.
+       * rcs.c (rcs_internal_lockfile): Ditto.
+       * server.c (server): Ditto.
+
+       * fileattr.c (fileattr_write): Don't delete the unrecog_head list
+       when writing...
+       (fileattr_free): Delete it when freeing!
+
+2000-07-05  Larry Jones  <address@hidden>
+
+       * admin.c (admin): Handle -t in client so reading from files works
+       correctly in client/server mode.
+       * sanity.sh (log2): Update to match.
+
+2000-07-04  Karl Fogel  <address@hidden>
+
+       * server.c (pserver_authenticate_connection): use new
+       getline_safe() during authentication phase, to avoid a
+       denial-of-service attack in which client sends arbitrary
+       amounts of data with no newlines.
+       (Reported by <address@hidden>.)
+
+       * sanity.sh: new test pserver-14 for above.
+
+       * myndbm.c: #include getline.h.
+       (mydbm_load_file): pass new GETLINE_NO_LIMIT flag to getstr().
+
+2000-07-03  Larry Jones  <address@hidden>
+
+       * sanity.sh (modules): Rewrite using dotest.  Add "modules-"
+       prefix to test names.
+
+2000-06-28  Larry Jones  <address@hidden>
+
+       * error.c (error_exit): Call rcs_cleanup () to release any rcs locks.
+       * rcs.c, rcs.h (rcs_cleanup): Make public, close file before trying
+       to remove (some systems won't remove open files).
+       (RCS_putdtree): Don't worry about cleaning up before call error
+       since it now does it for us.
+       (rcs_internal_lockfile, rcs_internal_unlockfile): Keep track of
+       lock file fd for rcs_cleanup ().
+
+       * client.c (handle_set_checkin_prog, handle_set_update_prog):
+       Just ignore the request when exporting.
+
+2000-06-27  Larry Jones  <address@hidden>
+
+       * create_adm.c, cvs.h (Create_Admin): Add dotemplate argument.
+       Change all callers.
+       * checkout.c (checkout_proc): Don't create CVS/Template if
+       exporting.
+
+2000-06-26  Pavel Roskin <address@hidden>
+       and Larry Jones  <address@hidden>
+
+       * server.c (switch_to_user): Only set CVS_Username if
+       AUTH_SERVER_SUPPORT is defined.
+
+2000-06-23  Larry Jones  <address@hidden>
+
+       * client.c (send_dirent_proc): Don't allocate ignlist if you're
+       going to skip the directory (plugs memory leak).
+       (send_dirleave_proc): New function.
+       (send_files): Use it (plugs memory leak).
+       * root.c (root_allow_free): Plug memory leaks.
+       * server.c (serve_directory, serve_notify, check_password,
+       pserver_authenticate_connection): Ditto.
+       * update.c (update): Ditto.
+
+       This completes the memory leak shoot-out -- the Purify'ed version
+       of CVS now runs the entire test suite, both local and remote (except
+       for remote crerepos, which causes Purify to choke) with *no* memory
+       leaks.
+
+       * server.c (pserver_authenticate_connection): Don't free null pointer.
+
+2000-06-21  Larry Jones  <address@hidden>
+
+       * client.c (update_entries, get_responses_and_close): Plug memory leaks.
+       * commit.c (find_fileproc, commit): Ditto.
+       * import.c (import): Ditto.
+       * log.c (cvslog): Ditto.
+       * recurse.c (start_recursion): Ditto.
+       * remove.c (cvsremove): Ditto.
+       * server.c (fd_buffer_initialize, server_notify, do_cvs_command): Ditto.
+       (fd_buffer_shutdown): New function.
+
+2000-06-20  Larry Jones  <address@hidden>
+
+       * root.c (parse_cvsroot): Put the terminating NUL byte into the
+       string *before* copying it, not after. :-(
+
+2000-06-19  Larry Jones  <address@hidden>
+
+       * main.c (main): Plug memory leaks.
+       * root.c (parse_cvsroot, set_local_cvsroot): Ditto.
+       * server.c (serve_root): Ditto.
+
+2000-06-16  Larry Jones  <address@hidden>
+
+       * fileattr.c (fileattr_read): Plug memory leak.
+       * rcs.c (RCS_whatbranch): Ditto.
+       * update.c (update_dirleave_proc): Ditto.
+
+       * ignore.c (ign_dir_add): Duplicate string so caller can free.
+
+       * modules.c (do_module): Don't write into dbm's memory!
+
+2000-06-15  Larry Jones  <address@hidden>
+
+       * checkout.c (checkout_proc): Fix non-ANSI code in call to
+       findslash(), minor cleanups.
+
+2000-06-14  Larry Jones  <address@hidden>
+
+       * tag.c (val_direntproc): Return R_PROCESS instead of 0.
+
+       * client.c (update_entries): Fix type clash calling gunzip_and_write().
+       * server.c (receive_file): Fix type clash calling gunzip_and_write().
+       (server_updated): Fix type clash calling buf_output().
+       * error.c (error): Make buf char instead of unsigned char to avoid
+       type clashes.
+
+       * modules.c (do_module): Change callback_proc to pass argc by
+       value instead of by reference: callback procs shouldn't be
+       messing with the callers argc/argv, it makes correct memory
+       management impossible.  Plug memory leaks.
+       * cvs.h: Change to match.
+       * checkout.c (checkout_proc): Ditto; use a local argv array instead
+       of messing with caller's.
+       * modules.c (callback_proc): Ditto.
+       * patch.c (patch_proc): Ditto; use a local argv array instead
+       of messing with caller's.
+       * rtag.c (rtag_proc): Ditto; use a local argv array instead
+       of messing with caller's.
+       * server.c (expand_proc): Ditto.
+       * subr.c (line2argv): Change initial argv_allocated back to 1.
+
+       * checkout.c (findslash): Fix non-ANSI code.
+
+       * sanity.sh (modes3): Fix test names.
+
+2000-06-13  Larry Jones  <address@hidden>
+
+       * add.c (add): Plug memory leaks.
+       * admin.c (admin_fileproc): Ditto.
+       * checkout.c (build_dirs_and_chdir): Ditto.
+       * edit.c (editors_fileproc): Ditto.
+       * log.c (cvslog, log_parse_revlist, log_parse_date): Ditto.
+       * rcs.c (RCS_addaccess): Ditto.
+       * tag.c (check_fileproc): Ditto.
+       * vers_ts.c (Version_TS): Ditto.
+       * watch.c (watchers_fileproc): Ditto.
+
+2000-06-12  Larry Jones  <address@hidden>
+
+       * rcs.c (rcsbuf_valword): Set rcsbuf->vlen to keep rcsbuf_valcopy()
+       from allocating more memory than needed for @ strings.  Don't declare
+       unless PRESERVE_PERMISSIONS_SUPPORT (since not defined).
+
+       * rcs.c (RCS_abandon): New function to abandon changes.
+       * rcs.h: Declare it.
+       * admin.c (admin_fileproc): Use it instead of RCS_reparsercsfile.
+
+       * commit.c (commit_fileproc): Fix memory leaks.
+       * patch.c (patch_fileproc): Ditto.
+       * rcs.c (RCS_nodeisbranch, RCS_copydeltas): Ditto.
+       * tag.c (tag_fileproc): Ditto.
+       * update.c (update): Ditto.
+
+2000-06-09  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_reparsercsfile, RCS_fully_parse, getdelta,
+       RCS_getdeltatext): Handle newphrases with composite values.
+       (rcsbuf_getkey): Don't remove @s in composite values -- it makes
+       it impossible to parse the value!  Set special flag to indicate
+       a composite value.
+       (rcsbuf_valcopy, rcsbuf_valpolish_internal): Handle composite values.
+       (putrcsfield): Write composite values.
+       (RCS_checkin): Set node types in other_delta list.
+       * hash.h: Add RCSCMPFLD.
+       * hash.c (nodetypestring): Ditto.
+
+       * rcs.c (getdelta): Never allocate space for value, just return
+       pointer into rcsbuf (fixes memory leaks).  Use rcsbuf_getkey to
+       read a key and value and then parse the value if needed rather
+       than trying to read it in bits and pieces with rcsbuf_getid,
+       rcsbuf_getstring, and rcsbuf_getword.
+       (RCS_reparsercsfile): Change callers to compensate.
+       (rcsbuf_valcmp, rcsbuf_valword): New functions.
+       (rcsbuf_getid, rcsbuf_getstring, rcsbuf_getword): Deleted.
+       * sanity.sh (rcs3-1): Now get slightly different error message.
+
+2000-06-08  Larry Jones  <address@hidden>
+
+       * main.c (usg): Update CVS home page URL.
+
+       * main.c (main): Provide an actual error message for an unknown
+       command in addition to the usage message.
+
+2000-06-07  Larry Jones  <address@hidden>
+
+       * server.c (serve_root, dirswitch, serve_repository,
+       serve_static_directory, serve_sticky, receive_partial_file,
+       receive_file, serve_modified, server_write_entries, serve_notify,
+       serve_checkin_prog, serve_update_prog, server): Don't set
+       pending_error before calling alloc_pending, it makes it fail;
+       use alloc_pending instead of malloc when reasonable; be sure to
+       save errno before calling functions that might change it.
+       (Patch submitted by Dietmar Petras <address@hidden>.)
+
+2000-06-03  Larry Jones  <address@hidden>
+
+       * commit.c (checkaddfile): Plug memory leak.
+       * rcs.c (RCS_checkin): Plug memory leaks.
+       * server.c (do_cvs_command): Plug file descriptor leaks.
+       * tag.c (check_fileproc): Plug memory leak.
+
+2000-05-26  Larry Jones  <address@hidden>
+
+       * recurse.c (unroll_files_proc): Plug memory leak.
+
+       * recurse.c (addfile): Fix nonportable pointer cast.
+
+       * rcs.c (rcsbuf_getstring, rcsbuf_getword, getdelta): Plug memory
+       leaks.
+
+2000-05-25  Larry Jones  <address@hidden>
+
+       * checkout.c (checkout, build_one_dir, checkout_proc): Move m_type
+       to file scope and use it instead of continually doing strcmp on
+       command_name.
+       (build_one_dir, checkout_proc): Don't allow export if CVSADM
+       directory already exists.
+
+2000-05-23  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_checkin, RCS_cmp_file): Plug memory leaks.  (Patch
+       submitted by Chris G. Demetriou <address@hidden>.)
+
+2000-05-20  Ian Lance Taylor  <address@hidden>
+
+       * client.c (connect_to_gserver): Handle server error messages
+       reasonably.
+
+2000-05-19  Larry Jones  <address@hidden>
+
+       * server.c (requests): Make Global_option RQ_ROOTLESS so it can be
+       used with init.
+
+2000-05-18  Larry Jones  <address@hidden>
+
+       * client.c (start_server): Don't do encryption, authentication,
+       compression, or case insensitivity when doing init because init
+       is ROOTLESS and they're not.
+
+       * client.c (connect_to_pserver): Include repository and username in
+       authorization failed message -- if a directory tree crosses multiple
+       repositories, it can be quite difficult for the user to figure out
+       which one is the problem.
+
+2000-05-17  Larry Jones  <address@hidden>
+
+       * main.c (main): Use full set of options when looking for -f to
+       avoid misparsing options that take values (previously, -sVAR=foo
+       was incorrectly parsed as though it were -s -V -A -R -= -f -o -o
+       because it didn't know that -s takes a value).
+       * sanity.sh (info-6b): New test for above.
+
+       * sanity.sh (conflicts-status): Fix tests so they work remotely, too.
+
+2000-05-17  Jim Meyering  <address@hidden>
+
+       * sanity.sh (TESTDIR): Fix braino in last change:
+       cd to /tmp before invoking pwd.
+
+       * sanity.sh: Set TESTDIR so that `make check' passes even when /tmp
+       is a symlink.
+       (join-36): Use $TESTDIR rather than hard-coding `/tmp/cvs-sanity'.
+       (conflicts-132): Remove unnecessary `rm aa'.
+
+2000-05-16  Jim Kingdon  <address@hidden>
+
+       * cvs.h, checkout.c (safe_location): Make extern.
+       * import.c (import): Call it rather than reimplementing
+       (incompletely) the same check.
+
+2000-05-16  Larry Jones  <address@hidden>
+
+       * rcs.h, subr.c (file_has_markers): Check for any of the three
+       conflict marker lines, not just one.
+       * sanity.sh (conflicts-status): New tests for above.
+       * sanity.sh: Revise to avoid tripping the above check when merging
+       changes into sanity.sh itself.
+
+2000-05-15  Larry Jones  <address@hidden>
+
+       * update.c (join_file): When registering the result of the merge,
+       make sure that the version number is valid (vers->vn_rcs may be
+       null if the file doesn't exist on the branch yet).  (Patch submitted
+       by Robert de Vries <address@hidden>.)
+       * update.c (join_file): Correct diagnostics (previous change was not
+       correct -- the file *does* exist in the specified revision, it just
+       doesn't exist in the sandbox).
+       * sanity.sh (import-113, join): New tests and changes for above.
+
+2000-05-04  Larry Jones  <address@hidden>
+
+       * sanity.sh: Look for a useful id program.  Since we're getting
+       the real username for some tests anyway, use it for all the
+       tests instead of a generic regular expression that may or may
+       not match the actual username.
+
+2000-05-04  Larry Jones  <address@hidden>
+
+       * server.c: More error messages.
+
+2000-05-02  Donald Sharp <address@hidden>
+       and Larry Jones  <address@hidden>
+
+       * history.c (report_hrecs): Added code to print out year instead of
+       just month/day.
+       * sanity.sh (basic2-64, history): Update to match.
+
+2000-04-19  Larry Jones  <address@hidden>
+
+       * server.c (dirswitch): Set pending_error_text in addition to
+       pending_error to aid in problem determination.
+
+2000-03-23  Larry Jones  <address@hidden>
+
+       * mkmodules.c (mkmodules): Return without doing anything if noexec
+       is set to avoid trashing existing files.
+
+2000-03-23  Larry Jones  <address@hidden>
+
+       * main.c: Alphabetize cmds[] and cmd_usage[] and add server
+       commands to cmd_usage[].
+
+2000-03-21  Larry Jones  <address@hidden>
+
+       * sanity.sh (client-1): May get "Broken pipe" message from the
+       "server" in addition to the expected output.
+
+2000-03-17  Larry Jones  <address@hidden>
+
+       * server.c (switch_to_user): Set CVS_Username if it hasn't already
+       been set elsewhere.  (Patch submitted by Gordon Matzigkeit
+       <address@hidden>).
+
+2000-03-13  Larry Jones  <address@hidden>
+
+       * parseinfo.c: Add extern to logHistory declaration.  (Reported by
+       <address@hidden>.)
+       (parse_config): Reformat logHistory code.
+
+2000-03-10  Larry Jones  <address@hidden>
+
+       * add.c (add): Don't try to set cvsroot_len until after checking
+       for help only -- CVSroot_directory isn't set in that case.
+
+2000-03-03  Larry Jones  <address@hidden>
+
+       * mkmodules.c (init): Use mkdir_if_needed to create CVSROOT/Emptydir
+       so we don't fail if run multiple times.  (Reported by KOIE Hidetaka
+       <address@hidden>.)
+       * sanity.sh (1a): New test for above.
+
+2000-03-02  Larry Jones  <address@hidden>
+
+       * main.c: Use identical #if's in the command table and the code
+       for pserver and kserver to prevent "peculiar" configurations from
+       having really perverse behavior because the command table entries
+       are present but the related code isn't.
+
+2000-03-01  Larry Jones  <address@hidden>
+
+       * import.c (import): Don't allow importing the repository.
+       * sanity.sh (errmsg2-20, errmsg2-21): New tests for above.
+
+2000-03-01  Larry Jones  <address@hidden>
+
+       * main.c (main): Update year in copyright message.
+
+2000-03-01  Larry Jones  <address@hidden>
+
+       * logmsg.c (do_editor): Correct previous change.
+
+2000-02-29  Larry Jones  <address@hidden>
+
+       * logmsg.c (do_editor): When reading temp file, check that message
+       buffer is large enough to hold the next line and expand if needed.
+
+2000-02-28  Larry Jones  <address@hidden>
+
+       * commit.c (commit): Use get_file() to read log file correctly
+       and in text mode rather than binary mode.
+
+       * subr.c (get_file): Ignore bufsize if buf is NULL.  Include
+       terminating NUL byte when estimating required buffer size.
+
+2000-02-28  Larry Jones  <address@hidden>
+
+       * sanity.sh (find_tool): New function to replace duplicated code.
+
+2000-02-25  Larry Jones  <address@hidden>
+
+       * import.c (add_rcs_file): Don't abort just because lstat fails.
+
+2000-02-16  Jim Meyering  <address@hidden>
+
+       Avoid race condition whereby a catchable signal could
+       end up corrupting the repository.
+       * commit.c (checkaddfile): Put a critical section around the code
+       that handles the first commit on the trunk of a file that's already
+       been committed on a branch.
+       * cvs.h (Sig_inCrSect): Declare new function.
+
+2000-02-21  Karl Fogel  <address@hidden>
+
+       * main.c (main): still check for repository, but not history file
+       (correction to 2000-02-18 change -- that's what I get for
+       believing the comment rather than the code).
+
+2000-02-21  K.J. Paradise <address@hidden>
+
+       * history.c mkmodules.c parseinfo.c: control which actions
+       get logged to the cvs history file via CVSROOT/config file
+       and LogHistory keyword. (John P Cavanaugh <address@hidden>)
+
+2000-02-18  Karl Fogel  <address@hidden>
+
+       * history.c (history_write): don't die if history file not
+       writable, just warn (unless `really_quiet') and skip out.
+
+       * main.c (main): don't bother checking if history file is
+       writable.
+
+       * server.c (serve_root): same.
+
+2000-02-17  Larry Jones  <address@hidden>
+
+       * sanity.sh (perms symlinks symlinks2 hardlinks): Don't run by
+       default since PreservePermissions code is now disabled.
+
+2000-02-17  Larry Jones  <address@hidden>
+
+       * sanity.sh (import-113): Revise to match Jim Meyering's fix.
+
+2000-02-16  Larry Jones  <address@hidden>
+
+       * add.c (add): Don't allow adding files or directories to Emptydir.
+       (Patch submitted by Chris Cameron <address@hidden>.)
+       * sanity.sh (emptydir): Revise (emptydir-7 and emptydir-8) for this.
+
+2000-02-16  Jim Meyering  <address@hidden>
+
+       * update.c (join_file): Correct typo in diagnostic:
+       change `file %s is present...' to `file %s is not present...'.
+
+2000-02-10  Larry Jones  <address@hidden>
+
+       * parseinfo.c (Parse_Info): Treat matching lines with bad expansions
+       as errors rather than just ignoring.
+
+2000-02-10  Larry Jones  <address@hidden>
+
+       * edit.c (edit): Check for invalid characters in hostname and CurDir.
+       (Reported by "Andrew S. Townley" <address@hidden>.)
+       * sanity.sh (devcom2): New tests for above.
+
+2000-02-10  Larry Jones  <address@hidden>
+
+       * cvs.h: Always #include "server.h" to prevent compile errors when
+       neither CLIENT_SUPPORT nor SERVER_SUPPORT is defined.
+       (Reported by "Crow, Ian" <address@hidden>.)
+       * log.c (send_one, send_arg_list): Only define when CLIENT_SUPPORT
+       is defined to prevent link errors.
+
+       * server.c (server): Always create a new temporary directory, don't
+       try to reuse an existing one since we might not have correct
+       permissions.  Also, include directory name in error messages.
+
+2000-01-29  Jim Kingdon  <http://developer.redhat.com/>
+
+       * ignore.c (ignore_files): Correctly set errno to 0 when we go
+       back to the top of the loop.  Fixes spurious errors like "cvs
+       update: error reading current directory: No such file or
+       directory".
+
+2000-01-26  Larry Jones  <address@hidden>
+
+       * run.c (run_exec): Conditionalize K.J.'s change so that it only
+       applies when SETXID_SUPPORT is defined since some platforms don't
+       have setegid().
+
+2000-01-26  Larry Jones  <address@hidden>
+
+       * sanity.sh: Make TESTDIR earlier then use it to check for versions
+       of expr that don't work right with long expressions.
+
+       * sanity.sh (dotest_line_by_line): Have wc read from stdin so it
+       doesn't output the file name and confuse expr.  Make the output a
+       bit less verbose and easier to read.
+
+2000-01-24  K.J. Paradise <address@hidden>
+
+       * run.c :> prevents a user from creating a privileged shell from the
+       text editor when the SETXID_SUPPORT option is selected.  This came from
+       Bob Colle <address@hidden>, and is his completely.
+
+2000-01-22  Jim Kingdon  <http://developer.redhat.com/>
+
+       * sanity.sh (emptydir): Add a case in which one might hope for a
+       non-Emptydir result, but which result?
+
+2000-01-18  Larry Jones  <address@hidden>
+
+       * main.c (main): Allow -z0 to disable gzip compression.
+
+2000-01-17  Larry Jones  <address@hidden> for
+       K.J. Paradise (address@hidden)
+
+       * version.c: Push version number to 1.10.8.1.
+
+       * version.c: Version 1.10.8.
+
+2000-01-17  Larry Jones  <address@hidden>
+
+       * mkmodules.c (init): Create CVSROOT/Emptydir to avoid problems
+       with users not having sufficient permissions to create it later.
+
+2000-01-04  Larry Jones  <address@hidden>
+
+       * client.c (get_responses_and_close): Simplify time-stamp race
+       avoidance code.
+       * commit.c (commit): Ditto.
+       * update.c (do_update): Ditto.
+       (Prompted by patch submitted by Pavel Roskin
+       <address@hidden>.)
+
+       * hardlink.c: sizeof (char) is 1, by definition.
+       * logmsg.c: Ditto.
+       * rcs.c: Ditto.
+
+2000-01-03  Karl Fogel  <address@hidden>
+
+       * filesubr.c, subr.c (backup_file): moved this function from
+       filesubr.c to subr.c, at JimK's suggestion.
+
+2000-01-03  Jim Kingdon  <http://developer.redhat.com/>
+
+       * sanity.sh (clean): Test the contents of the .#cleanme.txt.1.1
+       file, not just its existence.
+
+2000-01-03  Karl Fogel  <address@hidden>
+
+       * cvs.h, filesubr.c (backup_file): use `const' for suffix too;
+       correct suffix length calculation and appending behavior; discard
+       unnecessary `void' cast.  Thanks to Jim Meyering for noticing.
+
+2000-01-03  Larry Jones <address@hidden>
+
+       * sanity.sh (clean): Fix up expected output.
+
+2000-01-02  John P Cavanaugh <address@hidden>
+        and Karl Fogel <address@hidden>
+
+       New -C option to update: overwrites local changes with clean
+       copies from the repository.  (This is an unreversion of the
+       1999-12-10 change, further modified to work remotely.)
+
+       * client.h (BACKUP_MODIFIED_FILES): new #define.
+
+       * client.c (struct send_data): new element `backup_modified'.
+       (send_files): set above element if BACKUP_MODIFIED_FILES flag is
+       present.
+
+       * filesubr.c (backup_file): new function.
+
+       * cvs.h: prototype for new function `backup_file'.
+
+       * update.c (toss_local_changes): new file-scoped global.
+       (update): set toss_local_changes if -C flag seen.  If
+       client_active, send "-C" to server, and set SEND_NO_CONTENTS and
+       BACKUP_MODIFIED_FILES flags before calling send_files().
+
+       (update_fileproc): if file is modified and toss_local_changes is
+       set, then back the file up and then check out a fresh copy from
+       the repository.  Also, fixed indentation and formatting for a
+       particularly bad stretch of code near (but unrelated to) these
+       changes.
+
+       * sanity.sh: new test `clean', for update -C option.
+
+1999-12-29  Jim Kingdon  <http://developer.redhat.com/>
+
+       * history.c (read_hrecs): st_blksize is unsigned long, not int.
+       This isn't just cosmetic - getting it wrong will cause coredumps
+       and such on 64 bit machines.
+
+       * import.c (import_descend), ignore.c (ignore_files): Placate gcc
+       -Wall by parenthesizing foo || (bar && baz).
+
+1999-12-24  Larry Jones <address@hidden>
+
+       * release.c (release): Use fputs to echo lines from update instead
+       of printf to avoid problems with lines containing "%".  (Reported
+       by Jean-Luc Simard <address@hidden>.)
+
+       * history.c (read_hrecs): Allocate a single 2-block buffer instead
+       of allocating and freeing a buffer for each block.
+       (fill_hrec): Remove redundant code.
+       (select_hrec): Plug memory leak.
+
+1999-12-22  Larry Jones <address@hidden>
+
+       * history.c (history): For "modified" or "checkout", sort on
+       file if user specified -l, even if user also specified a date-
+       oriented flag.
+       * sanity.sh (history): Update to match; add new tests.
+
+1999-12-15  Pavel Roskin <address@hidden>
+       and Larry Jones <address@hidden>
+
+       * lock.c (lock_name): fixed assertion failure for the
+       top-level CVS directory when LockDir is used
+       * sanity.sh (lockfiles-9): new test for this case
+
+1999-12-11  Karl Fogel  <address@hidden>
+
+       * Revert previous change -- it doesn't work remotely yet.
+
+1999-12-10  John P Cavanaugh <address@hidden>
+        and Karl Fogel <address@hidden>
+
+       * update.c: new -C option to update, overwrites local changes with
+       clean copies from the repository.
+       Also, fixed indentation and formatting for a particularly bad
+       stretch of code near these changes in update_fileproc().
+
+       * sanity.sh: test new update -C option.
+
+1999-12-10  Larry Jones <address@hidden>
+
+       * commit.c (remove_file): Call history_write with update_dir NULL
+       like Checkin() does for add and modify.
+       * sanity.sh (basic2-64): Update to match, add "R" records to expected
+       remote output.
+
+1999-12-09  K.J. Paradise (address@hidden)
+
+       * history.c, commit.c, sanity.sh: found (I think) final
+       cause of seg fault in history command.  Also, added the "R"
+       history functionality.  Fixed basic2-64 so it looks correct for
+       the change.
+
+1999-11-30  K.J. Paradise (address@hidden)
+
+       * history.c: fixed seg fault caused by 11-03 changes.
+       off by one in block memory allocations.
+
+1999-11-29  Karl Fogel  <address@hidden>
+
+       * login.c (logout): free `tmp_name' when done.
+       Correct a comment.
+
+1999-11-29  Larry Jones <address@hidden>
+
+       * cvs.h, error.c, import.c: Rename fperror to avoid name clash
+       on LynxOS.  (Reported by Markus Braun <address@hidden>.)
+
+1999-11-23  Larry Jones <address@hidden>
+
+       * checkout.c (checkout_proc): Split declaration and initialization
+       of rp to placate neurotic compilers that gripe about jumping past
+       an initialization, even when the variable is not subsequently used.
+
+1999-11-19  Larry Jones <address@hidden>
+
+       * server.c (switch_to_user): Correct setgid error messages.
+
+1999-11-19  Karl Fogel  <address@hidden>
+
+       * edit.c (unedit_usage, unedit): new struct, use it.  Now "cvs
+       unedit" prints an accurate usage message (formerly it printed the
+       message for "cvs edit", even though the two commands do not have
+       identical usages).
+
+1999-11-19  Larry Jones <address@hidden>
+
+       * history.c: Move -e documentation from Flags to Reports.
+       (history): Add -e to list of report types in error message.
+
+       * history.c (history): Process file arguments before client/server
+       processing so they get sent to the server.
+       * sanity.sh (history): New tests for above.  (Also remove comments
+       about variable spacing -- history output is in variable-width
+       columns with exactly one space between.)
+
+1999-11-19  Larry Jones <address@hidden>
+
+       * sanity.sh: Reestablish check for running as root (using ``id -u''
+       instead of ``whoami'').
+
+       * sanity.sh(dotest, dotest_lit, dotest_fail, dotest_status,
+       dotest_sort): Eval the command so quoting and pipes work right.
+       (spacefiles, dirs, rcslib, modules, unedit-without-baserev,
+       ignore, rcs, rcs2, history, tagdate, pserver, server, server2)
+       Simplify various tests based on above.
+
+1999-11-19  Karl Fogel  <address@hidden>
+
+       * mkmodules.c (init): make history file world-writeable after
+       creating it, since it needs to be writeable for virtually any
+       CVS operation.
+
+1999-11-10  Jim Kingdon  <http://developer.redhat.com/>
+
+       * admin.c: Revert change to add -H command option.  The help
+       invocation is "cvs -H admin" not "cvs admin -H" (see cvs.texinfo,
+       basicb-21 in sanity.sh; fix to cvs.1)
+
+1999-11-08  Jim Kingdon  <http://developer.redhat.com/>
+
+       * log.c (cvslog): If client_active, send options to the server
+       based on our parsed options rather than trying to send the exact
+       strings specified (using canonical forms, like RFC822/1123
+       dates, in the protocol is just cleaner).
+       (send_one, send_arg_list): New functions, helpers for above.
+       * sanity.sh (logopt-6a): New test, for this fix.
+
+1999-11-09  K.J. Paradise <address@hidden>
+
+       * admin.c: made the -H option do what it is documented to
+       do.  a
+
+1999-11-08  Tom Tromey  <address@hidden>
+
+       * client.c (connect_to_gserver): Print more error text if gssapi
+       initialization fails.  From Assar Westerlund <address@hidden>.
+
+1999-11-06  Larry Jones <address@hidden>
+
+       *sanity.sh(rcs3-5): Remote output can be out-of-order, so need a
+       more general pattern to match the assertion failure.
+
+1999-11-05  K.J. Paradise (address@hidden)
+
+       * history.c: Added a trap to verify that if a
+       read(file, buffer,blocksize) returns less than blocksize,
+       that we really are at the end of the file.  I can't easily
+       come up with a test case where this code gets touched, so
+       it may cause problems.  All sanity tests still pass though.
+
+1999-11-05  Jim Kingdon  <http://developer.redhat.com/>
+
+       * sanity.sh (logopt): New test, for Larry's fix.
+       * sanity.sh (log-18a, rcs-15 to rcs-19): New tests, to test -d
+       and -r more thoroughly.
+
+1999-11-05  Larry Jones <address@hidden>
+
+       * log.c (cvslog): Fix -s and -d with spaces on client side.
+       (log_usage): Revert Karl's change once again.
+       sanity.sh(rcs3-5): No longer get different results from local
+       and client/server.
+
+1999-11-04  Karl Fogel  <address@hidden>
+
+       * log.c (log_usage): Revert Jim Kingdon's reversion of my change
+       of 1999-11-03.  Allowing a space between option and argument
+       results in lossage; here is a reproduction recipe: run this from
+       the top of a remote copy of the cvs source tree
+
+          cvs log -d '>1999-03-01' > log-out.with-space
+
+       and then run this (note there's no space after -d now):
+
+          cvs log -d'>1999-03-01' > log-out.no-space
+
+       The resulting files differ; furthermore, a glance at the output of
+       cvs shows that the first command failed to recurse into
+       subdirectories.  Until this misbehavior can be fixed in the source
+       code, the documentation should reflect the true state of affairs:
+       if one simply omits the space, everything works fine.
+
+1999-11-04  Jim Kingdon  <http://developer.redhat.com/>
+
+       * log.c (log_usage): Revert Karl's change regarding -d and
+       -s.  A space is allowed (see sanity.sh for example).
+
+1999-11-03  K.J. Paradise (address@hidden>
+
+       * history.c: cleaned up my prior change a bit, per Larry Jones'
+       comments, and John O'Conner's additional comments about bits of
+       non MS-Visual C++ compliancy of my code.
+
+1999-11-04  Larry Jones <address@hidden>
+
+       * sanity.sh: Check that tr that correctly handles NULs; if not, try
+       to find a version that does; if none can be found, warn user.
+       Also fix warnings for defective expr.
+
+1999-11-04  Karl Fogel  <address@hidden>
+
+       Changes for empty/random passwords in anon pserver access:
+
+       * server.c (check_repository_password): if password empty, grant
+       access no matter what password is received; this is so anon CVS no
+       longer requires a password but remains backwards-compatible with
+       all those clients out there.
+
+       * client.c (connect_to_pserver): proceed with login even if
+       password not found in .cvspass file -- just use empty string as
+       password.  And if such a login fails, print a descriptive error.
+
+       * login.c (get_cvs_password): don't complain if file or password
+       not found.  That condition is no longer a showstopper, now that
+       empty passwords are permissible.
+       Cleaned up conditional chaining a bit, too.
+
+       * sanity.sh (pserver-9, pserver-10, pserver-11, pserver-12,
+       pserver-13): new tests, about empty-password pserver access.
+
+1999-11-03  K.J. Paradise (address@hidden>
+
+       * history.c:  modify parsing routines to parse the history
+       file a block at a time, rather than all at once.  This allows
+       people with large history files and small amount of memory
+       to still get some functionality out of the history file.
+
+1999-11-03  Karl Fogel  <address@hidden>
+
+       * log.c (log_usage): correct usage message for -d and -s options.
+       Because the space between the option letter and its argument has
+       been eliminated, I capitalized the argument portion to distinguish
+       it from the option letter.  This makes it slightly inconsistent
+       with other such usage summaries, but at least it is now both
+       correct and readable.
+
+1999-10-22  Larry Jones  <address@hidden>
+
+       * sanity.sh (dotest_sort): Old versions of tr don't understand \t
+       so use a literal tab instead.
+
+1999-10-21  Larry Jones  <address@hidden>
+
+       * sanity.sh (dotest_sort): Convert any tabs in the output into spaces
+       before sorting to avoid POSIX.2 sort weirdness.
+       (import-106, importb-2): Change expected output per above.
+
+1999-10-18  K.J. Paradise <address@hidden>
+
+       Bug: users 'stan' and 'cartman' both have full read/write access
+       to the cvs repository.  'cartman' does a 'cvs admin -l foo.c'.
+       'stan' then does a 'cvs admin -u foo.c'.  The lock wouldn't be
+       removed, and no warning/error would be given.  This is now fixed.
+       * rcs.c:(c.6157) remove caller/user check on the multiple lock
+        detection routines.  Sanity.sh runs with no errors after this fix.
+
+1999-10-14  Larry Jones  <address@hidden>
+
+       Make "cvs admin -e" (with no list of users) work:
+       * admin.c (admin): Remove error message.
+       (admin_fileproc): If no args for -e, call RCS_delaccess with NULL user.
+       * rcs.c (RCS_delaccess): Interpret NULL user as request to delete
+       entire access list.
+       * sanity.sh (admin-19a-*): Test.
+
+1999-09-29  Larry Jones  <address@hidden>
+
+       * entries.c (Subdirs_Known): Use entfilename when opening CVSADM_ENTLOG
+       like everywhere else.  Although this isn't strictly necessary (since
+       we immediately close it again), it keeps the code consistent and fixes
+       a bug where an open error reported the wrong file name.
+
+1999-09-16  Larry Jones  <address@hidden>
+
+       * log.c (log_parse_revlist): Handle peculiar revision specs like
+       "-r.", "-r:", and "-r," correctly.  (Thanks to Pavel Roskin
+       <address@hidden> for submitting a patch, this fix is
+       somewhat different.)
+       * sanity.sh (log): New tests for above.
+
+1999-09-15  Larry Jones  <address@hidden>
+
+       * sanity.sh (basica-8b1): New test to check fix for bad diff options
+       causing cvs to crash.
+
+1999-09-02  Larry Jones  <address@hidden>
+
+       * modules.c (do_module): Handle case where module definition has
+       options and special options but no directory; fix potential problems
+       running off beginning of string while stripping trailing blanks.
+       * sanity.sh (modules2): New tests for above.
+
+1999-08-26  Larry Jones  <address@hidden>
+
+       * lock.c (lock_name): Remove side-effects from assert() expression
+       since they won't occur if NDEBUG is defined (not that that's a good
+       thing to do).  (Reported by KOIE Hidetaka <address@hidden>.)
+
+1999-08-25  Larry Jones  <address@hidden>
+
+       * sanity.sh: Use "${AWK}" instead of "awk" to make it easier for
+       people to use nawk/gawk/etc.; use an explicit "-print" with find
+       since some older version don't assume it; rename tests to avoid
+       duplicate importc-8.  (Changes along these lines suggested by
+       Chris Cameron <address@hidden>.)
+
+1999-08-24  Larry Jones  <address@hidden>
+
+       * commit.c (check_fileproc): Don't crash when a file has no
+       repository, just treat it as unknown.  (Reported by Stefaan
+       Diericx <address@hidden>.)
+       * sanity.sh (errmsg2): New tests, for this fix.
+
+1999-08-18  Larry Jones  <address@hidden>
+
+       * update.c (special_file_mismatch): Initialize *_hardlinks to
+       avoid trying to free garbage later on.  (Reported by Jan
+       Scheffczyk <address@hidden>.)
+
+1999-08-17  Larry Jones  <address@hidden>
+
+       * sanity.sh (basicc-11): Older versions of sh don't understand
+       ``if ! test...''.  (Patch submitted by David J N Begley
+       <address@hidden>.)
+
+1999-08-17  Larry Jones  <address@hidden>
+
+       * client.c, hardlink.c, hash.c, hash.h, main.c, recurse.c: Change
+       enum constant UNKNOWN to avoid conflicts on HPUX 11.0.  (Reported
+       by Laurent Duperval <address@hidden>.)
+
+1999-08-16  Larry Jones  <address@hidden>
+
+       client.c: Eliminate redundant #if.  (Patch submitted by Assar
+       Westerlund <address@hidden>.)
+
+1999-07-30  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_checkin): Terminate cleanly if RCS_addbranch fails
+       rather than blithely continuing on and crashing.
+       * sanity.sh (basica): New tests, for this fix.
+
+1999-07-29  Larry Jones  <address@hidden>
+
+       * import.c (add_rcs_file): change "cannot lstat" message to include
+       userfile (the actual file causing the problem) instead of user
+       (which may or may not be the same).
+
+1999-07-29  Eric Sink   <address@hidden>
+
+       * version.c: Push version number to 1.10.7.1.
+
+       * version.c: Version 1.10.7.
+
+1999-07-28  Eric Sink   <address@hidden>
+
+       * sanity.sh: before running basicc-11, we need to see if
+       the cwd has been deleted (by basicc-8).  If so, we
+       recreate it to allow basicc-11 to proceed.  This may be
+       something that only happens under the Linux 2.2 kernel.
+
+1999-07-18  Karl Fogel  <address@hidden>
+
+       * edit.c (notify_do): chop newline, if any, from the value
+       obtained from CVSROOT/users.  Otherwise it just gets passed along
+       in the argument to the notification program (usually mail), which
+       will misinterpret it as signifying the end of the command.
+
+1999-07-19  Larry Jones  <address@hidden>
+
+       * rcs.c (RCS_delete_revs): In the WIN32 kludge, be sure that the result
+       of RCS_getexpand is not NULL before trying to use what it points to.
+       (Patch submitted by Timothy L. Taylor <address@hidden>.)
+
+1999-07-16  Tom Tromey  <address@hidden>
+
+       * admin.c (admin): Allow `-k' options to be used unrestricted.
+
+1999-06-23  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (symlinks2): New test, for symlinks in working
+       directory without PreservePermissions.  This test (modulo a few
+       details not relevant to testing whether we are following symlinks)
+       worked remote as of now, or either remote or local for CVS 1.9.
+       * subr.c (get_file): Revert 1998-02-15 change to special-case
+       symlinks.  This makes the above test work local too.
+       * rcs.c (RCS_checkin): Move the logic to handle special-case
+       symlinks (and other files other than regular files) here, and make
+       it only happen if PreservePermissions is on.
+
+1999-06-18  Larry Jones  <address@hidden>
+
+       * sanity.sh (devcom3-9a): Be less specific about the expected
+       error message (BSD/OS 4.0 has a bug that can cause exec* to fail
+       with EACCES instead of ENOENT).
+
+1999-06-08  Larry Jones  <address@hidden>
+
+       * sanity.sh (diff-4, dirs2-10, tagf-13, importc-7, conflicts2-142b8):
+       Use ${PROG} instead of "cvs".
+
+1999-06-05  Jim Kingdon  <http://www.cyclic.com>
+
+       * recurse.c (do_recursion, do_dir_proc): Make the SERVER_ACTIVE
+       #ifdef be only around the check for server_active.  Modulo a few
+       cosmetic tweaks, same as a patch submitted by Johannes Stezenbach
+       of propack-data.de.
+
+1999-06-01  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh: Add comment about rcs2-7 failures on certain days.
+
+       Make "cvs status -v" on a removed file work:
+       * status.c (cvsstatus): Reindent the client code.
+       (status_fileproc): Don't need a CVS/Entries listing to show the
+       tags.
+       * sanity.sh (rmadd2): New test rmadd2-16 tests the existing
+       behavior with "cvs log"; new test rmadd2-17 tests the new behavior
+       with "cvs status".
+
+       * sanity.sh (basicc): To match no output in dotest, put the empty
+       regexp first.  Remove tests which check that first-dir exists,
+       since that isn't true in the case where the OS let us delete it.
+       (dotest_internal): Fix so that things work with two regexps, with
+       an empty one first.
+
+1999-05-28  Larry Jones  <address@hidden>
+
+       * sanity.sh (server-4): Replace bogus directory with real one since
+       the server now checks it.
+
+1999-05-27  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (spacefiles): Clean up -c, top, and -b at end.
+       (spacefiles, files): Fix bad references to CVSROOT_DIRNAME.
+
+       Fix two problems pointed out by Olaf Kirch of swb.de/caldera.de:
+       * server.c (outside_root): New function, contains expanded version
+       of code from serve_directory.
+       (serve_directory): Call outside_root.
+       (outside_dir): New function
+       (serve_modified, serve_is_modified, serve_notify,
+       serve_questionable, serve_unchanged): Call outside_dir.
+       * sanity.sh (server2): New tests, for these fixes.
+
+1999-05-26  Jim Kingdon  <http://www.cyclic.com>
+
+       * cvs.h, subr.c (xmalloc): Return void* not char*, like xrealloc
+       has done for some time.
+       * modules.c (do_module): If we find the module as a directory/file
+       (rather than in the modules file), skip a bunch of processing
+       which was unnecessary and also broken in most of the cases
+       now tested for by the spacefiles sanity.sh test.
+       * sanity.sh (spacefiles): New test, for specifying filenames
+       (containing spaces, or starting with '-', or starting with '/') to
+       "cvs co".
+
+1999-05-25  Jim Kingdon  <http://www.cyclic.com>
+
+       * client.c (update_entries): Make the old DONT_USE_PATCH code the
+       only code.  This means that if people are still on CVS 1.9
+       servers, then CVS will fall back to transferring entire files.
+       This is better than looking for an external "patch" program which
+       causes no end of troubles (especially on Windows, but someone just
+       posted to info-cvs about a problem with the Solaris patch).  (This
+       change was run by devel-cvs and feedback was positive).
+
+       * subr.c (xmalloc, xrealloc): The new error.c does not support
+       %lu; use sprintf instead.
+
+1999-05-25 Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+
+       * sanity.sh (server): Escaped a few more newlines in
+       another awk script.  Solaris awk still don't like 'em.
+
+1999-05-25 Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+       and Jim Kingdon
+
+       * log.c: Remove comment which said "you can delete [this line]"
+       and which stuck around for over 3 years.
+       * sanity.sh (errmsg2 & tagdate): Added tests to prove the
+       current functionality with respect to combining -r and -D.
+
+1999-05-20  Larry Jones  <address@hidden>
+
+       * server.c (pserver_authenticate_connection): Previous changes
+       broke verify_and_exit (reported by Robert Fitzsimons, thanks).
+       * sanity.sh (pserver): New tests pserver-7 and pserver-8 for this.
+
+1999-05-18 Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+
+       * sanity.sh (keyword2): Escaped a newline in an awk script.
+       Apparently Solaris awk don't like 'em.
+
+1999-05-18  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (basicc): Allow the behavior whereby unlink(".")
+       succeeds.  Reported by Jeremy Buhler and Pavel Roskin.
+
+1999-05-17  Steve Cameron of Compaq
+
+       * sanity.sh: Modified to no longer use "test -e" for existence
+       test as it has turned out to be not portable enough.  Instead use
+       "test -f", "test -d", etc.
+       [SCO Unixware 7 apparently doesn't always support it -kingdon]
+
+1999-05-17  Jim Kingdon  <http://www.cyclic.com>
+
+       * version.c: Push version number to 1.10.6.1.
+
+       * version.c: Version 1.10.6.
+
+1999-05-16  Jim Kingdon  <http://www.cyclic.com>
+
+       * update.c (patch_file): When we are passing vn_rcs to
+       RCS_checkout, pass vn_tag as well.
+       * sanity.sh (keyword): In test keyword-22, test for the fixed
+       behavior rather than the buggy behavior.  Adjust keyword-23.  Add
+       test keyword-24, to see whether keyword-23 really worked.
+
+1999-05-12  Larry Jones  <address@hidden>
+
+       * sanity.sh (pserver-4, pserver-5): Bogus error messages from
+       non-root initgroups on some 4.4BSD derived systems now show up
+       in different places in the output.
+
+1999-05-12  Jim Kingdon  <http://www.cyclic.com>
+
+       * import.c (import): Don't allow the user to supply a repository
+       directory which takes us out of the cvsroot.
+       * sanity.sh (importc): New tests importc-10 to importc-12, for this.
+
+1999-05-11  Larry Jones  <address@hidden>
+
+       * server.c (serve_notify): Allocate enough memory to hold the
+       "misformed Notify request" message in pending_error_text.
+
+1999-05-11  Jim Kingdon  <http://www.cyclic.com>
+
+       * server.c (switch_to_user): Ignore EPERM from initgroups.  Fixes
+       pserver-4 in testsuite.
+       (pserver_authenticate_connection): Only print "I LOVE YOU" after
+       switch_to_user has come back successfully.
+
+       * server.c (pserver_authenticate_connection): Call error_exit
+       rather than reinventing the wheel ourselves.
+       (switch_to_user): Check for errors from setuid, setgid, and
+       initgroups.  Fix the #ifdef's (the previous code would skip the
+       setuid call if SETXID_SUPPORT).
+
+1999-05-10  Jim Kingdon  <http://www.cyclic.com>
+
+       * server.c (serve_notify), edit.c (notify_do): Check for
+       and reject characters which will get confused with delimiters.
+       * sanity.sh (server): New tests server-7 through server-15 test
+       for this and for other notify behaviors.
+
+       * rcs.c (RCS_tag2rev): Also look for a physical branch with
+       RCS_getversion.
+       * sanity.sh (tagf): Adjust tagf-12 and following tests to test for
+       the fixed behavior rather than the broken behavior.
+
+1999-05-07  Jim Kingdon  <http://www.cyclic.com>
+
+       * server.c (server_notify): Also set last_node to NULL.
+       * sanity.sh (server): New tests server-6 and server-7, for this.
+
+1999-05-05  Jim Kingdon  <http://www.cyclic.com>
+
+       * rcs.c (rcs_internal_lockfile): Remove unused variable lockfile.
+
+       * add.c (add): Look for directories with the same name in a
+       different case where appropriate (analogous to fopen_case).
+       In client code, add comment about how this doesn't do quite
+       everything.
+
+1999-05-03  Jim Meyering  <address@hidden>
+
+       Remove rcs-style ,file, lock files upon signal.
+       * rcs.c (rcs_lockfile): New file-scoped global.
+       (rcs_cleanup): New function (similar to patch_cleanup).
+       (rcs_internal_lockfile): Register rcs_cleanup the first time this
+       function is called.  Rename uses of local `lockfile' to refer to new
+       global, `rcs_lockfile'.  Don't free the lock file name string, now
+       that it's global.
+       (rcs_internal_unlockfile): Rename `lockfile', as above, and carefully
+       free and NULL-out the global, rcs_lockfile.
+
+1999-04-30  Jim Kingdon  <http://www.cyclic.com>
+
+       * rcs.c (annotate_fileproc): Don't cast NULL in passing it to
+       RCS_deltas.  Because there is a prototype in scope the cast is
+       unnecessary (per HACKING's ANSI C or SunOS4 rule), and in fact it
+       was causing failures on UNICOS because it cast to size_t instead
+       of size_t*.  (Thanks to Dean Kopesky for reporting this).
+
+1999-04-29  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh: If invoked without any arguments, print a usage
+       message (thanks to Pavel Roskin for a report/patch).
+
+       * run.c (piped_child): Make the error messages more verbose.
+       (close_on_exec): Reindent.
+       * sanity.sh (devcom3): Several errors are possible in devcom3-9a.
+       Adjust for change to piped_child error message.
+
+1999-04-28  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (devcom3): Add some tests of the CVS/Notify file and
+       disconnected "cvs edit".
+
+       * main.c (opt_usage): Remove -b.
+
+1999-04-20 Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+
+       * rcs.c (RCS_delete_revs):  RCS_delete_revs uses an
+       RCS_checkout call to get a new copy of a revision to be
+       used internally after old revisions were deleted and it was
+       performing keyword substitutions.  This munged all the
+       the revisions of the file on the branch containing the
+       deleted revisions and its sub-branches, as the original they
+       were being patched from was incorrect.  Corrected this by
+       passing in "-ko" as an option to RCS_checkout.
+       * sanity.sh (keywordlog):  modified this test to verify the
+       correct behavior of 'cvs admin -o'.
+       [Fixed use of \$ in keywordlog test; added code in RCS_delete_revs
+       to abort on binary file on Windows -kingdon]
+
+1999-04-21  Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+       and Jim Kingdon
+
+       * tag.c (tag_check_valid): A bug was causing CVS to spin
+       indefinately when -j:<date> was specified.  CVS now returns
+       an error.
+       * sanity.sh: Added a test (tagdate-12) to test this.
+
+1999-04-19  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (backuprecover): Clean up the repository at the end.
+
+1999-04-18  Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+
+       * sanity.sh added a test (backuprecover) to test cvs behavior
+       with a repository that is out of date relative to the
+       developer's workspaces.
+       [Fix --keep code; move test to "Repository Storage" section since
+       it doesn't really exercise the diff/diff3 library. -kingdon]
+
+1999-04-13  Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+
+       * sanity.sh (diff):  Tests to verify correct operation of
+       the --ifdef parameter to cvs diff.
+       [indentation fixed -kingdon].
+
+1999-04-13  Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+       for Noah Friedman  <address@hidden>
+
+        * diff.c (diff): Put "--ifdef=" in opts string, not "-D"; the
+        latter is confused by pserver for a date spec.
+
+1999-04-14  Jim Kingdon  <http://www.cyclic.com>
+
+       * fileattr.h: Adjust comments to reflect the official version of
+       the fileattr format now being in cvs.texinfo.
+
+1999-04-05  Jim Kingdon
+
+       * sanity.sh (watch5): Remove nonstandard --keep code.  Don't pass
+       -f to rm when cleaning up (that tends to mask bugs).  Add watch5
+       to list of tests at start.  Add comment explaining why we consider
+       the behavior we test for the right one.  Rename a few tests which
+       had been erroneously named watch6* instead of watch5*.
+       * client.c (update_entries): Add comment with brief discussion of
+       whether there is a better way.
+
+1999-04-05  Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+
+       * client.c (update_entries):  Only call mark_up_to_date
+       (which deletes the CVS/Base/<filename> file for watched
+       and edited files) on commit.
+       * sanity.sh:  Make sure the CVS/Base/<filename> file for
+       a watched and edited file is not removed on a status or
+       update of a touched/unmodfied file.
+
+1999-03-30  Larry Jones  <address@hidden>
+
+       * client.c (get_responses_and_close), commit.c (commit),
+       update.c (do_update): If the sleep(1) call returns prematurely
+       (due to the way wakeup is scheduled or receiving a signal), do
+       it again.
+
+1999-03-26  Jim Kingdon  <http://www.cyclic.com>
+
+       * server.c (server): Add comment about Gzip-stream vs. RQ_ROOTLESS.
+
+       * sanity.sh (modules3-11b): Adjust exact text of error message to
+       reflect 1999-03-24 change to dirswitch.
+
+1999-03-25  Jim Kingdon  <http://www.cyclic.com>
+
+       * admin.c (admin): Make argument to -e optional, to match the
+       documentation.
+       * sanity.sh (admin-19a-2): Test for this.
+
+       * server.c (serve_root): Update comment about checking for missing
+       Root request.
+
+1999-03-24  Jim Kingdon  <http://www.cyclic.com>
+
+       * server.c (dirswitch): Also check dir here, similar to
+       what server_pathname_check does for other cases.
+       * sanity.sh (files): Adjust files-14 to test for this.
+
+1999-03-24  Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+       and Jim Kingdon
+
+       * sanity.sh: added a test (files-13) to test .. indirection
+       in a path and another (files-14) to make sure we still fail
+       out when the '..' indirection takes us into the $CVSROOT
+       directory or beyond.
+
+1999-03-24  Larry Jones  <address@hidden>
+
+       * rcs.c: Change enum constants ADD and DELETE to something less
+       likely to run into conflicts.
+
+1999-03-21  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (tagf): New test, tests for moving a branch tag to a
+       non-branch tag and trying to recover.
+
+1999-03-12  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (branches): Tweak test branches-5 to test the case in
+       which one modifies a file and then branches it.
+
+1999-03-09  John Bley of duke.edu
+
+       * mkmodules.c (filelist): Missed a NULL in this struct (should
+       have 3 members, only had 2).
+
+1999-03-07  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (Index): Rename new test from rm_CVS/Root to rmroot
+       (we don't have a formal rule about funky punctuation in test names
+       but both underscore and a slash is too funky for me :-)).
+       Reindent a few tests which were off.
+
+       * root.c: Remove the sentence which had the improper English;
+       there isn't really a need for that sentence and it isn't
+       particularly accurate any more.
+
+1999-02-27  Derek Price
+       <http://www-personal.engin.umich.edu/~oberon/resume.html>
+
+       * sanity.sh:  Added rm_CVS/Root test to test that CVS uses
+       $CVSROOT rather than dumping core when running remotely and
+       the admin file CVS/Root is deleted from the workspace.
+
+       Also, altered a few 'cvs commit' 's in regular expressions to
+       fit the .${PROG} commit. portability syntax.
+
+       * recurse.c:  Stopped CVS from dumping core in the case tested
+       above.
+
+       * root.c:  Fixed somebody's improper english.
+
+1999-02-25  Larry Jones  <address@hidden>
+
+       * sanity.sh (keyword2-12): Use ${QUESTION} instead of ? in the
+       expected result.
+
+1999-02-24  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (keyword2): Restore the original \\\$ instead of $.
+       The latter ends up working due to various kludgy semantics in the
+       shell and regular expressions, but the former is cleaner.
+
+       * sanity.sh (keyword2): Protect keywords against accidental
+       expansion in sanity.sh itself (most occurrences had this, but not
+       all).
+
+1999-02-23  Derek Price  <http://www.cyclic.com>
+       and Jim Kingdon.
+
+       * sanity.sh (keyword2): New test, tests for merging with -kk.
+
+1999-02-22  Jim Kingdon  <http://www.cyclic.com>
+
+       * version.c: Ease version number to 1.10.5.1.
+
+       * version.c: Version 1.10.5.
+
+1999-02-18  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (files): New test, for a relatively obscure spurious
+       "Up-to-date check failed" in client/server.
+
+       * main.c (lookup_command_attribute): Don't check for "history"
+       twice.
+
+1999-02-17  Jim Kingdon  <http://www.cyclic.com>
+           and Hallvard B Furuseth
+
+       * root.c (parse_cvsroot): Rearrange ifdefs to squelch possible
+       warnings about statement not reached.
+
+1999-02-16  Jim Kingdon  <http://www.cyclic.com>
+
+       * recurse.c (start_recursion): If we are skipping the current
+       directory (due to it being from the wrong repository), also adjust
+       the arguments we send to the server accordingly (like we already
+       do for the case in which there is no CVS directory).
+       * sanity.sh (multiroot4): New test, for this.  All these tests had
+       passed locally, but remote multiroot4-12 tests for this fix.
+       (multiroot): Adjust multiroot-diff-1, multiroot-update-2,
+       multiroot-tag-1, multiroot-status-1, multiroot-update-3, and
+       multiroot-log-1 to reflect the cosmetic change this produces (one
+       less "Diffing ." message).
+       (multiroot2): multiroot2-8 likewise.
+
+1999-02-10  Jim Kingdon  <http://www.cyclic.com>
+
+       * tag.c (cvstag): Don't pass SEND_NO_CONTENTS if -c specified.
+       * sanity.sh (tagc): New test, for various tag -c behaviors.
+       Test tagc-6 tests for this fix.
+
+1999-02-09  Jim Kingdon  <http://www.cyclic.com>
+
+       * error.c (error): Rewrite to no longer use vasprintf (see
+       ../lib/ChangeLog for rationale).  Note the slight change in
+       interface - callers which want %8.8s or similar formats need to
+       call sprintf.
+       * lock.c (lock_wait, lock_obtained): Use sprintf.
+
+1999-02-08  Jim Kingdon  <http://www.cyclic.com>
+
+       * rcs.c (RCS_delete_revs): Pass -a to diff_exec.
+       * sanity.sh (binfiles3): New tests binfiles3-9 through
+       binfiles3-13 test for this fix.
+       * sanity.sh (binfiles): New tests binfiles-o4 and binfiles-o5
+       (which don't test this bug, just on general principles).
+
+1999-02-04  Jim Kingdon  <http://www.cyclic.com>
+
+       * lock.c (lock_name): Permissions of directories in LockDir
+       shouldn't depend on the umask.
+       * sanity.sh (lockfiles): Set umask and CVSUMASK, to test for this.
+
+1999-02-01  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (keywordlog): New tests keywordlog-22 and
+       keywordlog-23 test keyword expansion and $Log.  Adjust other tests
+       so that revisions differ more from each other, so this is a
+       better test.
+
+1999-01-29  Jim Kingdon  <http://www.cyclic.com>
+
+       * commit.c (checkaddfile): If options is "", treat it the same as
+       NULL.  Centralize this check, and the one for it starting with
+       "-k", at the start of the function.
+
+       * rcs.c, rcs.h (RCS_setexpand): New function.
+       * admin.c (admin_fileproc): Access keyword expansion field via
+       RCS_getexpand and RCS_setexpand, rather than directly.
+       * commit.c (checkaddfile): When resurrecting, set the keyword
+       expansion mode.
+       * sanity.sh (binfiles3): Adjust tests binfiles3-7 and binfiles3-8
+       for the new behavior.
+
+1999-01-27  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (multiroot3): Add new variant of multiroot3-10 test
+       for RELATIVE_REPOS.  Move multiroot3-11 test out of the
+       conditionals; it works the same for remote or local,
+       RELATIVE_REPOS or no.
+
+       * options.h.in: Make RELATIVE_REPOS the default, as has been
+       announced as a future direction since 1997-10-11.
+       * sanity.sh (multiroot): Tweak multiroot-update-1a and
+       multiroot-update-1b tests to work with either RELATIVE_REPOS or
+       non-RELATIVE_REPOS.
+
+       * sanity.sh (client-9): Don't assume the time zone.
+
+1999-01-26  Jim Kingdon  <http://www.cyclic.com>
+
+       Fix one facet of the "cvs add -kb" re-adding problem (the other
+       known facet is tested for by binfiles3-8).
+       * add.c (add): When re-adding a file, set the keyword expansion
+       as we normally would.
+       * sanity.sh (binfiles3): New test binfiles3-6a tests for this.
+
+1999-01-22  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (rmadd2): New tests, for undoing a commit.
+
+1999-01-21  Eric Mumpower  <address@hidden>
+
+       * sanity.sh (reposmv): Actually modify CVSROOT in current
+       environment when calling functions, rather than trying to achieve
+       the same effect with "CVSROOT=foo functionname". (Many common
+       bourne shells, including those in SunOS and Solaris 2.4-2.7,
+       do not properly handle "ENVVAR=foo command" when "command" is
+       a user-defined shell function rather than an actual executable.)
+
+1999-01-15  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (rcs3): Redirect awk's stdin to /dev/null like all the
+       other awk invocations.  GNU awk seems not to read stdin in this
+       case, but that behavior is hard to reconcile with the Single Unix
+       Spec and some awks don't do it.
+
+       * sanity.sh (binfiles, binfiles2, binfiles3, server): Use the same
+       tr trick as in rcs3.  People don't seem to have been complaining,
+       and this should fix server-4 for HPUX.
+
+1999-01-14  Jim Kingdon  <http://www.cyclic.com>
+
+       * client.c (recv_line): If the line we are reading contains a
+       character which would sign-extend to EOF, don't treat it as end of
+       file.  recv() doesn't report end of file this way and this might
+       fix bugs with 0xff characters.
+
+1999-01-14  Larry Jones  <address@hidden>
+
+       * client.c (recv_line): Handle EOF from server.
+
+       * sanity.sh (importc-8, importc-9): Accept anything in the seconds
+       fields of the timestamps since touch doesn't set it reliably.
+       (This isn't great, but it's better than nothing.)
+
+1999-01-14  Jim Kingdon  <http://www.cyclic.com>
+
+       * run.c (run_exec): Adjust comment about vfork; this isn't the place
+       to get into a treatise about fork performance vs. vfork
+       performance but it isn't quite as simple as whether one has
+       copy-on-write.
+
+1999-01-13  Larry Jones  <address@hidden>
+
+       * sanity.sh (dotest_fail): Handle spurrious output from assert better.
+
+       * sanity.sh (rcs3-4, rcs3-5a): Handle even more variants of the
+       assertion failure message.
+
+1999-01-12  Larry Jones  <address@hidden>
+
+       * sanity.sh (mtfr-3): ls behavior varies wildly on nonexistant files,
+        just use echo instead.
+
+1999-01-11  Jim Meyering  <address@hidden>
+
+       * sanity.sh (mkmodules-temp-file-removal): New test, for this.
+       * mkmodules.c (mkmodules): Remove each `CVSROOT/.#[0-9]*' temporary
+       file that's used to check out files listed in CVSROOT/checkoutlist.
+       Remove extra semicolon at end of line.
+
+1999-01-11  Larry Jones  <address@hidden>
+
+       * sanity.sh (rcs3-5a): Allow for multiple lines of output before the
+       assertion failure message.
+
+       * sanity.sh (lockfiles-6, client-8): Work around bug in HP-UX chmod
+       (doesn't allow anything to follow omitted permissions).
+
+1999-01-09  Jim Kingdon  <http://www.cyclic.com>
+
+       * client.c (set_sticky): Nonfatal error if we can't write it.
+       * sanity.sh (dirs2-8 through dirs2-14): New tests, for this.
+
+       * sanity.sh (rcs3): Write NUL character with tr not awk, in
+       accordance with Single Unix Specification.  Hopefully will fix
+       rcs3-7 for HPUX.  Will not work on SunOS4, but then again neither
+       did the old syntax.
+
+1999-01-05  Jim Kingdon  <http://www.cyclic.com>
+
+       * client.c, update.c: Rename MD5* functions to cvs_MD5* per
+       corresponding change to ../lib/md5.h.
+
+1999-01-03  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (client): Give file1 a predictable mode so that the
+       output in client-9 will not depend on the umask of the user
+       running the tests.
+
+1998-12-29  Jim Kingdon  <http://www.cyclic.com>
+
+       * client.c (client_senddate): Use date_to_internet rather than
+       using our own "5/26/1997 13:01:40 GMT" date format.
+       * main.c (date_to_internet): Check for errors from sscanf.  Always
+       send a four digit year.  Send hours, minutes, and seconds as two
+       digits per RFC822.
+       * sanity.sh (client): New tests client-8 and client-9 test for this.
+
+       * sanity.sh (rcs2): New tests rcs2-6 through rcs2-8 test for fix
+       to lib/getdate.y (before the fix, "100 months" or "8 years" would
+       tend to mean the year 1969, thus the tests would give "cvs update:
+       file1 is no longer in the repository").
+
+1998-12-28  Larry Jones  <address@hidden>
+
+       * entries.c (Register): Return if unable to open log file to avoid
+       referencing the invalid file pointer.
+       * sanity.sh (dirs2-7): With above change, no longer fails.
+       * sanity.sh (rcs3-5a): Another assertion failure message.
+       * sanity.sh (pserver-4, pserver-5): Some 4.4BSD derived systems spit
+       out bogus error messages when initgroups is called as non-root.
+
+1998-12-23  Larry Jones  <address@hidden>
+
+       * sanity.sh (rcs3, dotest_fail): The assertion failure message varies
+       wildly between different systems and the resulting abort call can
+       even result in spurrious output.  Fix the regexp to accept nearly
+       anything containing some kind of assertion failure and ensure that
+       any spurrious output ends up in the output file instead of on the
+       terminal.
+
+1998-12-23  Jim Kingdon  <http://www.cyclic.com>
+
+       * admin.c, checkout.c, commit.c, cvsrc.c, expand_path.c,
+       history.c, ignore.c, import.c, log.c, mkmodules.c, modules.c,
+       myndbm.c, parseinfo.c, rcs.c, remove.c, rtag.c, status.c, subr.c,
+       tag.c, wrapper.c: Cast all char's to unsigned char before passing
+       them to ctype.h functions (isalpha, isgraph, isalnum, isspace,
+       isdigit, isprint, isupper).  Whether using ctype.h is the right
+       thing at all is unclear to me (having the server depend on locale
+       seems wrong, as we don't necessarily have any good way to set the
+       right locale, if there even is such a concept as 'right' locale in
+       this context), but as long as we use ctype.h we might as use it
+       according to the standards (this affects systems where plain char
+       is signed but users supply characters with the 8th bit set).
+       Thanks to Paul Eggert for suggesting this.
+
+1998-12-22  Jim Kingdon  <http://www.cyclic.com>
+
+       * sanity.sh (rcs3): Oops, the earlier fix for srcdir only fixed
+       the non-remote case, not the remote case.  Fix the other occurrence.
+
+1998-12-22  Jim Kingdon
+
+       * sanity.sh (rcs3): The assertion failure message varies slightly
+       depending on whether CVS was built with srcdir != ".".  Fix regexp.
+
+1998-12-21  Jim Kingdon
+
+       * rcs.c (RCS_getdate): Reindent Jim Meyering's change; remove
+       unused variable x_vers.
+
+       * rcs.c: When printing an unexpected character we found in the RCS
+       file, print it in hex rather than as a character (see comment for
+       rationale).
+       * sanity.sh (rcs3): Adjust rcs3-2 and rcs3-7 tests accordingly.
+
+       * sanity.sh (rcs3): New test, for some error handling cases
+       involving parsing RCS files.
+
+1998-12-16  Jim Meyering  <address@hidden>
+
+       * rcs.c (RCS_getdate): Handle the case in which a file is first
+       imported after its initial version has been created.
+       * sanity.sh (import-after-initial): New test for that.
+
+1998-12-17  Jim Kingdon
+
+       * server.c (serve_root): Pserver_Repos only exists if
+       AUTH_SERVER_SUPPORT is defined.
+
+1998-12-12  Jim Kingdon, and Derek R. Price of Stortek.
+
+       * sanity.sh (multiroot): Change + to ${PLUS}.
+
+1998-12-12  Jim Kingdon, and Gary Young of Motorola
+
+       * sanity.sh (admin): In tests admin-13, admin-25, and admin-29,
+       allow 4 digit year in addition to 2 digit year.
+
+1998-12-12  Jim Kingdon
+
+       * sanity.sh (log): New tests log-14a and log-14b test for -rHEAD
+       and for HEAD as (nonexistent) file name.
+
+1998-12-02  Jim Kingdon
+
+       * version.c: Squish version number to 1.10.4.1.
+
+       * version.c: Version 1.10.4.
+
+1998-11-24  Jim Kingdon
+
+       * recurse.c (do_file_proc): Check for errors from RCS_parse.
+       * sanity.sh (rcslib-symlink-7 through rcslib-symlink-10): New
+       tests, test for this.
+
+       * sanity.sh (reposmv-2): Adjust for 22-Nov change to Find_Names.
+
+       * entries.c (Register): If we can't write Entries.Log, make it a
+       nonfatal error.
+       * sanity.sh (dirs2): Test for this fix.
+
+       * sanity.sh (dirs2): Clean up working directory at end of test.
+
+1998-11-23  Jim Kingdon
+
+       * sanity.sh (dirs2): New test, for some more cases involving
+       deleting directories and such.
+
+       * sanity.sh (dirs): Update for yesterday's change in Find_Names
+       error handling.  The error in dirs-4 is fairly different now; in
+       dirs-3 and dirs-3a it is the obvious change.
+
+1998-11-22  Jim Kingdon
+
+       * sanity.sh (release): Move the commments listing "cvs release"
+       tests from modules2-6 to here.
+       * release.c (release): Update comment to reflect "? foo" case.
+
+       * find_names.c (Find_Names): If we can't read the repository, make
+       it a nonfatal error.  Tell the caller whether this happened.
+       (find_rcs): Add comment regarding this behavior.
+       * recurse.c (do_recursion): If Find_Names gives an error, skip
+       the directory and print a message saying so.
+       * sanity.sh (modes3): New test, for this.
+
+1998-11-18  Jim Kingdon
+
+       * rtag.c (rtag_usage), tag.c (tag_usage): Use "-r rev"
+       consistently.
+
+       * sanity.sh (conflicts3): Tests conflicts3-24 through
+       conflicts3-28 test for another case similar to conflicts3-22.
+
+1998-11-14  Jim Kingdon
+
+       * sanity.sh (diff): New test, for now just tests for the "I know
+       nothing" message.
+
+       * sanity.sh (conflicts2-142b7 through conflicts2-142b11): New
+       tests; resurrecting doesn't work from one level up.
+
+       * sanity.sh (mwrap-7): Remote prints the messages in a different
+       order.
+
+1998-11-13  Jim Kingdon
+
+       * tag.c (check_fileproc): Log tag deletions.
+       * rtag.c (check_fileproc): Likewise.
+       * sanity.sh (taginfo-14 through taginfo-18): New tests, for
+       these behaviors.
+
+1998-11-12  Jim Kingdon
+
+       * sanity.sh (mwrap-7): Update for the noexec fix.
+
+       * server.c (server_copy_file): Add comment about noexec.
+
+       * update.c (checkout_file): Handle noexec case involving revbuf
+       and modes.
+       (update_fileproc): In case T_NEEDS_MERGE, let merge_file take care
+       of noexec, so it can tell the user if there would be conflicts.
+       (merge_file): Print "conflicts found in FILE" message
+       regardless of noexec.  Add comment about checking for whether the
+       file already contained the changes, and noexec.
+       * sanity.sh (conflicts-192a): New test, for this.
+
+1998-10-20  Jim Kingdon
+
+       Use the gzip library on the server.  Probably doesn't speed things
+       up as currently implemented, but does avoid hassles in terms of
+       finding an external gzip program.
+       * zlib.c, server.h (gunzip_and_write, read_and_gzip): Now returns
+       whether a fatal error occurred, rather than expecting error (1,
+       ...) to work.
+       * client.c (update_entries, send_modified): Change callers.
+       * server.c (receive_file): Rewrite gzip code to use
+       gunzip_and_write rather than filter_through_gunzip.
+       (server_updated): Likewise, use read_and_gzip rather than
+       filter_through_gzip.
+       * client.c, client.h (filter_through_gzip, filter_through_gunzip),
+       run.c, cvs.h (filter_stream_through_program): Removed; no longer used.
+       * sanity.sh (server): New tests server-4 and server-5 test
+       this feature (note that CVS 1.10 also passes these tests; the
+       behavior is supposed to be unchanged).
+
+1998-10-19  Jim Kingdon
+
+       * sanity.sh (multiroot3): New test, tests for a few more
+       multiroot cases.
+
+       * lock.c (lock_name): Set the permissions on each directory we
+       create to that of the parent directory.
+       * sanity.sh (lockfiles): New chmod and tests lockfiles-7a and
+       lockfiles-7b test for this.  Adjust lockfiles-5 for new text of
+       error message.
+
+1998-10-15  Jim Kingdon
+
+       * server.c (requests): Set RQ_ROOTLESS for "Set".
+       * sanity.sh (info): Also clean up $HOME/.cvsrc.
+       (server): Test that we can send Set before Root (had been tested
+       by crerepos-6b, but only if you ran the info test first).  Tests
+       for this fix.
+
+1998-10-14  Jim Kingdon
+
+       * subr.c (expand_string): Tweak the algorithm so that the size
+       that it allocates is generally a power of two.
+
+1998-10-14  Eivind Eklund and Jim Kingdon
+
+       * commit.c (commit): For the client, don't worry about whether we
+       are root.
+
+1998-10-13  Jim Kingdon
+
+       * server.h (struct request): Change status field to flags and add
+       RQ_ROOTLESS.
+       * client.c (handle_valid_requests, supported_request): Change
+       status to flags.
+       * server.c (requests): Change status to flags.  Add RQ_ROOTLESS.
+       * server.c (server): If not RQ_ROOTLESS, and we haven't gotten a
+       Root request, give an error.
+
+1998-10-12  Jim Kingdon
+
+       * version.c: Slide version number to 1.10.3.1.
+
+       * Version 1.10.3.
+
+       * sanity.sh (modules2-17): Update for 9 Oct 1998 change to
+       update_dirent_proc.
+
+1998-10-11  Jim Kingdon
+
+       * commit.c (checkaddfile, commit_fileproc): A numeric value for
+       'tag' does not mean that we are adding on a branch.
+       * sanity.sh (keywordlog): Adjust this test, to test for this
+       (replaces comment saying we should be doing it).
+       (rmadd): Likewise.
+
+       * sanity.sh (rmadd): New test, tests for various existing
+       behaviors with "cvs ci -r".
+
+1998-10-09  Jim Kingdon
+
+       * update.c (update_dirent_proc): For local CVS, if the directory
+       does not exist in the working directory nor in the repository,
+       just skip it.
+       * sanity.sh (dirs): New tests dirs-3a, dirs-7 and dirs-8 test for
+       this and related behaviors.  Note that the new behavior was also
+       the previous behavior for remote; we are only changing it for local.
+
+       * wrapper.c, cvsrc.c, ignore.c: Add comments about ignoring .cvsrc
+       and friends if we can't find a home directory.
+       * expand_path.c (expand_path): If we can't find the home
+       directory, give an error rather than a coredump (or worse).
+       * login.c (construct_cvspass_filename): Don't use errno in error
+       message; get_homedir doesn't set it.  Add comment about this
+       message.
+
+1998-10-07  Jim Kingdon  <address@hidden>
+
+       * diff.c (diff): Set variables to NULL at the start, and free
+       memory at the end.
+       * sanity.sh (multiroot2): Add tests for this (before the fix,
+       multiroot2-12 would abort with "no more than two revisions/dates
+       can be specified").
+
+1998-10-06  Jim Kingdon  <address@hidden>
+
+       * Makefile.in (installcheck check): Remove references to RCSBIN;
+       they don't do anything now that RCSBIN is ignored.
+
+       * client.c: Clean up horrible confusion about whether stored_mode
+       or stored_mode_valid (or nothing :-)) indicates whether
+       stored_mode is allocated.  Should fix crashes (for example, on NT
+       when the server has renamed multiple files from uppercase to
+       lowercase).
+
+       * sanity.sh (dirs): New tests, tests for some cases involving
+       admins who do surgery on the repository.
+
+1998-10-03  Johannes Stezenbach <address@hidden>
+
+       * vers_ts.c (Version_TS): If UTIME_EXPECTS_WRITABLE, if
+       necessary change the file to be writable temporarily to set its
+       modification time.
+
+1998-10-03  Jim Kingdon  <address@hidden>
+
+       * client.c (handle_error): Add comment about indicating which
+       errors are from the server.
+
+1998-10-01  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (devcom-180): Allow one digit day.
+
+1998-09-30  Jim Kingdon  <address@hidden>
+
+       * main.c (main): Don't call Name_Root if -d specified.
+       * recurse.c (do_recursion, do_dir_proc): Don't check CVS/Root
+       if -d was specified.
+       * import.c (import): Indentation fix.
+       * sanity.sh (multiroot): Update for this change.
+       (reposmv): New test, tests for this.
+
+1998-09-28  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (multiroot2): New test, tests some nested directory
+       cases.
+
+1998-09-25  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (multiroot): Change a few comments which said modules
+       when they meant directories.
+
+1998-09-25  Jim Meyering  <address@hidden>
+
+       * sanity.sh (devcom-180): Add 0-9 to the range of characters allowed
+       in hostname regexp.
+
+1998-09-25  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (log2): New test log2-7a tests for one error handling
+       case.  Add a comment about another.
+
+1998-09-24  Jim Kingdon  <address@hidden>
+
+       * sanity.sh: Change crerepos test back to :ext: (for several
+       reasons; see comments).
+
+1998-09-24  Noel Cragg  <address@hidden>
+
+       * sanity.sh (rcslib-symlink-5, rcslib-symlink-6): new tests to
+       check the operation of "tag" when there are symlinks in the
+       repository.
+
+       * rcs.c (RCS_checkin): remove old code that resolved the symlink
+       and call resolve_symlink instead.
+       (RCS_rewrite): call resolve_symlink before doing anything else to
+       make sure we're operating on the file and not the symlink.
+
+       * subr.c (resolve_symlink): new routine -- resolves a symbolic
+       link chain to its destination.
+       * cvs.h: add prototype.
+
+       * sanity.sh (basica-6.2, basica-6.3): changed match expressions to
+       reflect new diff output.
+
+       * rcs.c (make_file_label): generate labels for files that include
+       the pathname so that output from "cvs diff" is useable by patch.
+       Looks like I came up with the mods as Andy Piper
+       <address@hidden>; his patch was on the Cyclic unofficial
+       patches page.
+
+       * sanity.sh: change remote access method from ext to fork.  This
+       results in a significant speed improvement when running the
+       testsuite.  The ext method on my machine (i586 120MHz Linux 2.0.35
+       with TCP wrappers installed) runs in 450% of the time of the local
+       method while the fork method runs in only 150% of the time of the
+       local method!  Yow!  Am I SWAPPING yet?!
+       (crerepos-6a, crerepos-6b): change to reflect different error
+       messages for fork method.
+       (modes-15): same.
+
+       * client.c (connect_to_forked_server): new routine.
+       (start_server): call the above when method is fork_method.
+
+       * root.c: add a new method named "fork".  This method uses the
+       remote protocol, but does so by forking a "cvs server" process
+       directly rather than doing "rsh host cvs server" (for example).
+       This new method has few advantages for day-to-day use, but has
+       three important benefits for debugging:
+
+         1) Most secure installations these days don't allow rsh access.
+         With this new method, we can still test the remote protocol on
+         these machines because we don't need to be able to make a local
+         TCP connection.
+
+         2) Even if installations allow rsh access, they almost always
+         have TCP wrappers to check permissions by IP/hostname.  This
+         causes a short delay for every connection.  For invocations from
+         the command line, this doesn't matter much, but it adds up to a
+         significant amount of time when running the testsuite.
+
+         3) On machines that can't (or do not usually) provide rshd
+         access (I'm thinking of WNT/W95 in particular), we can now run
+         tests of the remote protocol using this method.  Indeed, we can
+         run remote protocol tests on any machine that has an
+         implementation of piped_child().
+
+       (parse_cvsroot): handle new method.
+       (error_exit, xstrdup, isabsolute): new stub functions to use when
+       compiling root.c with the DEBUG option.
+       (main): fix a few typos.
+       * cvs.h (CVSmethod): add fork_method.
+
+       * server.c (create_adm_p): use Emptydir as the placeholder
+       directory instead of "." to avoid problems with "cvs update -d" et
+       al.
+
+1998-09-22  Noel Cragg  <address@hidden>
+
+       * sanity.sh (devcom-180): fixed typo in regexp.
+
+       * main.c (main): remove need_to_create_root and related code
+       (including CVS_IGNORE_REMOTE_ROOT environment variable).  The
+       current implementation (just removed) of rewriting the contents of
+       the CVS/Root file isn't desirable for a number of reasons:
+
+         1) Only the top-level CVS/Root directory is updated.  If we're
+         really interested in pointing our WD at another CVSROOT, we
+         should have a separate command.
+
+         2) With the new multiroot mods, we don't ever want to rewrite
+         CVS/Root files in the way the removed code did.  Consider:
+
+           cvs -d repository1 co a
+           cd a
+           cvs -d repository2 co b
+            cvs -d repository2 update b
+
+         The update command would rewrite the contents of a/CVS/Root to
+         the incorrect value.  Bad.  We then wouldn't be talking to the
+         correct repository for files in a.
+
+         3) The removed code seems to be a quick hack to support working
+         directories checked out from multiple repositories.  With the
+         CVS_IGNORE_REMOTE_ROOT variable set, one could perform commands
+         as in example 2, above, without worring about updating CVS/Root
+         files.  While in pre-1.10.1 recursive commands wouldn't handle
+         that working directory hierarchy, one could use commands like
+         "cvs foo -l" instead.  While not great, this allows you (with a
+         lot of manual interaction) to have a multiroot WD.  Since we now
+         have multiroot mods checked in, we don't need this code.
+
+       (lookup_command_attribute): while we don't need the
+       CVS_CMD_USES_WORK_DIR flag anymore (since it only was supporting
+       the need_to_create_root code), I'm leaving it in.  It may come in
+       handy at some later date.
+
+1998-09-18  Jim Kingdon  <address@hidden>
+
+       * version.c: Advance version number to 1.10.2.1.
+
+       * Version 1.10.2.
+
+1998-09-13  Jim Kingdon  <address@hidden>
+
+       * client.c: Refuse to Copy-file to another directory
+       * sanity.sh (client): New test, tests for this.
+
+       * edit.c (editors_fileproc), watch.c (watchers_fileproc): Use
+       cvs_output rather than writing to stdout.
+       * sanity.sh (devcom): Use dotest for tests 178, 180, and 183
+       (tests that we preserve existing behavior on "cvs editors").
+
+       * commit.c (check_fileproc): Don't allow commits in Emptydir.
+       * sanity.sh (emptydir-8): Test for this change in behavior.
+
+       * sanity.sh: Add some compatibility tests to TODO comments at end.
+
+1998-09-10  Jim Kingdon  <address@hidden>
+
+       * wrapper.c (wrap_add): Remove obsolete comment about -m.
+
+       * server.c (server_updated): Check for error from CVS_UNLINK.
+
+1998-09-09  Jim Kingdon  <address@hidden>
+
+       * server.c (serve_root): Allocate with malloc, not xmalloc.
+
+       * root.c (set_local_cvsroot): Move memory allocation from here...
+       * server.c (serve_root): ...to here.  Fixes error handling.
+
+       * root.c (parse_cvsroot): Don't call check_root_consistent;
+       parse_cvsroot is only used for local and client.
+       * root.c (set_local_cvsroot): Move check_root_consistent
+       functionality from here...
+       * server.c (serve_root): ...to here.  Fixes error handling.  Also
+       made the error more explicit, while I am at it.
+       * server.c (Pserver_Repos): Now static.
+       * cvs.h: Don't declare it.
+       * root.c (check_root_consistent): Removed; no longer needed.
+       * sanity.sh (pserver): New test, tests for this behavior and some
+       other basic pserver stuff.
+
+       * update.c (merge_file): Use cvs_output for "already contains the
+       differences" message.  Found this one when I actually observed the
+       out-of-order bug in Real Life(TM).
+
+1998-09-09  Jim Kingdon
+
+       * find_names.c (find_dirs): Make sure to zero errno before
+       going around the loop again.
+       * find_names.c (find_rcs): Make sure to set save_errno.
+       (thanks to Alexandre Parenteau for reporting both problems).
+
+1998-09-09  Jim Kingdon  <address@hidden> and Michael Pakovic
+
+       * edit.c (notify_do): Only free line if it is not NULL.
+
+1998-09-07  Jim Kingdon  <address@hidden>
+
+       * cvs.h: dirs_sent_to_server should not be inside
+       AUTH_SERVER_SUPPORT (reported by both Richard Levitte and Murray
+       Bishop, thanks).
+
+       * lock.c, cvs.h: New variable lock_dir.
+       * parseinfo.c (parse_config): New option LockDir.
+       * lock.c (lock_name): New function, abstracts out lock file naming
+       and also supports LockDir.
+       * lock.c (lock_simple_remove, Reader_Lock, write_lock, set_lock):
+       Call it (6 places, to create/remove read/write/master locks).
+       (Lock_Cleanup): Refuse to reenter this function.
+       * sanity.sh (lockfiles): New test, tests for this feature.
+
+1998-09-03  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (multiroot): Expect ${TESTDIR} in output instead of
+       assuming it is /tmp/cvs-sanity (thanks to Mark D. Baushke of Cisco).
+       Clean up working directory when done (fixes apparent thinko).
+
+       * server.c (create_adm_p): Fix one "return" which didn't return a
+       value.
+       (dirswitch): Check for errors from create_adm_p.
+
+       * sanity.sh: Set LC_ALL rather than just LC_COLLATE.
+
+Wed Sep  2 02:30:22 1998  Jim Kingdon  <address@hidden>
+
+       * version.c: Bump version number to 1.10.1.1.
+
+       * Version 1.10.1.
+
+1998-09-01  Jim Kingdon  <address@hidden>
+
+       Administrative note regarding Noel's changes to allow one to
+       switch from one CVS root to another in a single command: The
+       ChangeLog entries for the changes which Noel just checked in
+       appear for 1998-09-01, 1998-08-28, 1998-08-25, 1998-08-19, and
+       1998-08-18, rather than being all together.
+
+       * main.c (set_root_directory): Fix whitespace.
+       (main): Nuke new -m option and just have that message controlled
+       by -t.
+       * server.c (server): Revert the CVS_SERVER_SLEEP code back the way
+       it was in CVS 1.10.  Attaching to the parent process is relatively
+       boring (you can just run "cvs server" under a debugger instead),
+       but connecting to the child process is what the old code was for.
+       * recurse.c, server.c: Remove DEBUG_NJC code.
+
+1998-09-01  Noel Cragg  <address@hidden>
+
+       * server.c (do_cvs_command): add another environment variable,
+       CVS_SERVER_SLEEP2, after forking to pause the program so one can
+       attach a debugger.
+
+       * sanity.sh (crerepos): clean up crerepos-18 now that multiroot
+       works in this case.
+       (multiroot): finalize tests for local vs. remote operation.
+
+       * recurse.c (start_recursion): near the beginning, save the list
+       of directories to spoof as command-line arguments, if necessary.
+       Use that list near the end and call send_file_names to send those
+       arguments to the server.
+       (do_argument_proc): removed, since we call send_file_names now.
+
+       * main.c (main): re-initialize dirs_sent_to_server on each pass
+       through the loop for each CVSROOT.
+
+       * cvs.h: add proto for global variable which keeps track of which
+       directories have been sent to the server when in client mode.
+
+       * client.c (is_arg_a_parent_or_listed_dir): new function.
+       (arg_should_not_be_sent_to_server): new function.  Tries to decide
+       whether the given argument should be sent to the server, based on
+       the current CVSROOT and the list of directories sent to the
+       server.
+       (send_repository): add the directory name to the list of
+       directories sent to the server.
+       (send_file_names): call arg_should_not_be_sent_to_server.
+
+       * add.c (add): switch the order of send_files and send_file_names
+       to make multiple repository support possible.  send_files needs to
+       create a list of directories being requested so that
+       send_file_names can decide which command-line arguments to send to
+       the server for the given current CVSROOT.
+       * admin.c (admin): same.
+       * commit.c (commit): same.
+       * diff.c (diff): same.
+       * edit.c (editors): same.
+       * log.c (cvslog): same.
+       * rcs.c (annotate): same.
+       * remove.c (cvsremove): same.
+       * status.c (cvsstatus): same.
+       * tag.c (cvstag): same.
+       * update.c (update): same.
+       * watch.c (watch_addremove): same.
+       (watchers): same.
+
+1998-08-31  Jim Kingdon  <address@hidden>
+
+       * sanity.sh: Remove "debug" function; it was apparently checked
+       in accidentally by Norbert Kiesel's change.
+
+1998-08-31  Norbert Kiesel  <address@hidden>
+
+       * release.c (release): modify last patch to release so that
+       save_cwd is called only once and restore_cwd is always called when
+       neccessary.  Also fixed a tiny memory leak.
+
+       * sanity.sh (release): added some more tests for "cvs release"
+       including a test with two dirs and a "no" for the first one (which
+       fails without the above patch).
+
+1998-08-28  Noel Cragg  <address@hidden>
+
+       * sanity.sh (crerepos-18): add new comment and change test
+       slightly to support multiroot.
+       (multiroot): add more tests.
+
+       * server.c (create_adm_p): new function.
+       (dirswitch): call create_adm_p.  Modify the code to always write a
+       new CVSADM_REP file, since create_adm_p might have put a
+       placeholder there and our value is guaranteed to be correct.
+       (server): move the CVS_SERVER_SLEEP check here so we can debug
+       things at an earlier stage.
+
+       * recurse.c (start_recursion): add large comment about the ideal
+       solution to the "Argument xxx" problem.
+
+       * main.c (main): move position of debugging comment for -m flag.
+
+       * diff.c (diff): clear a static variable.
+
+       * client.c (send_file_names): check to see if we should send this
+       argument to the server based on the contents of the appropriate
+       CVSADM directory.  This avoids "nothing known about foo" messages
+       and problems with duplicate modules names in multiple
+       repositories.
+       (send_a_repository): change method of calculating toplevel_repos
+       to support multiple CVSROOTs.
+       (start_server): clear some static variables.
+
+1998-08-28  Jim Meyering  <address@hidden>
+
+       * sanity.sh (basicc-8, basicc-11): Use `.*' instead of explicit
+       `Operation not permitted'.  Solaris2.5.1 gets a different error:
+       `Invalid argument'.
+
+1998-08-26  Eric M. Hopper
+
+       * sanity.sh: Set LC_COLLATE to "C".
+
+1998-08-25  Noel Cragg  <address@hidden>
+
+       * sanity.sh (multiroot): new set of tests to check the behavior of
+       multiroot.
+
+       * diff.c (diff): set options value to NULL after freeing to reset
+       the state for the next time around.
+
+1998-08-25  Jim Kingdon  <address@hidden>
+
+       Fix problems with trying to rename an open file:
+       * rcs.c, rcs.h (RCS_setattic): New function.
+       * commit.c (remove_file, checkaddfile): Call it.
+
+1998-08-24  Jim Kingdon  <address@hidden>
+
+       * release.c (release): Use save_cwd and restore_cwd to get back to
+       where we started, rather than hoping that CVS_CHDIR ("..") will do
+       something useful.  This removes the need for most of
+       release_delete, so remove that function and inline what is left.
+       * sanity.sh (basicc): Adjust tests for this fix, also some tests
+       with multiple arguments to "cvs release" (in the non-"-d"-case, it
+       would seem like the old code would CVS_CHDIR into directories and not
+       CVS_CHDIR back, but I'm not going to investigate this and it
+       should be a moot point with this fix.).
+
+       * sanity.sh (basicc): Add tests for a serious bug in "cvs release
+       -d .".
+
+       More error handling fixes:
+       * ignore.c (ignore_files): Check for errors from opendir and
+       readdir.
+       * find_names.c (Find_Names): Check for errors from find_rcs.
+       (find_rcs, find_dirs): Comment error handling better; also return
+       an error if we got one from readdir.
+       * filesubr.c (deep_remove_dir): Also check for errors from readdir.
+       * import.c (import_descend): Print message on error from opendir
+       or readdir.
+       * commit.c (remove_file): Check for errors from CVS_MKDIR and
+       CVS_RENAME.
+       (remove_file): No need to remove the file in the temporary
+       directory; server.c now informs time_stamp_server of what is going
+       on via CVS/Entries rather than a file with a kludged up timestamp.
+       * client.c, entries.c, login.c, logmsg.c, mkmodules.c, patch.c,
+       remove.c, update.c: Check for errors from unlink_file.
+       * mkmodules.c (write_dbmfile, rename_dbfile, rename_rcsfile):
+       Check for errors from fclose, CVS_RENAME, and CVS_STAT.
+       * mkmodules.c (checkout_file): Clarify error handling convention.
+       * mkmodules.c (mkmodules): Call checkout_file accordingly.
+       * entries.c (Entries_Open): Check for errors from fclose.
+
+1998-08-21  Ian Lance Taylor  <address@hidden>
+
+       * import.c (import): Output suggested merge command using
+       cvs_output_tagged rather than just cvs_output.  Don't put
+       CVSroot_cmdline in the log file.
+       * client.c (importmergecmd): New static struct.
+       (handle_mt): Handle +importmergecmd tag.
+       * sanity.sh (import): Use an explicit -d in importb-2, to test
+       whether it is reported in the suggested merge command.
+
+1998-08-20  Ian Lance Taylor  <address@hidden>
+
+       * sanity.sh (import): Rewrite tests to use dotest.
+
+1998-08-20  Jim Kingdon  <address@hidden>
+
+       * sanity.sh: Add comments about binary files and cvs import.
+
+1998-08-19  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (importc): Use ${username} in one place where I had
+       missed it.
+
+       Make import -d work client/server:
+       * client.c, client.h (client_process_import_file): Take new
+       argument, for whether -d is specified, and send Checkin-time
+       request if it is set.
+       * import.c (import_descend): Pass it.
+       * main.c, cvs.h (date_to_internet): New function.
+       * server.c (server_modtime): Call date_to_internet.
+       * server.c (serve_checkin_time): New function.
+       (requests): Add "Checkin-time" request.
+       (serve_modified): If it was sent, set the timestamp in the
+       temporary directory.
+       * import.c (import): If the client sends a -d option, complain.
+       (import): For the server, always use the timestamps from the temp
+       directory.
+       (import): Don't send a -d option to the server.
+       * sanity.sh (importc): Add tests for import -d.
+
+Wed Aug 19 15:19:13 1998  Larry Jones  <address@hidden>
+
+       * sanity.sh (unedit-without-baserev-5): use ${DOTSTAR} instead
+       of .* since we expect to match multiple lines.
+
+1998-08-19  Ian Lance Taylor  <address@hidden>
+
+       * cvs.h (CVSroot_cmdline): Declare.
+       * root.c (CVSroot_cmdline): Define.
+       * main.c (main): Set CVSroot_cmdline if the -d option is used.
+       * import.c (import): If CVSroot_cmdline is not NULL, then mention
+       an explicit -d option in the suggested merge command line.
+
+Wed Aug 19 00:28:50 1998  Noel Cragg  <address@hidden>
+
+       * recurse.c (do_dir_proc): don't muck with CVS/Root directories
+       when running in server mode.
+       (do_recursion): same.
+
+       * main.c (main): add the command-line option `m' to help debug the
+       multiroot environment; it prints out the value of CVSROOT for each
+       iteration through the main loop.  Also, changed the main loop so
+       that it gets executed only once when running in server mode (the
+       server will only deal with a single CVSROOT).
+
+       * recurse.c (do_recursion): change default for
+       PROCESS_THIS_DIRECTORY to true; we should always process a
+       directory's contents unless there's an existing CVS/Root file with
+       a different root than the current root to tell us otherwise.
+       (do_dir_proc): same.
+
+Tue Aug 18 14:30:59 1998  Noel Cragg  <address@hidden>
+
+       * recurse.c (do_recursion): check the current value of CVS/Root
+       and add it to our list of CVSROOTs if it doesn't exist.  Decide
+       whether or not to process files in this directory based based on
+       the value of CURRENT_ROOT.
+       (do_dir_proc): same.
+
+       * main.c: add two new globals -- root_directories and current_root
+       -- which keep track of the values of CVSROOT we've seen and which
+       value of CVSROOT we're currently processing.
+       (main): put the main loop for stepping through cvsroot values
+       here, since we might need to send command-specific arguments for
+       every unique non-local cvsroot.  Moved blocks of code around so
+       that one-time initializations happen first (outside the loop) and
+       the other stuff happens inside the loop.
+       (set_root_directory): helper function.
+
+       * cvs.h: add prototypes for root_directories and current_root, two
+       new globals for keeping track of multiple CVSROOT information.
+
+1998-08-18  Jim Kingdon  <address@hidden>
+
+       * sanity.sh: Don't assume that the shell leaves $^ unexpanded in
+       an unquoted here-document (suggested by Bart Schaefer to help when
+       zsh is the shell).
+
+1998-08-17  Ian Lance Taylor  <address@hidden>
+
+       * commit.c (checkaddfile): Don't call fix_rcs_modes.
+       (fix_rcs_modes): Remove.
+
+1998-08-16  Jim Kingdon  <address@hidden>
+
+       * create_adm.c (Create_Admin): Don't condition traces on
+       SERVER_SUPPORT; SERVER_SUPPORT shouldn't do (much of) anything
+       independent of server_active.
+
+       * sanity.sh (binfiles3): New test, for yet another binary file
+       bug (sigh).  Thanks to Jason Aten for reporting this one.
+
+1998-08-15  Jim Kingdon  <address@hidden>
+
+       * rcscmds.c (call_diff_write_output): Update to reflect new
+       calling convention for the write_output callback.
+
+1998-08-15  Jim Meyering  <address@hidden>
+
+       * update.c (merge_file): Warn about failed unlink when not due
+       to ENOENT.
+
+       * server.h (CLIENT_SERVER_STR): New macro
+       * create_adm.c (Create_Admin): Use it.
+       * entries.c (Scratch_Entry, Register): Use it.
+       * filesubr.c (copy_file, xchmod, rename_file, unlink_file): Use it.
+       * history.c (history_write): Use it.
+       * modules.c (do_module): Use it.
+       * no_diff.c (No_Difference): Use it.
+       * run.c (run_popen): Use it.
+       * server.c (server_register): Use it.
+
+1998-08-14  Jim Meyering  <address@hidden>
+
+       * hardlink.c (lookup_file_by_inode): Use existence_error rather than
+       comparing errno to ENOENT directly.
+
+       * client.c (copy_a_file): Unlink destination before doing copy.
+       * sanity.sh (join-readonly-conflict): New test for this -- it would
+       fail only in client/server mode.
+
+       * sanity.sh (rcsmerge-symlink-4): Don't use `test -L', it's not
+       portable.  Instead, match against the output of `ls -l'.
+       (dotest tag8k-16): Simplify tag-construction code and at the same
+       time, avoid using expr's `length' and `substr' operators.  Not
+       all versions of expr support those.
+
+1998-08-14  Jim Kingdon  <address@hidden>
+
+       * version.c: Bump version number to 1.10.0.1.
+
+Thu Aug 13 11:15:24 1998  Noel Cragg  <address@hidden>
+
+       * version.c: Change version number to 1.10 and name to `Halibut'.
+
+       * sanity.sh (rcslib): new tests to check behavior of symlinks in
+       the repository.
+
+Wed Aug 12 15:39:38 1998  Noel Cragg  <address@hidden>
+
+       * main.c (lookup_command_attribute): the `annotate' command
+       shouldn't require access to the repository.  Add comment about
+       commands that do not use the working directory.
+
+Mon Aug 10 10:26:38 1998  Noel Cragg  <address@hidden>
+
+       * version.c: Change version number to 1.9.30.
+
+Thu Aug  6 17:44:50 1998  Noel Cragg  <address@hidden>
+
+       * server.c (serve_rdiff): change the name of the command (for
+       error reporting, etc.) from "patch" to "rdiff."
+       (serve_remove): rename from "cvsremove" to "remove."
+
+       * main.c (lookup_command_attribute): the `rdiff' command shouldn't
+       require write access to the repository.
+
+1998-08-06  David Masterson of kla-tencor.com
+       and Jim Kingdon
+
+       * commit.c (commit_filesdoneproc): Don't call strlen ("CVSROOT")
+       from within the assert statement.  Apparently HP's cc compiler on
+       HPUX 10.20 has trouble with that.
+
+1998-08-06  Jim Kingdon  <address@hidden>
+
+       * rcs.c (RCS_checkin): When adding branch, if there is a lock on
+       the branchpoint owned by someone else, leave it alone.  This
+       restores CVS 1.9 (RCS 5.7) behavior, fixing a core dump.
+       * sanity.sh (reserved): New tests reserved-16 through reserved-19
+       test for this fix.
+
+1998-08-05  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (unedit-without-baserev): Use ${QUESTION} not "?".
+       This makes it work with GNU expr 1.12 as well as 1.16.
+
+Sun Aug  2 20:27:44 1998  Noel Cragg  <address@hidden>
+
+       * mkmodules.c: add comment about TopLevelAdmin for the initial
+       contents of CVSROOT/config.
+
+1998-07-29  Jim Kingdon  <address@hidden>
+
+       * rcs.c (RCS_checkin): Only try to call xreadlink if HAVE_READLINK
+       is defined.
+
+Tue Jul 28 19:33:08 1998  Noel Cragg  <address@hidden>
+
+       * version.c: Change version number to 1.9.29.
+
+       * rcs.c (RCS_checkin): add code to follow symbolic links in the
+       repository.
+
+Sun Jul 26 05:14:41 1998  Noel Cragg  <address@hidden>
+
+       * This set of changes reverts the code to pre-1.9.2 behavior and
+       does not create CVS directories at top-level (except for the
+       obvious "cvs co .").  Added a new configuration option to switch
+       between 1.9 and 1.9.2 behavior.
+
+       * recurse.c (do_argument_proc): new function.
+       (start_recursion): in the case that we've done a command from
+       top-level but have no CVS directory there, the behavior should be
+       the same as "cvs <cmd> dir1 dir2 dir3...".  Make sure that the
+       appropriate "Argument" commands are sent to the server by calling
+       walklist with do_argument_proc.
+
+       * client.c (call_in_directory): only create the top-level CVS
+       directory when we're checking out "." explicitly.  The server will
+       force creation of this directory in all other cases.
+
+       * checkout.c (checkout_proc): only generate the top-level
+       directory when the TopLevelAdmin=yes.  Also send a message to the
+       client to do the same.
+
+       * parseinfo.c (parse_config): handle TopLevelAdmin option.  Set
+       top_level_admin.
+
+       * main.c: add new variable top_level_admin.
+       * cvs.h: add extern definition for above.
+
+       * sanity.sh: since we're reverting to pre 1.9.2 behavior for
+       top-level CVS directories, I needed to make changes to a bunch of
+       tests that made assumptions about said directories.
+       (preamble): make sure to add read and execute access to everything
+       in TMPDIR before removing, since some tests make things read-only.
+       (basicb-1a, basicb-1b, basicb-9a, basicb-9b): use dotest_fail
+       because these tests check for the non-existant top-level CVS
+       directory.
+       (basicc-3, emptydir-6, emptydir-7, crerepos-6): use "rm -rf" so it
+       won't complain when trying to remove the non-existant top-level
+       CVS directory.
+       (106.5): remove imported-f2-orig.tmp.
+       (modules2-10, emptydir-4, abspath-1ba, abspath-1bb): cd into the
+       directory where files exist before using the "add" command so cvs
+       can find CVSROOT in CVS/Root.
+       (cvsadm-2): look at a different CVS/Repository file, since the
+       top-level one doesn't exist.
+       (taginfo-3): create the directory in the repository directly
+       rather than relying on the fact that the top-level CVS directory
+       was created in a previous test.
+       (serverpatch-6): update first-dir explicity, rather than relying
+       on the non-existant top-level CVS/Entries file.
+       (crerepos-18): look at CVS/Repository in a subdirectory rather
+       than in the non-existant top-level CVS directory.
+       (toplevel): add code to set TopLevelAdmin=yes.
+       (toplevel2): new tests -- same as toplevel, but TopLevelAdmin=no.
+
+1998-07-21  Jim Meyering  <address@hidden>
+
+       * rcs.c (RCS_checkout): Hoist frees of rev and value.
+       Warn and return 1 in several cases rather than exiting via
+       `error (1, ...'.  The latter could abort a multi-file commit
+       in mid-stream, leaving stale locks in the repository.
+
+1998-07-16  Jim Kingdon  <address@hidden>
+
+       * build_src.com (rcscmds.c): Also look for include files in
+       [-.diff], just like Ian's 1998-06-18 change to Makefile.in
+
+1998-07-14  Jim Kingdon  <address@hidden>
+
+       * tag.c (pretag_proc), rtag.c (pretag_proc): Don't pass RUN_REALLY
+       to run_exec.  This means that taginfo does not get executed if the
+       global -n option is specified.  Which makes it like loginfo, -i,
+       -e, -o, -t, -u in modules, editinfo, and verifymsg and unlike
+       commitinfo.  The old behavior was pretty bad in the sense that it
+       doesn't provide any way to log only the tags which actually
+       happen.
+       * sanity.sh (taginfo): New tests taginfo-11 to taginfo-13, for this.
+
+1998-07-12  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (ann-id): Write the test so that it tests for the
+       current (buggy) behavior.
+
+       * sanity.sh (taginfo): Also clean up cvsroot/first-dir.
+
+1998-07-12  Jim Meyering  <address@hidden>
+
+       * sanity.sh (ann-id): New (currently failing) test for bug in how
+       rcs keywords are expanded in the output of `cvs annotate'.
+
+1998-07-12  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (taginfo): Write the TESTDIR into the script rather
+       than having the script look at the environment.  This means that
+       it will work if TESTDIR is set by sanity.sh as well as if
+       sanity.sh finds TESTDIR in the environment.
+
+1998-07-11  Jim Kingdon  <address@hidden>
+
+       * tag.c (check_fileproc): Calculate the revision to be tagged the
+       same way that tag_fileproc does.
+       * sanity.sh (taginfo): New tests, test for this (before this fix,
+       brtag had said 1.1 not 1.1.2.1).
+
+1998-07-10  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (unedit-without-baserev): Also clean up "2" directory.
+
+1998-07-08  Jim Kingdon  <address@hidden>
+
+       * edit.c (unedit_fileproc): If the Baserev file is missing, don't
+       get the working file from CVS/Base.  The previous code could get
+       you version 1.1 of the working file and put 1.2 in CVS/Entries.
+       * sanity.sh (unedit-without-baserev): New tests test for this.
+
+1998-07-02  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (unedit-without-baserev): Move the test itself to be
+       in the same order as in the "tests" variable.
+
+1998-07-02  Ian Lance Taylor  <address@hidden>
+
+       * rcscmds.c: Don't include <stdarg.h> or <vasprintf.h>.  Don't
+       declare vasprintf.
+       (call_diff_printf_output): Remove.
+       (call_diff_stdout_callbacks): Don't initialize printf_output
+       field--it has been removed from the interface.
+       (call_diff_file_callbacks): Likewise.
+
+1998-07-01  Jim Meyering  <address@hidden>
+
+       * edit.c (unedit_fileproc): Handle the case in which base_get
+       returns a NULL baserev.  That happens when a file being `unedit'ed
+       exists in the CVS/Base directory, but isn't listed in the CVS/Baserev
+       file.  The one case I've seen had no Baserev file at all.  The symptom
+       (if you're lucky) is a segmentation fault upon unedit.  If you use
+       SunOS4.1.4 for which printf prints NULL pointers as `(null)', your
+       unedit command will complete normally, but it will have corrupted
+       your CVS/Entries file and a subsequent update may result in an
+       assertion failure, a core dump, and a stale lock in the repository.
+       * sanity.sh (unedit-without-baserev): New test for this.
+
+1998-07-01  Andy Mortimer of aeat.co.uk
+       and Jim Kingdon  <address@hidden>
+
+       * server.c (server_updated): Use a prototype if we are using them
+       for declarations.
+
+1998-06-29  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (commit-readonly): Protect keyword against expansion
+       in sanity.sh itself.  Keep the keyword in the file which we check
+       in (or else this fails to test for the RCS_checkout change).
+
+1998-06-27  Jim Meyering  <address@hidden>
+
+       * rcs.c (RCS_checkout): If opening the local workfile fails due to
+       lack of write access, try to chmod the file and retry the open.
+       Before, a commit could fail part way through merely because the
+       open to rewrite with newly expanded rcs keywords would fail.  It's
+       easy to make this happen if you use `cvs -r' or CVSREAD and you
+       apply a patch to one of your read-only source files -- patch
+       preserves the read-only setting for the file and your next commit
+       will fail after committing that file, but before rewriting
+       (checking out) your working copy.
+       * sanity.sh (commit-readonly): New test for this.
+
+1998-06-25  Jim Kingdon  <address@hidden>
+
+       * update.c (patch_file): Update comments regarding context diffs
+       to reflect diff library.
+
+1998-06-23  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (modules4): Add tests for reversing the order of the
+       "!first-dir/sdir" and "first-dir".
+
+1998-06-23  Jim Kingdon  <address@hidden>
+       and Dave address@hidden
+
+       * sanity.sh (modes2): Touch the file before chmod'ing it.
+
+1998-06-21  Ian Lance Taylor  <address@hidden>
+
+       * update.c (merge_files): Revert changes of 1998-06-19.  Instead,
+       register a merged file with a dummy time stamp.  Only set
+       last_register_time if we need to.
+       (join_file): Likewise.  Always register a merged file, not just
+       when the merge fails.
+
+1998-06-21  Jim Kingdon  <address@hidden>
+
+       * call_diff_write_output, call_diff_printf_output,
+       call_diff_flush_output, call_diff_write_stdout, call_diff_error,
+       call_diff_stdout_callbacks, call_diff_file_callbacks): Re-indent.
+
+1998-06-19  Ian Lance Taylor  <address@hidden>
+
+       * update.c (merge_file): Make sure the time stamp of the file is
+       different from the time stamp we register in the Entries file.
+       (join_file): Likewise.
+
+1998-06-18  Ian Lance Taylor  <address@hidden>
+
+       * rcscmds.c: Include <stdio.h>.  Include either <stdarg.h> or
+       <varargs.h>.  Declare vasprintf.
+       (call_diff_write_output): New static function.
+       (call_diff_printf_output): New static function.
+       (call_diff_flush_output): New static function.
+       (call_diff_write_stdout): New static function.
+       (call_diff_error): New static function.
+       (call_diff_stdout_callbacks): New static variable.
+       (call_diff_file_callbacks): New static variable.
+       (call_diff): Don't sleep.  Use a callback structure when calling
+       the diff library.
+       (call_diff3): Likewise.
+
+       * rcscmds.c: Include diffrun.h.
+       (call_diff, call_diff3): Pass NULL callback parameter.
+       (diff_run, diff3_run): Don't declare.
+       * Makefile.in (rcscmds.o): New target, to use -I for diff
+       directory.
+       (zlib.o): Depend upon zlib.h.
+
+1998-06-09  Mike address@hidden
+
+       Make it compile with Sun's bundled K&R C compiler:
+       * rcs.c (count_delta_actions): Change to static to match
+       declaration.
+       * client.c (handle_wrapper_rcs_option): Rename error label to
+       handle_error to avoid clash with function name.
+
+1998-06-09  Jim Kingdon  <address@hidden>
+
+       * rcs.c (RCS_delete_revs): If we are trying to delete all
+       revisions, give an error rather than assertion failed.
+       * sanity.sh (basicb): New tests basicb-o* test for this.
+
+1998-06-04  Jim Kingdon  <address@hidden>
+
+       * add.c (add): Only send "Directory" requests if we need to.
+
+1998-06-02  Assar Westerlund  <address@hidden>
+
+       * client.c: Check for HAVE_GSS_C_NT_HOSTBASED_SERVICE rather than
+       assuming that GSS_C_NT_HOSTBASED_SERVICE is a macro.
+       * server.c: Likewise.
+
+1998-06-02  Jim Kingdon  <address@hidden>
+
+       * fileattr.c (fileattr_read): Check for NULL return from strchr.
+       * sanity.sh (devcom3): New test devcom3-10 checks for this.
+
+1998-06-01  Assar Westerlund  <address@hidden>
+       and Ian Lance Taylor  <address@hidden>
+
+       * client.c: If HAVE_GSSAPI_H, include <gssapi.h>.  Only include
+       <gssapi/gssapi.h> if HAVE_GSSAPI_GSSAPI_H.  Only include
+       <gssapi/gssapi_generic.h> if HAVE_GSSAPI_GSSAPI_GENERIC_H.
+       (GSS_C_NT_HOSTBASED_SERVICE): Define if not defined.
+       (connect_to_gserver): Use GSS_C_NT_HOSTBASED_SERVICE instead of
+       gss_nt_service_name.
+       * server.c: Same header file changes.
+       (GSS_C_NT_HOSTBASED_SERVICE): Define if not defined.
+       (gserver_authenticate_connection): Use GSS_C_NT_HOSTBASED_SERVICE
+       instead of gss_nt_service_name.
+
+1998-06-01  Jim Meyering  <address@hidden>
+
+       * sanity.sh (tag8k): Add a test for the 1998-05-02 rcs.c bug fix.
+
+1998-05-26  Jim Kingdon  <address@hidden>
+
+       * rcs.c (annotate): Call tag_check_valid like the other functions
+       which have a -r option.
+       * sanity.sh (ann): New test ann-14 tests for this.
+
+1998-05-24  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (importc): New tests importc-5 through importc-8 test
+       for a (fairly obscure) regression from CVS 1.9.
+
+1998-05-23  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (modules2): Add comment listing cvs release tests.
+       (info): New test info-cleanup-0 tests "cvs -n release".
+
+       * rcs.c (rcsbuf_getid): Remove semicolon at end of #undef.  I'm
+       kind of surprised that compilers accepted this at all, but
+       removing it squelches a warning for some compilers.
+
+       * version.c: Change version number to 1.9.28.1.
+
+       * Version 1.9.28.
+
+1998-05-22  Jim Kingdon  <address@hidden>
+
+       * rcs.c (RCS_cmp_file): Check for errors from CVS_FOPEN.  This
+       restores the CVS 1.9 behavior (fatal error if we can't open the
+       file), and corrects an apparent oversight in Ian's 13 Apr 1997
+       change.
+       * sanity.sh (modes2): New test, tests for this.
+
+1998-05-22  Ian Lance Taylor  <address@hidden>
+
+        * server.c (server_updated): Correct test for whether to unlink
+        the file.
+
+1998-05-20  Jim Kingdon  <address@hidden>
+
+       * wrapper.c (wrap_add): Disable -t/-f wrappers at least until the
+       serious bug can be fixed.
+
+1998-05-15  Jim Kingdon  <address@hidden>
+
+       * checkout.c (checkout): Call server_pathname_check on the
+       argument to "cvs co -d".
+       * server.c (server_pathname_check): Add comment about how we could
+       be handling absolute pathnames.
+       * sanity.sh (abspath): Rewrite the tests which run "cvs co -d /foo"
+       for remote, to reflect this.
+
+       * sanity.sh (abspath): Also do the "cannot rename" work-around for
+       abspath-7d.
+
+1998-05-13  Jim Kingdon  <address@hidden>
+
+       * commit.c (commit_filesdoneproc): Free admin_dir when done with it.
+
+1998-05-13  Jim Meyering  <address@hidden>
+
+       * sanity.sh (editor): Change bogus sed command, `s/^/x&/g', to `s/^/x/'.
+       The former exercised a bug in GNU sed-3.01-beta3.
+       (emptydir-8): Add `Rebuilding administrative file database' message,
+       since now it does that.
+       * commit.c (commit_filesdoneproc): Pass only the admin directory
+       pathname to mkmodules.
+       Remove #if 0, now that it's fixed.
+
+       * status.c (cvsstatus): Rename from `status' to avoid shadowing
+       lots of locals and parameters by the same name.
+       * server.c (serve_status): Update caller.
+       * main.c (cmds[]): Update table entry.
+       * cvs.h: Update prototype.
+
+       * commit.c (commit_filesdoneproc): Remove trailing blanks.
+       (commit) [CLIENT_SUPPORT]: Remove unnecessary (and local-shadowing)
+       declaration of `err'.
+       Rename global `tag' to `saved_tag' to avoid overshadowing `tag'
+       parameters of three functions.
+       Rename global `message' to `saved_message' to avoid overshadowing
+       `message' parameter of a function.
+       Rename global `ulist' to `saved_ulist' and move dcl up with others.
+
+1998-05-12  Jim Kingdon  <address@hidden>
+
+       * commit.c (commit_filesdoneproc): #if 0 the new code until it can
+       be fixed.
+
+       * commit.c (commit_filesdoneproc): Add comment explaining last
+       change.
+
+1998-05-12  Jim Meyering  <address@hidden>
+
+       * commit.c (commit_filesdoneproc): Call mkmodules not just when
+       committing a file directly under CVSROOT, but also when committing
+       files in subdirectories of CVSROOT.
+
+1998-05-08  Jim Meyering  <address@hidden>
+
+       * filesubr.c (xreadlink):  NUL-terminate the symbolic link name.
+       Use a much smaller initial buffer length.
+       Test errno only if readlink fails.
+       Use xstrdup then free the original link name so we don't waste space.
+
+1998-05-02  Jim Meyering  <address@hidden>
+
+       * rcs.c (rcsbuf_getword): Fix off-by-one error that would result in
+       an abort (the first one in rcsbuf_getkey) when operating on on some
+       ,v files with over 8192 bytes of tag and branch info.
+
+1998-05-04  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (ann): New tests ann-12 and ann-13 test for specifying
+       a numeric branch.
+
+1998-05-02  Jim Kingdon  <address@hidden>
+
+       * rcs.c: Add comments about getting rid of rcsbuf_getid,
+       rcsbuf_getword, and rcsbuf_getstring.
+
+       * sanity.sh (abspath): Revise the workarounds to deal with exit
+       status.
+
+1998-04-30  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (abspath): Work around the "cannot rename" bug.
+
+1998-04-27  Jim Kingdon  <address@hidden>
+
+       * classify.c (Classify_File): Add comments about checking whether
+       command name is "update".
+
+1998-04-22  Jim Kingdon  <address@hidden>
+
+       * version.c: Change version number to 1.9.27.1.
+
+       * Version 1.9.27.
+
+1998-04-20  Jim Kingdon  <address@hidden>
+
+       (This diff was run by devel-cvs and everyone seemed to like it).
+       * diff.c (diff_file_nodiff): Make HEAD mean the head of the branch
+       which contains the sticky tag, not the sticky tag itself.
+       * rcs.c, rcs.h (RCS_branch_head): New function.
+       * sanity.sh (head): Update for this changed behavior.
+
+1998-04-19  Jim Kingdon  <address@hidden>
+
+       * sanity.sh: Move emptydir tests from basicb to new test emptydir.
+       This is because we now need a module definition to create Emptydir;
+       "co -d" doesn't cut it anymore.
+
+1998-04-17  Petri Virkkula
+
+       * server.c (mkdir_p): Ignore EROFS error (like for EACCES).
+
+1998-04-16  Jim Kingdon  <address@hidden>
+
+       * checkout.c (checkout_proc): Don't create directories above the
+       last one specified in "co -d".
+       (build_dirs_and_chdir): Revert Noel's change of 17 Feb 1998.
+       (struct dir_to_build): New field just_chdir.
+       (build_dirs_and_chdir): Test it.
+       * sanity.sh (abspath): New tests abspath-7* test for a bug which
+       we fix, in which CVS would create bogus "D/////" entries in
+       CVS/Entries.
+       (abspath): Revise abspath-3* tests to test for the fact that we no
+       longer create directories above the last one specified in "co -d".
+       I checked that CVS 1.9 gives an error on this, so changing this
+       behavior back should be OK.
+       (cvsadm-2d3): Likewise (also checked CVS 1.9 for this case).
+       (cvsadm-2d3d): Likewise (also checked CVS 1.9 for this case).
+       (cvsadm-2d{4,5,6,7,8}, cvsadm-N2d{3,4,5,6,7,8}): Adjust for new
+       behavior (same case as cvsadm-2d3).
+       (cvsadm-2d{4,5,6,7,8}d, cvsadm-N2d{3,4,5,6,7,8}d): Remove test
+       (same case as cvsadm-2d3d).
+       (cvsadm): For remote, skip most these tests.
+       (abspath): When cleaning up, delete mod1 and mod2 rather than mod1
+       twice (longstanding bug, apparently only becomes visible if you
+       run the tests in a certain order).
+
+1998-04-14  Wilfredo Sanchez  <address@hidden>
+
+       * rcs.c: variable "lockfile" was being referenced after being
+       free'd.  Bad.  Moved the free() call down.
+
+1998-04-12  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (rcs): Add test for annotate and the year 2000.
+
+       * server.c (do_cvs_command): If there are partial lines left when
+       the child process is done, send them along.
+       * sanity.sh (rcs, rcs2): Enable all tests for remote; tests for
+       this fix.
+
+1998-04-11  Jim Kingdon  <address@hidden>
+
+       * client.c (client_senddate): Pass SDATEFORM not DATEFORM to
+       sscanf.  This fixes a Y2K bug.
+
+       * history.c (history, select_hrec): Change since_date from time_t
+       to RCS format.  Use the usual machinery (in particular, Make_Date
+       and client_senddate) so that it will work on VMS too.
+       * main.c, cvs.h (date_from_time_t): New function.
+       * sanity.sh (history): New test, to test that this didn't break
+       anything (also tests client_senddate fix).
+
+1998-04-11  Norbert Kiesel  <address@hidden>
+
+       * server.c (cvs_output_binary): Shut up "gcc -Wall" by removing
+       unnecessary else if test.
+       * server.c (check_password): Fix uninitialized memory read if
+       shadow passwords are used.  Also added some comments.
+       * rcs.c (RCS_checkout): Make sure to call chown with -1 for uid or
+       gid if they should not be changed
+
+1998-04-10  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (rcs2): New test, tests for various Y2K cases.
+       * rcs.c (getdelta): Value for "state" keyword is optional (bug
+       discovered incidentally in writing rcs2 test).
+
+1998-04-09  Jim Kingdon  <address@hidden>
+
+       * filesubr.c, cvs.h (link_file): Remove; no longer used.
+
+1998-04-08  Jim Kingdon  <address@hidden>
+
+       * recurse.c (do_dir_proc): Restore update_dir rather than a
+       computation which appears to, but does not necessarily, restore it
+       (reported by various people; this fix is from Greg Hudson).
+       * sanity.sh (importc): New test, tests for this fix.
+
+1998-03-27  Jim Kingdon  <address@hidden>
+
+       * rcs.c (RCS_lock): If the revision is already locked, give an
+       error rather than dumping core.
+       * sanity.sh (reserved): New test reserved-13c tests for this.
+
+1998-03-25  Loren J. Rittle
+
+       * import.c (add_rev): Rewrite to use RCS_FLAGS_KEEPFILE option
+       of RCS_checkin() to avoid damage to imported files instead of
+       externally undoing damage after the fact.  The side effect is
+       that callers of add_rev() may now incrementally walk the
+       entries of the current directory without seeing gratuitous
+       changes to the directory structure (under at least one file
+       system under at least one OS).
+
+1998-03-18  Jim Kingdon  <address@hidden>
+
+       * error.c (error): Save and restore errno.  Should fix test case
+       conflicts3-23 on SCO 5.0.2.  Reported by Steve Cameron.
+
+       * sanity.sh (admin): Rename admin-26-o* to admin-26-*; the "o"
+       stands for "cvs admin -o".  Add comment about length of tests.
+       Use ${PLUS}.
+
+1998-03-05  Dan Wilder <address@hidden>
+
+       * Fix problem with cvs admin in which -ntag:branch
+       option associated tag with the branch's head revision.
+       Should have used branch number.  Entailed in this fix,
+       the following.
+
+       * Add new functions "RCS_exist_rev", "RCS_exist_tag",
+       "RCS_tag2rev", and "RCS_valid_rev" to rcs.c.  RCS_tag2rev
+       is similar to RCS_gettag, but does less interpretation.
+
+       * Plug a small memory leak.
+
+       * Add tests admin-26 through admin-29 to sanity.sh,
+       to test "cvs admin -n".
+
+1998-03-17  Samuel Tardieu  <address@hidden>
+
+       * server.c (server_register): protect dereferencing timestamp in
+       the trace message when it is null, to avoid a segmentation fault.
+
+1998-03-16  Jim Kingdon  <address@hidden>
+
+       * options.h.in (MY_NDBM): Rewrite the comment explaining this
+       option.  It was not clear to everyone who "my" referred to, for
+       example.
+
+       * hardlink.c (list_linked_files_on_disk): Remove unused variables
+       err and p.
+       (list_linked_files_on_disk): Add comment about memory allocation
+       of return value.
+       * rcs.c (rcsbuf_getword): Shut up gcc -Wall with a "return 0".
+       (RCS_checkin): Remove unused variable fullpath.
+       * sanity.sh (hardlinks): Remove comment about spurious warnings;
+       the warnings are gone.
+
+1998-03-12  Tim Pierce  <address@hidden>
+
+       New functions for parsing and writing hardlink fields.
+       * rcs.c [PRESERVE_PERMISSIONS_SUPPORT] (puthardlink_proc): New
+       function.
+       (putdelta) [PRESERVE_PERMISSIONS_SUPPORT]: Use it.
+       (rcsbuf_getid, rcsbuf_getstring, rcsbuf_getword): New functions.
+       (getdelta): Call them, storing `hardlinks' field in vnode->hardlinks.
+       (RCS_reparsercsfile): When setting rdata->desc, xstrdup value
+       rather than rcsbuf_valcopying it (due to changes in how getdelta
+       handles keys and values in newphrases).
+
+       * sanity.sh (hardlinks): Use uglier filenames.  Checking out
+       hardlinked files no longer produces the same spurious diagnostics,
+       so fix that test.
+       (hardlinks-2.3): Renamed from hardlinks-2.2 (duplicate test name).
+
+       New infrastructure for managing hardlink lists internally...
+       * hardlink.c, hardlink.h (list_linked_files_on_disk,
+       compare_linkage_lists, find_checkedout_proc): New functions.
+       * rcs.h (struct rcsversnode) [PRESERVE_PERMISSIONS_SUPPORT]: New
+       member `hardlinks'.
+       * update.c (special_file_mismatch): Get hardlinks from
+       vp->hardlinks instead of from vp->other_delta.
+       * rcs.c (free_rcsvers_contents): Comment about freeing hardlinks
+       member.
+       (RCS_checkout) [PRESERVE_PERMISSIONS_SUPPORT]: Get hardlinks from
+       vers->hardlinks list instead of vers->other_delta.
+
+       ... and removed obsolete code from earlier revs.
+       * hardlink.c, hardlink.h (list_files_linked_to,
+       cache_hardlinks_proc, list_files_proc, set_hardlink_field_proc):
+       Removed.
+       * hardlink.h: Removed `links' member from hardlink_info struct.
+       * commit.c (commit): Remove the call to cache_hardlinks_proc.
+       (check_fileproc) [PRESERVE_PERMISSIONS_SUPPORT]: Removed reference
+       to hlinfo->links.
+       * hardlink.c (update_hardlink_info): Same.
+       * update.c (get_linkinfo_proc): Same.
+
+       * rcs.c (RCS_checkout) [PRESERVE_PERMISSIONS_SUPPORT]: Use
+       vp->hardlinks and find_checkedout_proc to find recently-updated
+       files that may be hardlinked.
+       * update.c (special_file_mismatch): Use List * structures and
+       compare_linkage_lists for rev1_hardlinks and rev2_hardlinks.
+
+1998-03-16  Larry Jones  <address@hidden>
+
+       * server.c (check_password): If shadow passwords are supported but no
+       entry is found in the shadow file, check the regular password file.
+
+1998-03-07  Jim Kingdon  <address@hidden>
+
+       * sanity.sh: Rename permissions test to perms since that is what
+       each of its individual tests are named.
+       * sanity.sh (perms symlinks hardlinks): Change CVSROOT to
+       CVSROOT_DIRNAME where appropriate.
+       (perms symlinks hardlinks): Disable/adjust the meat of the tests for
+       remote.
+       (symlinks): Link to ${TESTDIR}/fumble rather than
+       /fumble/mumble/grumble.  We shouldn't be making assumptions about
+       what might exist in random directories outside ${TESTDIR}.
+       * hardlink.c (cache_hardlinks_proc): Add comment about trimming
+       whitespace.
+
+1998-03-07  Tim Pierce  <address@hidden>
+
+       * rcs.c (RCS_checkout): Negation bug when checking out symlinks:
+       existence_error should be !existence_error.
+       * sanity.sh (permissions symlinks hardlinks): New tests, for
+       PreservePermissions.
+
+1998-03-04  Jim Kingdon  <address@hidden>
+
+       * version.c: Change version number to 1.9.26.1.
+
+       * Version 1.9.26.
+
+       * entries.c, cvs.h (Entries_Open): New argument update_dir; use it
+       in error message.
+       * add.c, checkout.c, client.c, find_names.c, import.c, recurse.c,
+       update.c: Pass it (as NULL except in call_in_directory).
+       * entries.c (Subdirs_Known): Just return if there is no CVSADM
+       directory (as in subdir_record).
+       * sanity.sh (conflicts3): New tests conflicts3-20a and
+       conflicts3-23 test for these fixes.
+
+       * commit.c (commit): Only set up hardlist if preserve_perms.
+
+       * commit.c, import.c, no_diff.c, parseinfo.c, rcs.c, rcscmds.c,
+       update.c: Omit the preserve_perms code if
+       PRESERVE_PERMISSIONS_SUPPORT is not defined.  Much of that code
+       won't even compile on non-unix systems.
+
+       * hardlink.c, hardlink.h: Use the 'standard' copyright (as found
+       in server.c).
+       * commit.c, rcs.c: Minor whitespace changes to Tim's submission.
+       * commit.c (check_fileproc), update.c (get_linkinfo_proc): Remove
+       unused variable delta.
+       * hardlink.c (set_hardlink_field_proc), update.c
+       (get_linkinfo_proc): Return a value rather than falling off the
+       end of the function.
+
+1998-03-02  Tim Pierce  <address@hidden>
+
+       * update.c (special_file_mismatch): Compare the hard links of the
+       two revisions.
+
+       * rcs.c (RCS_checkout):
+
+       * hardlink.c, hardlink.h: New files.
+       (hardlink_info): New struct.
+       (hardlist, working_dir): New variables.
+       (list_files_proc, cache_hardlinks_proc, set_hardlink_field_proc,
+       lookup_file_by_inode, update_hardlink_info, list_files_linked_to):
+       New functions.
+
+       * Makefile.in (SOURCES): Add hardlink.c.
+       (OBJECTS): Add hardlink.o.
+       (HEADERS): Add hardlink.h.
+       * commit.c: Include hardlink.h.
+       (commit): Save the working directory before recursing.  Walk the
+       hardlink list, calling set_hardlink_field_proc on each node.
+       (check_fileproc): Add each file's link information to hardlist.
+       * rcs.c: Include hardlink.h.
+       (RCS_checkin): Save list of hardlinks in delta node.
+       (RCS_checkout): Look up the file's `hardlinks' delta field, and
+       see if any of the files linked to it have been checked out
+       already.  Link to one of those files if so.
+       * update.c: Include hardlink.h.
+       (get_linkinfo_proc): New function.
+       (do_update): Extra recursion to collect hardlink info.
+       (special_file_mismatch): Reparse the RCS file if necessary.
+
+       fsortcmp is now used by several files, so let's make it extern.
+       * hash.c, hash.h (fsortcmp): New function.
+       * find_names.c (fsortcmp): Removed.
+       * lock.c (fsortcmp): Removed.
+
+1998-03-03  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (conflicts3): New tests conflicts3-14a,
+       conflicts3-14b, and conflicts3-21, conflicts3-22 test that we can
+       skip over a working directory with a CVSADM directory missing.
+
+1998-02-26  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (conflicts3): Tests conflicts3-16 and conflicts3-20
+       test that we include update_dir in messages.  Rename test
+       conflicts3-14 to fix typo.
+
+Sun Feb 22 23:14:25 1998  Steve Cameron  <address@hidden>
+       and Ian Lance Taylor  <address@hidden>
+
+       * update.c (tag_update_dir): New static variable.
+       (update_dirent_proc): If no tag or date were specified when
+       creating a subdirectory, use the tag and/or date of the parent
+       directory.
+       (update_dirleave_proc): If we set the tag and/or date in
+       update_dirent_proc, reset them when we leave the directory.
+       * sanity.sh (branches2): New set of tests for above patch, and
+       related behaviour.
+
+Sun Feb 22 13:31:51 1998  Ian Lance Taylor  <address@hidden>
+
+       * commit.c (lock_RCS): Don't call RCS_rewrite.
+
+       * update.c (patch_file): If the revision is dead, let
+       checkout_file handle it.
+       * sanity.sh (death2): Add test for above patch: add
+       death2-10a, death2-10b, death2-13a, and adjust
+       death2-{2,4,5,11,14,diff-11,diff-12,19}.
+
+       * cvs.h (RCS_FLAGS_KEEPFILE): Define.
+       * rcs.c (RCS_checkin): If RCS_FLAGS_KEEPFILE is set in the flags
+       parameter, don't unlink the working file.
+       * checkin.c (Checkin): Don't copy the file.  Instead pass
+       RCS_FLAGS_KEEPFILE to RCS_checkin, and only check the file out
+       again if it has changed.
+
+1998-02-21  Jim Kingdon  <address@hidden>
+
+       * rcs.c (rcs_internal_unlockfile, RCS_rewrite): Don't assume errno
+       means anything just because ferror is set.
+
+Sat Feb 21 20:02:24 1998  Ian Lance Taylor  <address@hidden>
+
+       * Makefile.in (clean): Change "/bin/rm" to "rm".
+
+       * buffer.c (buf_append_buffer): Correct typo in comment.
+       * rcs.c (RCS_putadmin): Likewise.
+
+Fri Feb 20 17:53:06 1998  Ian Lance Taylor  <address@hidden>
+
+       * rcs.c (rcs_internal_unlockfile): Pass errno when calling error
+       because ferror is true.
+
+1998-02-20  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (abspath): Don't assume that we can't write to /; this
+       is the kind of thing that is sure to break sooner or later
+       (especially on Windows).
+
+       * sanity.sh: Add summary of which modules tests are which (at
+       "modules").  Move cvsadm, abspath, and toplevel next to modules.
+       Add comments to clarify the structure (such as it is).
+
+Fri Feb 20 12:47:14 1998  Larry Jones  <address@hidden>
+
+       * admin.c (admin_fileproc): Better fix for -b.
+
+       * rcs.c (RCS_whatbranch): Back out previous change.
+       (RCS_getversion): Ditto.
+       (RCS_setbranch): Treat an empty revision string like a null pointer.
+
+1998-02-18  Jim Kingdon  <address@hidden>
+
+       * rcs.c (RCS_whatbranch): Fix indentation.
+
+       * patch.c (patch_fileproc): Check for errors from fclose; check
+       for errors from fopen properly.
+
+Wed Feb 18 16:03:37 1998  Larry Jones  <address@hidden>
+
+       * admin.c (admin_fileproc): Convert -b argument from symbolic name
+       to revision number before storing in the RCS file.
+       * rcs.c (RCS_whatbranch): Allow numeric as well as symbolic revision.
+       (RCS_getversion): Take advantage of above.
+       * sanity.sh (admin): Add/revise/renumber admin-10c, admin-11a,
+       admin-12, and admin-12a to check above.
+
+       * commmit.c (lock_RCS): Minor clean-up.
+
+       * sanity.sh (abspath-6a): Don't depend on the sepcific contents of
+       CVSROOT, it depends on which other tests have been run.
+
+Wed Feb 18 01:56:04 1998  Ian Lance Taylor  <address@hidden>
+
+       * rcs.c (putsymbol_proc): Use putc and fputs rather than fprintf.
+       (RCS_putadmin): Don't call RCS_symbols if the symbols have not yet
+       been converted to a list.
+
+       * rcs.c (rcsbuf_cache, rcsbuf_cache_open, rcsbuf_cache_close): New
+       static functions to avoid closing and reopening the RCS file.
+       (cached_rcs, cached_rcsbuf): New static variables.
+       (RCS_parse): Call rcsbuf_cache_close.  Don't call fclose.
+       (RCS_parsercsfile): Likewise.
+       (RCS_parsercsfile_i): Call rcsbuf_cache rather than
+       rcsbuf_close.  Call fclose on error.  Remove comment about
+       inefficiency of opening file twice.
+       (RCS_reparsercsfile): Call rcsbuf_cache_open rather than fopen and
+       rcsbuf_open.  Call rcsbuf_cache rather than rcsbuf_close and
+       fclose.
+       (RCS_fully_parse, RCS_checkout, RCS_deltas): Likewise.
+       (RCS_rewrite): Likewise.
+       (RCS_checkin): Call rcsbuf_cache_close.
+
+       * rcs.c (RCS_copydeltas): Fix code which checks for an extra
+       newline in buffered data.
+
+       * rcs.c (rcsbuf_getkey): Save an indirection by using start rather
+       than *valp when trimming trailing whitespace from value.
+
+       * rcs.c (rcsbuf_get_buffered): New static function.
+       (RCS_copydeltas): After we have done all the required special
+       actions, and inserted any new revision, just copy the file bytes
+       directly, rather than interpreting all the data.
+       (count_delta_actions): New static function.
+       * sanity.sh (rcs): Add rcs-6a and rcs-6b to commit a new branch
+       revision, to force CVS to interpret all the data, rather than just
+       copying it.  Adjust rcs-5 to add a branch tag.  Adjust rcs-8a and
+       rcs-14 for the changes created by rcs-6b.
+
+Tue Feb 17 18:34:01 1998  Ian Lance Taylor  <address@hidden>
+
+       * sanity.sh (cvsadm, diffmerge2): Remove directories at the end of
+       the test.
+
+       * import.c (expand_at_signs): Rewrite to use memchr and fwrite
+       rather than putc.
+
+       Rewrite RCS file reading routines for speed:
+       * rcs.c (struct rcsbuffer): Define.
+       (rcsbuf_open, rcsbuf_close, rcsbuf_getkey, rcsbuf_getrevnum,
+       rcsbuf_fill, rcsbuf_valcopy, rcsbuf_valpolish,
+       rcsbuf_valpolish_internal, rcsbuf_ftell): New static functions.
+       (getrcskey, getrcsrev, getrevnum): Remove.
+       (many functions): Change to use new rcsbuf functions instead of
+       old getrcskey/getrcsrev/getrevnum functions.
+       (RCS_reparsercsfile): Add rcsbufp parameter.  Change all callers.
+       (RCS_deltas): Add rcsbuf parameter.  Change all callers.
+       (getdelta): Change fp parameter to rcsbuf parameter.  Change all
+       callers.
+       (RCS_getdeltatext): Add rcsbuf parameter.  Change all callers.
+       (RCS_copydeltas): Add rcsbufin parameter.  Change all callers.
+       * rcs.h (RCS_reparsercsfile): Update declaration.
+       * admin.c (admin_fileproc): Update calls to RCS_reparsercsfile for
+       new parameters.
+
+1998-02-17  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (toplevel): Also clean up second-dir (not a new
+       bug, but triggered by running tests as "toplevel abspath").
+
+       * create_adm.c (Create_Admin): Just print update_dir to tell the
+       user where we are; not the whole xgetwd.  Cleaner than
+       Noel's change (which also had problems in errno handling).
+       * sanity.sh (toplevel-12): Update accordingly.
+
+Tue Feb 17 02:32:21 1998  Noel Cragg  <address@hidden>
+
+       [These mods make "checkout" work with "-d /absolute/pathname"
+       once again.]
+
+       * checkout.c (checkout_proc): the -d flag on the command line
+       should override the -d flag in the modules file if the latter is
+       an absolute path.  The loop that assembles the list of directories
+       to build has been reorganized slightly to prepare for rewriting
+       with last_component rather than assuming '/' as a path separator.
+       Also added to that loop was some code to handle absolute
+       pathnames.
+       (build_dirs_and_chdir): add a new argument that tells this routine
+       whether or not to check before it creates and populates
+       directories or not.
+
+       * filesubr.c (last_component): return the top-level directory when
+       asked about the top-level directory.
+
+       * sanity.sh (toplevel-12): change test to reflect the new style of
+       this error message.
+
+       * create_adm.c (Create_Admin): include the directory in the error
+       message.
+
+1998-02-16  Jim Kingdon  <address@hidden>
+
+       * diff.c (diff_fileproc), import.c (import, add_rcs_file), rcs.c
+       (RCS_cmp_file): Don't ignore errors from CVS_UNLINK and fclose.
+
+       * patch.c (patch_fileproc): Check for errors from fclose; if we
+       get -1 from getline check for end of file vs. error.
+
+       * rcs.c (RCS_checkout): Comment return value (0/1, not -1).
+       * commit.c, diff.c, mkmodules.c, patch.c, rcs.c, update.c: Update
+       to match this convention.  Don't suppress errors based on
+       quiet or really_quiet variables.
+
+       Fix a longstanding bug which also makes stamps-8kw in make
+       remotecheck work again (it stopped working with Ian's 8 Feb 98
+       checkin):
+       * client.c, client.h (change_mode): If new argument respect_umask
+       is set, then honor the umask.
+       * client.c, server.c: Update callers.
+
+       Cleanups to Tim's checkin:
+       * rcs.c (RCS_checkout): Use existence_error not ENOENT.
+       * commit.c (checkaddfile): Remove comment about whether we want to
+       check for errors from fclose; there is no reason not to.
+       * rcs.c (RCS_checkout), update.c (special_file_mismatch): sscanf
+       on %ld requires an unsigned long, not a dev_t.
+       * update.c (special_file_mismatch): Remove unused variable
+       check_devnums.
+       * mkmodules.c (config_contents): Between two settings, use a blank
+       line not a "#" line.
+
+1998-02-15  Tim Pierce  <address@hidden>
+
+       [This is the code as submitted.  I'll be checking in my cleanups
+       shortly.  This work sponsored by Abbott Labs.  -kingdon]
+
+       Support for device special files, symbolic links, user and group
+       ownerships, and file permissions.
+
+       * parseinfo.c: (parse_config): Handle new config variable
+       `PreservePermissions'.
+       * mkmodules.c (config_contents): Add new PreservePermissions var.
+
+       * rcs.c, rcs.h (preserve_perms): New variable.
+       (RCS_checkout, RCS_checkin): Support for newphrases `owner',
+       `group', `permissions', `special', `symlink'.
+       (RCS_checkout): If `workfile' and `sout' are symlinks, remove them
+       before attempting to open them for writing.
+       * import.c (add_rcs_file): Support for newphrases.  Do not attempt
+       to read data from special files or symlinks.  Error message
+       `cannot fstat' is now `cannot lstat'.
+
+       New metrics for deciding when two files are different:
+
+       * update.c, cvs.h (special_file_mismatch): New function.
+       (merge_file, join_file): Call it.
+       * no_diff.c (No_Difference): Call it.
+
+       * filesubr.c (xcmp): Consider files to be different if they are of
+       different types; if they are symlinks which link to different
+       pathnames; or if they are devices with different device numbers.
+       Error message is now `cannot lstat'.
+       * rcs.c (RCS_cmp_file): Use `xcmp' to compare files, simplifying
+       the special handling for nonregular files.
+
+       * rcscmds.c (diff_exec, diff_execv): If asked to obtain diffs for
+       special files, report no differences.
+
+       Miscellaneous changes to make special file support possible:
+
+       * commit.c (fix_rcs_modes): Don't attempt to `fix' permissions on
+       a symlink.
+
+       * import.c (add_rcs_file): Don't try to close fpuser if it was
+       never opened (e.g. when operating on a symlink).
+
+       * filesubr.c, cvs.h (isdevice, xreadlink): New functions.
+       * filesubr.c (copy_file): Handle special files and symlinks.
+       (xchmod): Do nothing if `preserve_perms' is set.
+
+       * commit.c (checkaddfile): Replace `copy_file (DEVNULL, ...)' with
+       fopen/fclose calls.  Copy_file no longer attempts to read data
+       from device files.
+
+       * filesubr.c (islink): Use CVS_LSTAT, not lstat.
+       * vers_ts.c (time_stamp, time_stamp_server): Use CVS_LSTAT, not stat,
+       to get symlinks right.
+       * subr.c (get_file): Same.  Don't attempt to read from special
+       files or symlinks.
+
+       * classify.c (Classify_File): Doc fix.
+
+Fri Feb 13 17:07:32 1998  Eric Mumpower  <address@hidden>
+       and Ian Lance Taylor  <address@hidden>
+
+       Fix some file system ordering problems found on Irix 6.4:
+       * sanity.sh (basic2): Use dotest_sort for test 56.
+       (importb): Use dotest_sort for tests importb-1 and importb-2.
+       (head): Use dotest_sort for test head-1.
+
+Thu Feb 12 15:15:33 1998  Jim Kingdon  <address@hidden>
+
+       * import.c (add_rcs_file): If add_logfp is NULL, don't call fperror.
+
+11 Feb 1998  Andy Piper
+
+       * server.c (cvs_output_binary): Use OPEN_BINARY not _O_BINARY.
+
+Mon Feb  9 18:34:39 1998  Jim Kingdon  <address@hidden>
+
+       Tweaks to Ian's checkin:
+       * update.c (merge_file): Remove comment about sending file to
+       client before the message.  It doesn't apply to this code any more
+       (it does apply to checkout_file, but I'm not sure it is important
+       to have such a comment anyway).
+       * buffer.c (buf_default_memory_error, buf_length): Reindent.
+       * server.h: Declare struct buffer before use.
+
+Mon Feb  9 21:05:28 1998  Ian Lance Taylor  <address@hidden>
+
+       * rcs.c (RCS_fully_parse): Call getrevnum rather than getrcsrev.
+       Don't bother with ungetc.
+
+       * rcs.c (getrcsrev): Rewrite to simply call getrevnum.
+
+Sun Feb  8 15:49:39 1998  Ian Lance Taylor  <address@hidden>
+
+       Don't have the server check out a revision into a file and then
+       immediately read the file; just read into a buffer instead.
+       * update.c: Include buffer.h.
+       (update_fileproc): Let checkout_file call server_updated.
+       (checkout_file): Add merging and update_server parameters.  Change
+       all callers.  If server_active, don't mess with backup files.  If
+       server_active, copy the revision into a buffer rather than a file
+       when possible.  If update_server, call server_updated.  Fix
+       handling of error status.
+       (checkout_to_buffer): New static function used by checkout_file.
+       (merge_file): Let checkout_file call server_updated.
+       (join_file): Likewise.
+       * server.c (server_updated): Change file_info parameter to mode
+       parameter.  Add filebuf parameter.  Change all callers.  If
+       filebuf is not NULL, don't read the file.
+       * server.h (server_updated): Update declaration.
+       * buffer.c (buf_free): New function.
+       (buf_append_buffer): New function.
+       (buf_length): New function.
+       * buffer.h (buf_free, buf_append_buffer, buf_length): Declare.
+
+       * buffer.c: (buf_initialize): If the memory parameter is NULL, use
+       buf_default_memory_error.
+       (buf_default_memory_error): New static function.
+       * buffer.h (BUFMEMERRPROC): Define typedef.
+       * client.c (buf_memory_error): Remove.
+       (start_server): Pass NULL rather than buf_memory_error as buffer
+       memory error function.
+
+Sat Feb  7 16:27:30 1998  Ian Lance Taylor  <address@hidden>
+
+       * rcs.c (RCS_parsercsfile_i): Read the expand keyword from the RCS
+       file.  We do this because Version_TS calls RCS_getexpand in many
+       common cases, and we don't want to reopen the file just for that.
+       (RCS_reparsercsfile): Skip the expand keyword.
+       (RCS_getexpand): Don't call RCS_reparsercsfile.
+
+       * rcs.c (STREQ): New macro.  In all string equality tests in the
+       file, replace strcmp with STREQ.
+
+Fri Feb  6 16:14:49 1998  Ian Lance Taylor  <address@hidden>
+
+       * update.c (checkout_file): If we've already removed the backup
+       file once, don't try to remove it again.
+
+       * filesubr.c (unlink_file_dir): Call stat rather than isdir, and
+       don't call unlink if the file does not exist.
+
+       * myndbm.c (mydbm_load_file): Rename line_len to line_size.  Call
+       getstr rather than getline, to avoid any confusion between \n and
+       \012.  Use the line length returned by getstr rather than calling
+       strlen.  Remove local variable len.
+
+Fri Feb  6 13:23:46 1998  Jim Kingdon  <address@hidden>
+
+       * rcs.c (RCS_parsercsfile_i): Don't suppress errors on
+       really_quiet.
+       (RCS_parsercsfile_i, RCS_reparsercsfile, RCS_fully_parse,
+       RCS_deltas, getdelta, getrcskey, RCS_getdeltatext):
+       Check for errors.  Include errno in error messages.  Include
+       filename in error messages.  Pass new argument to getrcskey.
+       (getrcskey): New argument NAME, so we can report errors ourself.
+
+Fri Feb  6 12:10:18 1998  Ian Lance Taylor  <address@hidden>
+
+       * rcs.c (RCS_reparsercsfile): Don't use ftell/fseek; just keep
+       track of whether we've already read a key/value pair.  Use sizeof
+       rather than strlen for a constant string.  Pass the current key
+       and value to getdelta, and get them back as well.
+       (getdelta): Add keyp and valp parameters.  Don't use ftell/fseek;
+       just return the key/value pair to the caller.  Don't allocate
+       vnode before we know we need it.  Check one getrcskey return
+       value.  Use sizeof rather than strlen for a constant string.
+
+       * rcs.c (getrcskey): Correct comment describing return value.
+
+Thu Feb  5 22:51:13 1998  Ian Lance Taylor  <address@hidden>
+
+       * subr.c (getcaller): Cache the result, so that we don't keep
+       searching the password file.
+
+Wed Feb  4 23:31:08 1998  Jim Kingdon  <address@hidden>
+
+       * rcs.c (max_rev): Don't prototype.  Interesting that noone
+       complained about this until now.
+
+4 Feb 1998  Jim Kingdon
+
+       * rcs.c (RCS_checkin): When adding a new file, read it
+       with "rb" if binary.
+
+Fri Jan 30 11:32:41 1998  Jim Kingdon  <address@hidden>
+
+       * sanity.sh: Also test "first-dir" as the regexp in loginfo in
+       addition to ALL.
+
+       * main.c (main): Update year in copyright notice to 1998.
+
+Thu Jan 29 00:01:05 1998  Jim Kingdon  <address@hidden>
+
+       * version.c: Change version number to 1.9.25.
+
+       * Version 1.9.24.
+
+       * sanity.sh (multibranch2): File file2 and tests multibranch2-13
+       through multibranch2-15 test a slightly different case than the
+       rest of multibranch2.
+
+       * mkmodules.c (cvswrappers_contents): Rewrite.  The text didn't
+       describe -k and had various other problems.
+
+28 Jan 1998  Karl Fogel and Jim Kingdon
+
+       New feature to let server tell client about wrappers.
+       * client.h (struct response): Add comment about args being
+       '\0' terminated when passed to handle_* functions.
+       * client.c (start_server): send "wrapper-sendme-rcsOptions" to
+       server iff supported.
+       (responses): new response "Wrapper-rcsOption"; allows the server
+       to send certain lines from its cvswrappers file.
+       (handle_wrapper_rcs_option): new func, handles "Wrapper-rcsOption"
+       response from server.
+       * server.c (serve_wrapper_sendme_rcs_options): new func, sends
+       server side CVSROOT/cvswrappers rcs option lines to client.
+       (requests): new request "wrapper-sendme-rcsOptions"; if received,
+       we know we can send "Wrapper-rcsOption..." to the client.
+       * wrapper.c (wrap_unparse_rcs_options): new func; repeated calls
+       step down the wrapper list returning rcs option entries, but
+       repackaged as cvswrappers lines.
+       (wrap_setup): new guard variable `wrap_setup_already_done'; if
+       this function has run already, just return having done nothing.
+       Add comment concerning environment variable.
+       * cvs.h: declare wrap_unparse_rcs_options().
+
+Tue Jan 27 18:27:19 1998  Ian Lance Taylor  <address@hidden>
+
+       * rtag.c (rtag_dirproc): Call ignore_directory, and skip the
+       directory if it returns true.
+       * sanity.sh (modules4): New set of tests to test some aspects of
+       excluding directories in the modules file, including the above
+       patch.
+
+Thu Jan 22 10:05:55 1998  Jim Kingdon  <address@hidden>
+
+       * server.c (serve_kopt): Check for length of arg.  Based on
+       inspection of the code, plugs a buffer overrun security hole which
+       was introduced Monday.
+
+       * server.c (serve_is_modified): Don't call xmalloc; we aren't
+       allowed to call error() here.  Remove duplicate (and potentially
+       confusing) variable 'p'.
+
+       * log.c (log_fileproc): Look for first character of version
+       '0' AND second character '\0', rather than OR.  I didn't try to
+       come up with a test case but this looks like a simple thinko
+       (albeit one which would show up in obscure cases if at all).
+
+Tue Jan 20 19:37:53 1998  Jim Kingdon  <address@hidden>
+
+       * client.c (send_dirent_proc): Don't send nonexistent directories
+       unless noexec.
+       * sanity.sh (modules2): New tests modules2-13 through modules2-18
+       test for this fix.
+
+Mon Jan 19 11:17:51 1998  Jim Kingdon  <address@hidden>
+
+       * server.c (serve_kopt): New function.
+       (requests): Add "Kopt" request.
+       (kopt): New variable.
+       (serve_is_modified): Write kopts from there into entries.
+       (serve_modified): Call serve_is_modified so we do the same.
+       Declare serve_modified and serve_is_modified.
+       * vers_ts.c (Version_TS): Set ->options even for a dummy ("D"
+       timestamp) entry.
+       * import.c (process_import_file): Check for -k options.
+       * client.c (client_process_import_file): Send Kopt request.
+       (send_fileproc): Likewise, for "cvs add".
+       * sanity.sh: Enable test binwrap3-sub2-add1 for remote.
+       Add -I .cvswrappers to binwrap3-2a; adjust binwrap3-2d
+       accordingly.  Tests for this fix.
+
+Mon Jan 19 08:48:59 1998  Larry Jones  <address@hidden>
+
+       * sanity.sh (errmsg1): Append test 168 output to log file.
+
+Sat Jan 17 08:01:51 1998  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (ann-10, ann-11): Don't make assumptions about the
+       number of characters in the username.
+
+Fri Jan 16 15:34:02 1998  Larry Jones  <address@hidden>
+
+       * diff.c (diff_fileproc): Free label1 and label2 when finished.
+
+       * edit.c (editor_set): Don't free edlist until after we're
+       done using it.
+
+       * rcscmds.c (RCS_merge): Free xrev1 and xrev2 when finished.
+
+       * subr.c (make_message_rcslegal): Don't access uninitialized or
+       unallocated memory; only strip trailing blank lines.
+       * sanity.sh (log-3): Enhance to test this fix.
+
+Fri Jan 16 12:41:03 1998  Jim Kingdon  <address@hidden>
+
+       * sanity.sh: Add keywordlog to list of tests run by default.
+
+       * rcs.c (RCS_deltas): Don't call cvs_output if length is zero;
+       passing zero length to cvs_output does not mean output zero
+       bytes.  The 27 Dec 1997 change to no longer '\0'-terminate the
+       ->text field turned this from a time bomb to a user-visible bug.
+       * sanity.sh (ann): New tests, test for this fix and other annotate
+       behaviors.
+
+Thu Jan 15 23:52:00 1998  Jim Kingdon  <address@hidden>
+
+       * root.c (root_allow_ok): If inetd.conf didn't specify an
+       --allow-root options at all, we know we are in trouble.  Give a
+       specific error message.
+
+Thu Jan 15 21:24:59 1998  Ian Lance Taylor  <address@hidden>
+
+       * sanity.sh (dotest_sort): New variant of dotest which sorts the
+       output, for use when the output depends upon details of the file
+       system, typically when doing an import.
+       (rdiff): Use dotest_sort for rdiff-1.
+       (ignore): Use dotest_sort for 188a, 188b, 189d, 190, and 191.
+
+       * sanity.sh: (TESTSHELL): New variable.
+       (editor, info, reserved): Use TESTSHELL in temporary script.
+
+       * sanity.sh (ignore): Do all tests in subdirectory, to avoid
+       conflict between cvsroot and CVSROOT on Windows.
+       (binwrap3, mwrap, info, config): Likewise.
+
+       * sanity.sh (binfiles2): Correct test name binfile2-7-brmod to
+       binfiles2-7-brmod.
+
+       * release.c (release_delete): If __CYGWIN32__ is defined, don't
+       worry about mismatched inodes.  This is a hack, but then I think
+       the test is rather peculiar anyhow.
+
+Thu Jan 15 16:07:36 1998  Larry Jones  <address@hidden>
+
+       * sanity.sh (reserved-9): Use ${PROG} instead of "cvs".
+
+Wed Jan 14 15:43:13 1998  Jim Kingdon  <address@hidden>
+
+       * Split ChangeLog into ChangeLog-97 and ChangeLog.
+       * Makefile.in (DISTFILES): Add ChangeLog-97.
+
+13 Jan 1998  Jim Kingdon
+
+       * client.c: Declare handle_mt.
+
+Tue Jan 13 22:21:30 1998  Jim Kingdon  <address@hidden>
+
+       * sanity.sh: Add comment about how pwd and /bin/pwd often differ
+       in behavior (but are not guaranteed to).
+
+Tue Jan 13 13:49:53 1998  Ian Lance Taylor  <address@hidden>
+
+       * sanity.sh: When setting TMPPWD use just pwd, not /bin/pwd.
+
+       * update.c (checkout_file): Don't pass set_time as true to
+       Version_TS if the file is dead.
+       * sanity.sh (modules): Add tests modules-155c6 through
+       modules-155c8 to test for above patch (without the above patch,
+       modules-155c8 will fail when remote).
+
+Tue Jan 13 10:37:02 1998  Larry Jones  <address@hidden>
+
+       * client.c (send_modified): Change bufsize and newsize from int
+       to size_t to avoid type clashes in call to read_and_gzip.
+
+Tue Jan 13 10:33:02 1998  Larry Jones  <address@hidden>
+
+       * zlib.c (read_and_gzip): Set finish to 0; it was uninitialized.
+
+Tue Jan 13 10:26:43 1998  Larry Jones  <address@hidden>
+
+       * add.c, rcs.c: Plug memory leaks.
+
+Mon Jan 12 10:45:27 1998  Larry Jones  <address@hidden>
+
+       * server.c (mkdir_p): Don't try to create nameless directories
+       (i.e., given "/foo//bar", don't try to create "/foo/",
+       just "/foo" and "/foo//bar") since it isn't necessary and
+       it fails on some systems in unexpected ways.
+
+1998-01-11  enami tsugutomo  <address@hidden>
+
+       * rcs.c (linevector_copy): Delete lines before overwriting them.
+
+Sat Jan 10 11:05:40 1998  Jim Kingdon  <address@hidden>
+
+       * cvsrc.c, entries.c, login.c, logmsg.c, myndbm.c, patch.c,
+       release.c, server.c: Check for errors from getline, CVS_FOPEN,
+       fprintf, CVS_UNLINK and fclose.  Note that the new errors are
+       nonfatal.  This is because of conservatism more than because
+       it is always the best thing.
+       * login.c (get_cvs_password): Close the file when done with it.
+       * client.c (notified_a_file): If -1 return from getline, check
+       feof rather than assuming errno is set.
+
+Fri Jan  9 14:38:54 1998  Jim Kingdon  <address@hidden>
+
+       * server.c (expand_proc): Also output server_dir in
+       "Module-expansion", not just in output_dir ("Created", &c).
+       * sanity.sh (modules2): New tests modules2-9 through modules2-12
+       test for this.
+
+Thu Jan  8 12:56:55 1998  Yasutoshi Hiroe  <address@hidden>
+
+       * import.c (import): Don't strcat on uninitialized memory.  Fixes
+       possible SIGSEGV with zero-length message.
+
+Tue Jan  6 22:56:29 1998  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (crerepos): Fix mistaken variable name which caused us
+       not to clean up at the end of the test.
+
+Mon Dec 22 01:40:57 1997  Jim Kingdon  <address@hidden>
+
+       * add.c (add): Also look for .cvswrappers files.
+       * sanity.sh (binwrap3): New tests binwrap3-2*, binwrap3-sub2-add*
+       test for this.
+
+Tue Jan  6 11:50:38 1998  Jim Kingdon  <address@hidden>
+
+       * sanity.sh (crerepos): New tests crerepos-8 through crerepos-18
+       test behaviors when mixing repositories.
+
+Sun Jan  4 17:40:22 1998  Jim Kingdon  <address@hidden>
+
+       * version.c: Change version number to 1.9.23.
+
+       * Version 1.9.22.
+
+
+For older changes see ChangeLog-97.
Index: ccvs/src/admin.c
diff -u /dev/null ccvs/src/admin.c:1.112.2.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/admin.c    Tue Jan 17 15:41:23 2006
@@ -0,0 +1,1181 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (c) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * Administration ("cvs admin")
+ * 
+ */
+
+#include "cvs.h"
+#ifdef CVS_ADMIN_GROUP
+#include <grp.h>
+#endif
+
+static Dtype admin_dirproc (void *callerdat, const char *dir,
+                            const char *repos, const char *update_dir,
+                            List *entries);
+static int admin_fileproc (void *callerdat, struct file_info *finfo);
+
+static const char *const admin_usage[] =
+{
+    "Usage: %s %s [options] files...\n",
+    "\t-a users   Append (comma-separated) user names to access list.\n",
+    "\t-A file    Append another file's access list.\n",
+    "\t-b[rev]    Set default branch (highest branch on trunk if omitted).\n",
+    "\t-c string  Set comment leader.\n",
+    "\t-e[users]  Remove (comma-separated) user names from access list\n",
+    "\t           (all names if omitted).\n",
+    "\t-I         Run interactively.\n",
+    "\t-k subst   Set keyword substitution mode:\n",
+    "\t   kv   (Default) Substitute keyword and value.\n",
+    "\t   kvl  Substitute keyword, value, and locker (if any).\n",
+    "\t   k    Substitute keyword only.\n",
+    "\t   o    Preserve original string.\n",
+    "\t   b    Like o, but mark file as binary.\n",
+    "\t   v    Substitute value only.\n",
+    "\t-l[rev]    Lock revision (latest revision on branch,\n",
+    "\t           latest revision on trunk if omitted).\n",
+    "\t-L         Set strict locking.\n",
+    "\t-m rev:msg  Replace revision's log message.\n",
+    "\t-n tag[:[rev]]  Tag branch or revision.  If :rev is omitted,\n",
+    "\t                delete the tag; if rev is omitted, tag the latest\n",
+    "\t                revision on the default branch.\n",
+    "\t-N tag[:[rev]]  Same as -n except override existing tag.\n",
+    "\t-o range   Delete (outdate) specified range of revisions:\n",
+    "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
+    "\t   rev1::rev2  Between rev1 and rev2, excluding rev1 and rev2.\n",
+    "\t   rev:        rev and following revisions on the same branch.\n",
+    "\t   rev::       After rev on the same branch.\n",
+    "\t   :rev        rev and previous revisions on the same branch.\n",
+    "\t   ::rev       Before rev on the same branch.\n",
+    "\t   rev         Just rev.\n",
+    "\t-q         Run quietly.\n",
+    "\t-s state[:rev]  Set revision state (latest revision on branch,\n",
+    "\t                latest revision on trunk if omitted).\n",
+    "\t-t[file]   Get descriptive text from file (stdin if omitted).\n",
+    "\t-t-string  Set descriptive text.\n",
+    "\t-u[rev]    Unlock the revision (latest revision on branch,\n",
+    "\t           latest revision on trunk if omitted).\n",
+    "\t-U         Unset strict locking.\n",
+    "\t--execute    Turn on execute bits on repository file.\n",
+    "\t--no-execute Turn off execute bits on repository file.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+/* This structure is used to pass information through start_recursion.  */
+struct admin_data
+{
+    /* Set default branch (-b).  It is "-b" followed by the value
+       given, or NULL if not specified, or merely "-b" if -b is
+       specified without a value.  */
+    char *branch;
+
+    /* Set comment leader (-c).  It is "-c" followed by the value
+       given, or NULL if not specified.  The comment leader is
+       relevant only for old versions of RCS, but we let people set it
+       anyway.  */
+    char *comment;
+
+    /* Set strict locking (-L).  */
+    int set_strict;
+
+    /* Set nonstrict locking (-U).  */
+    int set_nonstrict;
+
+    /* Delete revisions (-o).  It is "-o" followed by the value specified.  */
+    char *delete_revs;
+
+    /* Keyword substitution mode (-k), e.g. "-kb".  */
+    char *kflag;
+
+    /* Description (-t).  */
+    char *desc;
+
+    /* Interactive (-I).  Problematic with client/server.  */
+    int interactive;
+
+    enum {AVOID = 0, NOEXECUTE, EXECUTE} execute;
+
+    /* This is the cheesy part.  It is a vector with the options which
+       we don't deal with above (e.g. "-afoo" "-abar,baz").  In the future
+       this presumably will be replaced by other variables which break
+       out the data in a more convenient fashion.  AV as well as each of
+       the strings it points to is malloc'd.  */
+    int ac;
+    char **av;
+    int av_alloc;
+};
+
+/* Add an argument.  OPT is the option letter, e.g. 'a'.  ARG is the
+   argument to that option, or NULL if omitted (whether NULL can actually
+   happen depends on whether the option was specified as optional to
+   getopt).  */
+static void
+arg_add (struct admin_data *dat, int opt, char *arg)
+{
+    char *newelt = Xasprintf ("-%c%s", opt, arg ? arg : "");
+
+    if (dat->av_alloc == 0)
+    {
+       dat->av_alloc = 1;
+       dat->av = xnmalloc (dat->av_alloc, sizeof (*dat->av));
+    }
+    else if (dat->ac >= dat->av_alloc)
+    {
+       dat->av_alloc *= 2;
+       dat->av = xnrealloc (dat->av, dat->av_alloc, sizeof (*dat->av));
+    }
+    dat->av[dat->ac++] = newelt;
+}
+
+
+
+/*
+ * callback proc to run a script when admin finishes.
+ */
+static int
+postadmin_proc (const char *repository, const char *filter, void *closure)
+{
+    char *cmdline;
+    const char *srepos = Short_Repository (repository);
+
+    TRACE (TRACE_FUNCTION, "postadmin_proc (%s, %s)", repository, filter);
+
+    /* %c = cvs_cmd_name
+     * %R = referrer
+     * %p = shortrepos
+     * %r = repository
+     */
+    /*
+     * Cast any NULL arguments as appropriate pointers as this is an
+     * stdarg function and we need to be certain the caller gets what
+     * is expected.
+     */
+    cmdline = format_cmdline (
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                             false, srepos,
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                             filter,
+                             "c", "s", cvs_cmd_name,
+#ifdef SERVER_SUPPORT
+                             "R", "s", referrer ? referrer->original : "NONE",
+#endif /* SERVER_SUPPORT */
+                             "p", "s", srepos,
+                             "r", "s", current_parsed_root->directory,
+                             (char *) NULL);
+
+    if (!cmdline || !strlen (cmdline))
+    {
+       if (cmdline) free (cmdline);
+       error (0, 0, "postadmin proc resolved to the empty string!");
+       return 1;
+    }
+
+    run_setup (cmdline);
+
+    free (cmdline);
+
+    /* FIXME - read the comment in verifymsg_proc() about why we use abs()
+     * below() and shouldn't.
+     */
+    return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+                         RUN_NORMAL | RUN_SIGIGNORE));
+}
+
+
+
+/*
+ * Call any postadmin procs.
+ */
+static int
+admin_filesdoneproc (void *callerdat, int err, const char *repository,
+                     const char *update_dir, List *entries)
+{
+    TRACE (TRACE_FUNCTION, "admin_filesdoneproc (%d, %s, %s)", err, repository,
+           update_dir);
+    Parse_Info (CVSROOTADM_POSTADMIN, repository, postadmin_proc, PIOPT_ALL,
+                NULL);
+
+    return err;
+}
+
+
+
+static const char short_options[] =
+    "+ib::c:a:A:e::l::u::LUn:N:m:o:s:t::IqxV:k:";
+
+enum {OPT_NONE = 0, OPT_EXECUTE, OPT_NOEXECUTE} opt_values;
+static struct option long_options[] =
+{
+    {"execute", 0, NULL, OPT_EXECUTE},
+    {"no-execute", 0, NULL, OPT_NOEXECUTE},
+    {0, 0, NULL, OPT_NONE}
+};
+
+
+
+/* Accept a `;' delimited string and break it into tokens.  Allocate a
+ * return string.  Copy the first token into the return string
+ * checking to be sure that each character is a valid option character
+ * of the short_options string. For remaining tokens, convert to the
+ * long option VAL (from the global LONG_OPTIONS above) and append
+ * that char to the return value.  When long option tokens are
+ * unrecognized, a warning is printed and they are ignored.
+ *
+ * i.e., S will be of the format `[SHORTOPTIONS][;LONGOPTION]...'.  It is
+ * perfectly acceptable for SHORTOPTIONS to resolve to the empty string, 
+ * an empty LONGOPTION will also be ignored.
+ */
+char *
+make_UserAdminOptions (const char *infopath, unsigned int ln, const char *s)
+{
+    const char *cur_opt, *next_opt;
+    size_t len;
+    char *ns;
+
+    assert (s);
+
+    cur_opt = s;
+
+    next_opt = strchr (cur_opt, ';');
+    if (next_opt)
+       len = next_opt - cur_opt;
+    else
+       len = strlen (cur_opt);
+
+    ns = xmalloc (len + 1);
+    *ns = '\0';
+    if (len > 0)
+    {
+       const char *p;
+       size_t nspos = 0;
+       /* validate short options */
+       for (p = cur_opt; p < (cur_opt + len); p++)
+       {
+           if (*p == '+' || *p == ':' || strchr (short_options, *p) == NULL)
+               error (0, 0,
+                      "%s [%u]: Unrecognized short admin option `%c'.",
+                      infopath, ln, *p);
+           else
+               ns[nspos++] = *p;
+       }
+       ns[nspos] = '\0';
+       if (nspos > 0)
+           TRACE (TRACE_FUNCTION, "Setting short UserAdminOptions `%s'", ns);
+    }
+
+    /* process long options (if any) */
+    while ((cur_opt = next_opt))
+    {
+       next_opt = strchr (++cur_opt, ';');
+
+       if (next_opt)
+           len = next_opt - cur_opt;
+       else
+           len = strlen (cur_opt);
+
+       /* ignore empty long options (ie, ';;') */
+       if (len > 0)
+       {
+           struct option *found;
+
+           for (found = long_options; found->name; found++)
+               if (len == strlen (found->name)
+                   && !strncmp (cur_opt, found->name, len))
+                   break;
+
+           if (found->name)
+           {
+               size_t nslen = strlen (ns);
+
+               assert (found->val);
+
+               ns = xrealloc (ns, nslen + 2);
+               ns[nslen++] = found->val;
+               ns[nslen] = '\0';
+               TRACE (TRACE_FUNCTION, "Adding long UserAdminOptions `%s'",
+                      found->name);
+           }
+           else
+           {
+               char *tmp = xmalloc (len + 1);
+               strncpy (tmp, cur_opt, len);
+               tmp[len] = '\0';
+               error (0, 0,
+                      "%s [%u]: Unrecognized long admin option `%s'.",
+                      infopath, ln, tmp);
+               free (tmp);
+           }
+       }
+    }
+
+    return ns;
+}
+
+
+
+int
+admin (int argc, char **argv)
+{
+    int err;
+#ifdef CVS_ADMIN_GROUP
+    struct group *grp;
+    struct group *getgrnam (const char *);
+#endif
+    struct admin_data admin_data;
+    int c;
+    int i;
+    bool only_allowed_options;
+
+    if (argc <= 1)
+       usage (admin_usage);
+
+    wrap_setup ();
+
+    memset (&admin_data, 0, sizeof admin_data);
+
+    /* TODO: get rid of `-' switch notation in admin_data.  For
+       example, admin_data->branch should be not `-bfoo' but simply `foo'. */
+
+    optind = 0;
+    only_allowed_options = true;
+    while ((c = getopt_long
+           (argc, argv, short_options, long_options, NULL))
+          != EOF)
+    {
+       if (
+# ifdef CLIENT_SUPPORT
+           !current_parsed_root->isremote &&
+# endif        /* CLIENT_SUPPORT */
+           c != 'q' && !strchr (config->UserAdminOptions, c)
+          )
+           only_allowed_options = false;
+
+       switch (c)
+       {
+           case OPT_EXECUTE:   /* --execute */
+               admin_data.execute = EXECUTE;
+               break;
+
+           case OPT_NOEXECUTE: /* --no-execute */
+               admin_data.execute = NOEXECUTE;
+               break;
+
+           case 'i':
+               /* This has always been documented as useless in cvs.texinfo
+                  and it really is--admin_fileproc silently does nothing
+                  if vers->vn_user is NULL. */
+               error (0, 0, "the -i option to admin is not supported");
+               error (0, 0, "run add or import to create an RCS file");
+               goto usage_error;
+
+           case 'b':
+               if (admin_data.branch != NULL)
+               {
+                   error (0, 0, "duplicate 'b' option");
+                   goto usage_error;
+               }
+               if (optarg == NULL)
+                   admin_data.branch = xstrdup ("-b");
+               else
+                   admin_data.branch = Xasprintf ("-b%s", optarg);
+               break;
+
+           case 'c':
+               if (admin_data.comment != NULL)
+               {
+                   error (0, 0, "duplicate 'c' option");
+                   goto usage_error;
+               }
+               admin_data.comment = Xasprintf ("-c%s", optarg);
+               break;
+
+           case 'a':
+               arg_add (&admin_data, 'a', optarg);
+               break;
+
+           case 'A':
+               /* In the client/server case, this is cheesy because
+                  we just pass along the name of the RCS file, which
+                  then will want to exist on the server.  This is
+                  accidental; having the client specify a pathname on
+                  the server is not a design feature of the protocol.  */
+               arg_add (&admin_data, 'A', optarg);
+               break;
+
+           case 'e':
+               arg_add (&admin_data, 'e', optarg);
+               break;
+
+           case 'l':
+               /* Note that multiple -l options are valid.  */
+               arg_add (&admin_data, 'l', optarg);
+               break;
+
+           case 'u':
+               /* Note that multiple -u options are valid.  */
+               arg_add (&admin_data, 'u', optarg);
+               break;
+
+           case 'L':
+               /* Probably could also complain if -L is specified multiple
+                  times, although RCS doesn't and I suppose it is reasonable
+                  just to have it mean the same as a single -L.  */
+               if (admin_data.set_nonstrict)
+               {
+                   error (0, 0, "-U and -L are incompatible");
+                   goto usage_error;
+               }
+               admin_data.set_strict = 1;
+               break;
+
+           case 'U':
+               /* Probably could also complain if -U is specified multiple
+                  times, although RCS doesn't and I suppose it is reasonable
+                  just to have it mean the same as a single -U.  */
+               if (admin_data.set_strict)
+               {
+                   error (0, 0, "-U and -L are incompatible");
+                   goto usage_error;
+               }
+               admin_data.set_nonstrict = 1;
+               break;
+
+           case 'n':
+               /* Mostly similar to cvs tag.  Could also be parsing
+                  the syntax of optarg, although for now we just pass
+                  it to rcs as-is.  Note that multiple -n options are
+                  valid.  */
+               arg_add (&admin_data, 'n', optarg);
+               break;
+
+           case 'N':
+               /* Mostly similar to cvs tag.  Could also be parsing
+                  the syntax of optarg, although for now we just pass
+                  it to rcs as-is.  Note that multiple -N options are
+                  valid.  */
+               arg_add (&admin_data, 'N', optarg);
+               break;
+
+           case 'm':
+               /* Change log message.  Could also be parsing the syntax
+                  of optarg, although for now we just pass it to rcs
+                  as-is.  Note that multiple -m options are valid.  */
+               arg_add (&admin_data, 'm', optarg);
+               break;
+
+           case 'o':
+               /* Delete revisions.  Probably should also be parsing the
+                  syntax of optarg, so that the client can give errors
+                  rather than making the server take care of that.
+                  Other than that I'm not sure whether it matters much
+                  whether we parse it here or in admin_fileproc.
+
+                  Note that multiple -o options are invalid, in RCS
+                  as well as here.  */
+
+               if (admin_data.delete_revs != NULL)
+               {
+                   error (0, 0, "duplicate '-o' option");
+                   goto usage_error;
+               }
+               admin_data.delete_revs = Xasprintf ("-o%s", optarg);
+               break;
+
+           case 's':
+               /* Note that multiple -s options are valid.  */
+               arg_add (&admin_data, 's', optarg);
+               break;
+
+           case 't':
+               if (admin_data.desc != NULL)
+               {
+                   error (0, 0, "duplicate 't' option");
+                   goto usage_error;
+               }
+               if (optarg != NULL && optarg[0] == '-')
+                   admin_data.desc = xstrdup (optarg + 1);
+               else
+               {
+                   size_t bufsize = 0;
+                   size_t len;
+
+                   get_file (optarg, optarg, "r", &admin_data.desc,
+                             &bufsize, &len);
+               }
+               break;
+
+           case 'I':
+               /* At least in RCS this can be specified several times,
+                  with the same meaning as being specified once.  */
+               admin_data.interactive = 1;
+               break;
+
+           case 'q':
+               /* Silently set the global really_quiet flag.  This keeps admin 
in
+                * sync with the RCS man page and allows us to silently support
+                * older servers when necessary.
+                *
+                * Some logic says we might want to output a deprecation warning
+                * here, but I'm opting not to in order to stay quietly in sync
+                * with the RCS man page.
+                */
+               really_quiet = 1;
+               break;
+
+           case 'x':
+               error (0, 0, "the -x option has never done anything useful");
+               error (0, 0, "RCS files in CVS always end in ,v");
+               goto usage_error;
+
+           case 'V':
+               /* No longer supported. */
+               error (0, 0, "the `-V' option is obsolete");
+               break;
+
+           case 'k':
+               if (admin_data.kflag != NULL)
+               {
+                   error (0, 0, "duplicate '-k' option");
+                   goto usage_error;
+               }
+               admin_data.kflag = RCS_check_kflag (optarg);
+               break;
+           default:
+           case '?':
+               /* getopt will have printed an error message.  */
+
+           usage_error:
+               /* Don't use cvs_cmd_name; it might be "server".  */
+               error (1, 0, "specify %s -H admin for usage information",
+                      program_name);
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+#ifdef CVS_ADMIN_GROUP
+    /* The use of `cvs admin -k' is unrestricted.  However, any other
+       option is restricted if the group CVS_ADMIN_GROUP exists on the
+       server.  */
+    /* This is only "secure" on the server, since the user could edit the
+     * RCS file on a local host, but some people like this kind of
+     * check anyhow.  The alternative would be to check only when
+     * (server_active) rather than when not on the client.
+     */
+    if (!current_parsed_root->isremote && !only_allowed_options &&
+       (grp = getgrnam(CVS_ADMIN_GROUP)) != NULL)
+    {
+#ifdef HAVE_GETGROUPS
+       gid_t *grps;
+       int n;
+
+       /* get number of auxiliary groups */
+       n = getgroups (0, NULL);
+       if (n < 0)
+           error (1, errno, "unable to get number of auxiliary groups");
+       grps = xnmalloc (n + 1, sizeof *grps);
+       n = getgroups (n, grps);
+       if (n < 0)
+           error (1, errno, "unable to get list of auxiliary groups");
+       grps[n] = getgid ();
+       for (i = 0; i <= n; i++)
+           if (grps[i] == grp->gr_gid) break;
+       free (grps);
+       if (i > n)
+           error (1, 0, "usage is restricted to members of the group `%s'",
+                  CVS_ADMIN_GROUP);
+#else
+       char *me = getcaller ();
+       char **grnam;
+       
+       for (grnam = grp->gr_mem; *grnam; grnam++)
+           if (strcmp (*grnam, me) == 0) break;
+       if (!*grnam && getgid () != grp->gr_gid)
+           error (1, 0, "usage is restricted to members of the group %s",
+                  CVS_ADMIN_GROUP);
+#endif
+    }
+#endif /* defined CVS_ADMIN_GROUP */
+
+    for (i = 0; i < admin_data.ac; ++i)
+    {
+       assert (admin_data.av[i][0] == '-');
+       switch (admin_data.av[i][1])
+       {
+           case 'm':
+           case 'l':
+           case 'u':
+               check_numeric (&admin_data.av[i][2], argc, argv);
+               break;
+           default:
+               break;
+       }
+    }
+    if (admin_data.branch != NULL)
+       check_numeric (admin_data.branch + 2, argc, argv);
+    if (admin_data.delete_revs != NULL)
+    {
+       char *p;
+
+       check_numeric (admin_data.delete_revs + 2, argc, argv);
+       p = strchr (admin_data.delete_revs + 2, ':');
+       if (p != NULL && isdigit ((unsigned char) p[1]))
+           check_numeric (p + 1, argc, argv);
+       else if (p != NULL && p[1] == ':' && isdigit ((unsigned char) p[2]))
+           check_numeric (p + 2, argc, argv);
+    }
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       /* We're the client side.  Fire up the remote server.  */
+       start_server ();
+       
+       ign_setup ();
+
+       /* Note that option_with_arg does not work for us, because some
+          of the options must be sent without a space between the option
+          and its argument.  */
+       if (admin_data.interactive)
+           error (1, 0, "-I option not useful with client/server");
+       if (admin_data.branch != NULL)
+           send_arg (admin_data.branch);
+       if (admin_data.comment != NULL)
+           send_arg (admin_data.comment);
+       if (admin_data.set_strict)
+           send_arg ("-L");
+       if (admin_data.set_nonstrict)
+           send_arg ("-U");
+       if (admin_data.delete_revs != NULL)
+           send_arg (admin_data.delete_revs);
+       if (admin_data.execute == EXECUTE)
+           send_arg ("--execute");
+       else if (admin_data.execute == NOEXECUTE)
+           send_arg ("--no-execute");
+       if (admin_data.desc != NULL)
+       {
+           char *p = admin_data.desc;
+           send_to_server ("Argument -t-", 0);
+           while (*p)
+           {
+               if (*p == '\n')
+               {
+                   send_to_server ("\012Argumentx ", 0);
+                   ++p;
+               }
+               else
+               {
+                   char *q = strchr (p, '\n');
+                   if (q == NULL) q = p + strlen (p);
+                   send_to_server (p, q - p);
+                   p = q;
+               }
+           }
+           send_to_server ("\012", 1);
+       }
+       /* Send this for all really_quiets since we know that it will be 
silently
+        * ignored when unneeded.  This supports old servers.
+        */
+       if (really_quiet)
+           send_arg ("-q");
+       if (admin_data.kflag != NULL)
+           send_arg (admin_data.kflag);
+
+       for (i = 0; i < admin_data.ac; ++i)
+           send_arg (admin_data.av[i]);
+
+       send_arg ("--");
+       send_files (argc, argv, 0, 0, SEND_NO_CONTENTS);
+       send_file_names (argc, argv, SEND_EXPAND_WILD);
+       send_to_server ("admin\012", 0);
+        err = get_responses_and_close ();
+       goto return_it;
+    }
+#endif /* CLIENT_SUPPORT */
+
+    lock_tree_promotably (argc, argv, 0, W_LOCAL, 0);
+
+    err = start_recursion
+           (admin_fileproc, admin_filesdoneproc, admin_dirproc,
+            NULL, &admin_data,
+            argc, argv, 0,
+            W_LOCAL, 0, CVS_LOCK_WRITE, NULL, 1, NULL);
+
+    Lock_Cleanup ();
+
+/* This just suppresses a warning from -Wall.  */
+#ifdef CLIENT_SUPPORT
+ return_it:
+#endif /* CLIENT_SUPPORT */
+    if (admin_data.branch != NULL)
+       free (admin_data.branch);
+    if (admin_data.comment != NULL)
+       free (admin_data.comment);
+    if (admin_data.delete_revs != NULL)
+       free (admin_data.delete_revs);
+    if (admin_data.kflag != NULL)
+       free (admin_data.kflag);
+    if (admin_data.desc != NULL)
+       free (admin_data.desc);
+    for (i = 0; i < admin_data.ac; ++i)
+       free (admin_data.av[i]);
+    if (admin_data.av != NULL)
+       free (admin_data.av);
+
+    return err;
+}
+
+
+
+/*
+ * Called to run "rcs" on a particular file.
+ */
+/* ARGSUSED */
+static int
+admin_fileproc (void *callerdat, struct file_info *finfo)
+{
+    struct admin_data *admin_data = callerdat;
+    Vers_TS *vers;
+    char *version;
+    int i;
+    int status = 0;
+    RCSNode *rcs, *rcs2;
+
+    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
+
+    version = vers->vn_user;
+    if (version != NULL && strcmp (version, "0") == 0)
+    {
+       error (0, 0, "cannot admin newly added file `%s'", finfo->file);
+       status = 1;
+       goto exitfunc;
+    }
+
+    rcs = vers->srcfile;
+    if (rcs == NULL)
+    {
+       if (!really_quiet)
+           error (0, 0, "nothing known about %s", finfo->file);
+       status = 1;
+       goto exitfunc;
+    }
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (!really_quiet)
+    {
+       cvs_output ("RCS file: ", 0);
+       cvs_output (rcs->path, 0);
+       cvs_output ("\n", 1);
+    }
+
+    if (admin_data->branch != NULL)
+    {
+       char *branch = &admin_data->branch[2];
+       if (*branch != '\0' && !isdigit ((unsigned char) *branch))
+       {
+           branch = RCS_whatbranch (rcs, admin_data->branch + 2);
+           if (branch == NULL)
+           {
+               error (0, 0, "%s: Symbolic name %s is undefined.",
+                             rcs->path, admin_data->branch + 2);
+               status = 1;
+           }
+       }
+       if (status == 0)
+           RCS_setbranch (rcs, branch);
+       if (branch != NULL && branch != &admin_data->branch[2])
+           free (branch);
+    }
+    if (admin_data->comment != NULL)
+    {
+       if (rcs->comment != NULL)
+           free (rcs->comment);
+       rcs->comment = xstrdup (admin_data->comment + 2);
+    }
+    if (admin_data->set_strict)
+       rcs->strict_locks = 1;
+    if (admin_data->set_nonstrict)
+       rcs->strict_locks = 0;
+    if (admin_data->delete_revs != NULL)
+    {
+       char *s, *t, *rev1, *rev2;
+       /* Set for :, clear for ::.  */
+       int inclusive;
+       char *t2;
+
+       s = admin_data->delete_revs + 2;
+       inclusive = 1;
+       t = strchr (s, ':');
+       if (t != NULL)
+       {
+           if (t[1] == ':')
+           {
+               inclusive = 0;
+               t2 = t + 2;
+           }
+           else
+               t2 = t + 1;
+       }
+
+       /* Note that we don't support '-' for ranges.  RCS considers it
+          obsolete and it is problematic with tags containing '-'.  "cvs log"
+          has made the same decision.  */
+
+       if (t == NULL)
+       {
+           /* -orev */
+           rev1 = xstrdup (s);
+           rev2 = xstrdup (s);
+       }
+       else if (t == s)
+       {
+           /* -o:rev2 */
+           rev1 = NULL;
+           rev2 = xstrdup (t2);
+       }
+       else
+       {
+           *t = '\0';
+           rev1 = xstrdup (s);
+           *t = ':';   /* probably unnecessary */
+           if (*t2 == '\0')
+               /* -orev1: */
+               rev2 = NULL;
+           else
+               /* -orev1:rev2 */
+               rev2 = xstrdup (t2);
+       }
+
+       if (rev1 == NULL && rev2 == NULL)
+       {
+           /* RCS segfaults if `-o:' is given */
+           error (0, 0, "no valid revisions specified in `%s' option",
+                  admin_data->delete_revs);
+           status = 1;
+       }
+       else
+       {
+           status |= RCS_delete_revs (rcs, rev1, rev2, inclusive);
+           if (rev1)
+               free (rev1);
+           if (rev2)
+               free (rev2);
+       }
+    }
+    if (admin_data->desc != NULL)
+    {
+       free (rcs->desc);
+       rcs->desc = xstrdup (admin_data->desc);
+    }
+    if (admin_data->kflag != NULL)
+    {
+       char *kflag = admin_data->kflag + 2;
+       char *oldexpand = RCS_getexpand (rcs);
+       if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0)
+           RCS_setexpand (rcs, kflag);
+    }
+
+    /* Handle miscellaneous options.  TODO: decide whether any or all
+       of these should have their own fields in the admin_data
+       structure. */
+    for (i = 0; i < admin_data->ac; ++i)
+    {
+       char *arg;
+       char *p, *rev, *revnum, *tag, *msg;
+       char **users;
+       int argc, u;
+       Node *n;
+       RCSVers *delta;
+       
+       arg = admin_data->av[i];
+       switch (arg[1])
+       {
+           case 'a': /* fall through */
+           case 'e':
+               line2argv (&argc, &users, arg + 2, " ,\t\n");
+               if (arg[1] == 'a')
+                   for (u = 0; u < argc; ++u)
+                       RCS_addaccess (rcs, users[u]);
+               else if (argc == 0)
+                   RCS_delaccess (rcs, NULL);
+               else
+                   for (u = 0; u < argc; ++u)
+                       RCS_delaccess (rcs, users[u]);
+               free_names (&argc, users);
+               break;
+           case 'A':
+
+               /* See admin-19a-admin and friends in sanity.sh for
+                  relative pathnames.  It makes sense to think in
+                  terms of a syntax which give pathnames relative to
+                  the repository or repository corresponding to the
+                  current directory or some such (and perhaps don't
+                  include ,v), but trying to worry about such things
+                  is a little pointless unless you first worry about
+                  whether "cvs admin -A" as a whole makes any sense
+                  (currently probably not, as access lists don't
+                  affect the behavior of CVS).  */
+
+               rcs2 = RCS_parsercsfile (arg + 2);
+               if (rcs2 == NULL)
+                   error (1, 0, "cannot continue");
+
+               p = xstrdup (RCS_getaccess (rcs2));
+               line2argv (&argc, &users, p, " \t\n");
+               free (p);
+               freercsnode (&rcs2);
+
+               for (u = 0; u < argc; ++u)
+                   RCS_addaccess (rcs, users[u]);
+               free_names (&argc, users);
+               break;
+           case 'n': /* fall through */
+           case 'N':
+               if (arg[2] == '\0')
+               {
+                   cvs_outerr ("missing symbolic name after ", 0);
+                   cvs_outerr (arg, 0);
+                   cvs_outerr ("\n", 1);
+                   break;
+               }
+               p = strchr (arg, ':');
+               if (p == NULL)
+               {
+                   if (RCS_deltag (rcs, arg + 2) != 0)
+                   {
+                       error (0, 0, "%s: Symbolic name %s is undefined.",
+                              rcs->path, 
+                              arg + 2);
+                       status = 1;
+                       continue;
+                   }
+                   break;
+               }
+               *p = '\0';
+               tag = xstrdup (arg + 2);
+               *p++ = ':';
+
+               /* Option `n' signals an error if this tag is already bound. */
+               if (arg[1] == 'n')
+               {
+                   n = findnode (RCS_symbols (rcs), tag);
+                   if (n != NULL)
+                   {
+                       error (0, 0,
+                              "%s: symbolic name %s already bound to %s",
+                              rcs->path,
+                              tag, (char *)n->data);
+                       status = 1;
+                       free (tag);
+                       continue;
+                   }
+               }
+
+                /* Attempt to perform the requested tagging.  */
+
+               if ((*p == 0 && (rev = RCS_head (rcs)))
+                    || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */
+               {
+                   RCS_check_tag (tag); /* exit if not a valid tag */
+                   RCS_settag (rcs, tag, rev);
+                   free (rev);
+               }
+                else
+               {
+                   if (!really_quiet)
+                       error (0, 0,
+                              "%s: Symbolic name or revision %s is undefined.",
+                              rcs->path, p);
+                   status = 1;
+               }
+               free (tag);
+               break;
+           case 's':
+               p = strchr (arg, ':');
+               if (p == NULL)
+               {
+                   tag = xstrdup (arg + 2);
+                   rev = RCS_head (rcs);
+                   if (!rev)
+                   {
+                       error (0, 0, "No head revision in archive file `%s'.",
+                              rcs->path);
+                       status = 1;
+                       continue;
+                   }
+               }
+               else
+               {
+                   *p = '\0';
+                   tag = xstrdup (arg + 2);
+                   *p++ = ':';
+                   rev = xstrdup (p);
+               }
+               revnum = RCS_gettag (rcs, rev, 0, NULL);
+               if (revnum != NULL)
+               {
+                   n = findnode (rcs->versions, revnum);
+                   free (revnum);
+               }
+               else
+                   n = NULL;
+               if (n == NULL)
+               {
+                   error (0, 0,
+                          "%s: can't set state of nonexisting revision %s",
+                          rcs->path,
+                          rev);
+                   free (rev);
+                   status = 1;
+                   continue;
+               }
+               free (rev);
+               delta = n->data;
+               free (delta->state);
+               delta->state = tag;
+               break;
+
+           case 'm':
+               p = strchr (arg, ':');
+               if (p == NULL)
+               {
+                   error (0, 0, "%s: -m option lacks revision number",
+                          rcs->path);
+                   status = 1;
+                   continue;
+               }
+               *p = '\0';      /* temporarily make arg+2 its own string */
+               rev = RCS_gettag (rcs, arg + 2, 1, NULL); /* Force tag match */
+               if (rev == NULL)
+               {
+                   error (0, 0, "%s: no such revision %s", rcs->path, arg+2);
+                   status = 1;
+                   *p = ':';   /* restore the full text of the -m argument */
+                   continue;
+               }
+               msg = p+1;
+
+               n = findnode (rcs->versions, rev);
+               /* tags may exist against non-existing versions */
+               if (n == NULL)
+               {
+                    error (0, 0, "%s: no such revision %s: %s",
+                           rcs->path, arg+2, rev);
+                   status = 1;
+                   *p = ':';   /* restore the full text of the -m argument */
+                   free (rev);
+                   continue;
+               }
+               *p = ':';       /* restore the full text of the -m argument */
+               free (rev);
+
+               delta = n->data;
+               if (delta->text == NULL)
+               {
+                   delta->text = xmalloc (sizeof (Deltatext));
+                   memset (delta->text, 0, sizeof (Deltatext));
+               }
+               delta->text->version = xstrdup (delta->version);
+               delta->text->log = make_message_rcsvalid (msg);
+               break;
+
+           case 'l':
+               status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0);
+               break;
+           case 'u':
+               status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0);
+               break;
+           default: assert(0); /* can't happen */
+       }
+    }
+
+    if (status == 0)
+    {
+       RCS_rewrite (rcs, NULL, NULL);
+
+        /*
+         * Update the execute bit for the file if requested.
+         */
+        if (admin_data->execute != AVOID)
+        {
+            struct stat sb;
+ 
+            if (stat (rcs->path, &sb) < 0)
+                error (0, errno, "cannot stat `%s'", rcs->path);
+            else
+            {
+                mode_t mode;
+ 
+                if (admin_data->execute == EXECUTE)
+                    mode = (sb.st_mode
+                            | (((sb.st_mode & S_IRUSR) ? S_IXUSR : 0)
+                                  | ((sb.st_mode & S_IRGRP) ? S_IXGRP : 0)
+                                  | ((sb.st_mode & S_IROTH) ? S_IXOTH : 0)));
+                else /* admin_data->execute == NOEXECUTE */
+                    mode = (sb.st_mode
+                            & ~(S_IEXEC | S_IXGRP | S_IXOTH));
+ 
+                if (mode == sb.st_mode)
+                    error (0, 0, "%s: already has mode=0%o",
+                          rcs->path, (unsigned int) mode);
+                else
+                {
+                    TRACE (TRACE_FLOW, "chmod(%s,0%o)", rcs->path,
+                           (unsigned int) mode);
+                    
+                   if (!noexec)
+                   {
+                       if (chmod (rcs->path, mode) < 0)
+                           error (0, errno,
+                                  "cannot change mode of file `%s'",
+                                  rcs->path);
+                   }
+                }
+            }
+        }
+
+       if (!really_quiet)
+           cvs_output ("done\n", 5);
+    }
+    else
+    {
+       /* Note that this message should only occur after another
+          message has given a more specific error.  The point of this
+          additional message is to make it clear that the previous problems
+          caused CVS to forget about the idea of modifying the RCS file.  */
+       if (!really_quiet)
+           error (0, 0, "RCS file for `%s' not modified.", finfo->file);
+       RCS_abandon (rcs);
+    }
+
+  exitfunc:
+    freevers_ts (&vers);
+    return status;
+}
+
+
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+admin_dirproc (void *callerdat, const char *dir, const char *repos,
+               const char *update_dir, List *entries)
+{
+    if (!quiet)
+       error (0, 0, "Administrating %s", update_dir);
+    return R_PROCESS;
+}
Index: ccvs/src/annotate.c
diff -u /dev/null ccvs/src/annotate.c:1.21.2.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/annotate.c Tue Jan 17 15:41:23 2006
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (c) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * Show last revision where each line modified
+ * 
+ * Prints the specified files with each line annotated with the revision
+ * number where it was last modified.  With no argument, annotates all
+ * all the files in the directory (recursive by default).
+ */
+
+#include "cvs.h"
+
+/* Options from the command line.  */
+
+static int force_tag_match = 1;
+static int force_binary = 0;
+static char *tag = NULL;
+static int tag_validated;
+static char *date = NULL;
+int annotate_width = 8;                /* Used in RCS_deltas() */
+
+static int is_rannotate;
+
+static int annotate_fileproc (void *callerdat, struct file_info *);
+static int rannotate_proc (int argc, char **argv, char *xwhere,
+                                char *mwhere, char *mfile, int shorten,
+                                int local, char *mname, char *msg);
+
+static const char *const annotate_usage[] =
+{
+    "Usage: %s %s [-lRfF] [-r rev] [-D date] [files...]\n",
+    "\t-l\tLocal directory only, no recursion.\n",
+    "\t-R\tProcess directories recursively.\n",
+    "\t-f\tUse head revision if tag/date not found.\n",
+    "\t-F\tAnnotate binary files.\n",
+    "\t-r rev\tAnnotate file as of specified revision/tag.\n",
+    "\t-D date\tAnnotate file as of specified date.\n",
+    "\t-w width\tModify width of username field (default 8, 0 < width < 
80).\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+/* Command to show the revision, date, and author where each line of a
+   file was modified.  */
+
+int
+annotate (int argc, char **argv)
+{
+    int local = 0;
+    int err = 0;
+    char *widthstr = NULL;
+    int c;
+
+    is_rannotate = (strcmp(cvs_cmd_name, "rannotate") == 0);
+
+    if (argc == -1)
+       usage (annotate_usage);
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+lr:D:fFRw:")) != -1)
+    {
+       switch (c)
+       {
+           case 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case 'r':
+               parse_tagdate (&tag, &date, optarg);
+               break;
+           case 'D':
+               if (date) free (date);
+               date = Make_Date (optarg);
+               break;
+           case 'f':
+               force_tag_match = 0;
+               break;
+           case 'F':
+               force_binary = 1;
+               break;
+           case 'w':
+               {
+                   int w = atoi(optarg);
+                   /* check bounds */
+                   if (0 < w && w < 80)
+                   {
+                       widthstr = optarg;
+                       annotate_width = w;
+                   }
+                   else
+                       error (1, 0, "-w %d is invalid, must be > 0 && < 80",
+                              w);
+               }
+               break;
+           case '?':
+           default:
+               usage (annotate_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       start_server ();
+
+       if (is_rannotate && !supported_request ("rannotate"))
+           error (1, 0, "server does not support rannotate");
+
+       ign_setup ();
+
+       if (local)
+           send_arg ("-l");
+       if (!force_tag_match)
+           send_arg ("-f");
+       if (force_binary)
+           send_arg ("-F");
+       option_with_arg ("-r", tag);
+       if (date)
+           client_senddate (date);
+       if (widthstr)
+           option_with_arg ("-w", widthstr);
+       send_arg ("--");
+       if (is_rannotate)
+       {
+           int i;
+           for (i = 0; i < argc; i++)
+               send_arg (argv[i]);
+           send_to_server ("rannotate\012", 0);
+       }
+       else
+       {
+           send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+           send_file_names (argc, argv, SEND_EXPAND_WILD);
+           send_to_server ("annotate\012", 0);
+       }
+       return get_responses_and_close ();
+    }
+#endif /* CLIENT_SUPPORT */
+
+    if (is_rannotate)
+    {
+       DBM *db;
+       int i;
+       db = open_module ();
+       for (i = 0; i < argc; i++)
+       {
+           err += do_module (db, argv[i], MISC, "Annotating", rannotate_proc,
+                             NULL, 0, local, 0, 0, NULL);
+       }
+       close_module (db);
+    }
+    else
+    {
+       err = rannotate_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0,
+                             local, NULL, NULL);
+    }
+
+    return err;
+}
+    
+
+static int
+rannotate_proc (int argc, char **argv, char *xwhere, char *mwhere,
+               char *mfile, int shorten, int local, char *mname, char *msg)
+{
+    /* Begin section which is identical to patch_proc--should this
+       be abstracted out somehow?  */
+    char *myargv[2];
+    int err = 0;
+    int which;
+    char *repository;
+    char *where;
+
+    if (is_rannotate)
+    {
+       repository = xmalloc (strlen (current_parsed_root->directory) + strlen 
(argv[0])
+                             + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
+       (void) sprintf (repository, "%s/%s", current_parsed_root->directory, 
argv[0]);
+       where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) 
+ 1)
+                        + 1);
+       (void) strcpy (where, argv[0]);
+
+       /* if mfile isn't null, we need to set up to do only part of the module 
*/
+       if (mfile != NULL)
+       {
+           char *cp;
+           char *path;
+
+           /* if the portion of the module is a path, put the dir part on 
repos */
+           if ((cp = strrchr (mfile, '/')) != NULL)
+           {
+               *cp = '\0';
+               (void) strcat (repository, "/");
+               (void) strcat (repository, mfile);
+               (void) strcat (where, "/");
+               (void) strcat (where, mfile);
+               mfile = cp + 1;
+           }
+
+           /* take care of the rest */
+           path = Xasprintf ("%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
+           {
+               myargv[0] = argv[0];
+               myargv[1] = mfile;
+               argc = 2;
+               argv = myargv;
+           }
+           free (path);
+       }
+
+       /* cd to the starting repository */
+       if (CVS_CHDIR (repository) < 0)
+       {
+           error (0, errno, "cannot chdir to %s", repository);
+           free (repository);
+           free (where);
+           return 1;
+       }
+       /* End section which is identical to patch_proc.  */
+
+       if (force_tag_match && tag != NULL)
+           which = W_REPOS | W_ATTIC;
+       else
+           which = W_REPOS;
+    }
+    else
+    {
+        where = NULL;
+        which = W_LOCAL;
+        repository = "";
+    }
+
+    if (tag != NULL && !tag_validated)
+    {
+       tag_check_valid (tag, argc - 1, argv + 1, local, 0, repository, false);
+       tag_validated = 1;
+    }
+
+    err = start_recursion (annotate_fileproc, NULL, NULL, NULL, NULL,
+                          argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
+                          where, 1, repository);
+    if (which & W_REPOS)
+       free (repository);
+    if (where != NULL)
+       free (where);
+    return err;
+}
+
+
+static int
+annotate_fileproc (void *callerdat, struct file_info *finfo)
+{
+    char *expand;
+    char *version = NULL;
+
+    if (finfo->rcs == NULL)
+        return 1;
+
+    if (finfo->rcs->flags & PARTIAL)
+        RCS_reparsercsfile (finfo->rcs, NULL, NULL);
+
+    expand = RCS_getexpand (finfo->rcs);
+
+    /* bogus annotate behavior needs to be kept for backward compatibility */
+    if (!tag)
+        version = Xasprintf (".%s", TAG_TRUNK);
+    else if (RCS_is_relative (tag))
+    {
+        version = Version_resolve_relTag (finfo, tag, !is_rannotate);
+        if (!version)
+        {
+            if (!really_quiet)
+                error (0, 0, "Cannot resolve relative tag: `%s'.", tag);
+            return 0;
+        }
+    }
+    else
+        version = xstrdup (tag);
+
+    char *tmp = version;
+    version = RCS_getversion (finfo->rcs, version, date, force_tag_match, 
NULL);
+    free (tmp);
+
+    if (!version)
+        return 0;
+
+    /* Distinguish output for various files if we are processing
+       several files.  */
+    cvs_outerr ("\nAnnotations for ", 0);
+    cvs_outerr (finfo->fullname, 0);
+    cvs_outerr ("\n***************\n", 0);
+
+    if (!force_binary && expand && expand[0] == 'b')
+    {
+        cvs_outerr ("Skipping binary file -- -F not specified.\n", 0);
+    }
+    else
+    {
+        RCS_deltas (finfo->rcs, NULL, NULL,
+            version, RCS_ANNOTATE, NULL, NULL, NULL, NULL);
+    }
+
+    free (version);
+    return 0;
+}
Index: ccvs/src/commit.c
diff -u /dev/null ccvs/src/commit.c:1.258.2.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/commit.c   Tue Jan 17 15:41:23 2006
@@ -0,0 +1,2479 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * 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"
+#include "hardlink.h"
+
+static Dtype check_direntproc (void *callerdat, const char *dir,
+                               const char *repos, const char *update_dir,
+                               List *entries);
+static int check_fileproc (void *callerdat, struct file_info *finfo);
+static int check_filesdoneproc (void *callerdat, int err, const char *repos,
+                               const char *update_dir, List *entries);
+static int checkaddfile (const char *file, const char *repository,
+                         const char *tag, const char *options,
+                         RCSNode **rcsnode);
+static Dtype commit_direntproc (void *callerdat, const char *dir,
+                                const char *repos, const char *update_dir,
+                                List *entries);
+static int commit_dirleaveproc (void *callerdat, const char *dir, int err,
+                               const char *update_dir, List *entries);
+static int commit_fileproc (void *callerdat, struct file_info *finfo);
+static int commit_filesdoneproc (void *callerdat, int err,
+                                 const char *repository,
+                                const char *update_dir, List *entries);
+static int finaladd (struct file_info *finfo, char *revision, char *tag,
+                    char *options);
+static int findmaxrev (Node * p, void *closure);
+static int lock_RCS (const char *user, RCSNode *rcs, const char *rev,
+                     const char *repository);
+static int precommit_list_to_args_proc (Node * p, void *closure);
+static int precommit_proc (const char *repository, const char *filter,
+                           void *closure);
+static int remove_file (struct file_info *finfo, char *tag,
+                       char *message);
+static void fixaddfile (const char *rcs);
+static void fixbranch (RCSNode *, char *branch);
+static void unlockrcs (RCSNode *rcs);
+static void ci_delproc (Node *p);
+static void masterlist_delproc (Node *p);
+
+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 check_valid_edit = 0;
+static int force_ci = 0;
+static int got_message;
+static int aflag;
+static char *saved_tag;
+static char *write_dirtag;
+static int write_dirnonbranch;
+static char *logfile;
+static List *mulist;
+static char *saved_message;
+static time_t last_register_time;
+
+static const char *const commit_usage[] =
+{
+    "Usage: %s %s [-cRlf] [-m msg | -F logfile] [-r rev] files...\n",
+    "    -c          Check for valid edits before committing.\n",
+    "    -R          Process directories recursively.\n",
+    "    -l          Local directory only (not recursive).\n",
+    "    -f          Force the file to be committed; disables recursion.\n",
+    "    -F logfile  Read the log message from file.\n",
+    "    -m msg      Log message.\n",
+    "    -r rev      Commit to this branch or trunk revision.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+#ifdef CLIENT_SUPPORT
+/* Identify a file which needs "? foo" or a Questionable request.  */
+struct question
+{
+    /* The two fields for the Directory request.  */
+    char *dir;
+    char *repos;
+
+    /* The file name.  */
+    char *file;
+
+    struct question *next;
+};
+
+struct find_data
+{
+    List *ulist;
+    int argc;
+    char **argv;
+
+    /* This is used from dirent to filesdone time, for each directory,
+       to make a list of files we have already seen.  */
+    List *ignlist;
+
+    /* Linked list of files which need "? foo" or a Questionable request.  */
+    struct question *questionables;
+
+    /* Only good within functions called from the filesdoneproc.  Stores
+       the repository (pointer into storage managed by the recursion
+       processor.  */
+    const char *repository;
+
+    /* Non-zero if we should force the commit.  This is enabled by
+       either -f or -r options, unlike force_ci which is just -f.  */
+    int force;
+};
+
+
+
+static Dtype
+find_dirent_proc (void *callerdat, const char *dir, const char *repository,
+                  const char *update_dir, List *entries)
+{
+    struct find_data *find_data = callerdat;
+
+    /* This check seems to slowly be creeping throughout CVS (update
+       and send_dirent_proc by CVS 1.5, diff in 31 Oct 1995.  My guess
+       is that it (or some variant thereof) should go in all the
+       dirent procs.  Unless someone has some better idea...  */
+    if (!isdir (dir))
+       return R_SKIP_ALL;
+
+    /* initialize the ignore list for this directory */
+    find_data->ignlist = getlist ();
+
+    /* Print the same warm fuzzy as in check_direntproc, since that
+       code will never be run during client/server operation and we
+       want the messages to match. */
+    if (!quiet)
+       error (0, 0, "Examining %s", update_dir);
+
+    return R_PROCESS;
+}
+
+
+
+/* Here as a static until we get around to fixing ignore_files to pass
+   it along as an argument.  */
+static struct find_data *find_data_static;
+
+
+
+static void
+find_ignproc (const char *file, const char *dir)
+{
+    struct question *p;
+
+    p = xmalloc (sizeof (struct question));
+    p->dir = xstrdup (dir);
+    p->repos = xstrdup (find_data_static->repository);
+    p->file = xstrdup (file);
+    p->next = find_data_static->questionables;
+    find_data_static->questionables = p;
+}
+
+
+
+static int
+find_filesdoneproc (void *callerdat, int err, const char *repository,
+                    const char *update_dir, List *entries)
+{
+    struct find_data *find_data = callerdat;
+    find_data->repository = repository;
+
+    /* if this directory has an ignore list, process it then free it */
+    if (find_data->ignlist)
+    {
+       find_data_static = find_data;
+       ignore_files (find_data->ignlist, entries, update_dir, find_ignproc);
+       dellist (&find_data->ignlist);
+    }
+
+    find_data->repository = NULL;
+
+    return err;
+}
+
+
+
+/* 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 (void *callerdat, struct file_info *finfo)
+{
+    Vers_TS *vers;
+    enum classify_type status;
+    Node *node;
+    struct find_data *args = callerdat;
+    struct logfile_info *data;
+    struct file_info xfinfo;
+
+    /* if this directory has an ignore list, add this file to it */
+    if (args->ignlist)
+    {
+       Node *p;
+
+       p = getnode ();
+       p->type = FILES;
+       p->key = xstrdup (finfo->file);
+       if (addnode (args->ignlist, p) != 0)
+           freenode (p);
+    }
+
+    xfinfo = *finfo;
+    xfinfo.repository = NULL;
+    xfinfo.rcs = NULL;
+
+    vers = Version_TS (&xfinfo, NULL, saved_tag, NULL, 0, 0);
+    if (vers->vn_user == NULL)
+    {
+       if (vers->ts_user == NULL)
+           error (0, 0, "nothing known about `%s'", finfo->fullname);
+       else
+           error (0, 0, "use `%s add' to create an entry for `%s'",
+                  program_name, finfo->fullname);
+       freevers_ts (&vers);
+       return 1;
+    }
+    if (vers->vn_user[0] == '-')
+    {
+       if (vers->ts_user != NULL)
+       {
+           error (0, 0,
+                  "`%s' should be removed and is still there (or is back"
+                  " again)", finfo->fullname);
+           freevers_ts (&vers);
+           return 1;
+       }
+       /* else */
+       status = T_REMOVED;
+    }
+    else if (strcmp (vers->vn_user, "0") == 0)
+    {
+       if (vers->ts_user == NULL)
+       {
+           /* This happens when one has `cvs add'ed a file, but it no
+              longer exists in the working directory at commit time.
+              FIXME: What classify_file does in this case is print
+              "new-born %s has disappeared" and removes the entry.
+              We probably should do the same.  */
+           if (!really_quiet)
+               error (0, 0, "warning: new-born %s has disappeared",
+                      finfo->fullname);
+           status = T_REMOVE_ENTRY;
+       }
+       else
+           status = T_ADDED;
+    }
+    else if (vers->ts_user == NULL)
+    {
+       /* FIXME: What classify_file does in this case is print
+          "%s was lost".  We probably should do the same.  */
+       freevers_ts (&vers);
+       return 0;
+    }
+    else if (vers->ts_rcs != NULL
+            && (args->force || strcmp (vers->ts_user, vers->ts_rcs) != 0))
+       /* If we are forcing commits, pretend that the file is
+           modified.  */
+       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).  */
+       freevers_ts (&vers);
+       return 0;
+    }
+
+    node = getnode ();
+    node->key = xstrdup (finfo->fullname);
+
+    data = xmalloc (sizeof (struct logfile_info));
+    data->type = status;
+    data->tag = xstrdup (vers->tag);
+    data->rev_old = data->rev_new = NULL;
+
+    node->type = UPDATE;
+    node->delproc = update_delproc;
+    node->data = data;
+    (void)addnode (args->ulist, node);
+
+    ++args->argc;
+
+    freevers_ts (&vers);
+    return 0;
+}
+
+
+
+static int
+copy_ulist (Node *node, void *data)
+{
+    struct find_data *args = data;
+    args->argv[args->argc++] = node->key;
+    return 0;
+}
+#endif /* CLIENT_SUPPORT */
+
+
+
+#ifdef SERVER_SUPPORT
+# define COMMIT_OPTIONS "+cnlRm:fF:r:"
+#else /* !SERVER_SUPPORT */
+# define COMMIT_OPTIONS "+clRm:fF:r:"
+#endif /* SERVER_SUPPORT */
+int
+commit (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.
+     */
+    /* FIXME: Shouldn't this check be much more closely related to the
+       readonly user stuff (CVSROOT/readers, &c).  That is, why should
+       root be able to "cvs init", "cvs import", &c, but not "cvs ci"?  */
+    /* Who we are on the client side doesn't affect logging.  */
+    if (geteuid () == (uid_t) 0 && !current_parsed_root->isremote)
+    {
+       struct passwd *pw;
+
+       if ((pw = getpwnam (getcaller ())) == NULL)
+           error (1, 0,
+                   "your apparent username (%s) is unknown to this system",
+                   getcaller ());
+       if (pw->pw_uid == (uid_t) 0)
+           error (1, 0, "'root' is not allowed to commit files");
+    }
+#endif /* CVS_BADROOT */
+
+    optind = 0;
+    while ((c = getopt (argc, argv, COMMIT_OPTIONS)) != -1)
+    {
+       switch (c)
+       {
+            case 'c':
+                check_valid_edit = 1;
+                break;
+#ifdef SERVER_SUPPORT
+           case 'n':
+               /* Silently ignore -n for compatibility with old
+                * clients.
+                */
+               if (!server_active) error(0, 0, "the `-n' option is obsolete");
+               break;
+#endif /* SERVER_SUPPORT */
+           case 'm':
+#ifdef FORCE_USE_EDITOR
+               use_editor = 1;
+#else
+               use_editor = 0;
+#endif
+               if (saved_message)
+               {
+                   free (saved_message);
+                   saved_message = NULL;
+               }
+
+               saved_message = xstrdup (optarg);
+               break;
+           case 'r':
+               if (saved_tag)
+                   free (saved_tag);
+               saved_tag = xstrdup (optarg);
+               break;
+           case 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case 'f':
+               force_ci = 1;
+                check_valid_edit = 0;
+               local = 1;              /* also disable recursion */
+               break;
+           case 'F':
+#ifdef FORCE_USE_EDITOR
+               use_editor = 1;
+#else
+               use_editor = 0;
+#endif
+               logfile = optarg;
+               break;
+           case '?':
+           default:
+               usage (commit_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    /* numeric specified revision means we ignore sticky tags... */
+    if (saved_tag && isdigit ((unsigned char) *saved_tag))
+    {
+       char *p = saved_tag + strlen (saved_tag);
+       aflag = 1;
+       /* strip trailing dots and leading zeros */
+       while (*--p == '.') ;
+       p[1] = '\0';
+       while (saved_tag[0] == '0' && isdigit ((unsigned char) saved_tag[1]))
+           ++saved_tag;
+    }
+
+    /* some checks related to the "-F logfile" option */
+    if (logfile)
+    {
+       size_t size = 0, len;
+
+       if (saved_message)
+           error (1, 0, "cannot specify both a message and a log file");
+
+       get_file (logfile, logfile, "r", &saved_message, &size, &len);
+    }
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       struct find_data find_args;
+
+       ign_setup ();
+
+       find_args.ulist = getlist ();
+       find_args.argc = 0;
+       find_args.questionables = NULL;
+       find_args.ignlist = NULL;
+       find_args.repository = NULL;
+
+       /* It is possible that only a numeric tag should set this.
+          I haven't really thought about it much.
+          Anyway, I suspect that setting it unnecessarily only causes
+          a little unneeded network traffic.  */
+       find_args.force = force_ci || saved_tag != NULL;
+
+       err = start_recursion
+           (find_fileproc, find_filesdoneproc, find_dirent_proc, NULL,
+            &find_args, argc, argv, local, W_LOCAL, 0, CVS_LOCK_NONE,
+            NULL, 0, NULL );
+       if (err)
+           error (1, 0, "correct above errors first!");
+
+       if (find_args.argc == 0)
+       {
+           /* Nothing to commit.  Exit now without contacting the
+              server (note that this means that we won't print "?
+              foo" for files which merit it, because we don't know
+              what is in the CVSROOT/cvsignore file).  */
+           dellist (&find_args.ulist);
+           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.  */
+       if (size_overflow_p (xtimes (find_args.argc, sizeof (char **))))
+       {
+           find_args.argc = 0;
+           return 0;
+       }
+       find_args.argv = xnmalloc (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.).
+
+          On the other hand, calling start_server before do_editor
+          means that we chew up server resources the whole time that
+          the user has the editor open (hours or days if the user
+          forgets about it), which seems dubious.  */
+       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 (".", &saved_message, NULL, find_args.ulist);
+
+       /* We always send some sort of message, even if empty.  */
+       option_with_arg ("-m", saved_message ? saved_message : "");
+
+       /* OK, now process all the questionable files we have been saving
+          up.  */
+       {
+           struct question *p;
+           struct question *q;
+
+           p = find_args.questionables;
+           while (p != NULL)
+           {
+               if (ign_inhibit_server || !supported_request ("Questionable"))
+               {
+                   cvs_output ("? ", 2);
+                   if (p->dir[0] != '\0')
+                   {
+                       cvs_output (p->dir, 0);
+                       cvs_output ("/", 1);
+                   }
+                   cvs_output (p->file, 0);
+                   cvs_output ("\n", 1);
+               }
+               else
+               {
+                   /* This used to send the Directory line of its own accord,
+                    * but skipped some of the other processing like checking
+                    * for whether the server would accept "Relative-directory"
+                    * requests.  Relying on send_a_repository() to do this
+                    * picks up these checks but also:
+                    *
+                    *   1. Causes the "Directory" request to be sent only once
+                    *      per directory.
+                    *   2. Causes the global TOPLEVEL_REPOS to be set.
+                    *   3. Causes "Static-directory" and "Sticky" requests
+                    *      to sometimes be sent.
+                    *
+                    * (1) is almost certainly a plus.  (2) & (3) may or may
+                    * not be useful sometimes, and will ocassionally cause a
+                    * little extra network traffic.  The additional network
+                    * traffic is probably already saved several times over and
+                    * certainly cancelled out via the multiple "Directory"
+                    * request suppression of (1).
+                    */
+                   send_a_repository (p->dir, p->repos, p->dir);
+
+                   send_to_server ("Questionable ", 0);
+                   send_to_server (p->file, 0);
+                   send_to_server ("\012", 1);
+               }
+               free (p->dir);
+               free (p->repos);
+               free (p->file);
+               q = p->next;
+               free (p);
+               p = q;
+           }
+       }
+
+       if (local)
+           send_arg ("-l");
+        if (check_valid_edit)
+            send_arg ("-c");
+       if (force_ci)
+           send_arg ("-f");
+       option_with_arg ("-r", saved_tag);
+       send_arg ("--");
+
+       /* FIXME: This whole find_args.force/SEND_FORCE business is a
+          kludge.  It would seem to be a server bug that we have to
+          say that files are modified when they are not.  This makes
+          "cvs commit -r 2" across a whole bunch of files a very slow
+          operation (and it isn't documented in cvsclient.texi).  I
+          haven't looked at the server code carefully enough to be
+          _sure_ why this is needed, but if it is because the "ci"
+          program, which we used to call, wanted the file to exist,
+          then it would be relatively simple to fix in the server.  */
+       send_files (find_args.argc, find_args.argv, local, 0,
+                   find_args.force ? SEND_FORCE : 0);
+
+       /* 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);
+       free (find_args.argv);
+       dellist (&find_args.ulist);
+
+       send_to_server ("ci\012", 0);
+       err = get_responses_and_close ();
+       if (err != 0 && use_editor && saved_message != NULL)
+       {
+           /* If there was an error, don't nuke the user's carefully
+              constructed prose.  This is something of a kludge; a better
+              solution is probably more along the lines of #150 in TODO
+              (doing a second up-to-date check before accepting the
+              log message has also been suggested, but that seems kind of
+              iffy because the real up-to-date check could still fail,
+              another error could occur, &c.  Also, a second check would
+              slow things down).  */
+
+           char *fname;
+           FILE *fp;
+
+           fp = cvs_temp_file (&fname);
+           if (fp == NULL)
+               error (1, 0, "cannot create temporary file %s", fname);
+           if (fwrite (saved_message, 1, strlen (saved_message), fp)
+               != strlen (saved_message))
+               error (1, errno, "cannot write temporary file %s", fname);
+           if (fclose (fp) < 0)
+               error (0, errno, "cannot close temporary file %s", fname);
+           error (0, 0, "saving log message in %s", fname);
+           free (fname);
+       }
+       return err;
+    }
+#endif
+
+    if (saved_tag != NULL)
+       tag_check_valid (saved_tag, argc, argv, local, aflag, "", false);
+
+    /* XXX - this is not the perfect check for this */
+    if (argc <= 0)
+       write_dirtag = saved_tag;
+
+    wrap_setup ();
+
+    lock_tree_promotably (argc, argv, local, W_LOCAL, aflag);
+
+    /*
+     * Set up the master update list and hard link list
+     */
+    mulist = getlist ();
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    if (preserve_perms)
+    {
+       hardlist = getlist ();
+
+       /*
+        * We need to save the working directory so that
+        * check_fileproc can construct a full pathname for each file.
+        */
+       working_dir = xgetcwd ();
+    }
+#endif
+
+    /*
+     * Run the recursion processor to verify the files are all up-to-date
+     */
+    err = start_recursion (check_fileproc, check_filesdoneproc,
+                           check_direntproc, NULL, NULL, argc, argv, local,
+                           W_LOCAL, aflag, CVS_LOCK_NONE, NULL, 1, NULL);
+    if (err)
+       error (1, 0, "correct above errors first!");
+
+    /*
+     * Run the recursion processor to commit the files
+     */
+    write_dirnonbranch = 0;
+    if (noexec == 0)
+       err = start_recursion (commit_fileproc, commit_filesdoneproc,
+                               commit_direntproc, commit_dirleaveproc, NULL,
+                               argc, argv, local, W_LOCAL, aflag,
+                               CVS_LOCK_WRITE, NULL, 1, NULL);
+
+    /*
+     * Unlock all the dirs and clean up
+     */
+    Lock_Cleanup ();
+    dellist (&mulist);
+
+    /* add the commitid to val-tags
+     */
+    char *commitid = Xasprintf ("@%s", global_session_id);
+    tag_check_valid (commitid, argc, argv, local, aflag, "", true);
+    free (commitid);
+
+    /* see if we need to sleep before returning to avoid time-stamp races */
+    if (!server_active && last_register_time)
+    {
+       sleep_past (last_register_time);
+    }
+
+    return err;
+}
+
+
+
+/* This routine determines the status of a given file and retrieves
+   the version information that is associated with that file. */
+
+static
+Ctype
+classify_file_internal (struct file_info *finfo, Vers_TS **vers)
+{
+    int save_noexec, save_quiet, save_really_quiet;
+    Ctype status;
+
+    /* FIXME: Do we need to save quiet as well as really_quiet?  Last
+       time I glanced at Classify_File I only saw it looking at really_quiet
+       not quiet.  */
+    save_noexec = noexec;
+    save_quiet = quiet;
+    save_really_quiet = really_quiet;
+    noexec = quiet = really_quiet = 1;
+
+    /* handle specified numeric revision specially */
+    if (saved_tag && isdigit ((unsigned char) *saved_tag))
+    {
+       /* If the tag is for the trunk, make sure we're at the head */
+       if (numdots (saved_tag) < 2)
+       {
+           status = Classify_File (finfo, NULL, NULL,
+                                   NULL, 1, aflag, vers, 0);
+           if (status == T_UPTODATE || status == T_MODIFIED ||
+               status == T_ADDED)
+           {
+               Ctype xstatus;
+
+               freevers_ts (vers);
+               xstatus = Classify_File (finfo, saved_tag, NULL,
+                                        NULL, 1, aflag, vers, 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 (saved_tag);
+           if ((numdots (xtag) & 1) != 0)
+           {
+               cp = strrchr (xtag, '.');
+               *cp = '\0';
+           }
+           status = Classify_File (finfo, xtag, NULL,
+                                   NULL, 1, aflag, vers, 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, xtag, NULL,
+                                       NULL, 1, aflag, vers, 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 (saved_tag);
+           free (xtag);
+       }
+    }
+    else
+       status = Classify_File (finfo, saved_tag, NULL, NULL, 1, 0, vers, 0);
+    noexec = save_noexec;
+    quiet = save_quiet;
+    really_quiet = save_really_quiet;
+
+    return status;
+}
+
+
+
+/*
+ * Check to see if a file is ok to commit and make sure all files are
+ * up-to-date
+ */
+/* ARGSUSED */
+static int
+check_fileproc (void *callerdat, struct file_info *finfo)
+{
+    Ctype status;
+    const char *xdir;
+    Node *p;
+    List *ulist, *cilist;
+    Vers_TS *vers;
+    struct commit_info *ci;
+    struct logfile_info *li;
+    int retval = 1;
+
+    size_t cvsroot_len = strlen (current_parsed_root->directory);
+
+    if (!finfo->repository)
+    {
+       error (0, 0, "nothing known about `%s'", finfo->fullname);
+       return 1;
+    }
+
+    if (strncmp (finfo->repository, current_parsed_root->directory,
+                 cvsroot_len) == 0
+       && ISSLASH (finfo->repository[cvsroot_len])
+       && strncmp (finfo->repository + cvsroot_len + 1,
+                   CVSROOTADM,
+                   sizeof (CVSROOTADM) - 1) == 0
+       && ISSLASH (finfo->repository[cvsroot_len + sizeof (CVSROOTADM)])
+       && strcmp (finfo->repository + cvsroot_len + sizeof (CVSROOTADM) + 1,
+                  CVSNULLREPOS) == 0
+       )
+       error (1, 0, "cannot check in to %s", finfo->repository);
+
+    status = classify_file_internal (finfo, &vers);
+
+    /*
+     * 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:
+       case T_PATCH:
+       case T_NEEDS_MERGE:
+       case T_REMOVE_ENTRY:
+           error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname);
+           goto out;
+       case T_CONFLICT:
+       case T_MODIFIED:
+       case T_ADDED:
+       case T_REMOVED:
+        {
+            char *editor;
+
+           /*
+            * 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, file must not exist and its entry
+            *    can't have a numeric sticky tag.
+            *  - if status is T_ADDED, rcs file must not exist unless on
+            *    a branch or head is dead
+            *  - 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 (!saved_tag || !isdigit ((unsigned char) *saved_tag))
+           {
+               if (vers->date)
+               {
+                   error (0, 0,
+                          "cannot commit with sticky date for file `%s'",
+                          finfo->fullname);
+                   goto out;
+               }
+               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);
+                   goto out;
+               }
+           }
+           if (status == T_CONFLICT && !force_ci)
+           {
+               error (0, 0,
+                     "file `%s' had a conflict and has not been modified",
+                      finfo->fullname);
+               goto out;
+           }
+           if (status == T_MODIFIED && !force_ci && !really_quiet
+               && file_has_markers (finfo))
+           {
+               /* Make this a warning, not an error, because we have
+                  no way of knowing whether the "conflict indicators"
+                  are really from a conflict or whether they are part
+                  of the document itself (cvs.texinfo and sanity.sh in
+                  CVS itself, for example, tend to want to have strings
+                  like ">>>>>>>" at the start of a line).  Making people
+                  kludge this the way they need to kludge keyword
+                  expansion seems undesirable.  And it is worse than
+                  keyword expansion, because there is no -ko
+                  analogue.  */
+               error (0, 0,
+                      "\
+warning: file `%s' seems to still contain conflict indicators",
+                      finfo->fullname);
+           }
+
+           if (status == T_REMOVED)
+           {
+               if (vers->ts_user != NULL)
+               {
+                   error (0, 0,
+                          "`%s' should be removed and is still there (or is"
+                          " back again)", finfo->fullname);
+                   goto out;
+               }
+
+               if (vers->tag && isdigit ((unsigned char) *vers->tag))
+               {
+                   /* Remove also tries to forbid this, but we should check
+                      here.  I'm only _sure_ about somewhat obscure cases
+                      (hacking the Entries file, using an old version of
+                      CVS for the remove and a new one for the commit), but
+                      there might be other cases.  */
+                   error (0, 0,
+                          "cannot remove file `%s' which has a numeric sticky"
+                          " tag of `%s'", finfo->fullname, vers->tag);
+                   freevers_ts (&vers);
+                   goto out;
+               }
+           }
+           if (status == T_ADDED)
+           {
+               if (vers->tag == NULL)
+               {
+                   if (finfo->rcs != NULL &&
+                       !RCS_isdead (finfo->rcs, finfo->rcs->head))
+                   {
+                       error (0, 0,
+                   "cannot add file `%s' when RCS file `%s' already exists",
+                              finfo->fullname, finfo->rcs->path);
+                       goto out;
+                   }
+               }
+               else if (isdigit ((unsigned char) *vers->tag) &&
+                   numdots (vers->tag) > 1)
+               {
+                   error (0, 0,
+               "cannot add file `%s' with revision `%s'; must be on trunk",
+                              finfo->fullname, vers->tag);
+                   goto out;
+               }
+           }
+
+           /* 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;
+
+               ml = xmalloc (sizeof (struct master_lists));
+               ulist = ml->ulist = getlist ();
+               cilist = ml->cilist = getlist ();
+
+               p = getnode ();
+               p->key = xstrdup (xdir);
+               p->type = UPDATE;
+               p->data = 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;
+           li = xmalloc (sizeof (struct logfile_info));
+           li->type = status;
+
+           if (check_valid_edit)
+            {
+                char *editors = NULL;
+
+               editor = NULL;
+                editors = fileattr_get0 (finfo->file, "_editors");
+                if (editors != NULL)
+                {
+                    char *caller = getcaller ();
+                    char *p = NULL;
+                    char *p0 = NULL;
+
+                    p = editors;
+                    p0 = p;
+                    while (*p != '\0')
+                    {
+                        p = strchr (p, '>');
+                        if (p == NULL)
+                        {
+                            break;
+                        }
+                        *p = '\0';
+                        if (strcmp (caller, p0) == 0)
+                        {
+                            break;
+                        }
+                        p = strchr (p + 1, ',');
+                        if (p == NULL)
+                        {
+                            break;
+                        }
+                        ++p;
+                        p0 = p;
+                    }
+
+                    if (strcmp (caller, p0) == 0)
+                    {
+                        editor = caller;
+                    }
+
+                    free (editors);
+                }
+            }
+
+            if (check_valid_edit && editor == NULL)
+            {
+                error (0, 0, "Valid edit does not exist for %s",
+                       finfo->fullname);
+                freevers_ts (&vers);
+                return 1;
+            }
+
+           li->tag = xstrdup (vers->tag);
+           li->rev_old = xstrdup (vers->vn_rcs);
+           li->rev_new = NULL;
+           p->data = li;
+           (void) addnode (ulist, p);
+
+           p = getnode ();
+           p->key = xstrdup (finfo->file);
+           p->type = UPDATE;
+           p->delproc = ci_delproc;
+           ci = xmalloc (sizeof (struct commit_info));
+           ci->status = status;
+           if (vers->tag)
+               if (isdigit ((unsigned char) *vers->tag))
+                   ci->rev = xstrdup (vers->tag);
+               else
+                   ci->rev = RCS_whatbranch (finfo->rcs, vers->tag);
+           else
+               ci->rev = NULL;
+           ci->tag = xstrdup (vers->tag);
+           ci->options = xstrdup (vers->options);
+           p->data = ci;
+           (void) addnode (cilist, p);
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+           if (preserve_perms)
+           {
+               /* Add this file to hardlist, indexed on its inode.  When
+                  we are done, we can find out what files are hardlinked
+                  to a given file by looking up its inode in hardlist. */
+               char *fullpath;
+               Node *linkp;
+               struct hardlink_info *hlinfo;
+
+               /* Get the full pathname of the current file. */
+               fullpath = Xasprintf ("%s/%s", working_dir, finfo->fullname);
+
+               /* To permit following links in subdirectories, files
+                   are keyed on finfo->fullname, not on finfo->name. */
+               linkp = lookup_file_by_inode (fullpath);
+
+               /* If linkp is NULL, the file doesn't exist... maybe
+                  we're doing a remove operation? */
+               if (linkp != NULL)
+               {
+                   /* Create a new hardlink_info node, which will record
+                      the current file's status and the links listed in its
+                      `hardlinks' delta field.  We will append this
+                      hardlink_info node to the appropriate hardlist entry. */
+                   hlinfo = xmalloc (sizeof (struct hardlink_info));
+                   hlinfo->status = status;
+                   linkp->data = hlinfo;
+               }
+           }
+#endif
+
+           break;
+        }
+
+       case T_UNKNOWN:
+           error (0, 0, "nothing known about `%s'", finfo->fullname);
+           goto out;
+       case T_UPTODATE:
+           break;
+       default:
+           error (0, 0, "CVS internal error: unknown status %d", status);
+           break;
+    }
+
+    retval = 0;
+
+ out:
+
+    freevers_ts (&vers);
+    return retval;
+}
+
+
+
+/*
+ * By default, return the code that tells do_recursion to examine all
+ * directories
+ */
+/* ARGSUSED */
+static Dtype
+check_direntproc (void *callerdat, const char *dir, const char *repos,
+                  const char *update_dir, List *entries)
+{
+    if (!isdir (dir))
+       return R_SKIP_ALL;
+
+    if (!quiet)
+       error (0, 0, "Examining %s", update_dir);
+
+    return R_PROCESS;
+}
+
+
+
+/*
+ * Walklist proc to generate an arg list from the line in commitinfo
+ */
+static int
+precommit_list_to_args_proc (p, closure)
+    Node *p;
+    void *closure;
+{
+    struct format_cmdline_walklist_closure *c = closure;
+    struct logfile_info *li;
+    char *arg = NULL;
+    const char *f;
+    char *d;
+    size_t doff;
+
+    if (p->data == NULL) return 1;
+
+    f = c->format;
+    d = *c->d;
+    /* foreach requested attribute */
+    while (*f)
+    {
+       switch (*f++)
+       {
+           case 's':
+               li = p->data;
+               if (li->type == T_ADDED
+                       || li->type == T_MODIFIED
+                       || li->type == T_REMOVED)
+               {
+                   arg = p->key;
+               }
+               break;
+           default:
+               error (1, 0,
+                      "Unknown format character or not a list attribute: %c",
+                      f[-1]);
+               /* NOTREACHED */
+               break;
+       }
+       /* copy the attribute into an argument */
+       if (c->quotes)
+       {
+           arg = cmdlineescape (c->quotes, arg);
+       }
+       else
+       {
+           arg = cmdlinequote ('"', arg);
+       }
+       doff = d - *c->buf;
+       expand_string (c->buf, c->length, doff + strlen (arg));
+       d = *c->buf + doff;
+       strncpy (d, arg, strlen (arg));
+       d += strlen (arg);
+       free (arg);
+
+       /* and always put the extra space on.  we'll have to back up a char
+        * when we're done, but that seems most efficient
+        */
+       doff = d - *c->buf;
+       expand_string (c->buf, c->length, doff + 1);
+       d = *c->buf + doff;
+       *d++ = ' ';
+    }
+    /* correct our original pointer into the buff */
+    *c->d = d;
+    return 0;
+}
+
+
+
+/*
+ * Callback proc for pre-commit checking
+ */
+static int
+precommit_proc (const char *repository, const char *filter, void *closure)
+{
+    char *newfilter = NULL;
+    char *cmdline;
+    const char *srepos = Short_Repository (repository);
+    List *ulist = closure;
+
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+    if (!strchr (filter, '%'))
+    {
+       error (0, 0,
+               "warning: commitinfo line contains no format strings:\n"
+               "    \"%s\"\n"
+               "Appending defaults (\" %%r/%%p %%s\"), but please be aware 
that this usage is\n"
+               "deprecated.", filter);
+       newfilter = Xasprintf ("%s %%r/%%p %%s", filter);
+       filter = newfilter;
+    }
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+
+    /*
+     * Cast any NULL arguments as appropriate pointers as this is an
+     * stdarg function and we need to be certain the caller gets what
+     * is expected.
+     */
+    cmdline = format_cmdline (
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                             false, srepos,
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                             filter,
+                             "c", "s", cvs_cmd_name,
+#ifdef SERVER_SUPPORT
+                             "R", "s", referrer ? referrer->original : "NONE",
+#endif /* SERVER_SUPPORT */
+                             "p", "s", srepos,
+                             "r", "s", current_parsed_root->directory,
+                             "s", ",", ulist, precommit_list_to_args_proc,
+                             (void *) NULL,
+                             (char *) NULL);
+
+    if (newfilter) free (newfilter);
+
+    if (!cmdline || !strlen (cmdline))
+    {
+       if (cmdline) free (cmdline);
+       error (0, 0, "precommit proc resolved to the empty string!");
+       return 1;
+    }
+
+    run_setup (cmdline);
+    free (cmdline);
+
+    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 (void *callerdat, int err, const char *repos,
+                     const char *update_dir, List *entries)
+{
+    int n;
+    Node *p;
+    List *saved_ulist;
+
+    /* find the update list for this dir */
+    p = findnode (mulist, update_dir);
+    if (p != NULL)
+       saved_ulist = ((struct master_lists *) p->data)->ulist;
+    else
+       saved_ulist = NULL;
+
+    /* skip the checks if there's nothing to do */
+    if (saved_ulist == NULL || saved_ulist->list->next == saved_ulist->list)
+       return err;
+
+    /* run any pre-commit checks */
+    n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, PIOPT_ALL,
+                    saved_ulist);
+    if (n > 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;
+
+/* ARGSUSED */
+static int
+commit_fileproc (void *callerdat, struct file_info *finfo)
+{
+    Node *p;
+    int err = 0;
+    List *ulist, *cilist;
+    struct commit_info *ci;
+
+    /* Keep track of whether write_dirtag is a branch tag.
+       Note that if it is a branch tag in some files and a nonbranch tag
+       in others, treat it as a nonbranch tag.  It is possible that case
+       should elicit a warning or an error.  */
+    if (write_dirtag != NULL
+       && finfo->rcs != NULL)
+    {
+       char *rev = RCS_getversion (finfo->rcs, write_dirtag, NULL, 1, NULL);
+       if (rev != NULL
+           && !RCS_nodeisbranch (finfo->rcs, write_dirtag))
+           write_dirnonbranch = 1;
+       if (rev != NULL)
+           free (rev);
+    }
+
+    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 (!got_message)
+    {
+       got_message = 1;
+       if (!server_active && use_editor)
+           do_editor (finfo->update_dir, &saved_message,
+                      finfo->repository, ulist);
+       do_verify (&saved_message, finfo->repository, ulist);
+    }
+
+    p = findnode (cilist, finfo->file);
+    if (p == NULL)
+       return 0;
+
+    ci = p->data;
+    if (ci->status == T_MODIFIED)
+    {
+       if (finfo->rcs == NULL)
+           error (1, 0, "internal error: no parsed RCS file");
+       if (lock_RCS (finfo->file, finfo->rcs, ci->rev,
+                     finfo->repository) != 0)
+       {
+           unlockrcs (finfo->rcs);
+           err = 1;
+           goto out;
+       }
+    }
+    else if (ci->status == T_ADDED)
+    {
+       if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options,
+                         &finfo->rcs) != 0)
+       {
+           if (finfo->rcs != NULL)
+               fixaddfile (finfo->rcs->path);
+           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
+
+           /* If numeric, it is on the trunk; check_fileproc enforced
+              this.  */
+           && !isdigit ((unsigned char) ci->tag[0]))
+       {
+           if (finfo->rcs == NULL)
+               error (1, 0, "internal error: no parsed RCS file");
+           if (ci->rev)
+               free (ci->rev);
+           ci->rev = RCS_whatbranch (finfo->rcs, ci->tag);
+           err = Checkin ('A', finfo, ci->rev,
+                          ci->tag, ci->options, saved_message);
+           if (err != 0)
+           {
+               unlockrcs (finfo->rcs);
+               fixbranch (finfo->rcs, sbranch);
+           }
+
+           (void) time (&last_register_time);
+
+           ci->status = T_UPTODATE;
+       }
+    }
+
+    /*
+     * Add the file for real
+     */
+    if (ci->status == T_ADDED)
+    {
+       char *xrev = NULL;
+
+       if (ci->rev == NULL)
+       {
+           /* find the max major rev number in this directory */
+           maxrev = 0;
+           (void) walklist (finfo->entries, findmaxrev, NULL);
+           if (finfo->rcs->head)
+           {
+               /* resurrecting: include dead revision */
+               int thisrev = atoi (finfo->rcs->head);
+               if (thisrev > maxrev)
+                   maxrev = thisrev;
+           }
+           if (maxrev == 0)
+               maxrev = 1;
+           xrev = Xasprintf ("%d", maxrev);
+       }
+
+       /* XXX - an added file with symbolic -r should add tag as well */
+       err = finaladd (finfo, ci->rev ? ci->rev : xrev, ci->tag, ci->options);
+       if (xrev)
+           free (xrev);
+    }
+    else if (ci->status == T_MODIFIED)
+    {
+       err = Checkin ('M', finfo, ci->rev, ci->tag,
+                      ci->options, saved_message);
+
+       (void) time (&last_register_time);
+
+       if (err != 0)
+       {
+           unlockrcs (finfo->rcs);
+           fixbranch (finfo->rcs, sbranch);
+       }
+    }
+    else if (ci->status == T_REMOVED)
+    {
+       err = remove_file (finfo, ci->tag, saved_message);
+#ifdef SERVER_SUPPORT
+       if (server_active)
+       {
+           server_scratch_entry_only ();
+           server_updated (finfo,
+                           NULL,
+
+                           /* Doesn't matter, it won't get checked.  */
+                           SERVER_UPDATED,
+
+                           (mode_t) -1,
+                           NULL,
+                           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, finfo->update_dir, 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);
+    }
+    else
+    {
+       /* On success, retrieve the new version number of the file and
+           copy it into the log information (see logmsg.c
+           (logfile_write) for more details).  We should only update
+           the version number for files that have been added or
+           modified but not removed since classify_file_internal
+           will return the version number of a file even after it has
+           been removed from the archive, which is not the behavior we
+           want for our commitlog messages; we want the old version
+           number and then "NONE." */
+
+       if (ci->status != T_REMOVED)
+       {
+           p = findnode (ulist, finfo->file);
+           if (p)
+           {
+               Vers_TS *vers;
+               struct logfile_info *li;
+
+               (void) classify_file_internal (finfo, &vers);
+               li = p->data;
+               li->rev_new = xstrdup (vers->vn_rcs);
+               freevers_ts (&vers);
+           }
+       }
+    }
+    if (SIG_inCrSect ())
+       SIG_endCrSect ();
+
+    return err;
+}
+
+
+
+/*
+ * Log the commit and clean up the update list
+ */
+/* ARGSUSED */
+static int
+commit_filesdoneproc (void *callerdat, int err, const char *repository,
+                      const char *update_dir, List *entries)
+{
+    Node *p;
+    List *ulist;
+
+    assert (repository);
+
+    p = findnode (mulist, update_dir);
+    if (p == NULL)
+       return err;
+
+    ulist = ((struct master_lists *) p->data)->ulist;
+
+    got_message = 0;
+
+    /* Build the administrative files if necessary.  */
+    {
+       const char *p;
+
+       if (strncmp (current_parsed_root->directory, repository,
+                    strlen (current_parsed_root->directory)) != 0)
+           error (0, 0,
+                "internal error: repository (%s) doesn't begin with root (%s)",
+                  repository, current_parsed_root->directory);
+       p = repository + strlen (current_parsed_root->directory);
+       if (*p == '/')
+           ++p;
+       if (strcmp ("CVSROOT", p) == 0
+           /* Check for subdirectories because people may want to create
+              subdirectories and list files therein in checkoutlist.  */
+           || strncmp ("CVSROOT/", p, strlen ("CVSROOT/")) == 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.  */
+
+           /* mkmodules requires the absolute name of the CVSROOT directory.
+              Remove anything after the `CVSROOT' component -- this is
+              necessary when committing in a subdirectory of CVSROOT.  */
+           char *admin_dir = xstrdup (repository);
+           int cvsrootlen = strlen ("CVSROOT");
+           assert (admin_dir[p - repository + cvsrootlen] == '\0'
+                   || admin_dir[p - repository + cvsrootlen] == '/');
+           admin_dir[p - repository + cvsrootlen] = '\0';
+
+           if (!really_quiet)
+           {
+               cvs_output (program_name, 0);
+               cvs_output (" ", 1);
+               cvs_output (cvs_cmd_name, 0);
+               cvs_output (": Rebuilding administrative file database\n", 0);
+           }
+           mkmodules (admin_dir);
+           free (admin_dir);
+           WriteTemplate (".", 1, repository);
+       }
+    }
+
+    /* FIXME: This used to be above the block above.  The advantage of being
+     * here is that it is not called until after all possible writes from this
+     * process are complete.  The disadvantage is that a fatal error during
+     * update of CVSROOT can prevent the loginfo script from being called.
+     *
+     * A more general solution I have been considering is calling a generic
+     * "postwrite" hook from the remove write lock routine.
+     */
+    Update_Logfile (repository, saved_message, NULL, ulist);
+
+    return err;
+}
+
+
+
+/*
+ * Get the log message for a dir
+ */
+/* ARGSUSED */
+static Dtype
+commit_direntproc (void *callerdat, const char *dir, const char *repos,
+                   const char *update_dir, List *entries)
+{
+    Node *p;
+    List *ulist;
+    char *real_repos;
+
+    if (!isdir (dir))
+       return R_SKIP_ALL;
+
+    /* find the update list for this dir */
+    p = findnode (mulist, update_dir);
+    if (p != NULL)
+       ulist = ((struct master_lists *) p->data)->ulist;
+    else
+       ulist = NULL;
+
+    /* skip the files as an optimization */
+    if (ulist == NULL || ulist->list->next == ulist->list)
+       return R_SKIP_FILES;
+
+    /* get commit message */
+    got_message = 1;
+    real_repos = Name_Repository (dir, update_dir);
+    if (!server_active && use_editor)
+       do_editor (update_dir, &saved_message, real_repos, ulist);
+    do_verify (&saved_message, real_repos, ulist);
+    free (real_repos);
+    return R_PROCESS;
+}
+
+
+
+/*
+ * Process the post-commit proc if necessary
+ */
+/* ARGSUSED */
+static int
+commit_dirleaveproc (void *callerdat, const char *dir, int err,
+                     const char *update_dir, List *entries)
+{
+    /* update the per-directory tag info */
+    /* FIXME?  Why?  The "commit examples" node of cvs.texinfo briefly
+       mentions commit -r being sticky, but apparently in the context of
+       this being a confusing feature!  */
+    if (err == 0 && write_dirtag != NULL)
+    {
+       char *repos = Name_Repository (NULL, update_dir);
+       WriteTag (NULL, write_dirtag, NULL, write_dirnonbranch,
+                 update_dir, repos);
+       free (repos);
+    }
+
+    return err;
+}
+
+
+
+/*
+ * find the maximum major rev number in an entries file
+ */
+static int
+findmaxrev (Node *p, void *closure)
+{
+    int thisrev;
+    Entnode *entdata = p->data;
+
+    if (entdata->type != ENT_FILE)
+       return 0;
+    thisrev = atoi (entdata->version);
+    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.
+
+   Return value is 0 on success, or >0 on error (in which case we have
+   printed an error message).  */
+static int
+remove_file (struct file_info *finfo, char *tag, char *message)
+{
+    int retcode;
+
+    int branch;
+    int lockflag;
+    char *corev;
+    char *rev;
+    char *prev_rev;
+    char *old_path;
+
+    corev = NULL;
+    rev = NULL;
+    prev_rev = NULL;
+
+    retcode = 0;
+
+    if (finfo->rcs == NULL)
+       error (1, 0, "internal error: no parsed RCS file");
+
+    branch = 0;
+    if (tag && !(branch = RCS_nodeisbranch (finfo->rcs, tag)))
+    {
+       /* a symbolic tag is specified; just remove the tag from the file */
+       if ((retcode = RCS_deltag (finfo->rcs, tag)) != 0)
+       {
+           if (!quiet)
+               error (0, retcode == -1 ? errno : 0,
+                      "failed to remove tag `%s' from `%s'", tag,
+                      finfo->fullname);
+           return 1;
+       }
+       RCS_rewrite (finfo->rcs, NULL, NULL);
+       Scratch_Entry (finfo->entries, finfo->file);
+       return 0;
+    }
+
+    /* we are removing the file from either the head or a branch */
+    /* commit a new, dead revision. */
+
+    rev = NULL;
+    lockflag = 1;
+    if (branch)
+    {
+       char *branchname;
+
+       rev = RCS_whatbranch (finfo->rcs, tag);
+       if (rev == NULL)
+       {
+           error (0, 0, "cannot find branch \"%s\".", tag);
+           return 1;
+       }
+
+       branchname = RCS_getbranch (finfo->rcs, rev, 1);
+       if (branchname == NULL)
+       {
+           /* no revision exists on this branch.  use the previous
+              revision but do not lock. */
+           corev = RCS_gettag (finfo->rcs, tag, 1, NULL);
+           prev_rev = xstrdup (corev);
+           lockflag = 0;
+       } else
+       {
+           corev = xstrdup (rev);
+           prev_rev = xstrdup (branchname);
+           free (branchname);
+       }
+
+    } else  /* Not a branch */
+    {
+        /* Get current head revision of file. */
+       prev_rev = RCS_head (finfo->rcs);
+    }
+
+    /* if removing without a tag or a branch, then make sure the default
+       branch is the trunk. */
+    if (!tag && !branch)
+    {
+        if (RCS_setbranch (finfo->rcs, NULL) != 0)
+       {
+           error (0, 0, "cannot change branch to default for %s",
+                  finfo->fullname);
+           return 1;
+       }
+       RCS_rewrite (finfo->rcs, NULL, NULL);
+    }
+
+    /* check something out.  Generally this is the head.  If we have a
+       particular rev, then name it.  */
+    retcode = RCS_checkout (finfo->rcs, finfo->file, rev ? corev : NULL,
+                           NULL, NULL, RUN_TTY, NULL, NULL);
+    if (retcode != 0)
+    {
+       error (0, 0,
+              "failed to check out `%s'", finfo->fullname);
+       return 1;
+    }
+
+    /* Except when we are creating a branch, lock the revision so that
+       we can check in the new revision.  */
+    if (lockflag)
+    {
+       if (RCS_lock (finfo->rcs, rev ? corev : NULL, 1) == 0)
+           RCS_rewrite (finfo->rcs, NULL, NULL);
+    }
+
+    if (corev != NULL)
+       free (corev);
+
+    retcode = RCS_checkin (finfo->rcs, NULL, finfo->file, message,
+                          rev, 0, RCS_FLAGS_DEAD | RCS_FLAGS_QUIET);
+    if (retcode        != 0)
+    {
+       if (!quiet)
+           error (0, retcode == -1 ? errno : 0,
+                  "failed to commit dead revision for `%s'", finfo->fullname);
+       return 1;
+    }
+    /* At this point, the file has been committed as removed.  We should
+       probably tell the history file about it  */
+    corev = rev ? RCS_getbranch (finfo->rcs, rev, 1) : RCS_head (finfo->rcs);
+    history_write ('R', NULL, corev, finfo->file, finfo->repository);
+    free (corev);
+
+    if (rev != NULL)
+       free (rev);
+
+    old_path = xstrdup (finfo->rcs->path);
+    if (!branch)
+       RCS_setattic (finfo->rcs, 1);
+
+    /* Print message that file was removed. */
+    if (!really_quiet)
+    {
+       cvs_output (old_path, 0);
+       cvs_output ("  <--  ", 0);
+       if (finfo->update_dir && strlen (finfo->update_dir))
+       {
+           cvs_output (finfo->update_dir, 0);
+           cvs_output ("/", 1);
+       }
+       cvs_output (finfo->file, 0);
+       cvs_output ("\nnew revision: delete; previous revision: ", 0);
+       cvs_output (prev_rev, 0);
+       cvs_output ("\n", 0);
+    }
+
+    free (prev_rev);
+
+    free (old_path);
+
+    Scratch_Entry (finfo->entries, finfo->file);
+    return 0;
+}
+
+
+
+/*
+ * Do the actual checkin for added files
+ */
+static int
+finaladd (struct file_info *finfo, char *rev, char *tag, char *options)
+{
+    int ret;
+
+    ret = Checkin ('A', finfo, rev, tag, options, saved_message);
+    if (ret == 0)
+    {
+       char *tmp = Xasprintf ("%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
+       if (unlink_file (tmp) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", tmp);
+       free (tmp);
+    }
+    else if (finfo->rcs != NULL)
+       fixaddfile (finfo->rcs->path);
+
+    (void) time (&last_register_time);
+
+    return ret;
+}
+
+
+
+/*
+ * Unlock an rcs file
+ */
+static void
+unlockrcs (RCSNode *rcs)
+{
+    int retcode;
+
+    if ((retcode = RCS_unlock (rcs, NULL, 1)) != 0)
+       error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+              "could not unlock %s", rcs->path);
+    else
+       RCS_rewrite (rcs, NULL, NULL);
+}
+
+
+
+/*
+ * remove a partially added file.  if we can parse it, leave it alone.
+ *
+ * FIXME: Every caller that calls this function can access finfo->rcs (the
+ * parsed RCSNode data), so we should be able to detect that the file needs
+ * to be removed without reparsing the file as we do below.
+ */
+static void
+fixaddfile (const char *rcs)
+{
+    RCSNode *rcsfile;
+    int save_really_quiet;
+
+    save_really_quiet = really_quiet;
+    really_quiet = 1;
+    if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
+    {
+       if (unlink_file (rcs) < 0)
+           error (0, errno, "cannot remove %s", rcs);
+    }
+    else
+       freercsnode (&rcsfile);
+    really_quiet = save_really_quiet;
+}
+
+
+
+/*
+ * put the branch back on an rcs file
+ */
+static void
+fixbranch (RCSNode *rcs, char *branch)
+{
+    int retcode;
+
+    if (branch != NULL)
+    {
+       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->path);
+       RCS_rewrite (rcs, NULL, NULL);
+    }
+}
+
+
+
+/*
+ * 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.
+ *
+ * INPUTS
+ *   file      The name of the file in the workspace.
+ *   repository        The repository directory to expect to find FILE,v in.
+ *   tag       The name or rev num of the branch being added to, if any.
+ *   options   Any RCS keyword expansion options specified by the user.
+ *   rcsnode   A pointer to the pre-parsed RCSNode for this file, if the file
+ *             exists in the repository.  If this is NULL, assume the file
+ *             does not yet exist.
+ *
+ * RETURNS
+ *   0 on success.
+ *   1 on errors, after printing any appropriate error messages.
+ *
+ * ERRORS
+ *   This function will return an error when any of the following functions do:
+ *     add_rcs_file
+ *     RCS_setattic
+ *     lock_RCS
+ *     RCS_checkin
+ *     RCS_parse (called to verify the newly created archive file)
+ *     RCS_settag
+ */
+
+static int
+checkaddfile (const char *file, const char *repository, const char *tag,
+              const char *options, RCSNode **rcsnode)
+{
+    RCSNode *rcs;
+    char *fname;
+    int newfile = 0;           /* Set to 1 if we created a new RCS archive. */
+    int retval = 1;
+    int adding_on_branch;
+
+    assert (rcsnode != NULL);
+
+    /* Callers expect to be able to use either "" or NULL to mean the
+       default keyword expansion.  */
+    if (options != NULL && options[0] == '\0')
+       options = NULL;
+    if (options != NULL)
+       assert (options[0] == '-' && options[1] == 'k');
+
+    /* If numeric, it is on the trunk; check_fileproc enforced
+       this.  */
+    adding_on_branch = tag != NULL && !isdigit ((unsigned char) tag[0]);
+
+    if (*rcsnode == NULL)
+    {
+       char *rcsname;
+       char *desc = NULL;
+       size_t descalloc = 0;
+       size_t desclen = 0;
+       const char *opt;
+
+       if (adding_on_branch)
+       {
+           mode_t omask;
+           rcsname = xmalloc (strlen (repository)
+                              + sizeof (CVSATTIC)
+                              + strlen (file)
+                              + sizeof (RCSEXT)
+                              + 3);
+           (void) sprintf (rcsname, "%s/%s", repository, CVSATTIC);
+           omask = umask (cvsumask);
+           if (CVS_MKDIR (rcsname, 0777) != 0 && errno != EEXIST)
+               error (1, errno, "cannot make directory `%s'", rcsname);
+           (void) umask (omask);
+           (void) sprintf (rcsname,
+                           "%s/%s/%s%s",
+                           repository,
+                           CVSATTIC,
+                           file,
+                           RCSEXT);
+       }
+       else
+           rcsname = Xasprintf ("%s/%s%s", repository, file, RCSEXT);
+
+       /* this is the first time we have ever seen this file; create
+          an RCS file.  */
+       fname = Xasprintf ("%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))
+           /* FIXME: Should be including update_dir in the appropriate
+              place here.  */
+           get_file (fname, fname, "r", &desc, &descalloc, &desclen);
+       free (fname);
+
+       /* From reading the RCS 5.7 source, "rcs -i" adds a newline to the
+          end of the log message if the message is nonempty.
+          Do it.  RCS also deletes certain whitespace, in cleanlogmsg,
+          which we don't try to do here.  */
+       if (desclen > 0)
+       {
+           expand_string (&desc, &descalloc, desclen + 1);
+           desc[desclen++] = '\012';
+       }
+
+       /* Set RCS keyword expansion options.  */
+       if (options != NULL)
+           opt = options + 2;
+       else
+           opt = NULL;
+
+       if (add_rcs_file (NULL, rcsname, file, NULL, opt,
+                         NULL, NULL, 0, NULL,
+                         desc, desclen, NULL, 0) != 0)
+       {
+           if (rcsname != NULL)
+               free (rcsname);
+           goto out;
+       }
+       rcs = RCS_parsercsfile (rcsname);
+       newfile = 1;
+       if (rcsname != NULL)
+           free (rcsname);
+       if (desc != NULL)
+           free (desc);
+       *rcsnode = rcs;
+    }
+    else
+    {
+       /* file has existed in the past.  Prepare to resurrect. */
+       char *rev;
+       char *oldexpand;
+
+       rcs = *rcsnode;
+
+       oldexpand = RCS_getexpand (rcs);
+       if ((oldexpand != NULL
+            && options != NULL
+            && strcmp (options + 2, oldexpand) != 0)
+           || (oldexpand == NULL && options != NULL))
+       {
+           /* We tell the user about this, because it means that the
+              old revisions will no longer retrieve the way that they
+              used to.  */
+           error (0, 0, "changing keyword expansion mode to %s", options);
+           RCS_setexpand (rcs, options + 2);
+       }
+
+       if (!adding_on_branch)
+       {
+           /* We are adding on the trunk, so move the file out of the
+              Attic.  */
+           if (!(rcs->flags & INATTIC))
+           {
+               error (0, 0, "warning: expected %s to be in Attic",
+                      rcs->path);
+           }
+
+           /* Begin a critical section around the code that spans the
+              first commit on the trunk of a file that's already been
+              committed on a branch.  */
+           SIG_beginCrSect ();
+
+           if (RCS_setattic (rcs, 0))
+           {
+               goto out;
+           }
+       }
+
+       rev = RCS_getversion (rcs, tag, NULL, 1, NULL);
+       /* and lock it */
+       if (lock_RCS (file, rcs, rev, repository))
+       {
+           error (0, 0, "cannot lock revision %s in `%s'.",
+                  rev ? rev : tag ? tag : "HEAD", rcs->path);
+           if (rev != NULL)
+               free (rev);
+           goto out;
+       }
+
+       if (rev != NULL)
+           free (rev);
+    }
+
+    /* when adding a file for the first time, and using a tag, we need
+       to create a dead revision on the trunk.  */
+    if (adding_on_branch)
+    {
+       if (newfile)
+       {
+           char *tmp;
+           FILE *fp;
+           int retcode;
+
+           /* move the new file out of the way. */
+           fname = Xasprintf ("%s/%s%s", CVSADM, CVSPREFIX, file);
+           rename_file (file, fname);
+
+           /* Create empty FILE.  Can't use copy_file with a DEVNULL
+              argument -- copy_file now ignores device files. */
+           fp = fopen (file, "w");
+           if (fp == NULL)
+               error (1, errno, "cannot open %s for writing", file);
+           if (fclose (fp) < 0)
+               error (0, errno, "cannot close %s", file);
+
+           tmp = Xasprintf ("file %s was initially added on branch %s.",
+                            file, tag);
+           /* commit a dead revision. */
+           retcode = RCS_checkin (rcs, NULL, NULL, tmp, NULL, 0,
+                                  RCS_FLAGS_DEAD | RCS_FLAGS_QUIET);
+           free (tmp);
+           if (retcode != 0)
+           {
+               error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+                      "could not create initial dead revision %s", rcs->path);
+               free (fname);
+               goto out;
+           }
+
+           /* put the new file back where it was */
+           rename_file (fname, file);
+           free (fname);
+
+           /* double-check that the file was written correctly */
+           freercsnode (&rcs);
+           rcs = RCS_parse (file, repository);
+           if (rcs == NULL)
+           {
+               error (0, 0, "could not read %s", rcs->path);
+               goto out;
+           }
+           *rcsnode = rcs;
+
+           /* and lock it once again. */
+           if (lock_RCS (file, rcs, NULL, repository))
+           {
+               error (0, 0, "cannot lock initial revision in `%s'.",
+                      rcs->path);
+               goto out;
+           }
+       }
+
+       /* when adding with a tag, we need to stub a branch, if it
+          doesn't already exist.  */
+       if (!RCS_nodeisbranch (rcs, tag))
+       {
+           /* branch does not exist.  Stub it.  */
+           char *head;
+           char *magicrev;
+           int retcode;
+           time_t headtime = -1;
+           char *revnum, *tmp;
+           FILE *fp;
+           time_t t = -1;
+           struct tm *ct;
+
+           fixbranch (rcs, sbranch);
+
+           head = RCS_getversion (rcs, NULL, NULL, 0, NULL);
+           if (!head)
+               error (1, 0, "No head revision in archive file `%s'.",
+                      rcs->print_path);
+           magicrev = RCS_magicrev (rcs, head);
+
+           /* If this is not a new branch, then we will want a dead
+              version created before this one. */
+           if (!newfile)
+               headtime = RCS_getrevtime (rcs, head, 0, 0);
+
+           retcode = RCS_settag (rcs, tag, magicrev);
+           RCS_rewrite (rcs, NULL, NULL);
+
+           free (head);
+           free (magicrev);
+
+           if (retcode != 0)
+           {
+               error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+                      "could not stub branch %s for %s", tag, rcs->path);
+               goto out;
+           }
+           /* We need to add a dead version here to avoid -rtag -Dtime
+              checkout problems between when the head version was
+              created and now. */
+           if (!newfile && headtime != -1)
+           {
+               /* move the new file out of the way. */
+               fname = Xasprintf ("%s/%s%s", CVSADM, CVSPREFIX, file);
+               rename_file (file, fname);
+
+               /* Create empty FILE.  Can't use copy_file with a DEVNULL
+                  argument -- copy_file now ignores device files. */
+               fp = fopen (file, "w");
+               if (fp == NULL)
+                   error (1, errno, "cannot open %s for writing", file);
+               if (fclose (fp) < 0)
+                   error (0, errno, "cannot close %s", file);
+
+               /* As we will be hacking the delta date, put the time
+                  this was added into the log message. */
+               t = time (NULL);
+               ct = gmtime (&t);
+               tmp = Xasprintf ("file %s was added on branch %s on 
%d-%02d-%02d %02d:%02d:%02d +0000",
+                                file, tag,
+                                ct->tm_year + (ct->tm_year < 100 ? 0 : 1900),
+                                ct->tm_mon + 1, ct->tm_mday,
+                                ct->tm_hour, ct->tm_min, ct->tm_sec);
+                        
+               /* commit a dead revision. */
+               revnum = RCS_whatbranch (rcs, tag);
+               retcode = RCS_checkin (rcs, NULL, NULL, tmp, revnum, headtime,
+                                      RCS_FLAGS_DEAD |
+                                      RCS_FLAGS_QUIET |
+                                      RCS_FLAGS_USETIME);
+               free (revnum);
+               free (tmp);
+
+               if (retcode != 0)
+               {
+                   error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+                          "could not created dead stub %s for %s", tag,
+                          rcs->path);
+                   goto out;
+               }
+
+               /* put the new file back where it was */
+               rename_file (fname, file);
+               free (fname);
+
+               /* double-check that the file was written correctly */
+               freercsnode (&rcs);
+               rcs = RCS_parse (file, repository);
+               if (rcs == NULL)
+               {
+                   error (0, 0, "could not read %s", rcs->path);
+                   goto out;
+               }
+               *rcsnode = rcs;
+           }
+       }
+       else
+       {
+           /* lock the branch. (stubbed branches need not be locked.)  */
+           if (lock_RCS (file, rcs, NULL, repository))
+           {
+               error (0, 0, "cannot lock head revision in `%s'.", rcs->path);
+               goto out;
+           }
+       }
+
+       if (*rcsnode != rcs)
+       {
+           freercsnode (rcsnode);
+           *rcsnode = rcs;
+       }
+    }
+
+    fileattr_newfile (file);
+
+    /* At this point, we used to set the file mode of the RCS file
+       based on the mode of the file in the working directory.  If we
+       are creating the RCS file for the first time, add_rcs_file does
+       this already.  If we are re-adding the file, then perhaps it is
+       consistent to preserve the old file mode, just as we preserve
+       the old keyword expansion mode.
+
+       If we decide that we should change the modes, then we can't do
+       it here anyhow.  At this point, the RCS file may be owned by
+       somebody else, so a chmod will fail.  We need to instead do the
+       chmod after rewriting it.
+
+       FIXME: In general, I think the file mode (and the keyword
+       expansion mode) should be associated with a particular revision
+       of the file, so that it is possible to have different revisions
+       of a file have different modes.  */
+
+    retval = 0;
+
+ out:
+    if (retval != 0 && SIG_inCrSect ())
+       SIG_endCrSect ();
+    return retval;
+}
+
+
+
+/*
+ * 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 (const char *user, RCSNode *rcs, const char *rev,
+          const char *repository)
+{
+    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 ((unsigned char) *rev) && numdots (rev) < 2))
+    {
+       branch = xstrdup (rcs->branch);
+       if (branch != NULL)
+       {
+           if (RCS_setbranch (rcs, NULL) != 0)
+           {
+               error (0, 0, "cannot change branch to default for %s",
+                      rcs->path);
+               if (branch)
+                   free (branch);
+               return 1;
+           }
+       }
+       err = RCS_lock (rcs, NULL, 1);
+    }
+    else
+    {
+       RCS_lock (rcs, rev, 1);
+    }
+
+    /* We used to call RCS_rewrite here, and that might seem
+       appropriate in order to write out the locked revision
+       information.  However, such a call would actually serve no
+       purpose.  CVS locks will prevent any interference from other
+       CVS processes.  The comment above rcs_internal_lockfile
+       explains that it is already unsafe to use RCS and CVS
+       simultaneously.  It follows that writing out the locked
+       revision information here would add no additional security.
+
+       If we ever do care about it, the proper fix is to create the
+       RCS lock file before calling this function, and maintain it
+       until the checkin is complete.
+
+       The call to RCS_lock is still required at present, since in
+       some cases RCS_checkin will determine which revision to check
+       in by looking for a lock.  FIXME: This is rather roundabout,
+       and a more straightforward approach would probably be easier to
+       understand.  */
+
+    if (err == 0)
+    {
+       if (sbranch != NULL)
+           free (sbranch);
+       sbranch = branch;
+       return 0;
+    }
+
+    /* try to restore the branch if we can on error */
+    if (branch != NULL)
+       fixbranch (rcs, branch);
+
+    if (branch)
+       free (branch);
+    return 1;
+}
+
+
+
+/*
+ * free an UPDATE node's data
+ */
+void
+update_delproc (Node *p)
+{
+    struct logfile_info *li = p->data;
+
+    if (li->tag)
+       free (li->tag);
+    if (li->rev_old)
+       free (li->rev_old);
+    if (li->rev_new)
+       free (li->rev_new);
+    free (li);
+}
+
+/*
+ * Free the commit_info structure in p.
+ */
+static void
+ci_delproc (Node *p)
+{
+    struct commit_info *ci = 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 (Node *p)
+{
+    struct master_lists *ml = p->data;
+
+    dellist (&ml->ulist);
+    dellist (&ml->cilist);
+    free (ml);
+}
Index: ccvs/src/cvs.h
diff -u /dev/null ccvs/src/cvs.h:1.346.2.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/cvs.h      Tue Jan 17 15:41:23 2006
@@ -0,0 +1,930 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions 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.
+ */
+
+/*
+ * basic information used in all source files
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>           /* this is stuff found via autoconf */
+#endif /* CONFIG_H */
+
+/* Add GNU attribute suppport.  */
+#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 */
+# else
+#   if __GNUC__ == 2 && __GNUC_MINOR__ < 96
+#    define __pure__   /* empty */
+#   endif
+#   if __GNUC__ < 3
+#    define __malloc__ /* empty */
+#   endif
+# 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 __const__    const
+#  define __format__   format
+#  define __noreturn__ noreturn
+#  define __printf__   printf
+# endif
+#endif /* __attribute__ */
+
+/* Some GNULIB headers require that we include system headers first.  */
+#include "system.h"
+
+/* begin GNULIB headers */
+#include "dirname.h"
+#include "exit.h"
+#include "getdate.h"
+#include "minmax.h"
+#include "regex.h"
+#include "strcase.h"
+#include "stat-macros.h"
+#include "timespec.h"
+#include "unlocked-io.h"
+#include "xalloc.h"
+#include "xgetcwd.h"
+#include "xreadlink.h"
+#include "xsize.h"
+/* end GNULIB headers */
+
+#if ! STDC_HEADERS
+char *getenv();
+#endif /* ! STDC_HEADERS */
+
+/* Under OS/2, <stdio.h> doesn't define popen()/pclose(). */
+#ifdef USE_OWN_POPEN
+#include "popen.h"
+#endif
+
+#ifdef SERVER_SUPPORT
+/* If the system doesn't provide strerror, it won't be declared in
+   string.h.  */
+char *strerror (int);
+#endif
+
+#include "hash.h"
+#include "stack.h"
+
+#include "root.h"
+
+#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
+# include "client.h"
+#endif
+
+#ifdef MY_NDBM
+#include "myndbm.h"
+#else
+#include <ndbm.h>
+#endif /* MY_NDBM */
+
+#include "rcs.h"
+
+
+
+/* Note that the _ONLY_ reason for PATH_MAX is if various system calls (getwd,
+ * getcwd, readlink) require/want us to use it.  All other parts of CVS
+ * allocate pathname buffers dynamically, and we want to keep it that way.
+ */
+#include "pathmax.h"
+
+
+
+/* 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_TAG      "CVS/Tag."
+#define CVSADM_NOTIFY   "CVS/Notify."
+#define CVSADM_NOTIFYTMP "CVS/Notify.tmp"
+#define CVSADM_BASE      "CVS/Base"
+#define CVSADM_BASEREV   "CVS/Baserev."
+#define CVSADM_BASEREVTMP "CVS/Baserev.tmp"
+#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_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"
+#define CVSADM_BASEREV  "CVS/Baserev"
+#define CVSADM_BASEREVTMP "CVS/Baserev.tmp"
+/* 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_CHECKOUTLIST "checkoutlist"
+#define CVSROOTADM_COMMITINFO  "commitinfo"
+#define CVSROOTADM_CONFIG      "config"
+#define        CVSROOTADM_HISTORY      "history"
+#define        CVSROOTADM_IGNORE       "cvsignore"
+#define        CVSROOTADM_LOGINFO      "loginfo"
+#define        CVSROOTADM_MODULES      "modules"
+#define CVSROOTADM_NOTIFY      "notify"
+#define CVSROOTADM_PASSWD      "passwd"
+#define CVSROOTADM_POSTADMIN   "postadmin"
+#define CVSROOTADM_POSTPROXY   "postproxy"
+#define CVSROOTADM_POSTTAG     "posttag"
+#define CVSROOTADM_POSTWATCH   "postwatch"
+#define CVSROOTADM_PREPROXY    "preproxy"
+#define        CVSROOTADM_RCSINFO      "rcsinfo"
+#define CVSROOTADM_READERS     "readers"
+#define CVSROOTADM_TAGINFO      "taginfo"
+#define CVSROOTADM_USERS       "users"
+#define CVSROOTADM_VALTAGS     "val-tags"
+#define CVSROOTADM_VERIFYMSG    "verifymsg"
+#define CVSROOTADM_WRAPPER     "cvswrappers"
+#define CVSROOTADM_WRITERS     "writers"
+
+#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        CVSHISTORYLCK   "#cvs.history.lock"
+#define        CVSVALTAGSLCK   "#cvs.val-tags.lock"
+#define        CVSRFL          "#cvs.rfl"
+#define        CVSPFL          "#cvs.pfl"
+#define        CVSWFL          "#cvs.wfl"
+#define CVSPFLPAT      "#cvs.pfl.*"    /* wildcard expr to match plocks */
+#define CVSRFLPAT      "#cvs.rfl.*"    /* wildcard expr to match read locks */
+#define        CVSEXT_LOG      ",t"
+#define        CVSPREFIX       ",,"
+#define CVSDOTIGNORE   ".cvsignore"
+#define CVSDOTWRAPPER   ".cvswrappers"
+
+/* Command attributes -- see function lookup_command_attribute(). */
+#define CVS_CMD_IGNORE_ADMROOT        1
+
+/* Set if CVS needs to create a CVS/Root file upon completion of this
+   command.  The name may be slightly confusing, because the flag
+   isn't really as general purpose as it seems (it is not set for cvs
+   release).  */
+
+#define CVS_CMD_USES_WORK_DIR         2
+
+#define CVS_CMD_MODIFIES_REPOSITORY   4
+
+/* miscellaneous CVS defines */
+
+/* This is the string which is at the start of the non-log-message lines
+   that we put up for the user when they edit the log message.  */
+#define        CVSEDITPREFIX   "CVS: "
+/* Number of characters in CVSEDITPREFIX to compare when deciding to strip
+   off those lines.  We don't check for the space, to accomodate users who
+   have editors which strip trailing spaces.  */
+#define CVSEDITPREFIXLEN 4
+
+#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     "_$"
+#else /* USE_VMS_FILENAMES */
+# define BAKPREFIX     ".#"            /* when rcsmerge'ing */
+#endif /* USE_VMS_FILENAMES */
+
+/*
+ * 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"
+#define TAG_DOTHEAD     "head"
+#define TAG_DOTBASE     "base"
+#define TAG_COMMITID    "commitid"
+#define TAG_PREVIOUS    "prev"
+#define TAG_TRUNK       "trunk"
+#define TAG_ORIGIN      "origin"
+#define TAG_ROOT        "root"
+#define TAG_NEXT        "next"
+
+/* Environment variable used by CVS */
+#define        CVSREAD_ENV     "CVSREAD"       /* make files read-only */
+#define        CVSREAD_DFLT    0               /* writable files by default */
+
+#define        CVSREADONLYFS_ENV "CVSREADONLYFS" /* repository is read-only */
+
+#define        TMPDIR_ENV      "TMPDIR"        /* Temporary directory */
+#define        CVS_PID_ENV     "CVS_PID"       /* pid of running cvs */
+
+#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        CVSROOT_ENV     "CVSROOT"       /* source directory root */
+/* Define CVSROOT_DFLT to a fallback value for CVSROOT.
+ *
+#undef CVSROOT_DFL
+ */
+
+#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 */
+
+/*
+ * 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/"
+
+/* Large enough to hold DATEFORM.  Not an arbitrary limit as long as
+   it is used for that purpose, and not to hold a string from the
+   command line, the client, etc.  */
+#define MAXDATELEN     50
+
+/* The type of an entnode.  */
+enum ent_type
+{
+    ENT_FILE, ENT_SUBDIR
+};
+
+/* structure of a entry record */
+struct entnode
+{
+    enum ent_type type;
+    char *user;
+    char *version;
+
+    /* Timestamp, or "" if none (never NULL).  */
+    char *timestamp;
+
+    /* Keyword expansion options, or "" if none (never NULL).  */
+    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, MISC
+};
+
+/*
+ * structure used for list-private storage by Entries_Open() and
+ * Version_TS() and Find_Directories().
+ */
+struct stickydirtag
+{
+    /* These fields pass sticky tag information from Entries_Open() to
+       Version_TS().  */
+    int aflag;
+    char *tag;
+    char *date;
+    int nonbranch;
+
+    /* This field is set by Entries_Open() if there was subdirectory
+       information; Find_Directories() uses it to see whether it needs
+       to scan the directory itself.  */
+    int subdirs;
+};
+
+/* 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 */
+};
+#ifdef ENUMS_CAN_BE_TROUBLE
+typedef int Dtype;
+#else
+typedef enum direnter_type Dtype;
+#endif
+
+/* Recursion processor lock types */
+#define CVS_LOCK_NONE  0
+#define CVS_LOCK_READ  1
+#define CVS_LOCK_WRITE 2
+
+/* Option flags for Parse_Info() */
+#define PIOPT_ALL 1    /* accept "all" keyword */
+
+extern const char *program_name, *program_path, *cvs_cmd_name;
+extern char *Editor;
+extern int cvsadmin_root;
+extern char *CurDir;
+extern int really_quiet, quiet;
+extern int use_editor;
+extern int cvswrite;
+extern mode_t cvsumask;
+
+/* Temp dir abstraction.  */
+/* From main.c.  */
+const char *get_cvs_tmp_dir (void);
+/* From filesubr.c.  */
+const char *get_system_temp_dir (void);
+void push_env_temp_dir (void);
+
+
+/* This global variable holds the global -d option.  It is NULL if -d
+   was not used, which means that we must get the CVSroot information
+   from the CVSROOT environment variable or from a CVS/Root file.  */
+extern char *CVSroot_cmdline;
+
+/* This variable keeps track of all of the CVSROOT directories that
+ * have been seen by the client.
+ */
+extern List *root_directories;
+
+char *emptydir_name (void);
+int safe_location (char *);
+
+extern int trace;              /* Show all commands */
+extern int noexec;             /* Don't modify disk anywhere */
+extern int readonlyfs;         /* fail on all write locks; succeed all read 
locks */
+extern int logoff;             /* Don't write history entry */
+
+
+
+#define LOGMSG_REREAD_NEVER 0  /* do_verify - never  reread message */
+#define LOGMSG_REREAD_ALWAYS 1 /* do_verify - always reread message */
+#define LOGMSG_REREAD_STAT 2   /* do_verify - reread message if changed */
+
+/* This header needs the LOGMSG_* defns above.  */
+#include "parseinfo.h"
+
+/* This structure holds the global configuration data.  */
+extern struct config *config;
+
+#ifdef CLIENT_SUPPORT
+extern List *dirs_sent_to_server; /* used to decide which "Argument
+                                    xxx" commands to send to each
+                                    server in multiroot mode. */
+#endif
+
+extern char *hostname;
+
+/* Externs that are included directly in the CVS sources */
+
+int RCS_merge (RCSNode *, const char *, const char *, const char *,
+               const char *, const char *);
+/* Flags used by RCS_* functions.  See the description of the individual
+   functions for which flags mean what for each function.  */
+#define RCS_FLAGS_FORCE 1
+#define RCS_FLAGS_DEAD 2
+#define RCS_FLAGS_QUIET 4
+#define RCS_FLAGS_MODTIME 8
+#define RCS_FLAGS_KEEPFILE 16
+#define RCS_FLAGS_USETIME 32
+
+int RCS_exec_rcsdiff (RCSNode *rcsfile, int diff_argc,
+                      char * const *diff_argv, const char *options,
+                      const char *rev1, const char *rev1_cache,
+                      const char *rev2,
+                      const char *label1, const char *label2,
+                      const char *workfile);
+int diff_exec (const char *file1, const char *file2,
+               const char *label1, const char *label2,
+               int iargc, char * const *iargv, const char *out);
+
+
+#include "error.h"
+
+/* 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.
+ *
+ * This needs to be here rather than in error.h in order to use an unforked
+ * error.h from GNULIB.
+ */
+extern int error_use_protocol;
+
+
+DBM *open_module (void);
+List *Find_Directories (char *repository, int which, List *entries);
+void Entries_Close (List *entries);
+List *Entries_Open (int aflag, char *update_dir);
+void Subdirs_Known (List *entries);
+void Subdir_Register (List *, const char *, const char *);
+void Subdir_Deregister (List *, const char *, const char *);
+
+void parse_tagdate (char **tag, char **date, const char *input);
+char *Make_Date (const char *rawdate);
+char *date_from_time_t (time_t);
+void date_to_internet (char *, const char *);
+void date_to_tm (struct tm *, const char *);
+void tm_to_internet (char *, const struct tm *);
+char *gmformat_time_t (time_t unixtime);
+char *format_date_alloc (char *text);
+
+char *Name_Repository (const char *dir, const char *update_dir);
+const char *Short_Repository (const char *repository);
+void Sanitize_Repository_Name (char *repository);
+
+char *entries_time (time_t unixtime);
+time_t unix_time_stamp (const char *file);
+char *time_stamp (const char *file);
+
+typedef        int (*CALLPROC) (const char *repository, const char *value,
+                         void *closure);
+int Parse_Info (const char *infofile, const char *repository,
+                CALLPROC callproc, int opt, void *closure);
+
+typedef        RETSIGTYPE (*SIGCLEANUPPROC)    (int);
+int SIG_register (int sig, SIGCLEANUPPROC sigcleanup);
+bool isdir (const char *file);
+bool isfile (const char *file);
+ssize_t islink (const char *file);
+bool isdevice (const char *file);
+bool isreadable (const char *file);
+bool iswritable (const char *file);
+bool isaccessible (const char *file, const int mode);
+const char *last_component (const char *path);
+char *get_homedir (void);
+char *strcat_filename_onto_homedir (const char *, const char *);
+char *cvs_temp_name (void);
+FILE *cvs_temp_file (char **filename);
+
+int ls (int argc, char *argv[]);
+int unlink_file (const char *f);
+int unlink_file_dir (const char *f);
+
+/* 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.  */
+    const 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 "".  */
+    const 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.  */
+    const char *fullname;
+
+    /* Name of the directory corresponding to the repository which contains
+       this file.  */
+    const char *repository;
+
+    /* The pre-parsed entries for this directory.  */
+    List *entries;
+
+    RCSNode *rcs;
+};
+
+/* This needs to be included after the struct file_info definition since some
+ * of the functions subr.h defines refer to struct file_info.
+ */
+#include "subr.h"
+
+int update (int argc, char *argv[]);
+/* The only place this is currently used outside of update.c is add.c.
+ * Restricting its use to update.c seems to be in the best interest of
+ * modularity, but I can't think of a good way to get an update of a
+ * resurrected file done and print the fact otherwise.
+ */
+void write_letter (struct file_info *finfo, int letter);
+int xcmp (const char *file1, const char *file2);
+
+int Create_Admin (const char *dir, const char *update_dir,
+                  const char *repository, const char *tag, const char *date,
+                  int nonbranch, int warn, int dotemplate);
+int expand_at_signs (const char *, size_t, FILE *);
+
+/* Locking subsystem (implemented in lock.c).  */
+
+int Reader_Lock (char *xrepository);
+void Simple_Lock_Cleanup (void);
+void Lock_Cleanup (void);
+
+/* Writelock an entire subtree, well the part specified by ARGC, ARGV, LOCAL,
+   and AFLAG, anyway.  */
+void lock_tree_promotably (int argc, char **argv, int local, int which,
+                          int aflag);
+
+/* See lock.c for description.  */
+void lock_dir_for_write (const char *);
+
+/* Get a write lock for the history file.  */
+int history_lock (const char *);
+void clear_history_lock (void);
+
+/* Get a write lock for the val-tags file.  */
+int val_tags_lock (const char *);
+void clear_val_tags_lock (void);
+
+void Scratch_Entry (List * list, const char *fname);
+void ParseTag (char **tagp, char **datep, int *nonbranchp);
+void WriteTag (const char *dir, const char *tag, const char *date,
+               int nonbranch, const char *update_dir, const char *repository);
+void WriteTemplate (const char *update_dir, int dotemplate,
+                    const char *repository);
+void cat_module (int status);
+void check_entries (char *dir);
+void close_module (DBM * db);
+void copy_file (const char *from, const char *to);
+void fperrmsg (FILE * fp, int status, int errnum, char *message,...);
+
+int ign_name (char *name);
+void ign_add (char *ign, int hold);
+void ign_add_file (char *file, int hold);
+void ign_setup (void);
+void ign_dir_add (char *name);
+int ignore_directory (const char *name);
+typedef void (*Ignore_proc) (const char *, const char *);
+void ignore_files (List *, List *, const char *, Ignore_proc);
+extern int ign_inhibit_server;
+
+#include "update.h"
+
+void make_directories (const char *name);
+void make_directory (const char *name);
+int mkdir_if_needed (const char *name);
+void rename_file (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.  */
+void expand_wild (int argc, char **argv, 
+                  int *pargc, char ***pargv);
+
+/* exithandle.c */
+void signals_register (RETSIGTYPE (*handler)(int));
+void cleanup_register (void (*handler) (void));
+
+void update_delproc (Node * p);
+void usage (const char *const *cpp);
+void xchmod (const char *fname, int writable);
+List *Find_Names (char *repository, int which, int aflag,
+                 List ** optentries);
+void Register (List * list, const char *fname, const char *vn, const char *ts,
+               const char *options, const char *tag, const char *date,
+               const char *ts_conflict);
+void Update_Logfile (const char *repository, const char *xmessage,
+                     FILE *xlogfp, List *xchanges);
+void do_editor (const char *dir, char **messagep,
+                const char *repository, List *changes);
+
+void do_verify (char **messagep, const char *repository, List *changes);
+
+typedef        int (*CALLBACKPROC)     (int argc, char *argv[], char *where,
+       char *mwhere, char *mfile, int shorten, int local_specified,
+       char *omodule, char *msg);
+
+
+typedef        int (*FILEPROC) (void *callerdat, struct file_info *finfo);
+typedef        int (*FILESDONEPROC) (void *callerdat, int err,
+                              const char *repository, const char *update_dir,
+                              List *entries);
+typedef        Dtype (*DIRENTPROC) (void *callerdat, const char *dir,
+                             const char *repos, const char *update_dir,
+                             List *entries);
+typedef        int (*DIRLEAVEPROC) (void *callerdat, const char *dir, int err,
+                             const char *update_dir, List *entries);
+
+int mkmodules (char *dir);
+int init (int argc, char **argv);
+
+int do_module (DBM * db, char *mname, enum mtype m_type, char *msg,
+               CALLBACKPROC callback_proc, char *where, int shorten,
+               int local_specified, int run_module_prog, int build_dirs,
+               char *extra_arg);
+void history_write (int type, const char *update_dir, const char *revs,
+                    const char *name, const char *repository);
+int start_recursion (FILEPROC fileproc, FILESDONEPROC filesdoneproc,
+                    DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc,
+                    void *callerdat,
+                    int argc, char *argv[], int local, int which,
+                    int aflag, int locktype, char *update_preload,
+                    int dosrcs, char *repository);
+void SIG_beginCrSect (void);
+void SIG_endCrSect (void);
+int SIG_inCrSect (void);
+void read_cvsrc (int *argc, char ***argv, const char *cmdname);
+
+/* 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_add_arg_p (int *, size_t *, char ***, const char *s);
+void run_arg_free_p (int, char **);
+void run_add_arg (const char *s);
+void run_print (FILE * fp);
+void run_setup (const char *prog);
+int run_exec (const char *stin, const char *stout, const char *sterr,
+              int flags);
+int run_piped (int *, int *);
+
+/* other similar-minded stuff from run.c.  */
+FILE *run_popen (const char *, const char *);
+int piped_child (char *const *, int *, int *, bool);
+void close_on_exec (int);
+
+pid_t waitpid (pid_t, int *, int);
+
+/*
+ * 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:
+
+       NULL = file is not mentioned in Entries (this is also used for a
+             directory).
+       "" = INVALID!  The comment used to say that it meant "no user file"
+           but as far as I know CVS didn't actually use it that way.
+           Note that according to cvs.texinfo, "" is not valid in the
+           Entries 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 is a simple tag in the RCS file--a tag which really
+       exists which is not a magic revision--and if ->date is NULL,
+       then this is a copy of ->tag.  Otherwise, it is a copy of
+       ->vn_rcs.  */
+    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.  It is
+       "Is-modified" if we know the file is modified but don't have its
+       contents.  */
+    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), malloc'd.  If none,
+       then it is an empty string (never NULL).  */
+    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;
+    /* If this is 1, then tag is not a branch tag.  If this is 0, then
+       tag may or may not be a branch tag.  */
+    int nonbranch;
+
+    /* Pointer to entries file node  */
+    Entnode *entdata;
+
+    /* Pointer to parsed src file info */
+    RCSNode *srcfile;
+};
+typedef struct vers_ts Vers_TS;
+
+Vers_TS *Version_TS (struct file_info *finfo, char *options, char *tag,
+                           char *date, int force_tag_match,
+                           int set_time);
+void freevers_ts (Vers_TS ** versp);
+char *Version_resolve_relTag (struct file_info *, const char *, bool);
+
+
+/* Miscellaneous CVS infrastructure which layers on top of the recursion
+   processor (for example, needs struct file_info).  */
+
+int Checkin (int type, struct file_info *finfo, char *rev,
+            char *tag, char *options, char *message);
+int No_Difference (struct file_info *finfo, Vers_TS *vers);
+/* TODO: can the finfo argument to special_file_mismatch be changed? -twp */
+int special_file_mismatch (struct file_info *finfo,
+                                 char *rev1, char *rev2);
+
+/* CVSADM_BASEREV stuff, from entries.c.  */
+char *base_get (struct file_info *);
+void base_register (struct file_info *, char *);
+void base_deregister (struct file_info *);
+
+/*
+ * defines for Classify_File() to determine the current state of a file.
+ * These are also used as types in the data field for the list we make for
+ * Update_Logfile in commit, import, and add.
+ */
+enum classify_type
+{
+    T_UNKNOWN = 1,                     /* no old-style analog existed   */
+    T_CONFLICT,                                /* C (conflict) list            
 */
+    T_NEEDS_MERGE,                     /* G (needs merging) list        */
+    T_MODIFIED,                                /* M (needs checked in) list    
 */
+    T_CHECKOUT,                                /* O (needs checkout) list      
 */
+    T_ADDED,                           /* A (added file) list           */
+    T_REMOVED,                         /* R (removed file) list         */
+    T_REMOVE_ENTRY,                    /* W (removed entry) list        */
+    T_UPTODATE,                                /* File is up-to-date           
 */
+    T_PATCH,                           /* P Like C, but can patch       */
+    T_TITLE                            /* title for node type           */
+};
+typedef enum classify_type Ctype;
+
+Ctype Classify_File (struct file_info *finfo, char *tag, char *date, char 
*options,
+      int force_tag_match, int aflag, Vers_TS **versp, int pipeout);
+
+/*
+ * structure used for list nodes passed to Update_Logfile() and
+ * do_editor().
+ */
+struct logfile_info
+{
+  enum classify_type type;
+  char *tag;
+  char *rev_old;               /* rev number before a commit/modify,
+                                  NULL for add or import */
+  char *rev_new;               /* rev number after a commit/modify,
+                                  add, or import, NULL for remove */
+};
+
+/* Wrappers.  */
+
+typedef enum { WRAP_MERGE, WRAP_COPY } WrapMergeMethod;
+typedef enum {
+    /* -t and -f wrapper options.  Treating directories as single files.  */
+    WRAP_TOCVS,
+    WRAP_FROMCVS,
+    /* -k wrapper option.  Default keyword expansion options.  */
+    WRAP_RCSOPTION
+} WrapMergeHas;
+
+void  wrap_setup (void);
+int   wrap_name_has (const char *name,WrapMergeHas has);
+char *wrap_rcsoption (const char *fileName, int asFlag);
+char *wrap_tocvs_process_file (const char *fileName);
+int   wrap_merge_is_copy (const char *fileName);
+void wrap_fromcvs_process_file (const char *fileName);
+void wrap_add_file (const char *file,int temp);
+void wrap_add (char *line,int temp);
+void wrap_send (void);
+#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
+void wrap_unparse_rcs_options (char **, int);
+#endif /* SERVER_SUPPORT || CLIENT_SUPPORT */
+
+/* Pathname expansion */
+char *expand_path (const char *name, const char *cvsroot, bool formatsafe,
+                  const char *file, int line);
+
+/* User variables.  */
+extern List *variable_list;
+
+void variable_set (char *nameval);
+
+int watch (int argc, char **argv);
+int edit (int argc, char **argv);
+int unedit (int argc, char **argv);
+int editors (int argc, char **argv);
+int watchers (int argc, char **argv);
+int annotate (int argc, char **argv);
+int add (int argc, char **argv);
+int admin (int argc, char **argv);
+int checkout (int argc, char **argv);
+int commit (int argc, char **argv);
+int diff (int argc, char **argv);
+int history (int argc, char **argv);
+int import (int argc, char **argv);
+int cvslog (int argc, char **argv);
+#ifdef AUTH_CLIENT_SUPPORT
+/* Some systems (namely Mac OS X) have conflicting definitions for these
+ * functions.  Avoid them.
+ */
+#ifdef HAVE_LOGIN
+# define login         cvs_login
+#endif /* HAVE_LOGIN */
+#ifdef HAVE_LOGOUT
+# define logout                cvs_logout
+#endif /* HAVE_LOGOUT */
+int login (int argc, char **argv);
+int logout (int argc, char **argv);
+#endif /* AUTH_CLIENT_SUPPORT */
+int patch (int argc, char **argv);
+int release (int argc, char **argv);
+int cvsremove (int argc, char **argv);
+int rtag (int argc, char **argv);
+int cvsstatus (int argc, char **argv);
+int cvstag (int argc, char **argv);
+int version (int argc, char **argv);
+
+unsigned long int lookup_command_attribute (const char *);
+
+#if defined(AUTH_CLIENT_SUPPORT) || defined(AUTH_SERVER_SUPPORT)
+char *scramble (char *str);
+char *descramble (char *str);
+#endif /* AUTH_CLIENT_SUPPORT || AUTH_SERVER_SUPPORT */
+
+#ifdef AUTH_CLIENT_SUPPORT
+char *get_cvs_password (void);
+/* get_cvs_port_number() is not pure since the /etc/services file could change
+ * between calls.  */
+int get_cvs_port_number (const cvsroot_t *root);
+/* normalize_cvsroot() is not pure since it calls get_cvs_port_number.  */
+char *normalize_cvsroot (const cvsroot_t *root)
+       __attribute__ ((__malloc__));
+#endif /* AUTH_CLIENT_SUPPORT */
+
+void tag_check_valid (const char *, int, char **, int, int, char *, bool);
+
+#include "server.h"
+
+/* From server.c and documented there.  */
+void cvs_output (const char *, size_t);
+void cvs_output_binary (char *, size_t);
+void cvs_outerr (const char *, size_t);
+void cvs_flusherr (void);
+void cvs_flushout (void);
+void cvs_output_tagged (const char *, const char *);
+
+extern const char *global_session_id;
+
+/* From find_names.c.  */
+List *find_files (const char *dir, const char *pat);
Index: ccvs/src/import.c
diff -u /dev/null ccvs/src/import.c:1.175.8.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/import.c   Tue Jan 17 15:41:23 2006
@@ -0,0 +1,1782 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * "import" checks in the vendor release located in the current directory into
+ * the CVS source repository.  The CVS vendor branch support is utilized.
+ * 
+ * At least three arguments are expected to follow the options:
+ *     repository      Where the source belongs relative to the CVSROOT
+ *     VendorTag       Vendor's major tag
+ *     VendorReleTag   Tag for this particular release
+ *
+ * Additional arguments specify more Vendor Release Tags.
+ */
+
+#include "cvs.h"
+#include "lstat.h"
+#include "save-cwd.h"
+
+static char *get_comment (const char *user);
+static int add_rev (char *message, RCSNode *rcs, char *vfile,
+                         char *vers);
+static int add_tags (RCSNode *rcs, char *vfile, char *vtag, int targc,
+                    char *targv[]);
+static int import_descend (char *message, char *vtag, int targc, char 
*targv[]);
+static int import_descend_dir (char *message, char *dir, char *vtag,
+                              int targc, char *targv[]);
+static int process_import_file (char *message, char *vfile, char *vtag,
+                               int targc, char *targv[]);
+static int update_rcs_file (char *message, char *vfile, char *vtag, int targc,
+                           char *targv[], int inattic);
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+static int preserve_initial_permissions (FILE *fprcs, const char *userfile,
+                                        mode_t file_type, struct stat *sbp);
+#endif
+static int expand_and_copy_contents (FILE *fprcs, mode_t file_type,
+                                    const char *user, FILE *fpuser);
+static void add_log (int ch, char *fname);
+
+static int repos_len;
+static char *vhead;
+static char *vbranch;
+static FILE *logfp;
+static char *repository;
+static int conflicts;
+static int use_file_modtime;
+static char *keyword_opt = NULL;
+static bool killnew;
+
+static const char *const import_usage[] =
+{
+    "Usage: %s %s [-dX] [-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-X\tWhen importing new files, mark their trunk revisions as dead.\n",
+    "\t-k sub\tSet default RCS keyword substitution mode.\n",
+    "\t-I ign\tMore files to ignore (! to reset).\n",
+    "\t-b bra\tVendor branch id.\n",
+    "\t-m msg\tLog message.\n",
+    "\t-W spec\tWrappers specification line.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+int
+import (int argc, char **argv)
+{
+    char *message = NULL;
+    char *tmpfile;
+    char *cp;
+    int i, c, msglen, err;
+    List *ulist;
+    Node *p;
+    struct logfile_info *li;
+
+    if (argc == -1)
+       usage (import_usage);
+
+    /* Force -X behaviour or not based on the CVS repository
+       CVSROOT/config setting.  */
+#ifdef CLIENT_SUPPORT
+    killnew = !current_parsed_root->isremote
+             && config->ImportNewFilesToVendorBranchOnly;
+#else /* !CLIENT_SUPPORT */
+    killnew = config->ImportNewFilesToVendorBranchOnly;
+#endif /* CLIENT_SUPPORT */
+
+
+    ign_setup ();
+    wrap_setup ();
+
+    vbranch = xstrdup (CVSBRANCH);
+    optind = 0;
+    while ((c = getopt (argc, argv, "+Qqdb:m:I:k:W:X")) != -1)
+    {
+       switch (c)
+       {
+           case 'Q':
+           case 'q':
+               /* The CVS 1.5 client sends these options (in addition to
+                  Global_option requests), so we must ignore them.  */
+               if (!server_active)
+                   error (1, 0,
+                          "-q or -Q must be specified before \"%s\"",
+                          cvs_cmd_name);
+               break;
+           case 'd':
+               if (server_active)
+               {
+                   /* CVS 1.10 and older clients will send this, but it
+                      doesn't do any good.  So tell the user we can't
+                      cope, rather than silently losing.  */
+                   error (0, 0,
+                          "warning: not setting the time of import from the 
file");
+                   error (0, 0, "due to client limitations");
+               }
+               use_file_modtime = 1;
+               break;
+           case 'b':
+               free (vbranch);
+               vbranch = xstrdup (optarg);
+               break;
+           case 'm':
+#ifdef FORCE_USE_EDITOR
+               use_editor = 1;
+#else
+               use_editor = 0;
+#endif
+               if (message) free (message);
+               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 'X':
+               killnew = true;
+               break;
+           case '?':
+           default:
+               usage (import_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+    if (argc < 3)
+       usage (import_usage);
+
+    /* This is for handling the Checkin-time request.  It might seem a
+       bit odd to enable the use_file_modtime code even in the case
+       where Checkin-time was not sent for a particular file.  The
+       effect is that we use the time of upload, rather than the time
+       when we call RCS_checkin.  Since those times are both during
+       CVS's run, that seems OK, and it is easier to implement than
+       putting the "was Checkin-time sent" flag in CVS/Entries or some
+       such place.  */
+
+    if (server_active)
+       use_file_modtime = 1;
+
+    /* Don't allow "CVS" as any directory in module path.
+     *
+     * Could abstract this to valid_module_path, but I don't think we'll need
+     * to call it from anywhere else.
+     */
+    if ((cp = strstr (argv[0], "CVS")) &&   /* path contains "CVS" AND ... */
+        ((cp == argv[0]) || ISSLASH (*(cp-1))) && /* /^CVS/ OR m#/CVS# AND ... 
*/
+        ((*(cp+3) == '\0') || ISSLASH (*(cp+3))) /* /CVS$/ OR m#CVS/# */
+       )
+    {
+        error (0, 0,
+               "The word `CVS' is reserved by CVS and may not be used");
+        error (1, 0, "as a directory in a path or as a file name.");
+    }
+
+    for (i = 1; i < argc; i++)         /* check the tags for validity */
+    {
+       int j;
+
+       RCS_check_tag (argv[i]);
+       for (j = 1; j < i; j++)
+           if (strcmp (argv[j], argv[i]) == 0)
+               error (1, 0, "tag `%s' was specified more than once", argv[i]);
+    }
+
+    if (ISABSOLUTE (argv[0]) || pathname_levels (argv[0]) > 0)
+       /* It is somewhere between a security hole and "unexpected" to
+          let the client start mucking around outside the cvsroot
+          (wouldn't get the right CVSROOT configuration, &c).  */
+       error (1, 0, "directory %s not relative within the repository",
+              argv[0]);
+
+    if (current_parsed_root == NULL)
+    {
+       error (0, 0, "missing CVSROOT environment variable\n");
+       error (1, 0, "Set it or specify the '-d' option to %s.",
+              program_name);
+    }
+    repository = Xasprintf ("%s/%s", current_parsed_root->directory, argv[0]);
+    repos_len = strlen (current_parsed_root->directory);
+
+    /*
+     * 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").
+     */
+    {
+       regex_t pat;
+       int ret = regcomp (&pat, "^[1-9][0-9]*\\.[1-9][0-9]*\\.[1-9][0-9]*$",
+                          REG_EXTENDED);
+       assert (!ret);
+       if (regexec (&pat, vbranch, 0, NULL, 0))
+       {
+           error (1, 0,
+"Only numeric branch specifications with two dots are\n"
+"supported by import, not `%s'.  For example: `1.1.1'.",
+                  vbranch);
+       }
+       regfree (&pat);
+    }
+
+    /* Set vhead to the branch's parent.  */
+    vhead = xstrdup (vbranch);
+    cp = strrchr (vhead, '.');
+    *cp = '\0';
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       /* For rationale behind calling start_server before do_editor, see
+          commit.c  */
+       start_server ();
+    }
+#endif
+
+    if (!server_active && use_editor)
+    {
+       do_editor (NULL, &message,
+                  current_parsed_root->isremote ? NULL : repository,
+                  NULL);
+    }
+    msglen = message == NULL ? 0 : strlen (message);
+    if (msglen == 0 || message[msglen - 1] != '\n')
+    {
+       char *nm = xmalloc (msglen + 2);
+       *nm = '\0';
+       if (message != NULL)
+       {
+           (void) strcpy (nm, message);
+           free (message);
+       }
+       (void) strcat (nm + msglen, "\n");
+       message = nm;
+    }
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       int err;
+
+       if (vbranch[0] != '\0')
+           option_with_arg ("-b", vbranch);
+       option_with_arg ("-m", message ? message : "");
+       if (keyword_opt != NULL)
+           option_with_arg ("-k", keyword_opt);
+       if (killnew)
+           send_arg ("-X");
+       /* The only ignore processing which takes place on the server side
+          is the CVSROOT/cvsignore file.  But if the user specified -I !,
+          the documented behavior is to not process said file.  */
+       if (ign_inhibit_server)
+       {
+           send_arg ("-I");
+           send_arg ("!");
+       }
+       wrap_send ();
+
+       {
+           int i;
+           for (i = 0; i < argc; ++i)
+               send_arg (argv[i]);
+       }
+
+       logfp = stdin;
+       client_import_setup (repository);
+       err = import_descend (message, argv[1], argc - 2, argv + 2);
+       client_import_done ();
+       if (message)
+           free (message);
+       free (repository);
+       free (vbranch);
+       free (vhead);
+       send_to_server ("import\012", 0);
+       err += get_responses_and_close ();
+       return err;
+    }
+#endif
+
+    if (!safe_location (NULL))
+    {
+       error (1, 0, "attempt to import the repository");
+    }
+
+    ulist = getlist ();
+    p = getnode ();
+    p->type = UPDATE;
+    p->delproc = update_delproc;
+    p->key = xstrdup ("- Imported sources");
+    li = xmalloc (sizeof (struct logfile_info));
+    li->type = T_TITLE;
+    li->tag = xstrdup (vbranch);
+    li->rev_old = li->rev_new = NULL;
+    p->data = li;
+    (void) addnode (ulist, p);
+    do_verify (&message, repository, ulist);
+
+    /*
+     * 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 = cvs_temp_file (&tmpfile)) == NULL)
+       error (1, errno, "cannot create temporary file `%s'", tmpfile);
+    /* On systems where we can unlink an open file, do so, so it will go
+       away no matter how we exit.  FIXME-maybe: Should be checking for
+       errors but I'm not sure which error(s) we get if we are on a system
+       where one can't unlink open files.  */
+    (void) CVS_UNLINK (tmpfile);
+    (void) fprintf (logfp, "\nVendor Tag:\t%s\n", argv[1]);
+    (void) fprintf (logfp, "Release Tags:\t");
+    for (i = 2; i < argc; i++)
+       (void) fprintf (logfp, "%s\n\t\t", argv[i]);
+    (void) fprintf (logfp, "\n");
+
+    /* Just Do It.  */
+    err = import_descend (message, argv[1], argc - 2, argv + 2);
+    if (conflicts || killnew)
+    {
+       if (!really_quiet)
+       {
+           char buf[20];
+
+           cvs_output_tagged ("+importmergecmd", NULL);
+           cvs_output_tagged ("newline", NULL);
+           if (conflicts)
+               sprintf (buf, "%d", conflicts);
+           else
+               strcpy (buf, "No");
+           cvs_output_tagged ("conflicts", buf);
+           cvs_output_tagged ("text", " conflicts created by this import.");
+           cvs_output_tagged ("newline", NULL);
+           cvs_output_tagged ("text",
+                              "Use the following command to help the merge:");
+           cvs_output_tagged ("newline", NULL);
+           cvs_output_tagged ("newline", NULL);
+           cvs_output_tagged ("text", "\t");
+           cvs_output_tagged ("text", program_name);
+           if (CVSroot_cmdline != NULL)
+           {
+               cvs_output_tagged ("text", " -d ");
+               cvs_output_tagged ("text", CVSroot_cmdline);
+           }
+           cvs_output_tagged ("text", " checkout -j");
+           cvs_output_tagged ("mergetag1", "<prev_rel_tag>");
+           cvs_output_tagged ("text", " -j");
+           cvs_output_tagged ("mergetag2", argv[2]);
+           cvs_output_tagged ("text", " ");
+           cvs_output_tagged ("repository", argv[0]);
+           cvs_output_tagged ("newline", NULL);
+           cvs_output_tagged ("newline", NULL);
+           cvs_output_tagged ("-importmergecmd", NULL);
+       }
+
+       /* FIXME: I'm not sure whether we need to put this information
+           into the loginfo.  If we do, then note that it does not
+           report any required -d option.  There is no particularly
+           clean way to tell the server about the -d option used by
+           the client.  */
+       if (conflicts)
+           (void) fprintf (logfp, "\n%d", conflicts);
+       else
+           (void) fprintf (logfp, "\nNo");
+       (void) fprintf (logfp, " conflicts created by this import.\n");
+       (void) fprintf (logfp,
+                       "Use the following command to help the merge:\n\n");
+       (void) fprintf (logfp, "\t%s checkout ", program_name);
+       (void) fprintf (logfp, "-j%s:yesterday -j%s %s\n\n",
+                       argv[1], argv[1], argv[0]);
+    }
+    else
+    {
+       if (!really_quiet)
+           cvs_output ("\nNo conflicts created by this import\n\n", 0);
+       (void) fprintf (logfp, "\nNo conflicts created by this import\n\n");
+    }
+
+    /*
+     * Write out the logfile and clean up.
+     */
+    Update_Logfile (repository, message, logfp, ulist);
+    dellist (&ulist);
+    if (fclose (logfp) < 0)
+       error (0, errno, "error closing %s", tmpfile);
+
+    /* Make sure the temporary file goes away, even on systems that don't let
+       you delete a file that's in use.  */
+    if (CVS_UNLINK (tmpfile) < 0 && !existence_error (errno))
+       error (0, errno, "cannot remove %s", tmpfile);
+    free (tmpfile);
+
+    char *commitid = Xasprintf ("@%s", global_session_id);
+    tag_check_valid (commitid, argc, argv, 0, 1, "", true);
+    free (commitid);
+
+    if (message)
+       free (message);
+    free (repository);
+    free (vbranch);
+    free (vhead);
+
+    return err;
+}
+
+/* Process all the files in ".", then descend into other directories.
+   Returns 0 for success, or >0 on error (in which case a message
+   will have been printed).  */
+static int
+import_descend (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 (!current_parsed_root->isremote)
+       lock_dir_for_write (repository);
+
+    if ((dirp = CVS_OPENDIR (".")) == NULL)
+    {
+       error (0, errno, "cannot open directory");
+       err++;
+    }
+    else
+    {
+       errno = 0;
+       while ((dp = CVS_READDIR (dirp)) != NULL)
+       {
+           if (strcmp (dp->d_name, ".") == 0 || strcmp (dp->d_name, "..") == 0)
+               goto one_more_time_boys;
+
+           /* CVS directories are created in the temp directory by
+              server.c because it doesn't special-case import.  So
+              don't print a message about them, regardless of -I!.  */
+           if (server_active && strcmp (dp->d_name, CVSADM) == 0)
+               goto one_more_time_boys;
+
+           if (ign_name (dp->d_name))
+           {
+               add_log ('I', dp->d_name);
+               goto one_more_time_boys;
+           }
+
+           if (
+#ifdef DT_DIR
+               (dp->d_type == DT_DIR
+                || (dp->d_type == DT_UNKNOWN && isdir (dp->d_name)))
+#else
+               isdir (dp->d_name)
+#endif
+               && !wrap_name_has (dp->d_name, WRAP_TOCVS)
+               )
+           {
+               Node *n;
+
+               if (dirlist == NULL)
+                   dirlist = getlist ();
+
+               n = getnode ();
+               n->key = xstrdup (dp->d_name);
+               addnode (dirlist, n);
+           }
+           else if (
+#ifdef DT_DIR
+                    dp->d_type == DT_LNK
+                    || (dp->d_type == DT_UNKNOWN && islink (dp->d_name))
+#else
+                    islink (dp->d_name)
+#endif
+                    )
+           {
+               add_log ('L', dp->d_name);
+               err++;
+           }
+           else
+           {
+#ifdef CLIENT_SUPPORT
+               if (current_parsed_root->isremote)
+                   err += client_process_import_file (message, dp->d_name,
+                                                       vtag, targc, targv,
+                                                       repository,
+                                                       keyword_opt != NULL &&
+                                                      keyword_opt[0] == 'b',
+                                                      use_file_modtime);
+               else
+#endif
+                   err += process_import_file (message, dp->d_name,
+                                               vtag, targc, targv);
+           }
+       one_more_time_boys:
+           errno = 0;
+       }
+       if (errno != 0)
+       {
+           error (0, errno, "cannot read directory");
+           ++err;
+       }
+       (void) CVS_CLOSEDIR (dirp);
+    }
+
+    if (!current_parsed_root->isremote)
+       Simple_Lock_Cleanup ();
+
+    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 (char *message, char *vfile, char *vtag, int targc,
+                    char **targv)
+{
+    char *rcs;
+    int inattic = 0;
+
+    rcs = Xasprintf ("%s/%s%s", repository, vfile, RCSEXT);
+    if (!isfile (rcs))
+    {
+       char *attic_name;
+
+       attic_name = xmalloc (strlen (repository) + strlen (vfile) +
+                             sizeof (CVSATTIC) + sizeof (RCSEXT) + 10);
+       (void) sprintf (attic_name, "%s/%s/%s%s", repository, CVSATTIC,
+                       vfile, RCSEXT);
+       if (!isfile (attic_name))
+       {
+           int retval;
+           char *free_opt = NULL;
+           char *our_opt = keyword_opt;
+
+           /* If marking newly-imported files as dead, they must be
+              created in the attic!  */
+           if (!killnew)
+               free (attic_name);
+           else 
+           {
+               free (rcs);
+               rcs = attic_name;
+
+               /* Attempt to make the Attic directory, in case it
+                  does not exist.  */
+               (void) sprintf (rcs, "%s/%s", repository, CVSATTIC);
+               if (CVS_MKDIR (rcs, 0777 ) != 0 && errno != EEXIST)
+                   error (1, errno, "cannot make directory `%s'", rcs);
+
+               /* Note that the above clobbered the path name, so we
+                  recreate it here.  */
+               (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC,
+                               vfile, RCSEXT);
+           }
+
+           /*
+            * A new import source file; it doesn't exist as a ,v within the
+            * repository nor in the Attic -- create it anew.
+            */
+           add_log ('N', vfile);
+
+#ifdef SERVER_SUPPORT
+           /* The most reliable information on whether the file is binary
+              is what the client told us.  That is because if the client had
+              the wrong idea about binaryness, it corrupted the file, so
+              we might as well believe the client.  */
+           if (server_active)
+           {
+               Node *node;
+               List *entries;
+
+               /* Reading all the entries for each file is fairly silly, and
+                  probably slow.  But I am too lazy at the moment to do
+                  anything else.  */
+               entries = Entries_Open (0, NULL);
+               node = findnode_fn (entries, vfile);
+               if (node != NULL)
+               {
+                   Entnode *entdata = node->data;
+
+                   if (entdata->type == ENT_FILE)
+                   {
+                       assert (entdata->options[0] == '-'
+                               && entdata->options[1] == 'k');
+                       our_opt = xstrdup (entdata->options + 2);
+                       free_opt = our_opt;
+                   }
+               }
+               Entries_Close (entries);
+           }
+#endif
+
+           retval = add_rcs_file (message, rcs, vfile, vhead, our_opt,
+                                  vbranch, vtag, targc, targv,
+                                  NULL, 0, logfp, killnew);
+           if (free_opt != NULL)
+               free (free_opt);
+           free (rcs);
+           return retval;
+       }
+       free (attic_name);
+       inattic = 1;
+    }
+
+    free (rcs);
+    /*
+     * an rcs file exists. have to do things the official, slow, way.
+     */
+    return update_rcs_file (message, vfile, vtag, targc, targv, inattic);
+}
+
+/*
+ * The RCS file exists; update it by adding the new import file to the
+ * (possibly already existing) vendor branch.
+ */
+static int
+update_rcs_file (char *message, char *vfile, char *vtag, int targc,
+                char **targv, int inattic)
+{
+    Vers_TS *vers;
+    int letter;
+    char *tocvsPath;
+    char *expand;
+    struct file_info finfo;
+
+    memset (&finfo, 0, sizeof finfo);
+    finfo.file = vfile;
+    /* Not used, so don't worry about it.  */
+    finfo.update_dir = NULL;
+    finfo.fullname = finfo.file;
+    finfo.repository = repository;
+    finfo.entries = NULL;
+    finfo.rcs = NULL;
+    vers = Version_TS (&finfo, NULL, vbranch, NULL, 1, 0);
+    if (vers->vn_rcs != NULL
+       && !RCS_isdead (vers->srcfile, vers->vn_rcs))
+    {
+       int different;
+
+       /*
+        * The rcs file does have a revision on the vendor branch. Compare
+        * this revision with the import file; if they match exactly, there
+        * is no need to install the new import file as a new revision to the
+        * branch.  Just tag the revision with the new import tags.
+        * 
+        * This is to try to cut down the number of "C" conflict messages for
+        * locally modified import source files.
+        */
+       tocvsPath = wrap_tocvs_process_file (vfile);
+       /* FIXME: Why don't we pass tocvsPath to RCS_cmp_file if it is
+           not NULL?  */
+       expand = (vers->srcfile->expand != NULL
+                 && vers->srcfile->expand[0] == 'b') ? "-kb" : "-ko";
+       different = RCS_cmp_file (vers->srcfile, vers->vn_rcs, NULL,
+                                 NULL, expand, vfile);
+       if (tocvsPath)
+           if (unlink_file_dir (tocvsPath) < 0)
+               error (0, errno, "cannot remove %s", tocvsPath);
+
+       if (!different)
+       {
+           int retval = 0;
+
+           /*
+            * The two files are identical.  Just update the tags, print the
+            * "U", signifying that the file has changed, but needs no
+            * attention, and we're done.
+            */
+           if (add_tags (vers->srcfile, vfile, vtag, targc, targv))
+               retval = 1;
+           add_log ('U', vfile);
+           freevers_ts (&vers);
+           return retval;
+       }
+    }
+
+    /* We may have failed to parse the RCS file; check just in case */
+    if (vers->srcfile == NULL ||
+       add_rev (message, vers->srcfile, vfile, vers->vn_rcs) ||
+       add_tags (vers->srcfile, vfile, vtag, targc, targv))
+    {
+       freevers_ts (&vers);
+       return 1;
+    }
+
+    if (vers->srcfile->branch == NULL || inattic ||
+       strcmp (vers->srcfile->branch, vbranch) != 0)
+    {
+       conflicts++;
+       letter = 'C';
+    }
+    else
+       letter = 'U';
+    add_log (letter, vfile);
+
+    freevers_ts (&vers);
+    return 0;
+}
+
+/*
+ * Add the revision to the vendor branch
+ */
+static int
+add_rev (char *message, RCSNode *rcs, char *vfile, char *vers)
+{
+    int locked, status, ierrno;
+    char *tocvsPath;
+
+    if (noexec)
+       return 0;
+
+    locked = 0;
+    if (vers != NULL)
+    {
+       /* Before RCS_lock existed, we were directing stdout, as well as
+          stderr, from the RCS command, to DEVNULL.  I wouldn't guess that
+          was necessary, but I don't know for sure.  */
+       /* Earlier versions of this function printed a `fork failed' error
+          when RCS_lock returned an error code.  That's not appropriate
+          now that RCS_lock is librarified, but should the error text be
+          preserved? */
+       if (RCS_lock (rcs, vbranch, 1) != 0)
+           return 1;
+       locked = 1;
+       RCS_rewrite (rcs, NULL, NULL);
+    }
+    tocvsPath = wrap_tocvs_process_file (vfile);
+
+    status = RCS_checkin (rcs, NULL, tocvsPath == NULL ? vfile : tocvsPath,
+                         message, vbranch, 0,
+                         (RCS_FLAGS_QUIET | RCS_FLAGS_KEEPFILE
+                          | (use_file_modtime ? RCS_FLAGS_MODTIME : 0)));
+    ierrno = errno;
+
+    if ((tocvsPath != NULL) && (unlink_file_dir (tocvsPath) < 0))
+       error (0, errno, "cannot remove %s", tocvsPath);
+
+    if (status)
+    {
+       if (!noexec)
+       {
+           fperrmsg (logfp, 0, status == -1 ? ierrno : 0,
+                     "ERROR: Check-in of %s failed", rcs->path);
+           error (0, status == -1 ? ierrno : 0,
+                  "ERROR: Check-in of %s failed", rcs->path);
+       }
+       if (locked)
+       {
+           (void) RCS_unlock (rcs, vbranch, 0);
+           RCS_rewrite (rcs, NULL, NULL);
+       }
+       return 1;
+    }
+    return 0;
+}
+
+/*
+ * Add the vendor branch tag and all the specified import release tags to the
+ * RCS file.  The vendor branch tag goes on the branch root (1.1.1) while the
+ * vendor release tags go on the newly added leaf of the branch (1.1.1.1,
+ * 1.1.1.2, ...).
+ */
+static int
+add_tags (RCSNode *rcs, char *vfile, char *vtag, int targc, char **targv)
+{
+    int i, ierrno;
+    Vers_TS *vers;
+    int retcode = 0;
+    struct file_info finfo;
+
+    if (noexec)
+       return 0;
+
+    if ((retcode = RCS_settag (rcs, vtag, vbranch)) != 0)
+    {
+       ierrno = errno;
+       fperrmsg (logfp, 0, retcode == -1 ? ierrno : 0,
+                 "ERROR: Failed to set tag %s in %s", vtag, rcs->path);
+       error (0, retcode == -1 ? ierrno : 0,
+              "ERROR: Failed to set tag %s in %s", vtag, rcs->path);
+       return 1;
+    }
+    RCS_rewrite (rcs, NULL, NULL);
+
+    memset (&finfo, 0, sizeof finfo);
+    finfo.file = vfile;
+    /* Not used, so don't worry about it.  */
+    finfo.update_dir = NULL;
+    finfo.fullname = finfo.file;
+    finfo.repository = repository;
+    finfo.entries = NULL;
+    finfo.rcs = NULL;
+    vers = Version_TS (&finfo, NULL, vtag, NULL, 1, 0);
+    for (i = 0; i < targc; i++)
+    {
+       if ((retcode = RCS_settag (rcs, targv[i], vers->vn_rcs)) == 0)
+           RCS_rewrite (rcs, NULL, NULL);
+       else
+       {
+           ierrno = errno;
+           fperrmsg (logfp, 0, retcode == -1 ? ierrno : 0,
+                     "WARNING: Couldn't add tag %s to %s", targv[i],
+                     rcs->path);
+           error (0, retcode == -1 ? ierrno : 0,
+                  "WARNING: Couldn't add tag %s to %s", targv[i],
+                  rcs->path);
+       }
+    }
+    freevers_ts (&vers);
+    return 0;
+}
+
+/*
+ * Stolen from rcs/src/rcsfnms.c, and adapted/extended.
+ */
+struct compair
+{
+    char *suffix, *comlead;
+};
+
+static const struct compair comtable[] =
+{
+
+/*
+ * comtable pairs each filename suffix with a comment leader. The comment
+ * leader is placed before each line generated by the $Log keyword. This
+ * table is used to guess the proper comment leader from the working file's
+ * suffix during initial ci (see InitAdmin()). Comment leaders are needed for
+ * languages without multiline comments; for others they are optional.
+ *
+ * I believe that the comment leader is unused if you are using RCS 5.7, which
+ * decides what leader to use based on the text surrounding the $Log keyword
+ * rather than a specified comment leader.
+ */
+    {"a", "-- "},                      /* Ada           */
+    {"ada", "-- "},
+    {"adb", "-- "},
+    {"asm", ";; "},                    /* assembler (MS-DOS) */
+    {"ads", "-- "},                    /* Ada           */
+    {"bas", "' "},                     /* Visual Basic code */
+    {"bat", ":: "},                    /* batch (MS-DOS) */
+    {"body", "-- "},                   /* Ada           */
+    {"c", " * "},                      /* C             */
+    {"c++", "// "},                    /* C++ in all its infinite guises */
+    {"cc", "// "},
+    {"cpp", "// "},
+    {"cxx", "// "},
+    {"m", "// "},                      /* Objective-C */
+    {"cl", ";;; "},                    /* Common Lisp   */
+    {"cmd", ":: "},                    /* command (OS/2) */
+    {"cmf", "c "},                     /* CM Fortran    */
+    {"cs", " * "},                     /* C*            */
+    {"csh", "# "},                     /* shell         */
+    {"dlg", " * "},                    /* MS Windows dialog file */
+    {"e", "# "},                       /* efl           */
+    {"epsf", "% "},                    /* encapsulated postscript */
+    {"epsi", "% "},                    /* encapsulated postscript */
+    {"el", "; "},                      /* Emacs Lisp    */
+    {"f", "c "},                       /* Fortran       */
+    {"for", "c "},
+    {"frm", "' "},                     /* Visual Basic form */
+    {"h", " * "},                      /* C-header      */
+    {"hh", "// "},                     /* C++ header    */
+    {"hpp", "// "},
+    {"hxx", "// "},
+    {"in", "# "},                      /* for Makefile.in */
+    {"l", " * "},                      /* lex (conflict between lex and
+                                        * franzlisp) */
+    {"mac", ";; "},                    /* macro (DEC-10, MS-DOS, PDP-11,
+                                        * VMS, etc) */
+    {"mak", "# "},                     /* makefile, e.g. Visual C++ */
+    {"me", ".\\\" "},                  /* me-macros    t/nroff  */
+    {"ml", "; "},                      /* mocklisp      */
+    {"mm", ".\\\" "},                  /* mm-macros    t/nroff  */
+    {"ms", ".\\\" "},                  /* ms-macros    t/nroff  */
+    {"man", ".\\\" "},                 /* man-macros   t/nroff  */
+    {"1", ".\\\" "},                   /* feeble attempt at man pages... */
+    {"2", ".\\\" "},
+    {"3", ".\\\" "},
+    {"4", ".\\\" "},
+    {"5", ".\\\" "},
+    {"6", ".\\\" "},
+    {"7", ".\\\" "},
+    {"8", ".\\\" "},
+    {"9", ".\\\" "},
+    {"p", " * "},                      /* pascal        */
+    {"pas", " * "},
+    {"pl", "# "},                      /* perl (conflict with Prolog) */
+    {"ps", "% "},                      /* postscript    */
+    {"psw", "% "},                     /* postscript wrap */
+    {"pswm", "% "},                    /* postscript wrap */
+    {"r", "# "},                       /* ratfor        */
+    {"rc", " * "},                     /* Microsoft Windows resource file */
+    {"red", "% "},                     /* psl/rlisp     */
+#ifdef sparc
+    {"s", "! "},                       /* assembler     */
+#endif
+#ifdef mc68000
+    {"s", "| "},                       /* assembler     */
+#endif
+#ifdef pdp11
+    {"s", "/ "},                       /* assembler     */
+#endif
+#ifdef vax
+    {"s", "# "},                       /* assembler     */
+#endif
+#ifdef __ksr__
+    {"s", "# "},                       /* assembler     */
+    {"S", "# "},                       /* Macro assembler */
+#endif
+    {"sh", "# "},                      /* shell         */
+    {"sl", "% "},                      /* psl           */
+    {"spec", "-- "},                   /* Ada           */
+    {"tex", "% "},                     /* tex           */
+    {"y", " * "},                      /* yacc          */
+    {"ye", " * "},                     /* yacc-efl      */
+    {"yr", " * "},                     /* yacc-ratfor   */
+    {"", "# "},                                /* default for empty suffix     
 */
+    {NULL, "# "}                       /* default for unknown suffix;   */
+/* must always be last          */
+};
+
+
+
+static char *
+get_comment (const char *user)
+{
+    char *cp, *suffix;
+    char *suffix_path;
+    int i;
+    char *retval;
+
+    suffix_path = xmalloc (strlen (user) + 5);
+    cp = strrchr (user, '.');
+    if (cp != NULL)
+    {
+       cp++;
+
+       /*
+        * Convert to lower-case, since we are not concerned about the
+        * case-ness of the suffix.
+        */
+       (void) strcpy (suffix_path, cp);
+       for (cp = suffix_path; *cp; cp++)
+           if (isupper ((unsigned char) *cp))
+               *cp = tolower (*cp);
+       suffix = suffix_path;
+    }
+    else
+       suffix = "";                    /* will use the default */
+    for (i = 0;; i++)
+    {
+       if (comtable[i].suffix == NULL)
+       {
+           /* Default.  Note we'll always hit this case before we
+              ever return NULL.  */
+           retval = comtable[i].comlead;
+           break;
+       }
+       if (strcmp (suffix, comtable[i].suffix) == 0)
+       {
+           retval = comtable[i].comlead;
+           break;
+       }
+    }
+    free (suffix_path);
+    return retval;
+}
+
+/* Create a new RCS file from scratch.
+ *
+ * This probably should be moved to rcs.c now that it is called from
+ * places outside import.c.
+ *
+ * INPUTS
+ *   message    Log message for the addition.  Not used if add_vhead == NULL.
+ *   rcs        Filename of the RCS file to create.  Note that if 'do_killnew'
+ *             is set, this file should be in the Attic directory, and the
+ *             Attic directory must already exist.
+ *   user       Filename of the file to serve as the contents of the initial
+ *              revision.  Even if add_vhead is NULL, we use this to determine
+ *              the modes to give the new RCS file.
+ *   add_vhead  Revision number of head that we are adding.  Normally 1.1 but
+ *              could be another revision as long as ADD_VBRANCH is a branch
+ *              from it.  If NULL, then just add an empty file without any
+ *              revisions (similar to the one created by "rcs -i").
+ *   key_opt    Keyword expansion mode, e.g., "b" for binary.  NULL means the
+ *              default behavior.
+ *   add_vbranch
+ *              Vendor branch to import to, or NULL if none.  If non-NULL, then
+ *              vtag should also be non-NULL.
+ *   vtag
+ *   targc      Number of elements in TARGV.
+ *   targv      The list of tags to attached to this imported revision.
+ *   desctext   If non-NULL, description for the file.  If NULL, the
+ *              description will be empty.
+ *   desclen    The number of bytes in desctext.
+ *   add_logfp  Write errors to here as well as via error (), or NULL if we
+ *              should use only error ().
+ *   do_killnew        Mark newly-imported files as being dead on the trunk, 
i.e.,
+ *             as being imported only to the vendor branch.
+ *
+ * RETURNS
+ *   Return value is 0 for success, or nonzero for failure (in which
+ *   case an error message will have already been printed).
+ */
+int
+add_rcs_file (const char *message, const char *rcs, const char *user,
+              const char *add_vhead, const char *key_opt,
+              const char *add_vbranch, const char *vtag, int targc,
+              char **targv, const char *desctext, size_t desclen,
+              FILE *add_logfp, bool do_killnew)
+{
+    FILE *fprcs, *fpuser;
+    struct stat sb;
+    struct tm *ftm;
+    time_t now;
+    char altdate1[MAXDATELEN];
+    char *author;
+    int i, ierrno, err = 0;
+    mode_t mode;
+    char *tocvsPath;
+    const char *userfile;
+    char *free_opt = NULL;
+    mode_t file_type;
+    char *dead_revision = NULL;
+
+    if (noexec)
+       return 0;
+
+    if (do_killnew)
+    {
+       char *last_place;
+       int last_number;
+
+       /* If we are marking the newly imported file as dead, we must
+          have a head revision.  */
+       if (add_vhead == NULL)
+           error (1, 0, "killing new file attempted when no head revision is 
being added");
+
+       /* One extra byte for NUL, plus one for carry generated by adding
+          one to the last number in the add_vhead revision.  */
+       dead_revision = xmalloc (strlen (add_vhead) + 2);
+       strcpy (dead_revision, add_vhead);
+
+       /* Find the loacation of the last number, which we will increment
+          and overwrite.  Note that this handles single numbers (w/o
+          dots), which is probably unnecessary.  */
+       if ((last_place = strrchr (dead_revision, '.')) != NULL)
+           last_place++;
+       else
+           last_place = dead_revision;
+       last_number = atoi (last_place);
+       if (++last_number <= 0)
+         error (1, 0, "invalid revision number %s", add_vhead);
+       sprintf (last_place, "%d", last_number);
+    }
+
+    /* Note that as the code stands now, the -k option overrides any
+       settings in wrappers (whether CVSROOT/cvswrappers, -W, or
+       whatever).  Some have suggested this should be the other way
+       around.  As far as I know the documentation doesn't say one way
+       or the other.  Before making a change of this sort, should think
+       about what is best, document it (in cvs.texinfo and NEWS), &c.  */
+
+    if (key_opt == NULL)
+    {
+       if (wrap_name_has (user, WRAP_RCSOPTION))
+       {
+           key_opt = free_opt = wrap_rcsoption (user, 0);
+       }
+    }
+
+    tocvsPath = wrap_tocvs_process_file (user);
+    userfile = (tocvsPath == NULL ? user : tocvsPath);
+
+    /* Opening in text mode is probably never the right thing for the
+       server (because the protocol encodes text files in a fashion
+       which does not depend on what the client or server OS is, as
+       documented in cvsclient.texi), but as long as the server just
+       runs on unix it is a moot point.  */
+
+    /* If PreservePermissions is set, then make sure that the file
+       is a plain file before trying to open it.  Longstanding (although
+       often unpopular) CVS behavior has been to follow symlinks, so we
+       maintain that behavior if PreservePermissions is not on.
+
+       NOTE: this error message used to be `cannot fstat', but is now
+       `cannot lstat'.  I don't see a way around this, since we must
+       stat the file before opening it. -twp */
+
+    if (lstat (userfile, &sb) < 0)
+    {
+       /* not fatal, continue import */
+       if (add_logfp != NULL)
+           fperrmsg (add_logfp, 0, errno,
+                         "ERROR: cannot lstat file %s", userfile);
+       error (0, errno, "cannot lstat file %s", userfile);
+       goto read_error;
+    }
+    file_type = sb.st_mode & S_IFMT;
+
+    fpuser = NULL;
+    if (
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       !config->preserve_perms ||
+#endif /* PRESERVE_PERMISSIONS_SUPPORT */
+       file_type == S_IFREG)
+    {
+       fpuser = CVS_FOPEN (userfile,
+                           ((key_opt != NULL && strcmp (key_opt, "b") == 0)
+                            ? "rb"
+                            : "r")
+           );
+       if (fpuser == NULL)
+       {
+           /* not fatal, continue import */
+           if (add_logfp != NULL)
+               fperrmsg (add_logfp, 0, errno,
+                         "ERROR: cannot read file %s", userfile);
+           error (0, errno, "ERROR: cannot read file %s", userfile);
+           goto read_error;
+       }
+    }
+
+    fprcs = CVS_FOPEN (rcs, "w+b");
+    if (fprcs == NULL)
+    {
+       ierrno = errno;
+       goto write_error_noclose;
+    }
+
+    /*
+     * putadmin()
+     */
+    if (add_vhead != NULL)
+    {
+       if (fprintf (fprcs, "head     %s;\012",
+                    do_killnew ? dead_revision : add_vhead) < 0)
+           goto write_error;
+    }
+    else
+    {
+       if (fprintf (fprcs, "head     ;\012") < 0)
+           goto write_error;
+    }
+
+    /* This sets the default branch.  If using the 'do_killnew' functionality,
+       where imports don't show up until merged, no default branch should
+       be set.  */
+    if (add_vbranch != NULL && ! do_killnew)
+    {
+       if (fprintf (fprcs, "branch   %s;\012", add_vbranch) < 0)
+           goto write_error;
+    }
+    if (fprintf (fprcs, "access   ;\012") < 0 ||
+       fprintf (fprcs, "symbols  ") < 0)
+    {
+       goto write_error;
+    }
+
+    for (i = targc - 1; i >= 0; i--)
+    {
+       /* RCS writes the symbols backwards */
+       assert (add_vbranch != NULL);
+       if (fprintf (fprcs, "%s:%s.1 ", targv[i], add_vbranch) < 0)
+           goto write_error;
+    }
+
+    if (add_vbranch != NULL)
+    {
+       if (fprintf (fprcs, "%s:%s", vtag, add_vbranch) < 0)
+           goto write_error;
+    }
+    if (fprintf (fprcs, ";\012") < 0)
+       goto write_error;
+
+    if (fprintf (fprcs, "locks    ; strict;\012") < 0 ||
+       /* XXX - make sure @@ processing works in the RCS file */
+       fprintf (fprcs, "comment  @%s@;\012", get_comment (user)) < 0)
+    {
+       goto write_error;
+    }
+
+    if (key_opt != NULL && strcmp (key_opt, "kv") != 0)
+    {
+       if (fprintf (fprcs, "expand   @%s@;\012", key_opt) < 0)
+       {
+           goto write_error;
+       }
+    }
+
+    if (fprintf (fprcs, "\012") < 0)
+      goto write_error;
+
+    /* Write the revision(s), with the date and author and so on
+       (that is "delta" rather than "deltatext" from rcsfile(5)).  */
+
+    if (use_file_modtime)
+       now = sb.st_mtime;
+    else
+       (void) time (&now);
+    ftm = gmtime (&now);
+    (void) sprintf (altdate1, DATEFORM,
+                   ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+                   ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+                   ftm->tm_min, ftm->tm_sec);
+    author = getcaller ();
+
+    if (do_killnew)
+    {
+       if (fprintf (fprcs, "\012%s\012", dead_revision) < 0 ||
+       fprintf (fprcs, "date     %s;  author %s;  state %s;\012",
+                altdate1, author, RCSDEAD) < 0)
+       goto write_error;
+
+       if (fprintf (fprcs, "branches;\012") < 0)
+           goto write_error;
+       if (fprintf (fprcs, "next    %s;\012", add_vhead) < 0)
+           goto write_error;
+
+       if (fprintf (fprcs, "commitid        %s;\012", global_session_id) < 0)
+           goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       /* Store initial permissions if necessary. */
+       if (config->preserve_perms)
+       {
+           if (preserve_initial_permissions (fprcs, userfile,
+                                             file_type, sbp))
+               goto write_error;
+       }
+#endif
+    }
+
+    if (add_vhead != NULL)
+    {
+       if (fprintf (fprcs, "\012%s\012", add_vhead) < 0 ||
+       fprintf (fprcs, "date     %s;  author %s;  state Exp;\012",
+                altdate1, author) < 0)
+       goto write_error;
+
+       if (fprintf (fprcs, "branches") < 0)
+           goto write_error;
+       if (add_vbranch != NULL)
+       {
+           if (fprintf (fprcs, " %s.1", add_vbranch) < 0)
+               goto write_error;
+       }
+       if (fprintf (fprcs, ";\012") < 0)
+           goto write_error;
+
+       if (fprintf (fprcs, "next     ;\012") < 0)
+           goto write_error;
+
+       if (fprintf (fprcs, "commitid        %s;\012", global_session_id) < 0)
+           goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       /* Store initial permissions if necessary. */
+       if (config->preserve_perms)
+       {
+           if (preserve_initial_permissions (fprcs, userfile,
+                                             file_type, sbp))
+               goto write_error;
+       }
+#endif
+
+       if (add_vbranch != NULL)
+       {
+           if (fprintf (fprcs, "\012%s.1\012", add_vbranch) < 0 ||
+               fprintf (fprcs, "date     %s;  author %s;  state Exp;\012",
+                        altdate1, author) < 0 ||
+               fprintf (fprcs, "branches ;\012") < 0 ||
+               fprintf (fprcs, "next     ;\012") < 0 ||
+               fprintf (fprcs, "commitid        %s;\012", global_session_id) < 
0)
+               goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+           /* Store initial permissions if necessary. */
+           if (config->preserve_perms)
+           {
+               if (preserve_initial_permissions (fprcs, userfile,
+                                                 file_type, sbp))
+                   goto write_error;
+           }
+#endif
+
+           if (fprintf (fprcs, "\012") < 0)
+               goto write_error;
+       }
+    }
+
+    /* Now write the description (possibly empty).  */
+    if (fprintf (fprcs, "\012desc\012") < 0 ||
+       fprintf (fprcs, "@") < 0)
+       goto write_error;
+    if (desctext != NULL)
+    {
+       /* The use of off_t not size_t for the second argument is very
+          strange, since we are dealing with something which definitely
+          fits in memory.  */
+       if (expand_at_signs (desctext, (off_t) desclen, fprcs) < 0)
+           goto write_error;
+    }
+    if (fprintf (fprcs, "@\012\012\012") < 0)
+       goto write_error;
+
+    /* Now write the log messages and contents for the revision(s) (that
+       is, "deltatext" rather than "delta" from rcsfile(5)).  */
+
+    if (do_killnew)
+    {
+       if (fprintf (fprcs, "\012%s\012", dead_revision) < 0 ||
+           fprintf (fprcs, "log\012@") < 0)
+           goto write_error;
+       if (fprintf (fprcs, "Revision %s was added on the vendor branch.\012",
+                    add_vhead) < 0)
+           goto write_error;
+       if (fprintf (fprcs, "@\012") < 0 ||
+           fprintf (fprcs, "text\012@") < 0)
+       {
+           goto write_error;
+       }
+
+       /* Now copy over the contents of the file, expanding at signs.  */
+       if (expand_and_copy_contents (fprcs, file_type, user, fpuser))
+           goto write_error;
+
+       if (fprintf (fprcs, "@\012\012") < 0)
+           goto write_error;
+    }
+
+    if (add_vhead != NULL)
+    {
+       if (fprintf (fprcs, "\012%s\012", add_vhead) < 0 ||
+           fprintf (fprcs, "log\012@") < 0)
+           goto write_error;
+       if (add_vbranch != NULL)
+       {
+           /* We are going to put the log message in the revision on the
+              branch.  So putting it here too seems kind of redundant, I
+              guess (and that is what CVS has always done, anyway).  */
+           if (fprintf (fprcs, "Initial revision\012") < 0)
+               goto write_error;
+       }
+       else
+       {
+           if (expand_at_signs (message, (off_t) strlen (message), fprcs) < 0)
+               goto write_error;
+       }
+       if (fprintf (fprcs, "@\012") < 0 ||
+           fprintf (fprcs, "text\012@") < 0)
+       {
+           goto write_error;
+       }
+
+       /* Now copy over the contents of the file, expanding at signs.
+        * If config->preserve_perms is set, do this only for regular files.
+        */
+       if (!do_killnew)
+       {
+            /* Now copy over the contents of the file, expanding at signs,
+              if not done as part of do_killnew handling above.  */
+           if (expand_and_copy_contents (fprcs, file_type, user, fpuser))
+               goto write_error;
+       }
+
+       if (fprintf (fprcs, "@\012\012") < 0)
+           goto write_error;
+
+       if (add_vbranch != NULL)
+       {
+           if (fprintf (fprcs, "\012%s.1\012", add_vbranch) < 0 ||
+               fprintf (fprcs, "log\012@") < 0 ||
+               expand_at_signs (message,
+                                (off_t) strlen (message), fprcs) < 0 ||
+               fprintf (fprcs, "@\012text\012") < 0 ||
+               fprintf (fprcs, "@@\012") < 0)
+               goto write_error;
+       }
+    }
+
+    if (fclose (fprcs) == EOF)
+    {
+       ierrno = errno;
+       goto write_error_noclose;
+    }
+    /* Close fpuser only if we opened it to begin with. */
+    if (fpuser != NULL)
+    {
+       if (fclose (fpuser) < 0)
+           error (0, errno, "cannot close %s", user);
+    }
+
+    /*
+     * Fix the modes on the RCS files.  The user modes of the original
+     * user file are propagated to the group and other modes as allowed
+     * by the repository umask, except that all write permissions are
+     * turned off.
+     */
+    mode = (sb.st_mode |
+           (sb.st_mode & S_IRWXU) >> 3 |
+           (sb.st_mode & S_IRWXU) >> 6) &
+          ~cvsumask &
+          ~(S_IWRITE | S_IWGRP | S_IWOTH);
+    if (chmod (rcs, mode) < 0)
+    {
+       ierrno = errno;
+       if (add_logfp != NULL)
+           fperrmsg (add_logfp, 0, ierrno,
+                     "WARNING: cannot change mode of file %s", rcs);
+       error (0, ierrno, "WARNING: cannot change mode of file %s", rcs);
+       err++;
+    }
+    if (tocvsPath)
+       if (unlink_file_dir (tocvsPath) < 0)
+               error (0, errno, "cannot remove %s", tocvsPath);
+    if (free_opt != NULL)
+       free (free_opt);
+    return err;
+
+write_error:
+    ierrno = errno;
+    if (fclose (fprcs) < 0)
+       error (0, errno, "cannot close %s", rcs);
+write_error_noclose:
+    if (fclose (fpuser) < 0)
+       error (0, errno, "cannot close %s", user);
+    if (add_logfp != NULL)
+       fperrmsg (add_logfp, 0, ierrno, "ERROR: cannot write file %s", rcs);
+    error (0, ierrno, "ERROR: cannot write file %s", rcs);
+    if (ierrno == ENOSPC)
+    {
+       if (CVS_UNLINK (rcs) < 0)
+           error (0, errno, "cannot remove %s", rcs);
+       if (add_logfp != NULL)
+           fperrmsg (add_logfp, 0, 0, "ERROR: out of space - aborting");
+       error (1, 0, "ERROR: out of space - aborting");
+    }
+read_error:
+    if (tocvsPath)
+       if (unlink_file_dir (tocvsPath) < 0)
+           error (0, errno, "cannot remove %s", tocvsPath);
+
+    if (free_opt != NULL)
+       free (free_opt);
+
+    return err + 1;
+}
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+/* Write file permissions and symlink information for a file being
+ * added into its RCS file.
+ *
+ * INPUTS
+ *   fprcs     FILE pointer for the (newly-created) RCS file.  Permisisons
+ *             and symlink information should be written here.
+ *   userfile  Filename of the file being added.  (Used to read symbolic
+ *             link contents, for symlinks.)
+ *   file_type File type of userfile, extracted from sbp->st_mode.
+ *   sbp       'stat' information for userfile.
+ *
+ * RETURNS
+ *   Return value is 0 for success, or nonzero for failure (in which case
+ *   no error message has yet been printed).
+ */
+static int
+preserve_initial_permissions (fprcs, userfile, file_type, sbp)
+    FILE *fprcs;
+    const char *userfile;
+    mode_t file_type;
+    struct stat *sbp;
+{
+    if (file_type == S_IFLNK)
+    {
+       char *link = Xreadlink (userfile, sbp->st_size);
+       if (fprintf (fprcs, "symlink\t@") < 0 ||
+           expand_at_signs (link, strlen (link), fprcs) < 0 ||
+           fprintf (fprcs, "@;\012") < 0)
+           goto write_error;
+       free (link);
+    }
+    else
+    {
+       if (fprintf (fprcs, "owner\t%u;\012", sbp->st_uid) < 0)
+           goto write_error;
+       if (fprintf (fprcs, "group\t%u;\012", sbp->st_gid) < 0)
+           goto write_error;
+       if (fprintf (fprcs, "permissions\t%o;\012",
+                    sbp->st_mode & 07777) < 0)
+           goto write_error;
+       switch (file_type)
+       {
+           case S_IFREG: break;
+           case S_IFCHR:
+           case S_IFBLK:
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+               if (fprintf (fprcs, "special\t%s %lu;\012",
+                            (file_type == S_IFCHR
+                             ? "character"
+                             : "block"),
+                            (unsigned long) sbp->st_rdev) < 0)
+                   goto write_error;
+#else
+               error (0, 0,
+"can't import %s: unable to import device files on this system",
+userfile);
+#endif
+               break;
+           default:
+               error (0, 0,
+                      "can't import %s: unknown kind of special file",
+                      userfile);
+       }
+    }
+    return 0;
+
+write_error:
+    return 1;
+}
+#endif /* PRESERVE_PERMISSIONS_SUPPORT */
+
+/* Copy file contents into an RCS file, expanding at signs.
+ *
+ * If config->preserve_perms is set, nothing is copied if the source is not
+ * a regular file.
+ *
+ * INPUTS
+ *   fprcs     FILE pointer for the (newly-created) RCS file.  The expanded
+ *             contents should be written here.
+ *   file_type File type of the data source.  No data is copied if
+ *             preserve_permissions is set and the source is not a
+ *             regular file.
+ *   user      Filename of the data source (used to print error messages).
+ *   fpuser    FILE pointer for the data source, whose data is being
+ *             copied into the RCS file.
+ *
+ * RETURNS
+ *   Return value is 0 for success, or nonzero for failure (in which case
+ *   no error message has yet been printed).
+ */
+static int
+expand_and_copy_contents (fprcs, file_type, user, fpuser)
+    FILE *fprcs, *fpuser;
+    mode_t file_type;
+    const char *user;
+{
+    if (
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       !config->preserve_perms ||
+#endif /* PRESERVE_PERMISSIONS_SUPPORT */
+       file_type == S_IFREG)
+    {
+       char buf[8192];
+       unsigned int len;
+
+       while (1)
+       {
+           len = fread (buf, 1, sizeof buf, fpuser);
+           if (len == 0)
+           {
+               if (ferror (fpuser))
+                   error (1, errno, "cannot read file %s for copying",
+                          user);
+               break;
+           }
+           if (expand_at_signs (buf, len, fprcs) < 0)
+               goto write_error;
+       }
+    }
+    return 0;
+
+write_error:
+    return 1;
+}
+
+/*
+ * Write SIZE bytes at BUF to FP, expanding @ signs into double @
+ * signs.  If an error occurs, return a negative value and set errno
+ * to indicate the error.  If not, return a nonnegative value.
+ */
+int
+expand_at_signs (const char *buf, size_t size, FILE *fp)
+{
+    register const char *cp, *next;
+
+    cp = buf;
+    while ((next = memchr (cp, '@', size)) != NULL)
+    {
+       size_t len = ++next - cp;
+       if (fwrite (cp, 1, len, fp) != len)
+           return EOF;
+       if (putc ('@', fp) == EOF)
+           return EOF;
+       cp = next;
+       size -= len;
+    }
+
+    if (fwrite (cp, 1, size, fp) != size)
+       return EOF;
+
+    return 1;
+}
+
+/*
+ * Write an update message to (potentially) the screen and the log file.
+ */
+static void
+add_log (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 (char *message, char *dir, char *vtag, int targc,
+                   char **targv)
+{
+    struct saved_cwd cwd;
+    char *cp;
+    int ierrno, err;
+    char *rcs = NULL;
+
+    if (islink (dir))
+       return 0;
+    if (save_cwd (&cwd))
+    {
+       fperrmsg (logfp, 0, errno, "Failed to save current directory.");
+       return 1;
+    }
+
+    /* Concatenate DIR to the end of REPOSITORY.  */
+    if (repository[0] == '\0')
+    {
+       char *new = xstrdup (dir);
+       free (repository);
+       repository = new;
+    }
+    else
+    {
+       char *new = Xasprintf ("%s/%s", repository, dir);
+       free (repository);
+       repository = new;
+    }
+
+    if (!quiet && !current_parsed_root->isremote)
+       error (0, 0, "Importing %s", repository);
+
+    if (CVS_CHDIR (dir) < 0)
+    {
+       ierrno = errno;
+       fperrmsg (logfp, 0, ierrno, "ERROR: cannot chdir to %s", repository);
+       error (0, ierrno, "ERROR: cannot chdir to %s", repository);
+       err = 1;
+       goto out;
+    }
+    if (!current_parsed_root->isremote && !isdir (repository))
+    {
+       rcs = Xasprintf ("%s%s", repository, RCSEXT);
+       if (isfile (repository) || isfile (rcs))
+       {
+           fperrmsg (logfp, 0, 0,
+                     "ERROR: %s is a file, should be a directory!",
+                     repository);
+           error (0, 0, "ERROR: %s is a file, should be a directory!",
+                  repository);
+           err = 1;
+           goto out;
+       }
+       if (noexec == 0 && CVS_MKDIR (repository, 0777) < 0)
+       {
+           ierrno = errno;
+           fperrmsg (logfp, 0, ierrno,
+                     "ERROR: cannot mkdir %s -- not added", repository);
+           error (0, ierrno,
+                  "ERROR: cannot mkdir %s -- not added", repository);
+           err = 1;
+           goto out;
+       }
+    }
+    err = import_descend (message, vtag, targc, targv);
+  out:
+    if (rcs != NULL)
+       free (rcs);
+    if ((cp = strrchr (repository, '/')) != NULL)
+       *cp = '\0';
+    else
+       repository[0] = '\0';
+    if (restore_cwd (&cwd))
+       error (1, errno, "Failed to restore current directory, `%s'.",
+              cwd.name);
+    free_cwd (&cwd);
+    return err;
+}
Index: ccvs/src/log.c
diff -u /dev/null ccvs/src/log.c:1.103.8.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/log.c      Tue Jan 17 15:41:23 2006
@@ -0,0 +1,1808 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * Print Log Information
+ * 
+ * Prints the RCS "log" (rlog) information for the specified files.  With no
+ * argument, prints the log information for all the files in the directory
+ * (recursive by default).
+ */
+
+#include "cvs.h"
+#include <assert.h>
+
+/* This structure holds information parsed from the -r option.  */
+
+struct option_revlist
+{
+    /* The next -r option.  */
+    struct option_revlist *next;
+    /* The first revision to print.  This is NULL if the range is
+       :rev, or if no revision is given.  */
+    char *first;
+    /* The last revision to print.  This is NULL if the range is rev:,
+       or if no revision is given.  If there is no colon, first and
+       last are the same.  */
+    char *last;
+    /* Nonzero if there was a trailing `.', which means to print only
+       the head revision of a branch.  */
+    int branchhead;
+    /* Nonzero if first and last are inclusive.  */
+    int inclusive;
+};
+
+/* This structure holds information derived from option_revlist given
+   a particular RCS file.  */
+
+struct revlist
+{
+    /* The next pair.  */
+    struct revlist *next;
+    /* The first numeric revision to print.  */
+    char *first;
+    /* The last numeric revision to print.  */
+    char *last;
+    /* The number of fields in these revisions (one more than
+       numdots).  */
+    int fields;
+    /* Whether first & last are to be included or excluded.  */
+    int inclusive;
+};
+
+/* This structure holds information parsed from the -d option.  */
+
+struct datelist
+{
+    /* The next date.  */
+    struct datelist *next;
+    /* The starting date.  */
+    char *start;
+    /* The ending date.  */
+    char *end;
+    /* Nonzero if the range is inclusive rather than exclusive.  */
+    int inclusive;
+};
+
+/* This structure is used to pass information through start_recursion.  */
+struct log_data
+{
+    /* Nonzero if the -R option was given, meaning that only the name
+       of the RCS file should be printed.  */
+    int nameonly;
+    /* Nonzero if the -h option was given, meaning that only header
+       information should be printed.  */
+    int header;
+    /* Nonzero if the -t option was given, meaning that only the
+       header and the descriptive text should be printed.  */
+    int long_header;
+    /* Nonzero if the -N option was seen, meaning that tag information
+       should not be printed.  */
+    int notags;
+    /* Nonzero if the -b option was seen, meaning that only revisions
+       on the default branch should be printed.  */
+    int default_branch;
+    /* Nonzero if the -S option was seen, meaning that the header/name
+       should be suppressed if no revisions are selected.  */
+    int sup_header;
+    /* If not NULL, the value given for the -r option, which lists
+       sets of revisions to be printed.  */
+    struct option_revlist *revlist;
+    /* If not NULL, the date pairs given for the -d option, which
+       select date ranges to print.  */
+    struct datelist *datelist;
+    /* If not NULL, the single dates given for the -d option, which
+       select specific revisions to print based on a date.  */
+    struct datelist *singledatelist;
+    /* If not NULL, the list of states given for the -s option, which
+       only prints revisions of given states.  */
+    List *statelist;
+    /* If not NULL, the list of login names given for the -w option,
+       which only prints revisions checked in by given users.  */
+    List *authorlist;
+};
+
+/* This structure is used to pass information through walklist.  */
+struct log_data_and_rcs
+{
+    struct log_data *log_data;
+    struct revlist *revlist;
+    RCSNode *rcs;
+};
+
+static int rlog_proc (int argc, char **argv, char *xwhere,
+                      char *mwhere, char *mfile, int shorten,
+                      int local_specified, char *mname, char *msg);
+static Dtype log_dirproc (void *callerdat, const char *dir,
+                          const char *repository, const char *update_dir,
+                          List *entries);
+static int log_fileproc (void *callerdat, struct file_info *finfo);
+static struct option_revlist *log_parse_revlist (const char *);
+static void log_parse_date (struct log_data *, const char *);
+static void log_parse_list (List **, const char *);
+static struct revlist *log_expand_revlist (struct file_info *, char *,
+                                           struct option_revlist *, int);
+static void log_free_revlist (struct revlist *);
+static int log_version_requested (struct log_data *, struct revlist *,
+                                        RCSNode *, RCSVers *);
+static int log_symbol (Node *, void *);
+static int log_count (Node *, void *);
+static int log_fix_singledate (Node *, void *);
+static int log_count_print (Node *, void *);
+static void log_tree (struct log_data *, struct revlist *,
+                            RCSNode *, const char *);
+static void log_abranch (struct log_data *, struct revlist *,
+                               RCSNode *, const char *);
+static void log_version (struct log_data *, struct revlist *,
+                               RCSNode *, RCSVers *, int);
+static int log_branch (Node *, void *);
+static int version_compare (const char *, const char *, int);
+
+static struct log_data log_data;
+static int is_rlog;
+
+static const char *const log_usage[] =
+{
+    "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
+    "    [-w[logins]] [files...]\n",
+    "\t-l\tLocal directory only, no recursion.\n",
+    "\t-b\tOnly list revisions on the default branch.\n",
+    "\t-h\tOnly print header.\n",
+    "\t-R\tOnly print name of RCS file.\n",
+    "\t-t\tOnly print header and descriptive text.\n",
+    "\t-N\tDo not list tags.\n",
+    "\t-S\tDo not print name/header if no revisions selected.  -d, -r,\n",
+    "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n",
+    "\t\t-t without this option.\n",
+    "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
+    "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
+    "\t   rev1::rev2  Between rev1 and rev2, excluding rev1.\n",
+    "\t   rev:        rev and following revisions on the same branch.\n",
+    "\t   rev::       After rev on the same branch.\n",
+    "\t   :rev        rev and previous revisions on the same branch.\n",
+    "\t   ::rev       rev and previous revisions on the same branch.\n",
+    "\t   rev         Just rev.\n",
+    "\t   branch      All revisions on the branch.\n",
+    "\t   branch.     The last revision on the branch.\n",
+    "\t-d dates\tA semicolon-separated list of dates\n",
+    "\t        \t(D1<D2 for range, D for latest before).\n",
+    "\t-s states\tOnly list revisions with specified states.\n",
+    "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+#ifdef CLIENT_SUPPORT
+
+
+
+/* Helper function for send_arg_list.  */
+static int
+send_one (Node *node, void *closure)
+{
+    char *option = closure;
+
+    send_to_server ("Argument ", 0);
+    send_to_server (option, 0);
+    if (strcmp (node->key, "@@MYSELF") == 0)
+       /* It is a bare -w option.  Note that we must send it as
+          -w rather than messing with getcaller() or something (which on
+          the client will return garbage).  */
+       ;
+    else
+       send_to_server (node->key, 0);
+    send_to_server ("\012", 0);
+    return 0;
+}
+
+
+
+/* For each element in ARG, send an argument consisting of OPTION
+   concatenated with that element.  */
+static void
+send_arg_list (char *option, List *arg)
+{
+    if (arg == NULL)
+       return;
+    walklist (arg, send_one, option);
+}
+
+#endif
+
+
+
+int
+cvslog (int argc, char **argv)
+{
+    int c;
+    int err = 0;
+    int local = 0;
+    struct option_revlist **prl;
+
+    is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
+
+    if (argc == -1)
+       usage (log_usage);
+
+    memset (&log_data, 0, sizeof log_data);
+    prl = &log_data.revlist;
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+bd:hlNSRr::s:tw::")) != -1)
+    {
+       switch (c)
+       {
+           case 'b':
+               log_data.default_branch = 1;
+               break;
+           case 'd':
+               log_parse_date (&log_data, optarg);
+               break;
+           case 'h':
+               log_data.header = 1;
+               break;
+           case 'l':
+               local = 1;
+               break;
+           case 'N':
+               log_data.notags = 1;
+               break;
+           case 'S':
+               log_data.sup_header = 1;
+               break;
+           case 'R':
+               log_data.nameonly = 1;
+               break;
+           case 'r':
+               *prl = log_parse_revlist (optarg);
+               prl = &(*prl)->next;
+               break;
+           case 's':
+               log_parse_list (&log_data.statelist, optarg);
+               break;
+           case 't':
+               log_data.long_header = 1;
+               break;
+           case 'w':
+               if (optarg != NULL)
+                   log_parse_list (&log_data.authorlist, optarg);
+               else
+                   log_parse_list (&log_data.authorlist, "@@MYSELF");
+               break;
+           case '?':
+           default:
+               usage (log_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       struct datelist *p;
+       struct option_revlist *rp;
+       char datetmp[MAXDATELEN];
+
+       /* We're the local client.  Fire up the remote server.  */
+       start_server ();
+
+       if (is_rlog && !supported_request ("rlog"))
+           error (1, 0, "server does not support rlog");
+
+       ign_setup ();
+
+       if (log_data.default_branch)
+           send_arg ("-b");
+
+       while (log_data.datelist != NULL)
+       {
+           p = log_data.datelist;
+           log_data.datelist = p->next;
+           send_to_server ("Argument -d\012", 0);
+           send_to_server ("Argument ", 0);
+           date_to_internet (datetmp, p->start);
+           send_to_server (datetmp, 0);
+           if (p->inclusive)
+               send_to_server ("<=", 0);
+           else
+               send_to_server ("<", 0);
+           date_to_internet (datetmp, p->end);
+           send_to_server (datetmp, 0);
+           send_to_server ("\012", 0);
+           if (p->start)
+               free (p->start);
+           if (p->end)
+               free (p->end);
+           free (p);
+       }
+       while (log_data.singledatelist != NULL)
+       {
+           p = log_data.singledatelist;
+           log_data.singledatelist = p->next;
+           send_to_server ("Argument -d\012", 0);
+           send_to_server ("Argument ", 0);
+           date_to_internet (datetmp, p->end);
+           send_to_server (datetmp, 0);
+           send_to_server ("\012", 0);
+           if (p->end)
+               free (p->end);
+           free (p);
+       }
+           
+       if (log_data.header)
+           send_arg ("-h");
+       if (local)
+           send_arg("-l");
+       if (log_data.notags)
+           send_arg("-N");
+       if (log_data.sup_header)
+           send_arg("-S");
+       if (log_data.nameonly)
+           send_arg("-R");
+       if (log_data.long_header)
+           send_arg("-t");
+
+       while (log_data.revlist != NULL)
+       {
+           rp = log_data.revlist;
+           log_data.revlist = rp->next;
+           send_to_server ("Argument -r", 0);
+           if (rp->branchhead)
+           {
+               if (rp->first != NULL)
+                   send_to_server (rp->first, 0);
+               send_to_server (".", 1);
+           }
+           else
+           {
+               if (rp->first != NULL)
+                   send_to_server (rp->first, 0);
+               send_to_server (":", 1);
+               if (!rp->inclusive)
+                   send_to_server (":", 1);
+               if (rp->last != NULL)
+                   send_to_server (rp->last, 0);
+           }
+           send_to_server ("\012", 0);
+           if (rp->first)
+               free (rp->first);
+           if (rp->last)
+               free (rp->last);
+           free (rp);
+       }
+       send_arg_list ("-s", log_data.statelist);
+       dellist (&log_data.statelist);
+       send_arg_list ("-w", log_data.authorlist);
+       dellist (&log_data.authorlist);
+       send_arg ("--");
+
+       if (is_rlog)
+       {
+           int i;
+           for (i = 0; i < argc; i++)
+               send_arg (argv[i]);
+           send_to_server ("rlog\012", 0);
+       }
+       else
+       {
+           send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+           send_file_names (argc, argv, SEND_EXPAND_WILD);
+           send_to_server ("log\012", 0);
+       }
+        err = get_responses_and_close ();
+       return err;
+    }
+#endif
+
+    /* OK, now that we know we are local/server, we can resolve @@MYSELF
+       into our user name.  */
+    if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
+       log_parse_list (&log_data.authorlist, getcaller ());
+
+    if (is_rlog)
+    {
+       DBM *db;
+       int i;
+       db = open_module ();
+       for (i = 0; i < argc; i++)
+       {
+             err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
+                               NULL, 0, local, 0, 0, NULL);
+       }
+       close_module (db);
+    }
+    else
+    {
+        err = rlog_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, local, NULL,
+                         NULL);
+    }
+
+    while (log_data.revlist)
+    {
+       struct option_revlist *rl = log_data.revlist->next;
+       if (log_data.revlist->first)
+           free (log_data.revlist->first);
+       if (log_data.revlist->last)
+           free (log_data.revlist->last);
+       free (log_data.revlist);
+       log_data.revlist = rl;
+    }
+    while (log_data.datelist)
+    {
+       struct datelist *nd = log_data.datelist->next;
+       if (log_data.datelist->start)
+           free (log_data.datelist->start);
+       if (log_data.datelist->end)
+           free (log_data.datelist->end);
+       free (log_data.datelist);
+       log_data.datelist = nd;
+    }
+    while (log_data.singledatelist)
+    {
+       struct datelist *nd = log_data.singledatelist->next;
+       if (log_data.singledatelist->start)
+           free (log_data.singledatelist->start);
+       if (log_data.singledatelist->end)
+           free (log_data.singledatelist->end);
+       free (log_data.singledatelist);
+       log_data.singledatelist = nd;
+    }
+    dellist (&log_data.statelist);
+    dellist (&log_data.authorlist);
+
+    return err;
+}
+
+
+
+static int
+rlog_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
+           int shorten, int local, char *mname, char *msg)
+{
+    /* Begin section which is identical to patch_proc--should this
+       be abstracted out somehow?  */
+    char *myargv[2];
+    int err = 0;
+    int which;
+    char *repository = NULL;
+    char *where;
+
+    if (is_rlog)
+    {
+       repository = xmalloc (strlen (current_parsed_root->directory)
+                              + strlen (argv[0])
+                             + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
+       (void)sprintf (repository, "%s/%s",
+                       current_parsed_root->directory, argv[0]);
+       where = xmalloc (strlen (argv[0])
+                         + (mfile == NULL ? 0 : strlen (mfile) + 1)
+                        + 1);
+       (void)strcpy (where, argv[0]);
+
+       /* If mfile isn't null, we need to set up to do only part of theu
+         * module.
+         */
+       if (mfile != NULL)
+       {
+           char *cp;
+           char *path;
+
+           /* If the portion of the module is a path, put the dir part on
+             * repos.
+             */
+           if ((cp = strrchr (mfile, '/')) != NULL)
+           {
+               *cp = '\0';
+               (void)strcat (repository, "/");
+               (void)strcat (repository, mfile);
+               (void)strcat (where, "/");
+               (void)strcat (where, mfile);
+               mfile = cp + 1;
+           }
+
+           /* take care of the rest */
+           path = Xasprintf ("%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
+           {
+               myargv[0] = argv[0];
+               myargv[1] = mfile;
+               argc = 2;
+               argv = myargv;
+           }
+           free (path);
+       }
+
+       /* cd to the starting repository */
+       if (CVS_CHDIR (repository) < 0)
+       {
+           error (0, errno, "cannot chdir to %s", repository);
+           free (repository);
+           free (where);
+           return 1;
+       }
+       /* End section which is identical to patch_proc.  */
+
+       which = W_REPOS | W_ATTIC;
+    }
+    else
+    {
+        repository = NULL;
+        where = NULL;
+        which = W_LOCAL | W_REPOS | W_ATTIC;
+    }
+
+    err = start_recursion (log_fileproc, NULL, log_dirproc,
+                          NULL, &log_data,
+                          argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
+                          where, 1, repository);
+
+    if (!(which & W_LOCAL)) free (repository);
+    if (where) free (where);
+
+    return err;
+}
+
+
+
+/*
+ * Parse a revision list specification.
+ */
+static struct option_revlist *
+log_parse_revlist (const char *argstring)
+{
+    char *orig_copy, *copy;
+    struct option_revlist *ret, **pr;
+
+    /* Unfortunately, rlog accepts -r without an argument to mean that
+       latest revision on the default branch, so we must support that
+       for compatibility.  */
+    if (argstring == NULL)
+       argstring = "";
+
+    ret = NULL;
+    pr = &ret;
+
+    /* Copy the argument into memory so that we can change it.  We
+       don't want to change the argument because, at least as of this
+       writing, we will use it if we send the arguments to the server.  */
+    orig_copy = copy = xstrdup (argstring);
+    while (copy != NULL)
+    {
+       char *comma;
+       struct option_revlist *r;
+
+       comma = strchr (copy, ',');
+       if (comma != NULL)
+           *comma++ = '\0';
+
+       r = xmalloc (sizeof *r);
+       r->next = NULL;
+       r->first = copy;
+       r->branchhead = 0;
+       r->last = strchr (copy, ':');
+       if (r->last != NULL)
+       {
+           *r->last++ = '\0';
+           r->inclusive = (*r->last != ':');
+           if (!r->inclusive)
+               r->last++;
+       }
+       else
+       {
+           r->last = r->first;
+           r->inclusive = 1;
+           if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
+           {
+               r->branchhead = 1;
+               r->first[strlen (r->first) - 1] = '\0';
+           }
+       }
+
+       if (*r->first == '\0')
+           r->first = NULL;
+       if (*r->last == '\0')
+           r->last = NULL;
+
+       if (r->first != NULL)
+           r->first = xstrdup (r->first);
+       if (r->last != NULL)
+           r->last = xstrdup (r->last);
+
+       *pr = r;
+       pr = &r->next;
+
+       copy = comma;
+    }
+
+    free (orig_copy);
+    return ret;
+}
+
+
+
+/*
+ * Parse a date specification.
+ */
+static void
+log_parse_date (struct log_data *log_data, const char *argstring)
+{
+    char *orig_copy, *copy;
+
+    /* Copy the argument into memory so that we can change it.  We
+       don't want to change the argument because, at least as of this
+       writing, we will use it if we send the arguments to the server.  */
+    orig_copy = copy = xstrdup (argstring);
+    while (copy != NULL)
+    {
+       struct datelist *nd, **pd;
+       char *cpend, *cp, *ds, *de;
+
+       nd = xmalloc (sizeof *nd);
+
+       cpend = strchr (copy, ';');
+       if (cpend != NULL)
+           *cpend++ = '\0';
+
+       pd = &log_data->datelist;
+       nd->inclusive = 0;
+
+       if ((cp = strchr (copy, '>')) != NULL)
+       {
+           *cp++ = '\0';
+           if (*cp == '=')
+           {
+               ++cp;
+               nd->inclusive = 1;
+           }
+           ds = cp;
+           de = copy;
+       }
+       else if ((cp = strchr (copy, '<')) != NULL)
+       {
+           *cp++ = '\0';
+           if (*cp == '=')
+           {
+               ++cp;
+               nd->inclusive = 1;
+           }
+           ds = copy;
+           de = cp;
+       }
+       else
+       {
+           ds = NULL;
+           de = copy;
+           pd = &log_data->singledatelist;
+       }
+
+       if (ds == NULL)
+           nd->start = NULL;
+       else if (*ds != '\0')
+           nd->start = Make_Date (ds);
+       else
+       {
+         /* 1970 was the beginning of time, as far as get_date and
+            Make_Date are concerned.  FIXME: That is true only if time_t
+            is a POSIX-style time and there is nothing in ANSI that
+            mandates that.  It would be cleaner to set a flag saying
+            whether or not there is a start date.  */
+           nd->start = Make_Date ("1/1/1970 UTC");
+       }
+
+       if (*de != '\0')
+           nd->end = Make_Date (de);
+       else
+       {
+           /* We want to set the end date to some time sufficiently far
+              in the future to pick up all revisions that have been
+              created since the specified date and the time `cvs log'
+              completes.  FIXME: The date in question only makes sense
+              if time_t is a POSIX-style time and it is 32 bits
+              and signed.  We should instead be setting a flag saying
+              whether or not there is an end date.  Note that using
+              something like "next week" would break the testsuite (and,
+              perhaps less importantly, loses if the clock is set grossly
+              wrong).  */
+           nd->end = Make_Date ("2038-01-01");
+       }
+
+       nd->next = *pd;
+       *pd = nd;
+
+       copy = cpend;
+    }
+
+    free (orig_copy);
+}
+
+
+
+/*
+ * Parse a comma separated list of items, and add each one to *PLIST.
+ */
+static void
+log_parse_list (List **plist, const char *argstring)
+{
+    while (1)
+    {
+       Node *p;
+       char *cp;
+
+       p = getnode ();
+
+       cp = strchr (argstring, ',');
+       if (cp == NULL)
+           p->key = xstrdup (argstring);
+       else
+       {
+           size_t len;
+
+           len = cp - argstring;
+           p->key = xmalloc (len + 1);
+           strncpy (p->key, argstring, len);
+           p->key[len] = '\0';
+       }
+
+       if (*plist == NULL)
+           *plist = getlist ();
+       if (addnode (*plist, p) != 0)
+           freenode (p);
+
+       if (cp == NULL)
+           break;
+
+       argstring = cp + 1;
+    }
+}
+
+
+
+static int
+printlock_proc (Node *lock, void *foo)
+{
+    cvs_output ("\n\t", 2);
+    cvs_output (lock->data, 0);
+    cvs_output (": ", 2);
+    cvs_output (lock->key, 0);
+    return 0;
+}
+
+
+
+/*
+ * Do an rlog on a file
+ */
+static int
+log_fileproc (void *callerdat, struct file_info *finfo)
+{
+    struct log_data *log_data = callerdat;
+    Node *p;
+    char *baserev;
+    int selrev = -1;
+    RCSNode *rcsfile;
+    char buf[50];
+    struct revlist *revlist = NULL;
+    struct log_data_and_rcs log_data_and_rcs;
+
+    rcsfile = finfo->rcs;
+    p = findnode (finfo->entries, finfo->file);
+    if (p != NULL)
+    {
+       Entnode *e = p->data;
+       baserev = e->version;
+       if (baserev[0] == '-') ++baserev;
+    }
+    else
+       baserev = NULL;
+
+    if (rcsfile == NULL)
+    {
+       /* no rcs file.  What *do* we know about this file? */
+       if (baserev != NULL)
+       {
+           if (baserev[0] == '0' && baserev[1] == '\0')
+           {
+               if (!really_quiet)
+                   error (0, 0, "%s has been added, but not committed",
+                          finfo->file);
+               return 0;
+           }
+       }
+       
+       if (!really_quiet)
+           error (0, 0, "nothing known about %s", finfo->file);
+       
+       return 1;
+    }
+
+    if (log_data->sup_header || !log_data->nameonly)
+    {
+
+       /* We will need all the information in the RCS file.  */
+       RCS_fully_parse (rcsfile);
+
+       /* Turn any symbolic revisions in the revision list into numeric
+          revisions.  */
+       revlist = log_expand_revlist (finfo, baserev, log_data->revlist,
+                                     log_data->default_branch);
+       if (log_data->sup_header
+            || (!log_data->header && !log_data->long_header))
+       {
+           log_data_and_rcs.log_data = log_data;
+           log_data_and_rcs.revlist = revlist;
+           log_data_and_rcs.rcs = rcsfile;
+
+           /* If any single dates were specified, we need to identify the
+              revisions they select.  Each one selects the single
+              revision, which is otherwise selected, of that date or
+              earlier.  The log_fix_singledate routine will fill in the
+              start date for each specific revision.  */
+           if (log_data->singledatelist != NULL)
+               walklist (rcsfile->versions, log_fix_singledate,
+                         &log_data_and_rcs);
+
+           selrev = walklist (rcsfile->versions, log_count_print,
+                              &log_data_and_rcs);
+           if (log_data->sup_header && selrev == 0)
+           {
+               log_free_revlist (revlist);
+               return 0;
+           }
+       }
+
+    }
+
+    if (log_data->nameonly)
+    {
+       cvs_output (rcsfile->print_path, 0);
+       cvs_output ("\n", 1);
+       log_free_revlist (revlist);
+       return 0;
+    }
+
+    /* The output here is intended to be exactly compatible with the
+       output of rlog.  I'm not sure whether this code should be here
+       or in rcs.c; I put it here because it is specific to the log
+       function, even though it uses information gathered by the
+       functions in rcs.c.  */
+
+    cvs_output ("\n", 1);
+
+    cvs_output ("RCS file: ", 0);
+    cvs_output (rcsfile->print_path, 0);
+
+    if (!is_rlog)
+    {
+       cvs_output ("\nWorking file: ", 0);
+       if (finfo->update_dir[0] != '\0')
+       {
+           cvs_output (finfo->update_dir, 0);
+           cvs_output ("/", 0);
+       }
+       cvs_output (finfo->file, 0);
+    }
+
+    cvs_output ("\nhead:", 0);
+    if (rcsfile->head != NULL)
+    {
+       cvs_output (" ", 1);
+       cvs_output (rcsfile->head, 0);
+    }
+
+    cvs_output ("\nbranch:", 0);
+    if (rcsfile->branch != NULL)
+    {
+       cvs_output (" ", 1);
+       cvs_output (rcsfile->branch, 0);
+    }
+
+    cvs_output ("\nlocks:", 0);
+    if (rcsfile->strict_locks)
+       cvs_output (" strict", 0);
+    walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
+
+    cvs_output ("\naccess list:", 0);
+    if (rcsfile->access != NULL)
+    {
+       const char *cp;
+
+       cp = rcsfile->access;
+       while (*cp != '\0')
+       {
+               const char *cp2;
+
+               cvs_output ("\n\t", 2);
+               cp2 = cp;
+               while (!isspace ((unsigned char)*cp2) && *cp2 != '\0')
+                   ++cp2;
+               cvs_output (cp, cp2 - cp);
+               cp = cp2;
+               while (isspace ((unsigned char)*cp) && *cp != '\0')
+                   ++cp;
+       }
+    }
+
+    if (!log_data->notags)
+    {
+       List *syms;
+
+       cvs_output ("\nsymbolic names:", 0);
+       syms = RCS_symbols (rcsfile);
+       walklist (syms, log_symbol, NULL);
+    }
+
+    cvs_output ("\nkeyword substitution: ", 0);
+    if (rcsfile->expand == NULL)
+       cvs_output ("kv", 2);
+    else
+       cvs_output (rcsfile->expand, 0);
+
+    cvs_output ("\ntotal revisions: ", 0);
+    sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
+    cvs_output (buf, 0);
+
+    if (selrev >= 0)
+    {
+       cvs_output (";\tselected revisions: ", 0);
+       sprintf (buf, "%d", selrev);
+       cvs_output (buf, 0);
+    }
+
+    cvs_output ("\n", 1);
+
+    if (!log_data->header || log_data->long_header)
+    {
+       cvs_output ("description:\n", 0);
+       if (rcsfile->desc != NULL)
+           cvs_output (rcsfile->desc, 0);
+    }
+
+    if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
+    {
+       p = findnode (rcsfile->versions, rcsfile->head);
+       if (p == NULL)
+           error (1, 0, "can not find head revision in `%s'",
+                  finfo->fullname);
+       while (p != NULL)
+       {
+           RCSVers *vers = p->data;
+
+           log_version (log_data, revlist, rcsfile, vers, 1);
+           if (vers->next == NULL)
+               p = NULL;
+           else
+           {
+               p = findnode (rcsfile->versions, vers->next);
+               if (p == NULL)
+                   error (1, 0, "can not find next revision `%s' in `%s'",
+                          vers->next, finfo->fullname);
+           }
+       }
+
+       log_tree (log_data, revlist, rcsfile, rcsfile->head);
+    }
+
+    cvs_output("\
+=============================================================================\n",
+              0);
+
+    /* Free up the new revlist and restore the old one.  */
+    log_free_revlist (revlist);
+
+    /* If singledatelist is not NULL, free up the start dates we added
+       to it.  */
+    if (log_data->singledatelist != NULL)
+    {
+       struct datelist *d;
+
+       for (d = log_data->singledatelist; d != NULL; d = d->next)
+       {
+           if (d->start != NULL)
+               free (d->start);
+           d->start = NULL;
+       }
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Fix up a revision list in order to compare it against versions.
+ * Expand any symbolic revisions.
+ */
+static struct revlist *
+log_expand_revlist (struct file_info *finfo, char *baserev,
+                    struct option_revlist *revlist, int default_branch)
+{
+    struct option_revlist *r;
+    struct revlist *ret, **pr;
+    char *first = NULL;
+    char *last = NULL;
+    RCSNode *rcs = finfo->rcs;
+    ret = NULL;
+    pr = &ret;
+    for (r = revlist; r != NULL; r = r->next)
+    {
+       struct revlist *nr;
+
+       nr = xmalloc (sizeof *nr);
+       nr->inclusive = r->inclusive;
+
+        if (RCS_is_relative (r->first)) {
+           first = Version_resolve_relTag (finfo, r->first, !is_rlog);
+           if (!first)
+           {
+               if (!really_quiet)
+                   error (0, 0, "Cannot resolve relative tag: `%s'.", 
r->first);
+           }
+        } else {
+           first = xstrdup (r->first);
+        }
+        if (RCS_is_relative (r->last)) {
+           last = Version_resolve_relTag (finfo, r->last, !is_rlog);
+           if (!last && first)
+           {
+               if (!really_quiet)
+                   error (0, 0, "Cannot resolve relative tag: `%s'.", r->last);
+           }
+        } else {
+           last = xstrdup (r->last);
+        }
+
+       if (first == NULL && last == NULL)
+       {
+           /* If both first and last are NULL, it means that we want
+              just the head of the default branch, which is RCS_head.  */
+           nr->first = RCS_head (rcs);
+           if (!nr->first)
+           {
+               if (!really_quiet)
+                   error (0, 0, "No head revision in archive `%s'.",
+                          rcs->path);
+               nr->last = NULL;
+               nr->fields = 0;
+           }
+           else
+           {
+               nr->last = xstrdup (nr->first);
+               nr->fields = numdots (nr->first) + 1;
+           }
+       }
+       else if (r->branchhead)
+       {
+           char *branch;
+
+           /* Print just the head of the branch.  */
+           if (!RCS_is_symbolic (first))
+               nr->first = RCS_getbranch (rcs, first, 1);
+           else
+           {
+               branch = RCS_whatbranch (rcs, first);
+               if (branch == NULL)
+                   nr->first = NULL;
+               else
+               {
+                   nr->first = RCS_getbranch (rcs, branch, 1);
+                   free (branch);
+               }
+           }
+
+           if (!nr->first)
+           {
+               if (!really_quiet)
+                   error (0, 0, "warning: no branch `%s' in `%s'",
+                          r->first, rcs->print_path);
+               nr->last = NULL;
+               nr->fields = 0;
+           }
+           else
+           {
+               nr->last = xstrdup (nr->first);
+               nr->fields = numdots (nr->first) + 1;
+           }
+       }
+       else
+       {
+           if (first == NULL || !RCS_is_symbolic (first))
+               nr->first = xstrdup (first);
+           else
+           {
+               if (baserev && strcmp (first, TAG_BASE) == 0)
+                   nr->first = xstrdup (baserev);
+               else if (RCS_nodeisbranch (rcs, first))
+                   nr->first = RCS_whatbranch (rcs, first);
+               else
+                   nr->first = RCS_gettag (rcs, first, 1, NULL);
+               if (nr->first == NULL && !really_quiet)
+               {
+                   error (0, 0, "warning: no revision `%s' in `%s'",
+                          r->first, rcs->print_path);
+               }
+           }
+
+           if (r->last == r->first || (r->last != NULL && r->first != NULL &&
+                                       strcmp (r->last, r->first) == 0))
+               nr->last = xstrdup (nr->first);
+           else if (last == NULL || !RCS_is_symbolic (last))
+               nr->last = xstrdup (last);
+           else
+           {
+               if (baserev && strcmp (last, TAG_BASE) == 0)
+                   nr->last = xstrdup (baserev);
+               else if (RCS_nodeisbranch (rcs, last))
+                   nr->last = RCS_whatbranch (rcs, last);
+               else
+                   nr->last = RCS_gettag (rcs, last, 1, NULL);
+               if (nr->last == NULL && !really_quiet)
+               {
+                   error (0, 0, "warning: no revision `%s' in `%s'",
+                          r->last, rcs->print_path);
+               }
+           }
+
+           /* Process the revision numbers the same way that rlog
+               does.  This code is a bit cryptic for my tastes, but
+               keeping the same implementation as rlog ensures a
+               certain degree of compatibility.  */
+           if (first == NULL && nr->last != NULL)
+           {
+               nr->fields = numdots (nr->last) + 1;
+               if (nr->fields < 2)
+                   nr->first = xstrdup (".0");
+               else
+               {
+                   char *cp;
+
+                   nr->first = xstrdup (nr->last);
+                   cp = strrchr (nr->first, '.');
+                   assert (cp);
+                   strcpy (cp + 1, "0");
+               }
+           }
+           else if (last == NULL && nr->first != NULL)
+           {
+               nr->fields = numdots (nr->first) + 1;
+               nr->last = xstrdup (nr->first);
+               if (nr->fields < 2)
+                   nr->last[0] = '\0';
+               else
+               {
+                   char *cp;
+
+                   cp = strrchr (nr->last, '.');
+                   assert (cp);
+                   *cp = '\0';
+               }
+           }
+           else if (nr->first == NULL || nr->last == NULL)
+               nr->fields = 0;
+           else if (strcmp (nr->first, nr->last) == 0)
+               nr->fields = numdots (nr->last) + 1;
+           else
+           {
+               int ord;
+               int dots1 = numdots (nr->first);
+               int dots2 = numdots (nr->last);
+               if (dots1 > dots2 || (dots1 == dots2 &&
+                   version_compare (nr->first, nr->last, dots1 + 1) > 0))
+               {
+                   char *tmp = nr->first;
+                   nr->first = nr->last;
+                   nr->last = tmp;
+                   nr->fields = dots2 + 1;
+                   dots2 = dots1;
+                   dots1 = nr->fields - 1;
+               }
+               else
+                   nr->fields = dots1 + 1;
+               dots1 += (nr->fields & 1);
+               ord = version_compare (nr->first, nr->last, dots1);
+               if (ord > 0 || (nr->fields > 2 && ord < 0))
+               {
+                   error (0, 0,
+                          "invalid branch or revision pair %s:%s in `%s'",
+                          r->first, r->last, rcs->print_path);
+                   free (nr->first);
+                   nr->first = NULL;
+                   free (nr->last);
+                   nr->last = NULL;
+                   nr->fields = 0;
+               }
+               else
+               {
+                   if (nr->fields <= dots2 && (nr->fields & 1))
+                   {
+                       char *p = Xasprintf ("%s.0", nr->first);
+                       free (nr->first);
+                       nr->first = p;
+                       ++nr->fields;
+                   }
+                   while (nr->fields <= dots2)
+                   {
+                       char *p;
+                       int i;
+
+                       nr->next = NULL;
+                       *pr = nr;
+                       nr = xmalloc (sizeof *nr);
+                       nr->inclusive = 1;
+                       nr->first = xstrdup ((*pr)->last);
+                       nr->last = xstrdup ((*pr)->last);
+                       nr->fields = (*pr)->fields;
+                       p = (*pr)->last;
+                       for (i = 0; i < nr->fields; i++)
+                           p = strchr (p, '.') + 1;
+                       p[-1] = '\0';
+                       p = strchr (nr->first + (p - (*pr)->last), '.');
+                       if (p != NULL)
+                       {
+                           *++p = '0';
+                           *++p = '\0';
+                           nr->fields += 2;
+                       }
+                       else
+                           ++nr->fields;
+                       pr = &(*pr)->next;
+                   }
+               }
+           }
+       }
+
+       nr->next = NULL;
+       *pr = nr;
+       pr = &nr->next;
+    }
+
+    /* If the default branch was requested, add a revlist entry for
+       it.  This is how rlog handles this option.  */
+    if (default_branch
+       && (rcs->head != NULL || rcs->branch != NULL))
+    {
+       struct revlist *nr;
+
+       nr = xmalloc (sizeof *nr);
+       if (rcs->branch != NULL)
+           nr->first = xstrdup (rcs->branch);
+       else
+       {
+           char *cp;
+
+           nr->first = xstrdup (rcs->head);
+           assert (nr->first);
+           cp = strrchr (nr->first, '.');
+           assert (cp);
+           *cp = '\0';
+       }
+       nr->last = xstrdup (nr->first);
+       nr->fields = numdots (nr->first) + 1;
+       nr->inclusive = 1;
+
+       nr->next = NULL;
+       *pr = nr;
+    }
+
+    free (first);
+    free (last);
+    return ret;
+}
+
+
+
+/*
+ * Free a revlist created by log_expand_revlist.
+ */
+static void
+log_free_revlist (struct revlist *revlist)
+{
+    struct revlist *r;
+
+    r = revlist;
+    while (r != NULL)
+    {
+       struct revlist *next;
+
+       if (r->first != NULL)
+           free (r->first);
+       if (r->last != NULL)
+           free (r->last);
+       next = r->next;
+       free (r);
+       r = next;
+    }
+}
+
+
+
+/*
+ * Return nonzero if a revision should be printed, based on the
+ * options provided.
+ */
+static int
+log_version_requested (struct log_data *log_data, struct revlist *revlist,
+                       RCSNode *rcs, RCSVers *vnode)
+{
+    /* Handle the list of states from the -s option.  */
+    if (log_data->statelist != NULL
+       && findnode (log_data->statelist, vnode->state) == NULL)
+    {
+       return 0;
+    }
+
+    /* Handle the list of authors from the -w option.  */
+    if (log_data->authorlist != NULL)
+    {
+       if (vnode->author != NULL
+           && findnode (log_data->authorlist, vnode->author) == NULL)
+       {
+           return 0;
+       }
+    }
+
+    /* rlog considers all the -d options together when it decides
+       whether to print a revision, so we must be compatible.  */
+    if (log_data->datelist != NULL || log_data->singledatelist != NULL)
+    {
+       struct datelist *d;
+
+       for (d = log_data->datelist; d != NULL; d = d->next)
+       {
+           int cmp;
+
+           cmp = RCS_datecmp (vnode->date, d->start);
+           if (cmp > 0 || (cmp == 0 && d->inclusive))
+           {
+               cmp = RCS_datecmp (vnode->date, d->end);
+               if (cmp < 0 || (cmp == 0 && d->inclusive))
+                   break;
+           }
+       }
+
+       if (d == NULL)
+       {
+           /* Look through the list of specific dates.  We want to
+              select the revision with the exact date found in the
+              start field.  The commit code ensures that it is
+              impossible to check in multiple revisions of a single
+              file in a single second, so checking the date this way
+              should never select more than one revision.  */
+           for (d = log_data->singledatelist; d != NULL; d = d->next)
+           {
+               if (d->start != NULL
+                   && RCS_datecmp (vnode->date, d->start) == 0)
+               {
+                   break;
+               }
+           }
+
+           if (d == NULL)
+               return 0;
+       }
+    }
+
+    /* If the -r or -b options were used, REVLIST will be non NULL,
+       and we print the union of the specified revisions.  */
+    if (revlist != NULL)
+    {
+       char *v;
+       int vfields;
+       struct revlist *r;
+
+       /* This code is taken from rlog.  */
+       v = vnode->version;
+       vfields = numdots (v) + 1;
+       for (r = revlist; r != NULL; r = r->next)
+       {
+            if (vfields == r->fields + (r->fields & 1) &&
+                (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
+                                version_compare (v, r->first, r->fields) > 0)
+                && version_compare (v, r->last, r->fields) <= 0)
+           {
+               return 1;
+           }
+       }
+
+       /* If we get here, then the -b and/or the -r option was used,
+           but did not match this revision, so we reject it.  */
+
+       return 0;
+    }
+
+    /* By default, we print all revisions.  */
+    return 1;
+}
+
+
+
+/*
+ * Output a single symbol.  This is called via walklist.
+ */
+/*ARGSUSED*/
+static int
+log_symbol (Node *p, void *closure)
+{
+    cvs_output ("\n\t", 2);
+    cvs_output (p->key, 0);
+    cvs_output (": ", 2);
+    cvs_output (p->data, 0);
+    return 0;
+}
+
+
+
+/*
+ * Count the number of entries on a list.  This is called via walklist.
+ */
+/*ARGSUSED*/
+static int
+log_count (Node *p, void *closure)
+{
+    return 1;
+}
+
+
+
+/*
+ * Sort out a single date specification by narrowing down the date
+ * until we find the specific selected revision.
+ */
+static int
+log_fix_singledate (Node *p, void *closure)
+{
+    struct log_data_and_rcs *data = closure;
+    Node *pv;
+    RCSVers *vnode;
+    struct datelist *holdsingle, *holddate;
+    int requested;
+
+    pv = findnode (data->rcs->versions, p->key);
+    if (pv == NULL)
+       error (1, 0, "missing version `%s' in RCS file `%s'",
+              p->key, data->rcs->print_path);
+    vnode = pv->data;
+
+    /* We are only interested if this revision passes any other tests.
+       Temporarily clear log_data->singledatelist to avoid confusing
+       log_version_requested.  We also clear log_data->datelist,
+       because rlog considers all the -d options together.  We don't
+       want to reject a revision because it does not match a date pair
+       if we are going to select it on the basis of the singledate.  */
+    holdsingle = data->log_data->singledatelist;
+    data->log_data->singledatelist = NULL;
+    holddate = data->log_data->datelist;
+    data->log_data->datelist = NULL;
+    requested = log_version_requested (data->log_data, data->revlist,
+                                      data->rcs, vnode);
+    data->log_data->singledatelist = holdsingle;
+    data->log_data->datelist = holddate;
+
+    if (requested)
+    {
+       struct datelist *d;
+
+       /* For each single date, if this revision is before the
+          specified date, but is closer than the previously selected
+          revision, select it instead.  */
+       for (d = data->log_data->singledatelist; d != NULL; d = d->next)
+       {
+           if (RCS_datecmp (vnode->date, d->end) <= 0
+               && (d->start == NULL
+                   || RCS_datecmp (vnode->date, d->start) > 0))
+           {
+               if (d->start != NULL)
+                   free (d->start);
+               d->start = xstrdup (vnode->date);
+           }
+       }
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Count the number of revisions we are going to print.
+ */
+static int
+log_count_print (Node *p, void *closure)
+{
+    struct log_data_and_rcs *data = closure;
+    Node *pv;
+
+    pv = findnode (data->rcs->versions, p->key);
+    if (pv == NULL)
+       error (1, 0, "missing version `%s' in RCS file `%s'",
+              p->key, data->rcs->print_path);
+    if (log_version_requested (data->log_data, data->revlist, data->rcs,
+                              pv->data))
+       return 1;
+    else
+       return 0;
+}
+
+
+
+/*
+ * Print the list of changes, not including the trunk, in reverse
+ * order for each branch.
+ */
+static void
+log_tree (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
+          const char *ver)
+{
+    Node *p;
+    RCSVers *vnode;
+
+    p = findnode (rcs->versions, ver);
+    if (p == NULL)
+       error (1, 0, "missing version `%s' in RCS file `%s'",
+              ver, rcs->print_path);
+    vnode = p->data;
+    if (vnode->next != NULL)
+       log_tree (log_data, revlist, rcs, vnode->next);
+    if (vnode->branches != NULL)
+    {
+       Node *head, *branch;
+
+       /* We need to do the branches in reverse order.  This breaks
+           the List abstraction, but so does most of the branch
+           manipulation in rcs.c.  */
+       head = vnode->branches->list;
+       for (branch = head->prev; branch != head; branch = branch->prev)
+       {
+           log_abranch (log_data, revlist, rcs, branch->key);
+           log_tree (log_data, revlist, rcs, branch->key);
+       }
+    }
+}
+
+
+
+/*
+ * Log the changes for a branch, in reverse order.
+ */
+static void
+log_abranch (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
+             const char *ver)
+{
+    Node *p;
+    RCSVers *vnode;
+
+    p = findnode (rcs->versions, ver);
+    if (p == NULL)
+       error (1, 0, "missing version `%s' in RCS file `%s'",
+              ver, rcs->print_path);
+    vnode = p->data;
+    if (vnode->next != NULL)
+       log_abranch (log_data, revlist, rcs, vnode->next);
+    log_version (log_data, revlist, rcs, vnode, 0);
+}
+
+
+
+/*
+ * Print the log output for a single version.
+ */
+static void
+log_version (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs, 
+             RCSVers *ver, int trunk)
+{
+    Node *p;
+    int year, mon, mday, hour, min, sec;
+    char buf[100];
+    Node *padd, *pdel;
+
+    if (! log_version_requested (log_data, revlist, rcs, ver))
+       return;
+
+    cvs_output ("----------------------------\nrevision ", 0);
+    cvs_output (ver->version, 0);
+
+    p = findnode (RCS_getlocks (rcs), ver->version);
+    if (p != NULL)
+    {
+       cvs_output ("\tlocked by: ", 0);
+       cvs_output (p->data, 0);
+       cvs_output (";", 1);
+    }
+    cvs_output ("\n", 1);
+
+    cvs_output_tagged ("text", "date: ");
+    (void)sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
+                 &sec);
+    if (year < 1900)
+       year += 1900;
+    sprintf (buf, "%04d-%02d-%02d %02d:%02d:%02d +0000", year, mon, mday,
+            hour, min, sec);
+    cvs_output_tagged ("date", buf);
+
+    cvs_output_tagged ("text", ";  author: ");
+    cvs_output_tagged ("text", ver->author);
+
+    cvs_output_tagged ("text", ";  state: ");
+    cvs_output_tagged ("text", ver->state);
+    cvs_output_tagged ("text", ";");
+
+    if (! trunk)
+    {
+       padd = findnode (ver->other, ";add");
+       pdel = findnode (ver->other, ";delete");
+    }
+    else if (ver->next == NULL)
+    {
+       padd = NULL;
+       pdel = NULL;
+    }
+    else
+    {
+       Node *nextp;
+       RCSVers *nextver;
+
+       nextp = findnode (rcs->versions, ver->next);
+       if (nextp == NULL)
+           error (1, 0, "missing version `%s' in `%s'", ver->next,
+                  rcs->print_path);
+       nextver = nextp->data;
+       pdel = findnode (nextver->other, ";add");
+       padd = findnode (nextver->other, ";delete");
+    }
+
+    if (padd != NULL)
+    {
+       assert (pdel);
+       cvs_output_tagged ("text", "  lines: +");
+       cvs_output_tagged ("text", padd->data);
+       cvs_output_tagged ("text", " -");
+       cvs_output_tagged ("text", pdel->data);
+        cvs_output_tagged ("text", ";");
+    }
+
+    p = findnode(ver->other_delta,"commitid");
+    if(p && p->data)
+    {
+        cvs_output_tagged ("text", "  commitid: ");
+       cvs_output_tagged ("text", p->data);
+       cvs_output_tagged ("text", ";");
+    }
+
+    cvs_output_tagged ("newline", NULL);
+
+    if (ver->branches != NULL)
+    {
+       cvs_output ("branches:", 0);
+       walklist (ver->branches, log_branch, NULL);
+       cvs_output ("\n", 1);
+    }
+
+    p = findnode (ver->other, "log");
+    /* The p->date == NULL case is the normal one for an empty log
+       message (rcs-14 in sanity.sh).  I don't think the case where
+       p->data is "" can happen (getrcskey in rcs.c checks for an
+       empty string and set the value to NULL in that case).  My guess
+       would be the p == NULL case would mean an RCS file which was
+       missing the "log" keyword (which is invalid according to
+       rcsfile.5).  */
+    if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
+       cvs_output ("*** empty log message ***\n", 0);
+    else
+    {
+       /* FIXME: Technically, the log message could contain a null
+           byte.  */
+       cvs_output (p->data, 0);
+       if (((char *)p->data)[strlen (p->data) - 1] != '\n')
+           cvs_output ("\n", 1);
+    }
+}
+
+
+
+/*
+ * Output a branch version.  This is called via walklist.
+ */
+/*ARGSUSED*/
+static int
+log_branch (Node *p, void *closure)
+{
+    cvs_output ("  ", 2);
+    if ((numdots (p->key) & 1) == 0)
+       cvs_output (p->key, 0);
+    else
+    {
+       char *f, *cp;
+
+       f = xstrdup (p->key);
+       cp = strrchr (f, '.');
+       *cp = '\0';
+       cvs_output (f, 0);
+       free (f);
+    }
+    cvs_output (";", 1);
+    return 0;
+}
+
+
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+log_dirproc (void *callerdat, const char *dir, const char *repository,
+             const char *update_dir, List *entries)
+{
+    if (!isdir (dir))
+       return R_SKIP_ALL;
+
+    if (!quiet)
+       error (0, 0, "Logging %s", update_dir);
+    return R_PROCESS;
+}
+
+
+
+/*
+ * Compare versions.  This is taken from RCS compartial.
+ */
+static int
+version_compare (const char *v1, const char *v2, int len)
+{
+    while (1)
+    {
+       int d1, d2, r;
+
+       if (*v1 == '\0')
+           return 1;
+       if (*v2 == '\0')
+           return -1;
+
+       while (*v1 == '0')
+           ++v1;
+       for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
+           ;
+
+       while (*v2 == '0')
+           ++v2;
+       for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
+           ;
+
+       if (d1 != d2)
+           return d1 < d2 ? -1 : 1;
+
+       r = memcmp (v1, v2, d1);
+       if (r != 0)
+           return r;
+
+       --len;
+       if (len == 0)
+           return 0;
+
+       v1 += d1;
+       v2 += d1;
+
+       if (*v1 == '.')
+           ++v1;
+       if (*v2 == '.')
+           ++v2;
+    }
+}
Index: ccvs/src/patch.c
diff -u /dev/null ccvs/src/patch.c:1.106.8.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/patch.c    Tue Jan 17 15:41:23 2006
@@ -0,0 +1,853 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * Patch
+ * 
+ * Create a Larry Wall format "patch" file between a previous release and the
+ * current head of a module, or between two releases.  Can specify the
+ * release as either a date or a revision number.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+static RETSIGTYPE patch_cleanup (int);
+static Dtype patch_dirproc (void *callerdat, const char *dir,
+                            const char *repos, const char *update_dir,
+                            List *entries);
+static int patch_fileproc (void *callerdat, struct file_info *finfo);
+static int patch_proc (int argc, 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 char *options = NULL;
+static char *rev1 = NULL;
+static int rev1_validated = 0;
+static char *rev2 = NULL;
+static int rev2_validated = 0;
+static char *date1 = NULL;
+static char *date2 = NULL;
+static char *tmpfile1 = NULL;
+static char *tmpfile2 = NULL;
+static char *tmpfile3 = NULL;
+static int unidiff = 0;
+
+static const char *const patch_usage[] =
+{
+    "Usage: %s %s [-flR] [-c|-u] [-s|-t] [-V %%d] [-k kopt]\n",
+    "    -r rev|-D date [-r rev2 | -D date2] modules...\n",
+    "\t-f\tForce a head revision match if tag/date not found.\n",
+    "\t-l\tLocal directory only, not recursive\n",
+    "\t-R\tProcess directories recursively.\n",
+    "\t-c\tContext diffs (default)\n",
+    "\t-u\tUnidiff format.\n",
+    "\t-s\tShort patch - one liner per file.\n",
+    "\t-t\tTop two diffs - last change made to the file.\n",
+    "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
+    "\t-k kopt\tSpecify keyword expansion mode.\n",
+    "\t-D date\tDate.\n",
+    "\t-r rev\tRevision - symbolic or numeric.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+
+
+int
+patch (int argc, char **argv)
+{
+    register int i;
+    int local = 0;
+    int c;
+    int err = 0;
+    DBM *db;
+
+    if (argc == -1)
+       usage (patch_usage);
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+V:k:cuftsQqlRD:r:")) != -1)
+    {
+       switch (c)
+       {
+           case 'Q':
+           case 'q':
+               /* The CVS 1.5 client sends these options (in addition to
+                  Global_option requests), so we must ignore them.  */
+               if (!server_active)
+                   error (1, 0,
+                          "-q or -Q must be specified before \"%s\"",
+                          cvs_cmd_name);
+               break;
+           case 'f':
+               force_tag_match = 0;
+               break;
+           case 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case 't':
+               toptwo_diffs = 1;
+               break;
+           case 's':
+               patch_short = 1;
+               break;
+           case 'D':
+               if (rev2 != NULL || date2 != NULL)
+                   error (1, 0,
+                      "no more than two revisions/dates can be specified");
+               if (rev1 != NULL || date1 != NULL)
+                   date2 = Make_Date (optarg);
+               else
+                   date1 = Make_Date (optarg);
+               break;
+           case 'r':
+               if (rev2 != NULL || date2 != NULL)
+                   error (1, 0,
+                      "no more than two revisions/dates can be specified");
+               if (rev1 != NULL || date1 != NULL)
+                   rev2 = optarg;
+               else
+                   rev1 = optarg;
+               break;
+           case 'k':
+               if (options)
+                   free (options);
+               options = RCS_check_kflag (optarg);
+               break;
+           case 'V':
+               /* This option is pretty seriously broken:
+                  1.  It is not clear what it does (does it change keyword
+                  expansion behavior?  If so, how?  Or does it have
+                  something to do with what version of RCS we are using?
+                  Or the format we write RCS files in?).
+                  2.  Because both it and -k use the options variable,
+                  specifying both -V and -k doesn't work.
+                  3.  At least as of CVS 1.9, it doesn't work (failed
+                  assertion in RCS_checkout where it asserts that options
+                  starts with -k).  Few people seem to be complaining.
+                  In the future (perhaps the near future), I have in mind
+                  removing it entirely, and updating NEWS and cvs.texinfo,
+                  but in case it is a good idea to give people more time
+                  to complain if they would miss it, I'll just add this
+                  quick and dirty error message for now.  */
+               error (1, 0,
+                      "the -V option is obsolete and should not be used");
+               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 (current_parsed_root->isremote)
+    {
+       /* 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 SIGABRT
+    (void)SIG_register (SIGABRT, patch_cleanup);
+#endif
+#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,
+                         NULL, 0, local, 0, 0, NULL);
+    close_module (db);
+    free (options);
+    patch_cleanup (0);
+    return err;
+}
+
+
+
+/*
+ * callback proc for doing the real work of patching
+ */
+/* ARGSUSED */
+static int
+patch_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
+            int shorten, int local_specified, char *mname, char *msg)
+{
+    char *myargv[2];
+    int err = 0;
+    int which;
+    char *repository;
+    char *where;
+
+    TRACE ( TRACE_FUNCTION, "patch_proc ( %s, %s, %s, %d, %d, %s, %s )",
+           xwhere ? xwhere : "(null)",
+           mwhere ? mwhere : "(null)",
+           mfile ? mfile : "(null)",
+           shorten, local_specified,
+           mname ? mname : "(null)",
+           msg ? msg : "(null)" );
+
+    repository = xmalloc (strlen (current_parsed_root->directory)
+                          + strlen (argv[0])
+                          + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
+    (void)sprintf (repository, "%s/%s",
+                   current_parsed_root->directory, argv[0]);
+    where = xmalloc (strlen (argv[0])
+                     + (mfile == NULL ? 0 : strlen (mfile) + 1)
+                    + 1);
+    (void)strcpy (where, argv[0]);
+
+    /* if mfile isn't null, we need to set up to do only part of the module */
+    if (mfile != NULL)
+    {
+       char *cp;
+       char *path;
+
+       /* if the portion of the module is a path, put the dir part on repos */
+       if ((cp = strrchr (mfile, '/')) != NULL)
+       {
+           *cp = '\0';
+           (void)strcat (repository, "/");
+           (void)strcat (repository, mfile);
+           (void)strcat (where, "/");
+           (void)strcat (where, mfile);
+           mfile = cp + 1;
+       }
+
+       /* take care of the rest */
+       path = xmalloc (strlen (repository) + strlen (mfile) + 2);
+       (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
+       {
+           myargv[0] = argv[0];
+           myargv[1] = mfile;
+           argc = 2;
+           argv = myargv;
+       }
+       free (path);
+    }
+
+    /* cd to the starting repository */
+    if (CVS_CHDIR (repository) < 0)
+    {
+       error (0, errno, "cannot chdir to %s", repository);
+       free (repository);
+       free (where);
+       return 1;
+    }
+
+    if (force_tag_match)
+       which = W_REPOS | W_ATTIC;
+    else
+       which = W_REPOS;
+
+    if (rev1 != NULL && !rev1_validated)
+    {
+       tag_check_valid (rev1, argc - 1, argv + 1, local_specified, 0,
+                        repository, false);
+       rev1_validated = 1;
+    }
+    if (rev2 != NULL && !rev2_validated)
+    {
+       tag_check_valid (rev2, argc - 1, argv + 1, local_specified, 0,
+                        repository, false);
+       rev2_validated = 1;
+    }
+
+    /* start the recursion processor */
+    err = start_recursion (patch_fileproc, NULL, patch_dirproc, NULL, NULL,
+                          argc - 1, argv + 1, local_specified,
+                          which, 0, CVS_LOCK_READ, where, 1, repository );
+    free (repository);
+    free (where);
+
+    return err;
+}
+
+
+
+/*
+ * Called to examine a particular RCS file, as appropriate with the options
+ * that were set above.
+ */
+/* ARGSUSED */
+static int
+patch_fileproc (void *callerdat, struct file_info *finfo)
+{
+    struct utimbuf t;
+    char *vers_tag, *vers_head;
+    char *rcs = NULL;
+    char *rcs_orig = NULL;
+    RCSNode *rcsfile;
+    FILE *fp1, *fp2, *fp3;
+    int ret = 0;
+    int isattic = 0;
+    int retcode = 0;
+    char *file1;
+    char *file2;
+    char *strippath;
+    char *line1, *line2;
+    size_t line1_chars_allocated;
+    size_t line2_chars_allocated;
+    char *cp1, *cp2;
+    FILE *fp;
+    int line_length;
+    int dargc = 0;
+    size_t darg_allocated = 0;
+    char **dargv = NULL;
+    Vers_TS *vers;
+
+    line1 = NULL;
+    line1_chars_allocated = 0;
+    line2 = NULL;
+    line2_chars_allocated = 0;
+    vers_tag = vers_head = NULL;
+
+    /* find the parsed rcs file */
+    if ((rcsfile = finfo->rcs) == NULL)
+    {
+       ret = 1;
+       goto out2;
+    }
+    if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
+       isattic = 1;
+
+    rcs_orig = rcs = Xasprintf ("%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 = Version_TS (finfo, NULL, rev2, date2, force_tag_match, 0);
+        if (vers->vn_rcs != NULL)
+            vers_head = xstrdup (vers->vn_rcs);
+        freevers_ts (&vers);
+
+       if (vers_head != NULL && RCS_isdead (rcsfile, vers_head))
+       {
+           free (vers_head);
+           vers_head = NULL;
+       }
+    }
+
+    if (toptwo_diffs)
+    {
+       if (vers_head == NULL)
+       {
+           ret = 1;
+           goto out2;
+       }
+
+       if (!date1)
+           date1 = xmalloc (MAXDATELEN);
+       *date1 = '\0';
+       if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == (time_t)-1)
+       {
+           if (!really_quiet)
+               error (0, 0, "cannot find date in rcs file %s revision %s",
+                      rcs, vers_head);
+           ret = 1;
+           goto out2;
+       }
+    }
+    vers = Version_TS (finfo, NULL, rev1, date1, force_tag_match, 0);
+    if (vers->vn_rcs != NULL)
+        vers_tag = xstrdup (vers->vn_rcs);
+    freevers_ts (&vers);
+    if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag))
+    {
+        free (vers_tag);
+       vers_tag = NULL;
+    }
+
+    if ((vers_tag == NULL && vers_head == NULL) ||
+        (vers_tag != NULL && vers_head != NULL &&
+        strcmp (vers_head, vers_tag) == 0))
+    {
+       /* Nothing known about specified revs or
+        * not changed between releases.
+        */
+       ret = 0;
+       goto out2;
+    }
+
+    if (patch_short && (vers_tag == NULL || vers_head == NULL))
+    {
+       /* For adds & removes with a short patch requested, we can print our
+        * error message now and get out.
+        */
+       cvs_output ("File ", 0);
+       cvs_output (finfo->fullname, 0);
+       if (vers_tag == NULL)
+       {
+           cvs_output (" is new; ", 0);
+           cvs_output (rev2 ? rev2 : date2 ? date2 : "current", 0);
+           cvs_output (" revision ", 0);
+           cvs_output (vers_head, 0);
+           cvs_output ("\n", 1);
+       }
+       else
+       {
+           cvs_output (" is removed; ", 0);
+           cvs_output (rev1 ? rev1 : date1, 0);
+           cvs_output (" revision ", 0);
+           cvs_output (vers_tag, 0);
+           cvs_output ("\n", 1);
+       }
+       ret = 0;
+       goto out2;
+    }
+
+    /* Create 3 empty files.  I'm not really sure there is any advantage
+     * to doing so now rather than just waiting until later.
+     *
+     * There is - cvs_temp_file opens the file so that it can guarantee that
+     * we have exclusive write access to the file.  Unfortunately we spoil that
+     * by closing it and reopening it again.  Of course any better solution
+     * requires that the RCS functions accept open file pointers rather than
+     * simple file names.
+     */
+    if ((fp1 = cvs_temp_file (&tmpfile1)) == NULL)
+    {
+       error (0, errno, "cannot create temporary file %s", tmpfile1);
+       ret = 1;
+       goto out;
+    }
+    else
+       if (fclose (fp1) < 0)
+           error (0, errno, "warning: cannot close %s", tmpfile1);
+    if ((fp2 = cvs_temp_file (&tmpfile2)) == NULL)
+    {
+       error (0, errno, "cannot create temporary file %s", tmpfile2);
+       ret = 1;
+       goto out;
+    }
+    else
+       if (fclose (fp2) < 0)
+           error (0, errno, "warning: cannot close %s", tmpfile2);
+    if ((fp3 = cvs_temp_file (&tmpfile3)) == NULL)
+    {
+       error (0, errno, "cannot create temporary file %s", tmpfile3);
+       ret = 1;
+       goto out;
+    }
+    else
+       if (fclose (fp3) < 0)
+           error (0, errno, "warning: cannot close %s", tmpfile3);
+
+    if (vers_tag != NULL)
+    {
+       retcode = RCS_checkout (rcsfile, NULL, vers_tag, rev1, options,
+                                tmpfile1, NULL, NULL);
+       if (retcode != 0)
+       {
+           error (0, 0,
+                  "cannot check out revision %s of %s", vers_tag, rcs);
+           ret = 1;
+           goto out;
+       }
+       memset ((char *) &t, 0, sizeof (t));
+       if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
+                                                   NULL, 0)) != -1)
+           /* I believe this timestamp only affects the dates in our diffs,
+              and therefore should be on the server, not the client.  */
+           (void)utime (tmpfile1, &t);
+    }
+    else if (toptwo_diffs)
+    {
+       ret = 1;
+       goto out;
+    }
+    if (vers_head != NULL)
+    {
+       retcode = RCS_checkout (rcsfile, NULL, vers_head, rev2, options,
+                                tmpfile2, NULL, NULL);
+       if (retcode != 0)
+       {
+           error (0, 0,
+                  "cannot check out revision %s of %s", vers_head, rcs);
+           ret = 1;
+           goto out;
+       }
+       if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
+                                                   NULL, 0)) != -1)
+           /* I believe this timestamp only affects the dates in our diffs,
+              and therefore should be on the server, not the client.  */
+           (void)utime (tmpfile2, &t);
+    }
+
+    if (unidiff) run_add_arg_p (&dargc, &darg_allocated, &dargv, "-u");
+    else run_add_arg_p (&dargc, &darg_allocated, &dargv, "-c");
+    switch (diff_exec (tmpfile1, tmpfile2, NULL, NULL, dargc, dargv,
+                      tmpfile3))
+    {
+       case -1:                        /* fork/wait failure */
+           error (1, errno, "fork for diff failed on %s", rcs);
+           break;
+       case 0:                         /* nothing to do */
+           break;
+       case 1:
+           /*
+            * The two revisions are really different, so read the first two
+            * lines of the diff output file, and munge them to include more
+            * reasonable file names that "patch" will understand, unless the
+            * user wanted a short patch.  In that case, just output the short
+            * message.
+            */
+           if (patch_short)
+           {
+               cvs_output ("File ", 0);
+               cvs_output (finfo->fullname, 0);
+               cvs_output (" changed from revision ", 0);
+               cvs_output (vers_tag, 0);
+               cvs_output (" to ", 0);
+               cvs_output (vers_head, 0);
+               cvs_output ("\n", 1);
+               ret = 0;
+               goto out;
+           }
+
+           /* Output an "Index:" line for patch to use */
+           cvs_output ("Index: ", 0);
+           cvs_output (finfo->fullname, 0);
+           cvs_output ("\n", 1);
+
+           /* Now the munging. */
+           fp = xfopen (tmpfile3, "r");
+           if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
+               getline (&line2, &line2_chars_allocated, fp) < 0)
+           {
+               if (feof (fp))
+                   error (0, 0, "\
+failed to read diff file header %s for %s: end of file", tmpfile3, rcs);
+               else
+                   error (0, errno,
+                          "failed to read diff file header %s for %s",
+                          tmpfile3, rcs);
+               ret = 1;
+               if (fclose (fp) < 0)
+                   error (0, errno, "error closing %s", tmpfile3);
+               goto out;
+           }
+           if (!unidiff)
+           {
+               if (strncmp (line1, "*** ", 4) != 0 ||
+                   strncmp (line2, "--- ", 4) != 0 ||
+                   (cp1 = strchr (line1, '\t')) == NULL ||
+                   (cp2 = strchr (line2, '\t')) == NULL)
+               {
+                   error (0, 0, "invalid diff header for %s", rcs);
+                   ret = 1;
+                   if (fclose (fp) < 0)
+                       error (0, errno, "error closing %s", tmpfile3);
+                   goto out;
+               }
+           }
+           else
+           {
+               if (strncmp (line1, "--- ", 4) != 0 ||
+                   strncmp (line2, "+++ ", 4) != 0 ||
+                   (cp1 = strchr (line1, '\t')) == NULL ||
+                   (cp2 = strchr  (line2, '\t')) == NULL)
+               {
+                   error (0, 0, "invalid unidiff header for %s", rcs);
+                   ret = 1;
+                   if (fclose (fp) < 0)
+                       error (0, errno, "error closing %s", tmpfile3);
+                   goto out;
+               }
+           }
+           assert (current_parsed_root != NULL);
+           assert (current_parsed_root->directory != NULL);
+
+           strippath = Xasprintf ("%s/", current_parsed_root->directory);
+
+           if (strncmp (rcs, strippath, strlen (strippath)) == 0)
+               rcs += strlen (strippath);
+           free (strippath);
+           if (vers_tag != NULL)
+               file1 = Xasprintf ("%s:%s", finfo->fullname, vers_tag);
+           else
+               file1 = xstrdup (DEVNULL);
+
+           file2 = Xasprintf ("%s:%s", finfo->fullname,
+                              vers_head ? vers_head : "removed");
+
+           /* Note that the string "diff" is specified by POSIX (for -c)
+              and is part of the diff output format, not the name of a
+              program.  */
+           if (unidiff)
+           {
+               cvs_output ("diff -u ", 0);
+               cvs_output (file1, 0);
+               cvs_output (" ", 1);
+               cvs_output (file2, 0);
+               cvs_output ("\n", 1);
+
+               cvs_output ("--- ", 0);
+               cvs_output (file1, 0);
+               cvs_output (cp1, 0);
+               cvs_output ("+++ ", 0);
+           }
+           else
+           {
+               cvs_output ("diff -c ", 0);
+               cvs_output (file1, 0);
+               cvs_output (" ", 1);
+               cvs_output (file2, 0);
+               cvs_output ("\n", 1);
+
+               cvs_output ("*** ", 0);
+               cvs_output (file1, 0);
+               cvs_output (cp1, 0);
+               cvs_output ("--- ", 0);
+           }
+
+           cvs_output (finfo->fullname, 0);
+           cvs_output (cp2, 0);
+
+           /* spew the rest of the diff out */
+           while ((line_length
+                   = getline (&line1, &line1_chars_allocated, fp))
+                  >= 0)
+               cvs_output (line1, 0);
+           if (line_length < 0 && !feof (fp))
+               error (0, errno, "cannot read %s", tmpfile3);
+
+           if (fclose (fp) < 0)
+               error (0, errno, "cannot close %s", tmpfile3);
+           free (file1);
+           free (file2);
+           break;
+       default:
+           error (0, 0, "diff failed for %s", finfo->fullname);
+    }
+  out:
+    if (line1)
+        free (line1);
+    if (line2)
+        free (line2);
+    if (CVS_UNLINK (tmpfile1) < 0)
+       error (0, errno, "cannot unlink %s", tmpfile1);
+    if (CVS_UNLINK (tmpfile2) < 0)
+       error (0, errno, "cannot unlink %s", tmpfile2);
+    if (CVS_UNLINK (tmpfile3) < 0)
+       error (0, errno, "cannot unlink %s", tmpfile3);
+    free (tmpfile1);
+    free (tmpfile2);
+    free (tmpfile3);
+    tmpfile1 = tmpfile2 = tmpfile3 = NULL;
+    if (darg_allocated)
+    {
+       run_arg_free_p (dargc, dargv);
+       free (dargv);
+    }
+
+ out2:
+    if (vers_tag != NULL)
+       free (vers_tag);
+    if (vers_head != NULL)
+       free (vers_head);
+    if (rcs_orig)
+       free (rcs_orig);
+    return ret;
+}
+
+
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+patch_dirproc (void *callerdat, const char *dir, const char *repos,
+               const char *update_dir, List *entries)
+{
+    if (!quiet)
+       error (0, 0, "Diffing %s", update_dir);
+    return R_PROCESS;
+}
+
+
+
+/*
+ * Clean up temporary files
+ */
+static RETSIGTYPE
+patch_cleanup (int sig)
+{
+    /* Note that the checks for existence_error are because we are
+       called from a signal handler, without SIG_begincrsect, so
+       we don't know whether the files got created.  */
+
+    if (tmpfile1 != NULL)
+    {
+       if (unlink_file (tmpfile1) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", tmpfile1);
+       free (tmpfile1);
+    }
+    if (tmpfile2 != NULL)
+    {
+       if (unlink_file (tmpfile2) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", tmpfile2);
+       free (tmpfile2);
+    }
+    if (tmpfile3 != NULL)
+    {
+       if (unlink_file (tmpfile3) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", tmpfile3);
+       free (tmpfile3);
+    }
+    tmpfile1 = tmpfile2 = tmpfile3 = NULL;
+
+    if (sig != 0)
+    {
+       const char *name;
+       char temp[10];
+
+       switch (sig)
+       {
+#ifdef SIGABRT
+       case SIGABRT:
+           name = "abort";
+           break;
+#endif
+#ifdef SIGHUP
+       case SIGHUP:
+           name = "hangup";
+           break;
+#endif
+#ifdef SIGINT
+       case SIGINT:
+           name = "interrupt";
+           break;
+#endif
+#ifdef SIGQUIT
+       case SIGQUIT:
+           name = "quit";
+           break;
+#endif
+#ifdef SIGPIPE
+       case SIGPIPE:
+           name = "broken pipe";
+           break;
+#endif
+#ifdef SIGTERM
+       case SIGTERM:
+           name = "termination";
+           break;
+#endif
+       default:
+           /* This case should never be reached, because we list
+              above all the signals for which we actually establish a
+              signal handler.  */ 
+           sprintf (temp, "%d", sig);
+           name = temp;
+           break;
+       }
+       error (0, 0, "received %s signal", name);
+    }
+}
Index: ccvs/src/rcs.c
diff -u /dev/null ccvs/src/rcs.c:1.357.2.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/rcs.c      Tue Jan 17 15:41:23 2006
@@ -0,0 +1,9873 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * The routines contained in this file do all the rcs file parsing and
+ * manipulation
+ */
+
+#include "cvs.h"
+#include "edit.h"
+#include "hardlink.h"
+
+/* These need to be source after cvs.h or HAVE_MMAP won't be set... */
+#ifdef HAVE_MMAP
+# include "getpagesize.h"
+# include <sys/mman.h>
+
+/* Define MAP_FILE when it isn't otherwise.  */
+# ifndef MAP_FILE
+#  define MAP_FILE 0
+# endif
+/* Define MAP_FAILED for old systems which neglect to.  */
+# ifndef MAP_FAILED
+#  define MAP_FAILED ((void *)-1)
+# endif
+#endif
+
+/* The RCS -k options, and a set of enums that must match the array.
+   These come first so that we can use enum kflag in function
+   prototypes.  */
+static const char *const kflags[] =
+  {"kv", "kvl", "k", "v", "o", "b", NULL};
+enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B };
+
+/* A structure we use to buffer the contents of an RCS file.  The
+   various fields are only referenced directly by the rcsbuf_*
+   functions.  We declare the struct here so that we can allocate it
+   on the stack, rather than in memory.  */
+
+struct rcsbuffer
+{
+    /* Points to the current position in the buffer.  */
+    char *ptr;
+    /* Points just after the last valid character in the buffer.  */
+    char *ptrend;
+    /* The file.  */
+    FILE *fp;
+    /* The name of the file, used for error messages.  */
+    const char *filename;
+    /* The starting file position of the data in the buffer.  */
+    unsigned long pos;
+    /* The length of the value.  */
+    size_t vlen;
+    /* Whether the value contains an '@' string.  If so, we can not
+       compress whitespace characters.  */
+    int at_string;
+    /* The number of embedded '@' characters in an '@' string.  If
+       this is non-zero, we must search the string for pairs of '@'
+       and convert them to a single '@'.  */
+    int embedded_at;
+};
+
+static RCSNode *RCS_parsercsfile_i (FILE * fp, const char *rcsfile);
+static char *RCS_getdatetrunk (RCSNode * rcs, const char *date,
+                               int force_tag_match);
+static char *RCS_getdatebranch (RCSNode * rcs, const char *date,
+                                const char *branch);
+static void rcsbuf_open (struct rcsbuffer *, FILE *fp,
+                         const char *filename, unsigned long pos);
+static void rcsbuf_close (struct rcsbuffer *);
+static int rcsbuf_getkey (struct rcsbuffer *, char **keyp, char **valp);
+static int rcsbuf_getrevnum (struct rcsbuffer *, char **revp);
+static char *rcsbuf_fill (struct rcsbuffer *, char *ptr, char **keyp,
+                          char **valp);
+static int rcsbuf_valcmp (struct rcsbuffer *);
+static char *rcsbuf_valcopy (struct rcsbuffer *, char *val, int polish,
+                             size_t *lenp);
+static void rcsbuf_valpolish (struct rcsbuffer *, char *val, int polish,
+                              size_t *lenp);
+static void rcsbuf_valpolish_internal (struct rcsbuffer *, char *to,
+                                       const char *from, size_t *lenp);
+static off_t rcsbuf_ftello (struct rcsbuffer *);
+static void rcsbuf_get_buffered (struct rcsbuffer *, char **datap,
+                                size_t *lenp);
+static void rcsbuf_cache (RCSNode *, struct rcsbuffer *);
+static void rcsbuf_cache_close (void);
+static void rcsbuf_cache_open (RCSNode *, off_t, FILE **, struct rcsbuffer *);
+static int checkmagic_proc (Node *p, void *closure);
+static void do_branches (List * list, char *val);
+static void do_symbols (List * list, char *val);
+static void do_locks (List * list, char *val);
+static void free_rcsnode_contents (RCSNode *);
+static void free_rcsvers_contents (RCSVers *);
+static void rcsvers_delproc (Node * p);
+static char *RCS_getroot (RCSNode *, const char *);
+static char *RCS_getprevious (RCSNode *, const char *);
+static char *RCS_getnext (RCSNode *, const char *);
+static char *RCS_getorigin (RCSNode *, const char *);
+static char *RCS_getcommitid (RCSNode *, const char *, bool);
+static char *translate_tag (RCSNode *rcs, const char *, bool);
+static char *translate_symtag (RCSNode *, const char *);
+static char *RCS_addbranch (RCSNode *, const char *);
+static char *truncate_revnum (const char *);
+static char *printable_date (const char *);
+static char *escape_keyword_value (const char *, int *);
+static void expand_keywords (RCSNode *, RCSVers *, const char *,
+                             const char *, size_t, enum kflag, char *,
+                             size_t, char **, size_t *);
+static void cmp_file_buffer (void *, const char *, size_t);
+
+/* Routines for reading, parsing and writing RCS files. */
+static RCSVers *getdelta (struct rcsbuffer *, char *, char **, char **);
+static Deltatext *RCS_getdeltatext (RCSNode *, FILE *, struct rcsbuffer *);
+static void freedeltatext (Deltatext *);
+
+static void RCS_putadmin (RCSNode *, FILE *);
+static void RCS_putdtree (RCSNode *, char *, FILE *);
+static void RCS_putdesc (RCSNode *, FILE *);
+static void putdelta (RCSVers *, FILE *);
+static int putrcsfield_proc (Node *, void *);
+static int putsymbol_proc (Node *, void *);
+static void RCS_copydeltas (RCSNode *, FILE *, struct rcsbuffer *, FILE *,
+                           Deltatext *, char *);
+static int count_delta_actions (Node *, void *);
+static void putdeltatext (FILE *, Deltatext *);
+
+static FILE *rcs_internal_lockfile (char *);
+static void rcs_internal_unlockfile (FILE *, char *);
+static char *rcs_lockfilename (const char *);
+
+/* The RCS file reading functions are called a lot, and they do some
+   string comparisons.  This macro speeds things up a bit by skipping
+   the function call when the first characters are different.  It
+   evaluates its arguments multiple times.  */
+#define STREQ(a, b) (*(char *)(a) == *(char *)(b) && strcmp ((a), (b)) == 0)
+
+static char * getfullCVSname (char *, char **);
+
+/*
+ * We don't want to use isspace() from the C library because:
+ *
+ * 1. The definition of "whitespace" in RCS files includes ASCII
+ *    backspace, but the C locale doesn't.
+ * 2. isspace is an very expensive function call in some implementations
+ *    due to the addition of wide character support.
+ */
+static const char spacetab[] = {
+        0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,        /* 0x00 - 0x0f 
*/
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
+        1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x8f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0  /* 0xf0 - 0xff */
+};
+
+#define whitespace(c)  (spacetab[(unsigned char)c] != 0)
+
+static char *rcs_lockfile = NULL;
+static int rcs_lockfd = -1;
+
+
+
+/*
+ * char *
+ * locate_rcs ( const char* file, const char *repository , int *inattic )
+ *
+ * Find an RCS file in the repository, case insensitively when the cased name
+ * doesn't exist, we are running as the server, and a client has asked us to
+ * ignore case.
+ *
+ * Most parts of CVS will want to rely instead on RCS_parse which calls this
+ * function and is called by recurse.c which then puts the result in useful
+ * places like the rcs field of struct file_info.
+ *
+ * INPUTS
+ *
+ *  repository         the repository (including the directory)
+ *  file               the filename within that directory (without RCSEXT).
+ *  inattic            NULL or a pointer to the output boolean
+ *
+ * OUTPUTS
+ *
+ *  inattic            If this input was non-null, the destination will be
+ *                     set to true if the file was found in the attic or
+ *                     false if not.  If no RCS file is found, this value
+ *                     is undefined.
+ *
+ * RETURNS
+ *
+ *  a newly-malloc'd array containing the absolute pathname of the RCS
+ *  file that was found or NULL when none was found.
+ *
+ * ERRORS
+ *
+ *  errno can be set by the return value of the final call to
+ *  locate_file_in_dir().  This should resolve to the system's existence error
+ *  value (sometime ENOENT) if the Attic directory did not exist and ENOENT if
+ *  the Attic was found but no matching files were found in the Attic or its
+ *  parent.
+ */
+static char *
+locate_rcs (const char *repository, const char *file, int *inattic)
+{
+    char *retval;
+
+    /* First, try to find the file as cased. */
+    retval = xmalloc (strlen (repository)
+                      + sizeof (CVSATTIC)
+                      + strlen (file)
+                      + sizeof (RCSEXT)
+                      + 3);
+    sprintf (retval, "%s/%s%s", repository, file, RCSEXT);
+    if (isreadable (retval))
+    {
+       if (inattic)
+           *inattic = 0;
+       return retval;
+    }
+    sprintf (retval, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+    if (isreadable (retval))
+    {
+       if (inattic)
+           *inattic = 1;
+       return retval;
+    }
+    free (retval);
+
+    return NULL;
+}
+
+
+
+/* A few generic thoughts on error handling, in particular the
+   printing of unexpected characters that we find in the RCS file
+   (that is, why we use '\x%x' rather than %c or some such).
+
+   * Avoiding %c means we don't have to worry about what is printable
+   and other such stuff.  In error handling, often better to keep it
+   simple.
+
+   * Hex rather than decimal or octal because character set standards
+   tend to use hex.
+
+   * Saying "character 0x%x" might make it sound like we are printing
+   a file offset.  So we use '\x%x'.
+
+   * Would be nice to print the offset within the file, but I can
+   imagine various portability hassles (in particular, whether
+   unsigned long is always big enough to hold file offsets).  */
+
+/* Parse an rcsfile given a user file name and a repository.  If there is
+   an error, we print an error message and return NULL.  If the file
+   does not exist, we return NULL without printing anything (I'm not
+   sure this allows the caller to do anything reasonable, but it is
+   the current behavior).  */
+RCSNode *
+RCS_parse (const char *file, const char *repos)
+{
+    RCSNode *rcs;
+    FILE *fp;
+    RCSNode *retval = NULL;
+    char *rcsfile;
+    int inattic;
+
+    /* We're creating a new RCSNode, so there is no hope of finding it
+       in the cache.  */
+    rcsbuf_cache_close ();
+
+    if (!(rcsfile = locate_rcs (repos, file, &inattic)))
+    {
+       /* Handle the error cases */
+    }
+    else if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ))) 
+    {
+       rcs = RCS_parsercsfile_i (fp, rcsfile);
+       if (rcs)
+       {       
+           rcs->flags |= VALID;
+           if (inattic)
+               rcs->flags |= INATTIC;
+       }
+
+       free (rcsfile);
+       retval = rcs;
+    }
+    else if (!existence_error (errno))
+    {
+       error (0, errno, "cannot open `%s'", rcsfile);
+       free (rcsfile);
+    }
+
+    return retval;
+}
+
+
+
+/*
+ * Parse a specific rcsfile.
+ */
+RCSNode *
+RCS_parsercsfile (const char *rcsfile)
+{
+    FILE *fp;
+    RCSNode *rcs;
+
+    /* We're creating a new RCSNode, so there is no hope of finding it
+       in the cache.  */
+    rcsbuf_cache_close ();
+
+    /* open the rcsfile */
+    if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL)
+    {
+       error (0, errno, "Couldn't open rcs file `%s'", rcsfile);
+       return NULL;
+    }
+
+    rcs = RCS_parsercsfile_i (fp, rcsfile);
+
+    return rcs;
+}
+
+
+
+/*
+ */ 
+static RCSNode *
+RCS_parsercsfile_i (FILE *fp, const char *rcsfile)
+{
+    RCSNode *rdata;
+    struct rcsbuffer rcsbuf;
+    char *key, *value;
+
+    /* make a node */
+    rdata = xmalloc (sizeof (RCSNode));
+    memset (rdata, 0, sizeof (RCSNode));
+    rdata->refcount = 1;
+    rdata->path = xstrdup (rcsfile);
+    rdata->print_path = xstrdup (primary_root_inverse_translate (rcsfile));
+
+    /* Process HEAD, BRANCH, and EXPAND keywords from the RCS header.
+
+       Most cvs operations on the main branch don't need any more
+       information.  Those that do call RCS_reparsercsfile to parse
+       the rest of the header and the deltas.  */
+
+    rcsbuf_open (&rcsbuf, fp, rcsfile, 0);
+
+    if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+       goto l_error;
+    if (STREQ (key, RCSDESC))
+       goto l_error;
+
+    if (STREQ (RCSHEAD, key) && value != NULL)
+       rdata->head = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
+
+    if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+       goto l_error;
+    if (STREQ (key, RCSDESC))
+       goto l_error;
+
+    if (STREQ (RCSBRANCH, key) && value != NULL)
+    {
+       char *cp;
+
+       rdata->branch = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
+       if ((numdots (rdata->branch) & 1) != 0)
+       {
+           /* turn it into a branch if it's a revision */
+           cp = strrchr (rdata->branch, '.');
+           *cp = '\0';
+       }
+    }
+
+    /* Look ahead for expand, stopping when we see desc or a revision
+       number.  */
+    while (1)
+    {
+       char *cp;
+
+       if (STREQ (RCSEXPAND, key))
+       {
+           rdata->expand = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
+           break;
+       }
+
+       for (cp = key;
+            (isdigit ((unsigned char)*cp) || *cp == '.') && *cp != '\0';
+            cp++)
+           /* do nothing */ ;
+       if (*cp == '\0')
+           break;
+
+       if (STREQ (RCSDESC, key))
+           break;
+
+       if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+           break;
+    }
+
+    rdata->flags |= PARTIAL;
+
+    rcsbuf_cache (rdata, &rcsbuf);
+
+    return rdata;
+
+l_error:
+    error (0, 0, "`%s' does not appear to be a valid rcs file",
+          rcsfile);
+    rcsbuf_close (&rcsbuf);
+    freercsnode (&rdata);
+    fclose (fp);
+    return NULL;
+}
+
+
+
+/* Do the real work of parsing an RCS file.
+
+   On error, die with a fatal error; if it returns at all it was successful.
+
+   If PFP is NULL, close the file when done.  Otherwise, leave it open
+   and store the FILE * in *PFP.  */
+void
+RCS_reparsercsfile (RCSNode *rdata, FILE **pfp, struct rcsbuffer *rcsbufp)
+{
+    FILE *fp;
+    char *rcsfile;
+    struct rcsbuffer rcsbuf;
+    Node *q, *kv;
+    RCSVers *vnode;
+    int gotkey;
+    char *cp;
+    char *key, *value;
+
+    assert (rdata != NULL);
+    rcsfile = rdata->path;
+
+    rcsbuf_cache_open (rdata, 0, &fp, &rcsbuf);
+
+    /* make a node */
+    /* This probably shouldn't be done until later: if a file has an
+       empty revision tree (which is permissible), rdata->versions
+       should be NULL. -twp */
+    rdata->versions = getlist ();
+
+    /*
+     * process all the special header information, break out when we get to
+     * the first revision delta
+     */
+    gotkey = 0;
+    for (;;)
+    {
+       /* get the next key/value pair */
+       if (!gotkey)
+       {
+           if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+           {
+               error (1, 0, "`%s' does not appear to be a valid rcs file",
+                      rcsfile);
+           }
+       }
+
+       gotkey = 0;
+
+       /* Skip head, branch and expand tags; we already have them. */
+       if (STREQ (key, RCSHEAD)
+           || STREQ (key, RCSBRANCH)
+           || STREQ (key, RCSEXPAND))
+       {
+           continue;
+       }
+
+       if (STREQ (key, "access"))
+       {
+           if (value != NULL)
+           {
+               /* We pass the POLISH parameter as 1 because
+                   RCS_addaccess expects nothing but spaces.  FIXME:
+                   It would be easy and more efficient to change
+                   RCS_addaccess.  */
+               if (rdata->access)
+               {
+                   error (0, 0,
+                          "Duplicate `access' keyword found in RCS file.");
+                   free (rdata->access);
+               }
+               rdata->access = rcsbuf_valcopy (&rcsbuf, value, 1, NULL);
+           }
+           continue;
+       }
+
+       /* We always save lock information, so that we can handle
+           -kkvl correctly when checking out a file. */
+       if (STREQ (key, "locks"))
+       {
+           if (value != NULL)
+           {
+               if (rdata->locks_data)
+               {
+                   error (0, 0,
+                          "Duplicate `locks' keyword found in RCS file.");
+                   free (rdata->locks_data);
+               }
+               rdata->locks_data = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
+           }
+           if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+           {
+               error (1, 0, "premature end of file reading %s", rcsfile);
+           }
+           if (STREQ (key, "strict") && value == NULL)
+           {
+               rdata->strict_locks = 1;
+           }
+           else
+               gotkey = 1;
+           continue;
+       }
+
+       if (STREQ (RCSSYMBOLS, key))
+       {
+           if (value != NULL)
+           {
+               if (rdata->symbols_data)
+               {
+                   error (0, 0,
+                          "Duplicate `%s' keyword found in RCS file.",
+                          RCSSYMBOLS);
+                   free (rdata->symbols_data);
+               }
+               rdata->symbols_data = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
+           }
+           continue;
+       }
+
+       /*
+        * check key for '.''s and digits (probably a rev) if it is a
+        * revision or `desc', we are done with the headers and are down to the
+        * revision deltas, so we break out of the loop
+        */
+       for (cp = key;
+            (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
+            cp++)
+            /* do nothing */ ;
+       /* Note that when comparing with RCSDATE, we are not massaging
+           VALUE from the string found in the RCS file.  This is OK
+           since we know exactly what to expect.  */
+       if (*cp == '\0' && strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) == 0)
+           break;
+
+       if (STREQ (key, RCSDESC))
+           break;
+
+       if (STREQ (key, "comment"))
+       {
+           if (rdata->comment)
+           {
+               error (0, 0,
+                      "warning: duplicate key `%s' in RCS file `%s'",
+                      key, rcsfile);
+               free (rdata->comment);
+           }
+           rdata->comment = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
+           continue;
+       }
+       if (rdata->other == NULL)
+           rdata->other = getlist ();
+       kv = getnode ();
+       kv->type = rcsbuf_valcmp (&rcsbuf) ? RCSCMPFLD : RCSFIELD;
+       kv->key = xstrdup (key);
+       kv->data = rcsbuf_valcopy (&rcsbuf, value, kv->type == RCSFIELD, NULL);
+       if (addnode (rdata->other, kv) != 0)
+       {
+           error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
+                  key, rcsfile);
+           freenode (kv);
+       }
+
+       /* if we haven't grabbed it yet, we didn't want it */
+    }
+
+    /* We got out of the loop, so we have the first part of the first
+       revision delta in KEY (the revision) and VALUE (the date key
+       and its value).  This is what getdelta expects to receive.  */
+
+    while ((vnode = getdelta (&rcsbuf, rcsfile, &key, &value)) != NULL)
+    {
+       /* get the node */
+       q = getnode ();
+       q->type = RCSVERS;
+       q->delproc = rcsvers_delproc;
+       q->data = vnode;
+       q->key = vnode->version;
+
+       /* add the nodes to the list */
+       if (addnode (rdata->versions, q) != 0)
+       {
+#if 0
+               purify_printf("WARNING: Adding duplicate version: %s (%s)\n",
+                        q->key, rcsfile);
+               freenode (q);
+#endif
+       }
+    }
+
+    /* Here KEY and VALUE are whatever caused getdelta to return NULL.  */
+
+    if (STREQ (key, RCSDESC))
+    {
+       if (rdata->desc != NULL)
+       {
+           error (0, 0,
+                  "warning: duplicate key `%s' in RCS file `%s'",
+                  key, rcsfile);
+           free (rdata->desc);
+       }
+       rdata->desc = rcsbuf_valcopy (&rcsbuf, value, 1, NULL);
+    }
+
+    rdata->delta_pos = rcsbuf_ftello (&rcsbuf);
+
+    if (pfp == NULL)
+       rcsbuf_cache (rdata, &rcsbuf);
+    else
+    {
+       *pfp = fp;
+       *rcsbufp = rcsbuf;
+    }
+    rdata->flags &= ~PARTIAL;
+}
+
+
+
+/* Move RCS into or out of the Attic, depending on TOATTIC.  If the
+   file is already in the desired place, return without doing
+   anything.  At some point may want to think about how this relates
+   to RCS_rewrite but that is a bit hairy (if one wants renames to be
+   atomic, or that kind of thing).  If there is an error, print a message
+   and return 1.  On success, return 0.  */
+int
+RCS_setattic (RCSNode *rcs, int toattic)
+{
+    char *newpath;
+    const char *p;
+    char *q;
+
+    /* Some systems aren't going to let us rename an open file.  */
+    rcsbuf_cache_close ();
+
+    /* Could make the pathname computations in this file, and probably
+       in other parts of rcs.c too, easier if the REPOS and FILE
+       arguments to RCS_parse got stashed in the RCSNode.  */
+
+    if (toattic)
+    {
+       mode_t omask;
+
+       if (rcs->flags & INATTIC)
+           return 0;
+
+       /* Example: rcs->path is "/foo/bar/baz,v".  */
+       newpath = xmalloc (strlen (rcs->path) + sizeof CVSATTIC + 5);
+       p = last_component (rcs->path);
+       strncpy (newpath, rcs->path, p - rcs->path);
+       strcpy (newpath + (p - rcs->path), CVSATTIC);
+
+       /* Create the Attic directory if it doesn't exist.  */
+       omask = umask (cvsumask);
+       if (CVS_MKDIR (newpath, 0777) < 0 && errno != EEXIST)
+           error (0, errno, "cannot make directory %s", newpath);
+       (void) umask (omask);
+
+       strcat (newpath, "/");
+       strcat (newpath, p);
+
+       if (CVS_RENAME (rcs->path, newpath) < 0)
+       {
+           int save_errno = errno;
+
+           /* The checks for isreadable look awfully fishy, but
+              I'm going to leave them here for now until I
+              can think harder about whether they take care of
+              some cases which should be handled somehow.  */
+
+           if (isreadable (rcs->path) || !isreadable (newpath))
+           {
+               error (0, save_errno, "cannot rename %s to %s",
+                      rcs->path, newpath);
+               free (newpath);
+               return 1;
+           }
+       }
+    }
+    else
+    {
+       if (!(rcs->flags & INATTIC))
+           return 0;
+
+       newpath = xmalloc (strlen (rcs->path));
+
+       /* Example: rcs->path is "/foo/bar/Attic/baz,v".  */
+       p = last_component (rcs->path);
+       strncpy (newpath, rcs->path, p - rcs->path - 1);
+       newpath[p - rcs->path - 1] = '\0';
+       q = newpath + (p - rcs->path - 1) - (sizeof CVSATTIC - 1);
+       assert (strncmp (q, CVSATTIC, sizeof CVSATTIC - 1) == 0);
+       strcpy (q, p);
+
+       if (CVS_RENAME (rcs->path, newpath) < 0)
+       {
+           error (0, errno, "failed to move `%s' out of the attic",
+                  rcs->path);
+           free (newpath);
+           return 1;
+       }
+    }
+
+    free (rcs->path);
+    rcs->path = newpath;
+
+    return 0;
+}
+
+
+
+/*
+ * Fully parse the RCS file.  Store all keyword/value pairs, fetch the
+ * log messages for each revision, and fetch add and delete counts for
+ * each revision (we could fetch the entire text for each revision,
+ * but the only caller, log_fileproc, doesn't need that information,
+ * so we don't waste the memory required to store it).  The add and
+ * delete counts are stored on the OTHER field of the RCSVERSNODE
+ * structure, under the names ";add" and ";delete", so that we don't
+ * waste the memory space of extra fields in RCSVERSNODE for code
+ * which doesn't need this information.
+ */
+void
+RCS_fully_parse (RCSNode *rcs)
+{
+    FILE *fp;
+    struct rcsbuffer rcsbuf;
+
+    RCS_reparsercsfile (rcs, &fp, &rcsbuf);
+
+    while (1)
+    {
+       char *key, *value;
+       Node *vers;
+       RCSVers *vnode;
+
+       /* Rather than try to keep track of how much information we
+           have read, just read to the end of the file.  */
+       if (!rcsbuf_getrevnum (&rcsbuf, &key))
+           break;
+
+       vers = findnode (rcs->versions, key);
+       if (vers == NULL)
+           error (1, 0,
+                  "mismatch in rcs file %s between deltas and deltatexts (%s)",
+                  rcs->print_path, key);
+
+       vnode = vers->data;
+
+       while (rcsbuf_getkey (&rcsbuf, &key, &value))
+       {
+           if (!STREQ (key, "text"))
+           {
+               Node *kv;
+
+               if (vnode->other == NULL)
+                   vnode->other = getlist ();
+               kv = getnode ();
+               kv->type = rcsbuf_valcmp (&rcsbuf) ? RCSCMPFLD : RCSFIELD;
+               kv->key = xstrdup (key);
+               kv->data = rcsbuf_valcopy (&rcsbuf, value, kv->type == RCSFIELD,
+                                          NULL);
+               if (addnode (vnode->other, kv) != 0)
+               {
+                   error (0, 0,
+                          "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+                          key, vnode->version, rcs->print_path);
+                   freenode (kv);
+               }
+
+               continue;
+           }
+
+           if (!STREQ (vnode->version, rcs->head))
+           {
+               unsigned long add, del;
+               char buf[50];
+               Node *kv;
+
+               /* This is a change text.  Store the add and delete
+                   counts.  */
+               add = 0;
+               del = 0;
+               if (value != NULL)
+               {
+                   size_t vallen;
+                   const char *cp;
+
+                   rcsbuf_valpolish (&rcsbuf, value, 0, &vallen);
+                   cp = value;
+                   while (cp < value + vallen)
+                   {
+                       char op;
+                       unsigned long count;
+
+                       op = *cp++;
+                       if (op != 'a' && op  != 'd')
+                           error (1, 0, "\
+unrecognized operation '\\x%x' in %s",
+                                  op, rcs->print_path);
+                       (void) strtoul (cp, (char **) &cp, 10);
+                       if (*cp++ != ' ')
+                           error (1, 0, "space expected in %s revision %s",
+                                  rcs->print_path, vnode->version);
+                       count = strtoul (cp, (char **) &cp, 10);
+                       if (*cp++ != '\012')
+                           error (1, 0, "linefeed expected in %s revision %s",
+                                  rcs->print_path, vnode->version);
+
+                       if (op == 'd')
+                           del += count;
+                       else
+                       {
+                           add += count;
+                           while (count != 0)
+                           {
+                               if (*cp == '\012')
+                                   --count;
+                               else if (cp == value + vallen)
+                               {
+                                   if (count != 1)
+                                       error (1, 0, "\
+premature end of value in %s revision %s",
+                                              rcs->print_path, vnode->version);
+                                   else
+                                       break;
+                               }
+                               ++cp;
+                           }
+                       }
+                   }
+               }
+
+               sprintf (buf, "%lu", add);
+               kv = getnode ();
+               kv->type = RCSFIELD;
+               kv->key = xstrdup (";add");
+               kv->data = xstrdup (buf);
+               if (addnode (vnode->other, kv) != 0)
+               {
+                   error (0, 0,
+                          "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+                          key, vnode->version, rcs->print_path);
+                   freenode (kv);
+               }
+
+               sprintf (buf, "%lu", del);
+               kv = getnode ();
+               kv->type = RCSFIELD;
+               kv->key = xstrdup (";delete");
+               kv->data = xstrdup (buf);
+               if (addnode (vnode->other, kv) != 0)
+               {
+                   error (0, 0,
+                          "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+                          key, vnode->version, rcs->print_path);
+                   freenode (kv);
+               }
+           }
+
+           /* We have found the "text" key which ends the data for
+               this revision.  Break out of the loop and go on to the
+               next revision.  */
+           break;
+       }
+    }
+
+    rcsbuf_cache (rcs, &rcsbuf);
+}
+
+
+
+/*
+ * freercsnode - free up the info for an RCSNode
+ */
+void
+freercsnode (RCSNode **rnodep)
+{
+    if (rnodep == NULL || *rnodep == NULL)
+       return;
+
+    ((*rnodep)->refcount)--;
+    if ((*rnodep)->refcount != 0)
+    {
+       *rnodep = NULL;
+       return;
+    }
+    free ((*rnodep)->path);
+    free ((*rnodep)->print_path);
+    if ((*rnodep)->head != NULL)
+       free ((*rnodep)->head);
+    if ((*rnodep)->branch != NULL)
+       free ((*rnodep)->branch);
+    free_rcsnode_contents (*rnodep);
+    free (*rnodep);
+    *rnodep = NULL;
+}
+
+
+
+/*
+ * free_rcsnode_contents - free up the contents of an RCSNode without
+ * freeing the node itself, or the file name, or the head, or the
+ * path.  This returns the RCSNode to the state it is in immediately
+ * after a call to RCS_parse.
+ */
+static void
+free_rcsnode_contents (RCSNode *rnode)
+{
+    dellist (&rnode->versions);
+    if (rnode->symbols != NULL)
+       dellist (&rnode->symbols);
+    if (rnode->symbols_data != NULL)
+       free (rnode->symbols_data);
+    if (rnode->expand != NULL)
+       free (rnode->expand);
+    if (rnode->other != NULL)
+       dellist (&rnode->other);
+    if (rnode->access != NULL)
+       free (rnode->access);
+    if (rnode->locks_data != NULL)
+       free (rnode->locks_data);
+    if (rnode->locks != NULL)
+       dellist (&rnode->locks);
+    if (rnode->comment != NULL)
+       free (rnode->comment);
+    if (rnode->desc != NULL)
+       free (rnode->desc);
+}
+
+
+
+/* free_rcsvers_contents -- free up the contents of an RCSVers node,
+   but also free the pointer to the node itself. */
+/* Note: The `hardlinks' list is *not* freed, since it is merely a
+   pointer into the `hardlist' structure (defined in hardlink.c), and
+   that structure is freed elsewhere in the program. */
+static void
+free_rcsvers_contents (RCSVers *rnode)
+{
+    if (rnode->branches != NULL)
+       dellist (&rnode->branches);
+    if (rnode->date != NULL)
+       free (rnode->date);
+    if (rnode->next != NULL)
+       free (rnode->next);
+    if (rnode->author != NULL)
+       free (rnode->author);
+    if (rnode->state != NULL)
+       free (rnode->state);
+    if (rnode->other != NULL)
+       dellist (&rnode->other);
+    if (rnode->other_delta != NULL)
+       dellist (&rnode->other_delta);
+    if (rnode->text != NULL)
+       freedeltatext (rnode->text);
+    free (rnode);
+}
+
+
+
+/*
+ * rcsvers_delproc - free up an RCSVers type node
+ */
+static void
+rcsvers_delproc (Node *p)
+{
+    free_rcsvers_contents (p->data);
+}
+
+
+
+/* These functions retrieve keys and values from an RCS file using a
+   buffer.  We use this somewhat complex approach because it turns out
+   that for many common operations, CVS spends most of its time
+   reading keys, so it's worth doing some fairly hairy optimization.  */
+
+/* The number of bytes we try to read each time we need more data.  */
+
+#define RCSBUF_BUFSIZE (8192)
+
+/* The buffer we use to store data.  This grows as needed.  */
+
+static char *rcsbuf_buffer = NULL;
+static size_t rcsbuf_buffer_size = 0;
+
+/* Whether rcsbuf_buffer is in use.  This is used as a sanity check.  */
+
+static int rcsbuf_inuse;
+
+/* Set up to start gathering keys and values from an RCS file.  This
+   initializes RCSBUF.  */
+
+static void
+rcsbuf_open (struct rcsbuffer *rcsbuf, FILE *fp, const char *filename,
+            long unsigned int pos)
+{
+    if (rcsbuf_inuse)
+       error (1, 0, "rcsbuf_open: internal error");
+    rcsbuf_inuse = 1;
+
+#ifdef HAVE_MMAP
+    {
+       /* When we have mmap, it is much more efficient to let the system do the
+        * buffering and caching for us
+        */
+       struct stat fs;
+       size_t mmap_off = 0;
+
+       if ( fstat (fileno(fp), &fs) < 0 )
+           error ( 1, errno, "Could not stat RCS archive %s for mapping", 
filename );
+
+       if (pos)
+       {
+           size_t ps = getpagesize ();
+           mmap_off = ( pos / ps ) * ps;
+       }
+
+       /* Map private here since this particular buffer is read only */
+       rcsbuf_buffer = mmap ( NULL, fs.st_size - mmap_off,
+                               PROT_READ | PROT_WRITE,
+                               MAP_PRIVATE, fileno(fp), mmap_off );
+       if ( rcsbuf_buffer == NULL || rcsbuf_buffer == MAP_FAILED )
+           error ( 1, errno, "Could not map memory to RCS archive %s", 
filename );
+
+       rcsbuf_buffer_size = fs.st_size - mmap_off;
+       rcsbuf->ptr = rcsbuf_buffer + pos - mmap_off;
+       rcsbuf->ptrend = rcsbuf_buffer + fs.st_size - mmap_off;
+       rcsbuf->pos = mmap_off;
+    }
+#else /* !HAVE_MMAP */
+    if (rcsbuf_buffer_size < RCSBUF_BUFSIZE)
+       expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, RCSBUF_BUFSIZE);
+
+    rcsbuf->ptr = rcsbuf_buffer;
+    rcsbuf->ptrend = rcsbuf_buffer;
+    rcsbuf->pos = pos;
+#endif /* HAVE_MMAP */
+    rcsbuf->fp = fp;
+    rcsbuf->filename = filename;
+    rcsbuf->vlen = 0;
+    rcsbuf->at_string = 0;
+    rcsbuf->embedded_at = 0;
+}
+
+
+
+/* Stop gathering keys from an RCS file.  */
+static void
+rcsbuf_close (struct rcsbuffer *rcsbuf)
+{
+    if (! rcsbuf_inuse)
+       error (1, 0, "rcsbuf_close: internal error");
+#ifdef HAVE_MMAP
+    munmap ( rcsbuf_buffer, rcsbuf_buffer_size );
+#endif
+    rcsbuf_inuse = 0;
+}
+
+
+
+/* Read a key/value pair from an RCS file.  This sets *KEYP to point
+   to the key, and *VALUEP to point to the value.  A missing or empty
+   value is indicated by setting *VALUEP to NULL.
+
+   This function returns 1 on success, or 0 on EOF.  If there is an
+   error reading the file, or an EOF in an unexpected location, it
+   gives a fatal error.
+
+   This sets *KEYP and *VALUEP to point to storage managed by
+   rcsbuf_getkey.  Moreover, *VALUEP has not been massaged from the
+   RCS format: it may contain embedded whitespace and embedded '@'
+   characters.  Call rcsbuf_valcopy or rcsbuf_valpolish to do
+   appropriate massaging.  */
+
+/* Note that the extreme hair in rcsbuf_getkey is because profiling
+   statistics show that it was worth it. */
+static int
+rcsbuf_getkey (struct rcsbuffer *rcsbuf, char **keyp, char **valp)
+{
+    register const char * const my_spacetab = spacetab;
+    register char *ptr, *ptrend;
+    char c;
+
+#define my_whitespace(c)       (my_spacetab[(unsigned char)c] != 0)
+
+    rcsbuf->vlen = 0;
+    rcsbuf->at_string = 0;
+    rcsbuf->embedded_at = 0;
+
+    ptr = rcsbuf->ptr;
+    ptrend = rcsbuf->ptrend;
+
+    /* Sanity check.  */
+    assert (ptr >= rcsbuf_buffer && ptr <= rcsbuf_buffer + rcsbuf_buffer_size);
+    assert (ptrend >= rcsbuf_buffer && ptrend <= rcsbuf_buffer + 
rcsbuf_buffer_size);
+
+#ifndef HAVE_MMAP
+    /* If the pointer is more than RCSBUF_BUFSIZE bytes into the
+       buffer, move back to the start of the buffer.  This keeps the
+       buffer from growing indefinitely.  */
+    if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE)
+    {
+       int len;
+
+       len = ptrend - ptr;
+
+       /* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes
+           at a time, so we can't have more bytes than that past PTR.  */
+       assert (len <= RCSBUF_BUFSIZE);
+
+       /* Update the POS field, which holds the file offset of the
+           first byte in the RCSBUF_BUFFER buffer.  */
+       rcsbuf->pos += ptr - rcsbuf_buffer;
+
+       memcpy (rcsbuf_buffer, ptr, len);
+       ptr = rcsbuf_buffer;
+       ptrend = ptr + len;
+       rcsbuf->ptrend = ptrend;
+    }
+#endif /* HAVE_MMAP */
+
+    /* Skip leading whitespace.  */
+
+    while (1)
+    {
+       if (ptr >= ptrend)
+       {
+           ptr = rcsbuf_fill (rcsbuf, ptr, NULL, NULL);
+           if (ptr == NULL)
+               return 0;
+           ptrend = rcsbuf->ptrend;
+       }
+
+       c = *ptr;
+       if (! my_whitespace (c))
+           break;
+
+       ++ptr;
+    }
+
+    /* We've found the start of the key.  */
+
+    *keyp = ptr;
+
+    if (c != ';')
+    {
+       while (1)
+       {
+           ++ptr;
+           if (ptr >= ptrend)
+           {
+               ptr = rcsbuf_fill (rcsbuf, ptr, keyp, NULL);
+               if (ptr == NULL)
+                   error (1, 0, "EOF in key in RCS file %s",
+                          primary_root_inverse_translate (rcsbuf->filename));
+               ptrend = rcsbuf->ptrend;
+           }
+           c = *ptr;
+           if (c == ';' || my_whitespace (c))
+               break;
+       }
+    }
+
+    /* Here *KEYP points to the key in the buffer, C is the character
+       we found at the of the key, and PTR points to the location in
+       the buffer where we found C.  We must set *PTR to \0 in order
+       to terminate the key.  If the key ended with ';', then there is
+       no value.  */
+
+    *ptr = '\0';
+    ++ptr;
+
+    if (c == ';')
+    {
+       *valp = NULL;
+       rcsbuf->ptr = ptr;
+       return 1;
+    }
+
+    /* C must be whitespace.  Skip whitespace between the key and the
+       value.  If we find ';' now, there is no value.  */
+
+    while (1)
+    {
+       if (ptr >= ptrend)
+       {
+           ptr = rcsbuf_fill (rcsbuf, ptr, keyp, NULL);
+           if (ptr == NULL)
+               error (1, 0, "EOF while looking for value in RCS file %s",
+                      primary_root_inverse_translate (rcsbuf->filename));
+           ptrend = rcsbuf->ptrend;
+       }
+       c = *ptr;
+       if (c == ';')
+       {
+           *valp = NULL;
+           rcsbuf->ptr = ptr + 1;
+           return 1;
+       }
+       if (! my_whitespace (c))
+           break;
+       ++ptr;
+    }
+
+    /* Now PTR points to the start of the value, and C is the first
+       character of the value.  */
+
+    if (c != '@')
+       *valp = ptr;
+    else
+    {
+       char *pat;
+       size_t vlen;
+
+       /* Optimize the common case of a value composed of a single
+          '@' string.  */
+
+       rcsbuf->at_string = 1;
+
+       ++ptr;
+
+       *valp = ptr;
+
+       while (1)
+       {
+           while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
+           {
+               /* Note that we pass PTREND as the PTR value to
+                   rcsbuf_fill, so that we will wind up setting PTR to
+                   the location corresponding to the old PTREND, so
+                   that we don't search the same bytes again.  */
+               ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+               if (ptr == NULL)
+                   error (1, 0,
+                          "EOF while looking for end of string in RCS file %s",
+                          primary_root_inverse_translate (rcsbuf->filename));
+               ptrend = rcsbuf->ptrend;
+           }
+
+           /* Handle the special case of an '@' right at the end of
+               the known bytes.  */
+           if (pat + 1 >= ptrend)
+           {
+               /* Note that we pass PAT, not PTR, here.  */
+               pat = rcsbuf_fill (rcsbuf, pat, keyp, valp);
+               if (pat == NULL)
+               {
+                   /* EOF here is OK; it just means that the last
+                      character of the file was an '@' terminating a
+                      value for a key type which does not require a
+                      trailing ';'.  */
+                   pat = rcsbuf->ptrend - 1;
+
+               }
+               ptrend = rcsbuf->ptrend;
+
+               /* Note that the value of PTR is bogus here.  This is
+                  OK, because we don't use it.  */
+           }
+
+           if (pat + 1 >= ptrend || pat[1] != '@')
+               break;
+
+           /* We found an '@' pair in the string.  Keep looking.  */
+           ++rcsbuf->embedded_at;
+           ptr = pat + 2;
+       }
+
+       /* Here PAT points to the final '@' in the string.  */
+
+       *pat = '\0';
+
+       vlen = pat - *valp;
+       if (vlen == 0)
+           *valp = NULL;
+       rcsbuf->vlen = vlen;
+
+       ptr = pat + 1;
+    }
+
+    /* Certain keywords only have a '@' string.  If there is no '@'
+       string, then the old getrcskey function assumed that they had
+       no value, and we do the same.  */
+
+    {
+       char *k;
+
+       k = *keyp;
+       if (STREQ (k, RCSDESC)
+           || STREQ (k, "text")
+           || STREQ (k, "log"))
+       {
+           if (c != '@')
+               *valp = NULL;
+           rcsbuf->ptr = ptr;
+           return 1;
+       }
+    }
+
+    /* If we've already gathered a '@' string, try to skip whitespace
+       and find a ';'.  */
+    if (c == '@')
+    {
+       while (1)
+       {
+           char n;
+
+           if (ptr >= ptrend)
+           {
+               ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
+               if (ptr == NULL)
+                   error (1, 0, "EOF in value in RCS file %s",
+                          primary_root_inverse_translate (rcsbuf->filename));
+               ptrend = rcsbuf->ptrend;
+           }
+           n = *ptr;
+           if (n == ';')
+           {
+               /* We're done.  We already set everything up for this
+                   case above.  */
+               rcsbuf->ptr = ptr + 1;
+               return 1;
+           }
+           if (! my_whitespace (n))
+               break;
+           ++ptr;
+       }
+
+       /* The value extends past the '@' string.  We need to undo the
+           '@' stripping done in the default case above.  This
+           case never happens in a plain RCS file, but it can happen
+           if user defined phrases are used.  */
+       ((*valp)--)[rcsbuf->vlen++] = '@';
+    }
+
+    /* Here we have a value which is not a simple '@' string.  We need
+       to gather up everything until the next ';', including any '@'
+       strings.  *VALP points to the start of the value.  If
+       RCSBUF->VLEN is not zero, then we have already read an '@'
+       string, and PTR points to the data following the '@' string.
+       Otherwise, PTR points to the start of the value.  */
+
+    while (1)
+    {
+       char *start, *psemi, *pat;
+
+       /* Find the ';' which must end the value.  */
+       start = ptr;
+       while ((psemi = memchr (ptr, ';', ptrend - ptr)) == NULL)
+       {
+           int slen;
+
+           /* Note that we pass PTREND as the PTR value to
+              rcsbuf_fill, so that we will wind up setting PTR to the
+              location corresponding to the old PTREND, so that we
+              don't search the same bytes again.  */
+           slen = start - *valp;
+           ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+           if (ptr == NULL)
+               error (1, 0, "EOF in value in RCS file %s",
+                      primary_root_inverse_translate (rcsbuf->filename));
+           start = *valp + slen;
+           ptrend = rcsbuf->ptrend;
+       }
+
+       /* See if there are any '@' strings in the value.  */
+       pat = memchr (start, '@', psemi - start);
+
+       if (pat == NULL)
+       {
+           size_t vlen;
+
+           /* We're done with the value.  Trim any trailing
+               whitespace.  */
+
+           rcsbuf->ptr = psemi + 1;
+
+           start = *valp;
+           while (psemi > start && my_whitespace (psemi[-1]))
+               --psemi;
+           *psemi = '\0';
+
+           vlen = psemi - start;
+           if (vlen == 0)
+               *valp = NULL;
+           rcsbuf->vlen = vlen;
+
+           return 1;
+       }
+
+       /* We found an '@' string in the value.  We set RCSBUF->AT_STRING
+          and RCSBUF->EMBEDDED_AT to indicate that we won't be able to
+          compress whitespace correctly for this type of value.
+          Since this type of value never arises in a normal RCS file,
+          this should not be a big deal.  It means that if anybody
+          adds a phrase which can have both an '@' string and regular
+          text, they will have to handle whitespace compression
+          themselves.  */
+
+       rcsbuf->at_string = 1;
+       rcsbuf->embedded_at = -1;
+
+       ptr = pat + 1;
+
+       while (1)
+       {
+           while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
+           {
+               /* Note that we pass PTREND as the PTR value to
+                   rcsbuff_fill, so that we will wind up setting PTR
+                   to the location corresponding to the old PTREND, so
+                   that we don't search the same bytes again.  */
+               ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+               if (ptr == NULL)
+                   error (1, 0,
+                          "EOF while looking for end of string in RCS file %s",
+                          primary_root_inverse_translate (rcsbuf->filename));
+               ptrend = rcsbuf->ptrend;
+           }
+
+           /* Handle the special case of an '@' right at the end of
+               the known bytes.  */
+           if (pat + 1 >= ptrend)
+           {
+               ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
+               if (ptr == NULL)
+                   error (1, 0, "EOF in value in RCS file %s",
+                          primary_root_inverse_translate (rcsbuf->filename));
+               ptrend = rcsbuf->ptrend;
+           }
+
+           if (pat[1] != '@')
+               break;
+
+           /* We found an '@' pair in the string.  Keep looking.  */
+           ptr = pat + 2;
+       }
+
+       /* Here PAT points to the final '@' in the string.  */
+       ptr = pat + 1;
+    }
+
+#undef my_whitespace
+}
+
+
+
+/* Read an RCS revision number from an RCS file.  This sets *REVP to
+   point to the revision number; it will point to space that is
+   managed by the rcsbuf functions, and is only good until the next
+   call to rcsbuf_getkey or rcsbuf_getrevnum.
+
+   This function returns 1 on success, or 0 on EOF.  If there is an
+   error reading the file, or an EOF in an unexpected location, it
+   gives a fatal error.  */
+static int
+rcsbuf_getrevnum (struct rcsbuffer *rcsbuf, char **revp)
+{
+    char *ptr, *ptrend;
+    char c;
+
+    ptr = rcsbuf->ptr;
+    ptrend = rcsbuf->ptrend;
+
+    *revp = NULL;
+
+    /* Skip leading whitespace.  */
+
+    while (1)
+    {
+       if (ptr >= ptrend)
+       {
+           ptr = rcsbuf_fill (rcsbuf, ptr, NULL, NULL);
+           if (ptr == NULL)
+               return 0;
+           ptrend = rcsbuf->ptrend;
+       }
+
+       c = *ptr;
+       if (! whitespace (c))
+           break;
+
+       ++ptr;
+    }
+
+    if (! isdigit ((unsigned char) c) && c != '.')
+       error (1, 0,
+              "\
+unexpected '\\x%x' reading revision number in RCS file %s",
+              c, primary_root_inverse_translate (rcsbuf->filename));
+
+    *revp = ptr;
+
+    do
+    {
+       ++ptr;
+       if (ptr >= ptrend)
+       {
+           ptr = rcsbuf_fill (rcsbuf, ptr, revp, NULL);
+           if (ptr == NULL)
+               error (1, 0,
+                      "unexpected EOF reading revision number in RCS file %s",
+                      primary_root_inverse_translate (rcsbuf->filename));
+           ptrend = rcsbuf->ptrend;
+       }
+
+       c = *ptr;
+    }
+    while (isdigit ((unsigned char) c) || c == '.');
+
+    if (! whitespace (c))
+       error (1, 0, "\
+unexpected '\\x%x' reading revision number in RCS file %s",
+              c, primary_root_inverse_translate (rcsbuf->filename));
+
+    *ptr = '\0';
+
+    rcsbuf->ptr = ptr + 1;
+
+    return 1;
+}
+
+
+
+/* Fill RCSBUF_BUFFER with bytes from the file associated with RCSBUF,
+   updating PTR and the PTREND field.  If KEYP and *KEYP are not NULL,
+   then *KEYP points into the buffer, and must be adjusted if the
+   buffer is changed.  Likewise for VALP.  Returns the new value of
+   PTR, or NULL on error.  */
+static char *
+rcsbuf_fill (struct rcsbuffer *rcsbuf, char *ptr, char **keyp, char **valp)
+{
+#ifdef HAVE_MMAP
+    return NULL;
+#else /* HAVE_MMAP */
+    int got;
+
+    if (rcsbuf->ptrend - rcsbuf_buffer + RCSBUF_BUFSIZE > rcsbuf_buffer_size)
+    {
+       int poff, peoff, koff, voff;
+
+       poff = ptr - rcsbuf_buffer;
+       peoff = rcsbuf->ptrend - rcsbuf_buffer;
+       koff = keyp == NULL ? 0 : *keyp - rcsbuf_buffer;
+       voff = valp == NULL ? 0 : *valp - rcsbuf_buffer;
+
+       expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size,
+                      rcsbuf_buffer_size + RCSBUF_BUFSIZE);
+
+       ptr = rcsbuf_buffer + poff;
+       rcsbuf->ptrend = rcsbuf_buffer + peoff;
+       if (keyp != NULL)
+           *keyp = rcsbuf_buffer + koff;
+       if (valp != NULL)
+           *valp = rcsbuf_buffer + voff;
+    }
+
+    got = fread (rcsbuf->ptrend, 1, RCSBUF_BUFSIZE, rcsbuf->fp);
+    if (got == 0)
+    {
+       if (ferror (rcsbuf->fp))
+           error (1, errno, "cannot read %s", rcsbuf->filename);
+       return NULL;
+    }
+
+    rcsbuf->ptrend += got;
+
+    return ptr;
+#endif /* HAVE_MMAP */
+}
+
+
+
+/* Test whether the last value returned by rcsbuf_getkey is a composite
+   value or not. */
+static int
+rcsbuf_valcmp (struct rcsbuffer *rcsbuf)
+{
+    return rcsbuf->at_string && rcsbuf->embedded_at < 0;
+}
+
+
+
+/* Copy the value VAL returned by rcsbuf_getkey into a memory buffer,
+   returning the memory buffer.  Polish the value like
+   rcsbuf_valpolish, q.v.  */
+static char *
+rcsbuf_valcopy (struct rcsbuffer *rcsbuf, char *val, int polish, size_t *lenp)
+{
+    size_t vlen;
+    int embedded_at;
+    char *ret;
+
+    if (val == NULL)
+    {
+       if (lenp != NULL)
+           *lenp = 0;
+       return NULL;
+    }
+
+    vlen = rcsbuf->vlen;
+    embedded_at = rcsbuf->embedded_at < 0 ? 0 : rcsbuf->embedded_at;
+
+    ret = xmalloc (vlen - embedded_at + 1);
+
+    if (rcsbuf->at_string ? embedded_at == 0 : ! polish)
+    {
+       /* No special action to take.  */
+       memcpy (ret, val, vlen + 1);
+       if (lenp != NULL)
+           *lenp = vlen;
+       return ret;
+    }
+
+    rcsbuf_valpolish_internal (rcsbuf, ret, val, lenp);
+    return ret;
+}
+
+
+
+/* Polish the value VAL returned by rcsbuf_getkey.  The POLISH
+   parameter is non-zero if multiple embedded whitespace characters
+   should be compressed into a single whitespace character.  Note that
+   leading and trailing whitespace was already removed by
+   rcsbuf_getkey.  Within an '@' string, pairs of '@' characters are
+   compressed into a single '@' character regardless of the value of
+   POLISH.  If LENP is not NULL, set *LENP to the length of the value.  */
+static void
+rcsbuf_valpolish (struct rcsbuffer *rcsbuf, char *val, int polish,
+                 size_t *lenp)
+{
+    if (val == NULL)
+    {
+       if (lenp != NULL)
+           *lenp= 0;
+       return;
+    }
+
+    if (rcsbuf->at_string ? rcsbuf->embedded_at == 0 : ! polish)
+    {
+       /* No special action to take.  */
+       if (lenp != NULL)
+           *lenp = rcsbuf->vlen;
+       return;
+    }
+
+    rcsbuf_valpolish_internal (rcsbuf, val, val, lenp);
+}
+
+
+
+/* Internal polishing routine, called from rcsbuf_valcopy and
+   rcsbuf_valpolish.  */
+static void
+rcsbuf_valpolish_internal (struct rcsbuffer *rcsbuf, char *to,
+                          const char *from, size_t *lenp)
+{
+    size_t len;
+
+    len = rcsbuf->vlen;
+
+    if (! rcsbuf->at_string)
+    {
+       char *orig_to;
+       size_t clen;
+
+       orig_to = to;
+
+       for (clen = len; clen > 0; ++from, --clen)
+       {
+           char c;
+
+           c = *from;
+           if (whitespace (c))
+           {
+               /* Note that we know that clen can not drop to zero
+                   while we have whitespace, because we know there is
+                   no trailing whitespace.  */
+               while (whitespace (from[1]))
+               {
+                   ++from;
+                   --clen;
+               }
+               c = ' ';
+           }
+           *to++ = c;
+       }
+
+       *to = '\0';
+
+       if (lenp != NULL)
+           *lenp = to - orig_to;
+    }
+    else
+    {
+       const char *orig_from;
+       char *orig_to;
+       int embedded_at;
+       size_t clen;
+
+       orig_from = from;
+       orig_to = to;
+
+       embedded_at = rcsbuf->embedded_at;
+       assert (embedded_at > 0);
+
+       if (lenp != NULL)
+           *lenp = len - embedded_at;
+
+       for (clen = len; clen > 0; ++from, --clen)
+       {
+           char c;
+
+           c = *from;
+           *to++ = c;
+           if (c == '@')
+           {
+               ++from;
+
+               /* Sanity check.
+                *
+                * FIXME: I restored this to an abort from an assert based on
+                * advice from Larry Jones that asserts should not be used to
+                * confirm the validity of an RCS file...  This leaves two
+                * issues here: 1) I am uncertain that the fact that we will
+                * only find double '@'s hasn't already been confirmed; and:
+                * 2) If this is the proper place to spot the error in the RCS
+                * file, then we should print a much clearer error here for the
+                * user!!!!!!!
+                *
+                *      - DRP
+                */
+               if (*from != '@' || clen == 0)
+                   abort ();
+
+               --clen;
+
+               --embedded_at;
+               if (embedded_at == 0)
+               {
+                   /* We've found all the embedded '@' characters.
+                       We can just memcpy the rest of the buffer after
+                       this '@' character.  */
+                   if (orig_to != orig_from)
+                       memcpy (to, from + 1, clen - 1);
+                   else
+                       memmove (to, from + 1, clen - 1);
+                   from += clen;
+                   to += clen - 1;
+                   break;
+               }
+           }
+       }
+
+       /* Sanity check.  */
+       assert (from == orig_from + len
+           && to == orig_to + (len - rcsbuf->embedded_at));
+
+       *to = '\0';
+    }
+}
+
+
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+
+/* Copy the next word from the value VALP returned by rcsbuf_getkey into a
+   memory buffer, updating VALP and returning the memory buffer.  Return
+   NULL when there are no more words. */
+
+static char *
+rcsbuf_valword (struct rcsbuffer *rcsbuf, char **valp)
+{
+    register const char * const my_spacetab = spacetab;
+    register char *ptr, *pat;
+    char c;
+
+# define my_whitespace(c)      (my_spacetab[(unsigned char)c] != 0)
+
+    if (*valp == NULL)
+       return NULL;
+
+    for (ptr = *valp; my_whitespace (*ptr); ++ptr) ;
+    if (*ptr == '\0')
+    {
+       assert (ptr - *valp == rcsbuf->vlen);
+       *valp = NULL;
+       rcsbuf->vlen = 0;
+       return NULL;
+    }
+
+    /* PTR now points to the start of a value.  Find out whether it is
+       a num, an id, a string or a colon. */
+    c = *ptr;
+    if (c == ':')
+    {
+       rcsbuf->vlen -= ++ptr - *valp;
+       *valp = ptr;
+       return xstrdup (":");
+    }
+
+    if (c == '@')
+    {
+       int embedded_at = 0;
+       size_t vlen;
+
+       pat = ++ptr;
+       while ((pat = strchr (pat, '@')) != NULL)
+       {
+           if (pat[1] != '@')
+               break;
+           ++embedded_at;
+           pat += 2;
+       }
+
+       /* Here PAT points to the final '@' in the string.  */
+       *pat++ = '\0';
+       assert (rcsbuf->at_string);
+       vlen = rcsbuf->vlen - (pat - *valp);
+       rcsbuf->vlen = pat - ptr - 1;
+       rcsbuf->embedded_at = embedded_at;
+       ptr = rcsbuf_valcopy (rcsbuf, ptr, 0, NULL);
+       *valp = pat;
+       rcsbuf->vlen = vlen;
+       if (strchr (pat, '@') == NULL)
+           rcsbuf->at_string = 0;
+       else
+           rcsbuf->embedded_at = -1;
+       return ptr;
+    }
+
+    /* *PTR is neither `:', `;' nor `@', so it should be the start of a num
+       or an id.  Make sure it is not another special character. */
+    if (c == '$' || c == '.' || c == ',')
+       error (1, 0, "invalid special character in RCS field in %s",
+              primary_root_inverse_translate (rcsbuf->filename));
+
+    pat = ptr;
+    while (1)
+    {
+       /* Legitimate ID characters are digits, dots and any `graphic
+           printing character that is not a special.' This test ought
+          to do the trick. */
+       c = *++pat;
+       if (!isprint ((unsigned char) c) ||
+           c == ';' || c == '$' || c == ',' || c == '@' || c == ':')
+           break;
+    }
+
+    /* PAT points to the last non-id character in this word, and C is
+       the character in its memory cell.  Check to make sure that it
+       is a legitimate word delimiter -- whitespace or end. */
+    if (c != '\0' && !my_whitespace (c))
+       error (1, 0, "invalid special character in RCS field in %s",
+              primary_root_inverse_translate (rcsbuf->filename));
+
+    *pat = '\0';
+    rcsbuf->vlen -= pat - *valp;
+    *valp = pat;
+    return xstrdup (ptr);
+
+# undef my_whitespace
+}
+
+#endif /* PRESERVE_PERMISSIONS_SUPPORT */
+
+
+
+/* Return the current position of an rcsbuf.  */
+static off_t
+rcsbuf_ftello (struct rcsbuffer *rcsbuf)
+{
+    return rcsbuf->pos + rcsbuf->ptr - rcsbuf_buffer;
+}
+
+
+
+/* Return a pointer to any data buffered for RCSBUF, along with the
+   length.  */
+static void
+rcsbuf_get_buffered (struct rcsbuffer *rcsbuf, char **datap, size_t *lenp)
+{
+    *datap = rcsbuf->ptr;
+    *lenp = rcsbuf->ptrend - rcsbuf->ptr;
+}
+
+
+
+/* CVS optimizes by quickly reading some header information from a
+   file.  If it decides it needs to do more with the file, it reopens
+   it.  We speed that up here by maintaining a cache of a single open
+   file, to save the time it takes to reopen the file in the common
+   case.  */
+static RCSNode *cached_rcs;
+static struct rcsbuffer cached_rcsbuf;
+
+/* Cache RCS and RCSBUF.  This takes responsibility for closing
+   RCSBUF->FP.  */
+static void
+rcsbuf_cache (RCSNode *rcs, struct rcsbuffer *rcsbuf)
+{
+    if (cached_rcs != NULL)
+       rcsbuf_cache_close ();
+    cached_rcs = rcs;
+    ++rcs->refcount;
+    cached_rcsbuf = *rcsbuf;
+}
+
+
+
+/* If there is anything in the cache, close it.  */
+static void
+rcsbuf_cache_close (void)
+{
+    if (cached_rcs != NULL)
+    {
+       rcsbuf_close (&cached_rcsbuf);
+       if (fclose (cached_rcsbuf.fp) != 0)
+           error (0, errno, "cannot close %s", cached_rcsbuf.filename);
+       freercsnode (&cached_rcs);
+       cached_rcs = NULL;
+    }
+}
+
+
+
+/* Open an rcsbuffer for RCS, getting it from the cache if possible.
+   Set *FPP to the file, and *RCSBUFP to the rcsbuf.  The file should
+   be put at position POS.  */
+static void
+rcsbuf_cache_open (RCSNode *rcs, off_t pos, FILE **pfp,
+                  struct rcsbuffer *prcsbuf)
+{
+#ifndef HAVE_MMAP
+    if (cached_rcs == rcs)
+    {
+       if (rcsbuf_ftello (&cached_rcsbuf) != pos)
+       {
+           if (fseeko (cached_rcsbuf.fp, pos, SEEK_SET) != 0)
+               error (1, 0, "cannot fseeko RCS file %s",
+                      cached_rcsbuf.filename);
+           cached_rcsbuf.ptr = rcsbuf_buffer;
+           cached_rcsbuf.ptrend = rcsbuf_buffer;
+           cached_rcsbuf.pos = pos;
+       }
+       *pfp = cached_rcsbuf.fp;
+
+       /* When RCS_parse opens a file using fopen_case, it frees the
+           filename which we cached in CACHED_RCSBUF and stores a new
+           file name in RCS->PATH.  We avoid problems here by always
+           copying the filename over.  FIXME: This is hackish.  */
+       cached_rcsbuf.filename = rcs->path;
+
+       *prcsbuf = cached_rcsbuf;
+
+       cached_rcs = NULL;
+
+       /* Removing RCS from the cache removes a reference to it.  */
+       --rcs->refcount;
+       if (rcs->refcount <= 0)
+           error (1, 0, "rcsbuf_cache_open: internal error");
+    }
+    else
+    {
+#endif /* ifndef HAVE_MMAP */
+       /* FIXME:  If these routines can be rewritten to not write to the
+        * rcs file buffer, there would be a considerably larger memory savings
+        * from using mmap since the shared file would never need be copied to
+        * process memory.
+        *
+        * If this happens, cached mmapped buffers would be usable, but don't
+        * forget to make sure rcs->pos < pos here...
+        */
+       if (cached_rcs != NULL)
+           rcsbuf_cache_close ();
+
+       *pfp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
+       if (*pfp == NULL)
+           error (1, 0, "unable to reopen `%s'", rcs->path);
+#ifndef HAVE_MMAP
+       if (pos != 0)
+       {
+           if (fseeko (*pfp, pos, SEEK_SET) != 0)
+               error (1, 0, "cannot fseeko RCS file %s", rcs->path);
+       }
+#endif /* ifndef HAVE_MMAP */
+       rcsbuf_open (prcsbuf, *pfp, rcs->path, pos);
+#ifndef HAVE_MMAP
+    }
+#endif /* ifndef HAVE_MMAP */
+}
+
+
+
+/*
+ * process the symbols list of the rcs file
+ */
+static void
+do_symbols (List *list, char *val)
+{
+    Node *p;
+    char *cp = val;
+    char *tag, *rev;
+
+    assert (cp);
+
+    for (;;)
+    {
+       /* skip leading whitespace */
+       while (whitespace (*cp))
+           cp++;
+
+       /* if we got to the end, we are done */
+       if (*cp == '\0')
+           break;
+
+       /* split it up into tag and rev */
+       tag = cp;
+       cp = strchr (cp, ':');
+       *cp++ = '\0';
+       rev = cp;
+       while (!whitespace (*cp) && *cp != '\0')
+           cp++;
+       if (*cp != '\0')
+           *cp++ = '\0';
+
+       /* make a new node and add it to the list */
+       p = getnode ();
+       p->key = xstrdup (tag);
+       p->data = xstrdup (rev);
+       (void) addnode (list, p);
+    }
+}
+
+
+
+/*
+ * process the locks list of the rcs file
+ * Like do_symbols, but hash entries are keyed backwards: i.e.
+ * an entry like `user:rev' is keyed on REV rather than on USER.
+ */
+static void
+do_locks (List *list, char *val)
+{
+    Node *p;
+    char *cp = val;
+    char *user, *rev;
+
+    assert (cp);
+
+    for (;;)
+    {
+       /* skip leading whitespace */
+       while (whitespace (*cp))
+           cp++;
+
+       /* if we got to the end, we are done */
+       if (*cp == '\0')
+           break;
+
+       /* split it up into user and rev */
+       user = cp;
+       cp = strchr (cp, ':');
+       *cp++ = '\0';
+       rev = cp;
+       while (!whitespace (*cp) && *cp != '\0')
+           cp++;
+       if (*cp != '\0')
+           *cp++ = '\0';
+
+       /* make a new node and add it to the list */
+       p = getnode ();
+       p->key = xstrdup (rev);
+       p->data = xstrdup (user);
+       (void) addnode (list, p);
+    }
+}
+
+
+
+/*
+ * process the branches list of a revision delta
+ */
+static void
+do_branches (List *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);
+    }
+}
+
+
+
+/* Return true if TAG does not start with a number or
+ * does not entirely consist of numbers and dots.
+ *
+ * (N.N.N.N.prev is now a valid tag).
+ */
+static inline bool
+is_symbolic (const char *tag)
+{
+    assert (tag && *tag);
+    if (!isdigit (*tag)) return true;
+    for (tag++; *tag; tag++)
+       if (!(isdigit (*tag) || *tag == '.')) return true;
+    if (*--tag == '.') return true;
+    return false;
+}
+
+bool
+RCS_is_symbolic (const char *tag)
+{
+   return is_symbolic (tag);
+}
+
+bool
+RCS_is_relative (const char *tag)
+{
+    if (tag && *tag == '.')
+    {
+        int len;
+        char *next;
+        if (++tag)
+        {
+            next = strchr (tag, '.');
+            if (next)
+                len = next - tag;
+            else
+                len = strlen (tag);
+            if (!strncmp (tag, TAG_DOTHEAD, len)
+                  || !strncmp (tag, TAG_DOTBASE, len)
+                  || !strncmp (tag, TAG_PREVIOUS, len)
+                  || !strncmp (tag, TAG_NEXT, len)
+                  || !strncmp (tag, TAG_ROOT, len)
+                  || !strncmp (tag, TAG_ORIGIN, len))
+                return true;
+        }
+    }
+    return false;
+}
+
+/*
+ * 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 (RCSNode *rcs, const char *tag, const char *date,
+                int force_tag_match, int *simple_tag)
+{
+    if (simple_tag != NULL)
+       *simple_tag = 0;
+
+    /* make sure we have something to look at... */
+    assert (rcs != NULL);
+
+    if (tag && date)
+    {
+       char *branch, *rev;
+
+       if (! RCS_nodeisbranch (rcs, tag))
+       {
+           /* We can't get a particular date if the tag is not a
+              branch.  */
+           return NULL;
+       }
+
+       /* Work out the branch.  */
+       if (is_symbolic (tag))
+           branch = RCS_whatbranch (rcs, tag);
+       else
+           branch = xstrdup (tag);
+
+       int dots = numdots (branch);
+
+       /* Fetch the revision of branch as of date.  */
+       if (!dots)
+           rev = RCS_getdatetrunk (rcs, date, force_tag_match);
+       else 
+           rev = RCS_getdatebranch (rcs, date, branch);
+
+       free (branch);
+       return rev;
+    }
+    else if (tag)
+       return RCS_gettag (rcs, tag, force_tag_match, simple_tag);
+    else if (date)
+       return RCS_getdate (rcs, date, force_tag_match);
+    else
+       return RCS_head (rcs);
+
+}
+
+
+
+/*
+ * Get existing revision number corresponding to tag or revision.
+ * Similar to RCS_gettag but less interpretation imposed.
+ * For example:
+ * -- If tag designates a magic branch, RCS_tag2rev
+ *    returns the magic branch number.
+ * -- If tag is a branch tag, returns the branch number, not
+ *    the revision of the head of the branch.
+ * -- An exception is made for '.trunk' as it returns the
+ *    head revision of the trunk
+ * If tag or revision is not valid or does not exist in file,
+ * return NULL.
+ */
+char *
+RCS_tag2rev (RCSNode *rcs, char *tag)
+{
+    char *rev, *pa, *pb;
+    int i;
+
+    assert (rcs != NULL);
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    /* If a valid revision, try to look it up */
+    if ( RCS_valid_rev (tag) )
+    {
+       /* Make a copy so we can scribble on it */
+       rev =  xstrdup (tag);
+
+       /* If revision exists, return the copy */
+       if (RCS_exist_rev (rcs, tag))
+           return rev;
+
+       /* Nope, none such. If tag is not a branch we're done. */ 
+       i = numdots (rev);
+       if ((i & 1) == 1 )
+       {
+           pa = strrchr (rev, '.');
+           if (i == 1 || *(pa-1) != RCS_MAGIC_BRANCH || *(pa-2) != '.')
+           {
+               free (rev);
+               error (1, 0, "revision `%s' does not exist", tag);
+           }
+       }
+
+       /* Try for a real (that is, exists in the RCS deltas) branch
+          (RCS_exist_rev just checks for real revisions and revisions
+          which have tags pointing to them).  */
+       pa = RCS_getbranch (rcs, rev, 1);
+       if (pa != NULL)
+       {
+           free (pa);
+           return rev;
+       }
+
+       /* Tag is branch, but does not exist, try corresponding 
+       * magic branch tag.
+       *
+       * FIXME: assumes all magic branches are of       
+       * form "n.n.n ... .0.n".  I'll fix if somebody can
+       * send me a method to get a magic branch tag with
+       * the 0 in some other position -- <address@hidden>
+       */ 
+       pa = strrchr (rev, '.');
+       if (!pa)
+           /* This might happen, for instance, if an RCS file only contained
+            * revisions 2.x and higher, and REV == "1".
+            */
+           error (1, 0, "revision `%s' does not exist", tag);
+
+       *pa++ = 0;
+       pb = Xasprintf ("%s.%d.%s", rev, RCS_MAGIC_BRANCH, pa);
+       free (rev);
+       rev = pb;
+       if (RCS_exist_rev (rcs, rev))
+           return rev;
+       error (1, 0, "revision `%s' does not exist", tag);
+    }
+
+    /* If tag is "HEAD", special case to get head RCS revision */
+    if (tag && STREQ (tag, TAG_HEAD))
+        return RCS_head (rcs);
+
+    /* If valid tag let RCS_extract_tag say yea or nay. */
+    char *tmp = RCS_extract_tag (tag, true);
+    if (tmp) free (tmp);
+
+    /* We need to preserve magic branch numbers here */
+    rev = translate_tag (rcs, tag, true);
+
+    /* Trust the caller to print warnings. */
+    return rev;
+}
+
+
+
+/*
+ * Find the revision for a specific tag.
+ * If force_tag_match is set, return NULL if an exact match is not
+ * possible otherwise return RCS_head ().  We are careful to look for
+ * and handle "magic" revisions specially.
+ * 
+ * If the matched tag is a branch tag, find the head of the branch.
+ * 
+ * Returns pointer to newly malloc'd string, or NULL.
+ */
+char *
+RCS_gettag (RCSNode *rcs, const char *symtag, int force_tag_match,
+            int *simple_tag)
+{
+    char *tag;
+
+    if (simple_tag != NULL)
+       *simple_tag = 0;
+
+    /* make sure we have something to look at... */
+    assert (rcs != NULL);
+
+    /* XXX this is probably not necessary, --jtc */
+    if (rcs->flags & PARTIAL) 
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    /* If symtag is "HEAD", special case to get head RCS revision */
+    if (symtag && STREQ (symtag, TAG_HEAD))
+#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 NULL;        /* head request for removed file */
+       else
+#endif
+           return RCS_head (rcs);
+
+    if (is_symbolic (symtag))
+    {
+       char *version;
+
+       /* Resolve to a numeric revision number */
+       version = translate_tag (rcs, symtag, false);
+       if (version)
+       {
+           int dots;
+           tag = version;
+
+           /*
+            * If this is a branch tag, 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 && !(dots & 1))
+           {
+               version = RCS_getbranch (rcs, tag, 1);
+               if (version != NULL)
+               {
+                   free (tag);
+                   return version;
+               }
+               *strrchr (tag, '.') = '\0';
+               return tag;
+           }
+       }
+       else
+       {
+           /* The tag wasn't there, so return the head or NULL */
+           if (force_tag_match)
+               return NULL;
+           else
+               return RCS_head (rcs);
+       }
+    }
+    else
+       tag = xstrdup (symtag);
+
+    /* tag is always allocated and numeric now.  */
+
+    /*
+     * numeric tag processing:
+     *         1) revision number - just return it
+     *         2) branch number   - find head of branch
+     */
+
+    /* strip trailing dots */
+    while (tag[strlen (tag) - 1] == '.')
+       tag[strlen (tag) - 1] = '\0';
+
+    if ((numdots (tag) & 1) == 0)
+    {
+       char *branch;
+
+       /* we have a branch tag, so we need to walk the branch */
+       branch = RCS_getbranch (rcs, tag, force_tag_match);
+       free (tag);
+       return branch;
+    }
+    else
+    {
+       Node *p;
+
+       /* we have a revision tag, so make sure it exists */
+       p = findnode (rcs->versions, tag);
+       if (p != NULL)
+       {
+           /* We have found a numeric revision for the revision tag.
+              To support expanding the RCS keyword Name, if
+              SIMPLE_TAG is not NULL, tell the the caller that this
+              is a simple tag which co will recognize.  FIXME: Are
+              there other cases in which we should set this?  In
+              particular, what if we expand RCS keywords internally
+              without calling co?  */
+           if (simple_tag != NULL)
+               *simple_tag = 1;
+           return tag;
+       }
+       else
+       {
+           /* The revision wasn't there, so return the head or NULL */
+           free (tag);
+           if (force_tag_match)
+               return NULL;
+           else
+               return RCS_head (rcs);
+       }
+    }
+}
+
+
+
+/*
+ * Return a "magic" revision as a virtual branch off of REV for the RCS file.
+ * A "magic" revision is one which is unique in the RCS file.  By unique, I
+ * mean we return a revision which:
+ *     - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH)
+ *     - has a revision component which is not an existing branch off REV
+ *     - has a revision component which is not an existing magic revision
+ *     - is an even-numbered revision, to avoid conflicts with vendor branches
+ * The first point is what makes it "magic".
+ *
+ * As an example, if we pass in 1.37 as REV, we will look for an existing
+ * branch called 1.37.2.  If it did not exist, we would look for an
+ * existing symbolic tag with a numeric part equal to 1.37.0.2.  If that
+ * didn't exist, then we know that the 1.37.2 branch can be reserved by
+ * creating a symbolic tag with 1.37.0.2 as the numeric part.
+ *
+ * This allows us to fork development with very little overhead -- just a
+ * symbolic tag is used in the RCS file.  When a commit is done, a physical
+ * branch is dynamically created to hold the new revision.
+ *
+ * Note: We assume that REV is an RCS revision and not a branch number.
+ */
+static char *check_rev;
+char *
+RCS_magicrev (RCSNode *rcs, char *rev)
+{
+    int rev_num;
+    char *xrev, *test_branch, *local_branch_num;
+
+    xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */
+    check_rev = xrev;
+
+    local_branch_num = getenv("CVS_LOCAL_BRANCH_NUM");
+    if (local_branch_num)
+    {
+      rev_num = atoi(local_branch_num);
+      if (rev_num < 2)
+       rev_num = 2;
+      else
+       rev_num &= ~1;
+    }
+    else
+      rev_num = 2;
+
+    /* only look at even numbered branches */
+    for ( ; ; 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 (Node *p, void *closure)
+{
+    if (STREQ (check_rev, p->data))
+       return 1;
+    else
+       return 0;
+}
+
+
+
+/*
+ * Given an RCSNode, returns non-zero if the specified revision number 
+ * or symbolic tag resolves to a "branch" within the rcs file.
+ *
+ * FIXME: this is the same as RCS_nodeisbranch except for the special 
+ *        case for handling a null rcsnode and numeric magic branches.
+ */
+int
+RCS_isbranch (RCSNode *rcs, const char *rev)
+{
+    /* numeric revisions are easy -- even number of dots is a branch
+     * Note that magic branch numbers are not checked
+     */
+    if (isdigit ((unsigned char) *rev))
+       return (numdots (rev) & 1) == 0;
+
+    /* assume a revision if you can't find the RCS info */
+    if (rcs == NULL)
+       return 0;
+
+    /* now, look for a match in the symbols list */
+    return RCS_nodeisbranch (rcs, rev);
+}
+
+
+
+/*
+ * Given an RCSNode, returns non-zero if the specified revision number
+ * or symbolic TAG resolves to a "branch" within the rcs file.  We do
+ * take into account any magic branches as well.
+ *
+ * NOTES
+ *   Resolves TAG when it is symbolic but does not verify existance of
+ *   revisions.
+ */
+bool
+RCS_nodeisbranch (RCSNode *rcs, const char *tag)
+{
+    int dots;
+    char *version;
+
+    assert (rcs);
+
+    /* numeric revisions are easy -- even number of dots is a branch
+     * Note that we need to care about magic branch numbers
+     */
+    if (!is_symbolic (tag))
+    {
+        dots = numdots (tag);
+        if (dots > 1)
+       {
+           char *magic;
+           char *p = strrchr (tag, '.') - 1;
+           while (*p != '.') --p;
+           magic = Xasprintf (".%d.", RCS_MAGIC_BRANCH);
+           if (!strncmp (p, magic, strlen (magic)))
+           {
+               free (magic);
+               return (dots & 1);
+           }
+           free (magic);
+       }
+        return !(dots & 1);
+    }
+
+    /* resolve to its numeric equivalent and remove magic */
+    version = translate_tag (rcs, tag, false);
+
+    if (!version) return false;
+
+    dots = numdots (version);
+    free (version);
+
+    if (!(dots & 1))
+       return true;
+
+    return false;
+}
+
+
+
+/* Returns a pointer to malloc'ed memory which contains the branch
+ * for the specified, and possibly symbolic, TAG.  Magic branches are handled
+ * correctly.
+ */
+char *
+RCS_whatbranch (RCSNode *rcs, const char *tag)
+{
+    char *version;
+    int dots;
+
+    assert (tag);
+
+    /* assume no branch if you can't find the RCS info */
+    if (!rcs) return NULL;
+
+    if (is_symbolic (tag))
+    {
+       /* now, look for a match in the symbols list */
+       version = translate_tag (rcs, tag, false);
+       if (!version) return NULL;
+       dots = numdots (version);
+    }
+    else
+    {
+       version = xstrdup (tag);
+       dots = numdots (version);
+
+       if (dots > 2 && (dots & 1))
+       {
+           /* See if it's magic; Convert into physical equivalent if so */
+           char *magic;
+           char *branch = strrchr (version, '.');
+           char *p = branch++ - 1;
+           while (*p != '.') --p;
+
+           /* see if we have .magic-branch. (".0.") */
+           magic = Xasprintf (".%d.", RCS_MAGIC_BRANCH);
+           if (!strncmp (p, magic, strlen (magic)))
+           {
+               /* It's magic! Construct the real branch */
+               free (magic);
+               *p = '\0';    /* turn it into a revision */
+               magic = Xasprintf ("%s.%s", version, branch);
+               free (version);
+               return magic;
+           }
+           free (magic);
+       }
+    }
+
+    if (!(dots & 1))
+       return version;
+
+    free (version);
+    return NULL;
+}
+
+
+
+/*
+ * Get the head of the specified branch.  If the branch does not exist,
+ * return NULL or RCS_head depending on force_tag_match.
+ * Returns NULL or a newly malloc'd string.
+ */
+char *
+RCS_getbranch (RCSNode *rcs, const 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, 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 = Xasprintf ("%s.", tag);
+       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 = 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 = p->data;
+    if (vn->branches == NULL)
+       return NULL;
+    xtag = Xasprintf ("%s.", tag);
+    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 = p->data;
+       nextvers = vn->next;
+    } while (nextvers != NULL);
+
+    /* we have the version in our hand, so go for it */
+    return xstrdup (vn->version);
+}
+
+
+
+/* Revision number string, R, must contain a `.'.
+ * R must be writable.  Replace the rightmost `.' in R with
+ * the NUL byte and return a pointer to that NUL byte.
+ */
+static inline char *
+truncate_revnum_in_place (char *r)
+{
+    char *dot = strrchr (r, '.');
+    assert (dot);
+    *dot = '\0';
+    return dot;
+}
+
+
+
+/* Returns the head of the branch which the possibly symbolic TAG is on.
+ * TAG can be a branch tag or non-branch tag; symbolic or numeric.
+ *
+ * Returns a newly malloc'd string.  Returns NULL if a symbolic name
+ * isn't found.
+ */
+char *
+RCS_branch_head (RCSNode *rcs, const char *tag)
+{
+    char *num;
+    char *retval;
+
+    assert (rcs && tag);
+
+    if (is_symbolic (tag))
+    {
+       num = translate_tag (rcs, tag, false);
+       if (!num) return NULL;
+    }
+    else
+       /* FIXME: Validate revnum?  */
+       num = xstrdup (tag);
+
+    if (!RCS_nodeisbranch (rcs, num))
+       /* Make NUM = NUM's branch.  */
+       truncate_revnum_in_place (num);
+
+    retval = RCS_getbranch (rcs, num, 1);
+    free (num);
+    return retval;
+}
+
+
+
+/* Get the branch point for a particular branch, that is the first
+   revision on that branch.  For example, RCS_getbranchpoint (rcs,
+   "1.3.2") will normally return "1.3.2.1".  TARGET may be either a
+   branch number or a revision number; if a revnum, find the
+   branchpoint of the branch to which TARGET belongs.
+
+   Return RCS_head if TARGET is on the trunk or if the root node could
+   not be found (this is sort of backwards from our behavior on a branch;
+   the rationale is that the return value is a revision from which you
+   can start walking the next fields and end up at TARGET).
+   Return NULL on error.  */
+static char *
+RCS_getbranchpoint (RCSNode *rcs, char *target)
+{
+    char *branch, *bp;
+    Node *vp;
+    RCSVers *rev;
+    int dots, isrevnum, brlen;
+
+    dots = numdots (target);
+    isrevnum = dots & 1;
+
+    if (dots == 1)
+       /* TARGET is a trunk revision; return rcs->head. */
+       return RCS_head (rcs);
+
+    /* Get the revision number of the node at which TARGET's branch is
+       rooted.  If TARGET is a branch number, lop off the last field;
+       if it's a revision number, lop off the last *two* fields. */
+    branch = xstrdup (target);
+    bp = strrchr (branch, '.');
+    if (bp == NULL)
+       error (1, 0, "%s: confused revision number %s",
+              rcs->print_path, target);
+    if (isrevnum)
+       while (*--bp != '.')
+           ;
+    *bp = '\0';
+
+    vp = findnode (rcs->versions, branch);
+    if (vp == NULL)
+    {  
+       error (0, 0, "%s: can't find branch point %s", rcs->print_path, target);
+       free (branch);
+       return NULL;
+    }
+    rev = vp->data;
+
+    *bp++ = '.';
+    while (*bp && *bp != '.')
+       ++bp;
+    brlen = bp - branch;
+
+    vp = rev->branches->list->next;
+    while (vp != rev->branches->list)
+    {
+       /* BRANCH may be a genuine branch number, e.g. `1.1.3', or
+          maybe a full revision number, e.g. `1.1.3.6'.  We have
+          found our branch point if the first BRANCHLEN characters
+          of the revision number match, *and* if the following
+          character is a dot. */
+       if (strncmp (vp->key, branch, brlen) == 0 && vp->key[brlen] == '.')
+           break;
+       vp = vp->next;
+    }
+
+    free (branch);
+    if (vp == rev->branches->list)
+    {
+       error (0, 0, "%s: can't find branch point %s", rcs->print_path, target);
+       return NULL;
+    }
+    else
+       return xstrdup (vp->key);
+}
+
+
+
+/*
+ * Get the head of the RCS file.  If branch is set, this is the head of the
+ * branch, otherwise the real head.
+ *
+ * INPUTS
+ *   rcs       The parsed rcs node information.
+ *
+ * RETURNS
+ *   NULL when rcs->branch exists and cannot be found or when there are no
+ *   revisions in the RCS file.
+ *
+ *   A newly malloc'd string, otherwise.
+ */
+char *
+RCS_head (RCSNode *rcs)
+{
+    char *retval;
+
+    /* make sure we have something to look at... */
+    assert (rcs);
+
+    /*
+     * 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);
+
+    retval = xstrdup (rcs->head);
+    if (!retval) return NULL;
+
+    if (!(numdots (retval) & 1))
+    {
+       error (0, 0, "Head revision is a branch in `%s'.", rcs->path);
+       free (retval);
+       return NULL;
+    }
+
+    return retval;
+}
+
+
+
+/*
+ * Get the most recent revision, based on the supplied date, but use some
+ * funky stuff and follow the vendor branch maybe
+ */
+char *
+RCS_getdate (RCSNode *rcs, const 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, NULL);
+
+    /* if the head is on a branch, try the branch first */
+    if (rcs->branch != NULL)
+    {
+       retval = RCS_getdatebranch (rcs, date, rcs->branch);
+       if (retval != NULL)
+           return retval;
+    }
+
+    /* otherwise if we have a trunk, use it */
+    return RCS_getdatetrunk (rcs, date, force_tag_match);
+}
+
+
+
+/*
+ * Look up the last element on the trunk that was put in before or on
+ * the specified date and time (return the rev or NULL)
+ * Follow the vendor branch if not found on the trunk
+ */
+static char *
+RCS_getdatetrunk (RCSNode *rcs, const char *date, int force_tag_match)
+{
+    Node *p = NULL;
+    RCSVers *vers = NULL;
+    char *retval = NULL;
+
+    assert(rcs);
+
+    if (rcs->flags & PARTIAL)
+        RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (rcs->head)
+    {
+       p = findnode (rcs->versions, rcs->head);
+       if (p == NULL)
+       {
+           error (0, 0, "%s: head revision %s doesn't exist", rcs->print_path,
+                  rcs->head);
+       }
+       while (p != NULL)
+       {
+           /* if the date of this one is before date, take it */
+           vers = p->data;
+           if (RCS_datecmp (vers->date, date) <= 0)
+           {
+                retval = vers->version;
+                break;
+           }
+
+           /* if there is a next version, find the node */
+           if (vers->next != NULL)
+               p = findnode (rcs->versions, vers->next);
+           else
+               p = NULL;
+       }
+    }
+    else
+       error (0, 0, "%s: no head revision", rcs->print_path);
+
+    /*
+     * at this point, either we have the revision we want, or we have the
+     * first revision on the trunk (1.1?) in our hands, or we've come up
+     * completely empty
+     */
+
+    /* if we found what we're looking for, and it's not 1.1 return it */
+    if (retval != NULL)
+    {
+       if (! STREQ (retval, "1.1"))
+           return xstrdup (retval);
+
+       /* This is 1.1;  if the date of 1.1 is not the same as that for the
+          1.1.1.1 version, then return 1.1.  This happens when the first
+          version of a file is created by a regular cvs add and commit,
+          and there is a subsequent cvs import of the same file.  */
+       p = findnode (rcs->versions, "1.1.1.1");
+       if (p)
+       {
+           char *date_1_1 = vers->date;
+
+           vers = p->data;
+           if (RCS_datecmp (vers->date, date_1_1) != 0)
+               return xstrdup ("1.1");
+       }
+    }
+
+    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 (vers && (!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 or on
+ * the specified date and time (return the rev or NULL)
+ */
+static char *
+RCS_getdatebranch (RCSNode *rcs, const char *date, const 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, NULL);
+
+    p = findnode (rcs->versions, xrev);
+    free (xrev);
+    if (p == NULL)
+       return NULL;
+    vers = p->data;
+
+    /* Tentatively use this revision, if it is early enough.  */
+    if (RCS_datecmp (vers->date, date) <= 0)
+       cur_rev = vers->version;
+
+    /* If no branches list, return now.  This is what happens if the branch
+       is a (magic) branch with no revisions yet.  */
+    if (vers->branches == NULL)
+       return xstrdup (cur_rev);
+
+    /* walk the branches list looking for the branch number */
+    xbranch = Xasprintf ("%s.", branch);
+    for (p = vers->branches->list->next; p != vers->branches->list; p = 
p->next)
+       if (strncmp (p->key, xbranch, strlen (xbranch)) == 0)
+           break;
+    free (xbranch);
+    if (p == vers->branches->list)
+    {
+       /* This is what happens if the branch is a (magic) branch with
+          no revisions yet.  Similar to the case where vers->branches ==
+          NULL, except here there was a another branch off the same
+          branchpoint.  */
+       return xstrdup (cur_rev);
+    }
+
+    p = findnode (rcs->versions, p->key);
+
+    /* walk the next pointers until you find the end, or the date is too late 
*/
+    while (p != NULL)
+    {
+       vers = 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 = NULL;
+    }
+
+    /* Return whatever we found, which may be NULL.  */
+    return xstrdup (cur_rev);
+}
+
+
+
+/*
+ * Compare two dates in RCS format. Beware the change in format on January 1,
+ * 2000, when years go from 2-digit to full format.
+ */
+int
+RCS_datecmp (const char *date1, const char *date2)
+{
+    int length_diff = strlen (date1) - strlen (date2);
+
+    return length_diff ? length_diff : strcmp (date1, date2);
+}
+
+
+
+/* Look up revision REV in RCS and return the date specified for the
+   revision minus FUDGE seconds (FUDGE will generally be one, so that the
+   logically previous revision will be found later, or zero, if we want
+   the exact date).
+
+   The return value is the date being returned as a time_t, or (time_t)-1
+   on error (previously was documented as zero on error; I haven't checked
+   the callers to make sure that they really check for (time_t)-1, but
+   the latter is what this function really returns).  If DATE is non-NULL,
+   then it must point to MAXDATELEN characters, and we store the same
+   return value there in DATEFORM format.  */
+time_t
+RCS_getrevtime (RCSNode *rcs, const char *rev, char *date, int fudge)
+{
+    char *tdate;
+    struct tm xtm, *ftm;
+    struct timespec revdate;
+    Node *p;
+    RCSVers *vers;
+
+    /* make sure we have something to look at... */
+    assert (rcs != NULL);
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    /* look up the revision */
+    p = findnode (rcs->versions, rev);
+    if (p == NULL)
+       return -1;
+    vers = p->data;
+
+    /* split up the date */
+    if (sscanf (vers->date, SDATEFORM, &xtm.tm_year, &xtm.tm_mon,
+               &xtm.tm_mday, &xtm.tm_hour, &xtm.tm_min, &xtm.tm_sec) != 6)
+       error (1, 0, "%s: invalid date for revision %s (%s)", rcs->print_path,
+              rev, vers->date);
+
+    /* 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 (xtm.tm_year >= 100 && xtm.tm_year < 2000)
+       error (0, 0, "%s: non-standard date format for revision %s (%s)",
+              rcs->print_path, rev, vers->date);
+    if (xtm.tm_year >= 1900)
+       xtm.tm_year -= 1900;
+
+    /* put the date in a form getdate can grok */
+    tdate = Xasprintf ("%d-%d-%d %d:%d:%d -0000",
+                      xtm.tm_year + 1900, xtm.tm_mon, xtm.tm_mday,
+                      xtm.tm_hour, xtm.tm_min, xtm.tm_sec);
+
+    /* Turn it into seconds since the epoch.
+     *
+     * We use a struct timespec since that is what getdate requires, then
+     * truncate the nanoseconds.
+     */
+    if (!get_date (&revdate, tdate, NULL))
+    {
+       free (tdate);
+       return (time_t)-1;
+    }
+    free (tdate);
+
+    revdate.tv_sec -= fudge;   /* remove "fudge" seconds */
+    if (date)
+    {
+       /* Put an appropriate string into `date', if we were given one. */
+       ftm = gmtime (&revdate.tv_sec);
+       (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.tv_sec;
+}
+
+
+
+List *
+RCS_getlocks (RCSNode *rcs)
+{
+    assert(rcs != NULL);
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (rcs->locks_data) {
+       rcs->locks = getlist ();
+       do_locks (rcs->locks, rcs->locks_data);
+       free(rcs->locks_data);
+       rcs->locks_data = NULL;
+    }
+
+    return rcs->locks;
+}
+
+
+
+List *
+RCS_symbols(RCSNode *rcs)
+{
+    assert(rcs != NULL);
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, 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;
+}
+
+
+
+/*
+ * Find the previous revision
+ *
+ * RETURN
+ *     Branch: return the HEAD-1 revision or NULL
+ *     Revision: return the previous revision or NULL
+ *
+ * ASSUMPTIONS
+ *   The tag is valid, it has been validated by RCS_check_tag
+ *   The Tag does not contain RCS_MAGIC_BRANCH
+ */
+static char *
+RCS_getprevious (RCSNode *rcs, const char *rev)
+{
+    char *retval = NULL;
+    int dots = numdots (rev);
+    char *trev = NULL;
+
+    if (!(dots & 1)) /* branch handling => turn into a head revision */
+    {
+        trev = RCS_branch_head (rcs, rev);
+       if (!trev)
+        {
+            /* branch not found,
+             * so only the tag exists ... take its revision
+             */
+            trev = xstrdup (rev);
+            truncate_revnum_in_place (trev);
+        }
+       dots = numdots (trev);
+    }
+    else
+        trev = xstrdup (rev);
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (dots > 1) /* revision on a branch */
+    {
+        /* find the root revision */
+        char *p1 = strrchr (trev, '.');
+       *p1 = '\0';
+       int len = strlen (trev);
+        char *p2 = strrchr (trev, '.');
+       *p2 = '\0';
+
+        RCSVers *vers = NULL;
+        Node *node = findnode (rcs->versions, trev);
+        if (node && (vers = node->data) && vers->branches)
+       {
+           *p1 = '.';
+           *p2 = '.';
+           char *rootdate = vers->date;
+           Node *head = vers->branches->list;
+           Node *br;
+           for (br = head->next; br != head; br = br->next)
+           {
+               if (node = findnode (rcs->versions, br->key))
+               {
+                   if (strncmp (((RCSVers *)node->data)->version, trev, len))
+                       continue;
+                      
+                   ++len;
+                   char *p = NULL;
+                   while (node != NULL)
+                   {
+                       vers = node->data;
+                       if (STREQ (vers->version + len, trev + len))
+                       {
+                           if (p)
+                               retval = xstrdup (p);
+                           else if (RCS_datecmp (vers->date, rootdate))
+                           {
+                               *p2 = '\0';
+                               retval = xstrdup (trev);
+                           }
+                           break;
+                       }
+
+                       /* if there is a next version, find the node */
+                       if (vers->next != NULL)
+                       {
+                           p = vers->version;
+                           node = findnode (rcs->versions, vers->next);
+                       }
+                       else
+                           node = NULL;
+                   }
+                   break;
+               }
+           }
+       }
+    }
+    else /* revision on trunk */
+    {
+        char *prev = NULL;
+       char *curdate = NULL;
+
+       assert (dots == 1);
+
+        if (!STREQ (trev, "1.1"))
+       {
+            Node *node = findnode (rcs->versions, trev);
+           if (node)
+           {
+               RCSVers *v = node->data;
+               curdate = v->date;
+               if (v->next && (node = findnode (rcs->versions, v->next)))
+               {
+                   free (trev);
+                   prev = ((RCSVers *)node->data)->version;
+                   trev = xstrdup (prev);
+               }
+           }
+        }
+
+        if (prev && STREQ (trev, "1.1"))
+        {
+            /* This is 1.1;  if the date of 1.1 is the same as that for
+            * the VENDOR.1 version, then return the latest VENDOR version with
+            * a timestamp before the 1.2 timestamp (point of merge). The
+            * date of 1.1. and VENDOR.1 differs if the first version of
+            * a file is created by a regular cvs add and commit, and there
+            * is a subsequent cvs import of the same file ==> return 1.1.
+            * If 1.1 is dead, the file was initially added on a branch
+            * ==> return NULL.
+            */
+           Node *rootnode = findnode (rcs->versions, trev);
+           if (rootnode)
+           {
+               RCSVers *vers = rootnode->data;
+               if (!vers->dead && vers->branches)
+               {
+                   char *date_1_1 = vers->date;//vers 1.1
+
+                   Node *head = vers->branches->list;
+                   Node *br;
+                   for (br = head->next; br != head; br = br->next)
+                   {
+                       Node *node = NULL;
+                       if (node = findnode (rcs->versions, br->key))
+                       {
+                           vers = node->data;
+                           if (!vers->dead
+                               && !RCS_datecmp (vers->date, date_1_1))
+                           {
+                               /* get head of branch */
+                               retval = vers->version;
+                               while (vers->next)
+                               {
+                                   node = findnode (rcs->versions,
+                                                    vers->next);
+                                   if (node)
+                                   {
+                                       vers = node->data;
+                                       if (RCS_datecmp (curdate,
+                                                        vers->date) > 0)
+                                       {
+                                           vers = node->data;
+                                           retval = vers->version;
+                                           continue;
+                                       }
+                                   }
+                                   break;
+                               }
+                               retval = xstrdup (retval);
+                           }
+                       }
+                   }
+               }
+           }
+        }
+       if (!retval && prev)
+           retval = xstrdup (prev);
+    }
+    free (trev);
+
+    return retval;
+}
+
+
+
+/*
+ * Find the next revision
+ *
+ * RETURN
+ *     Branch: NULL (There cannot be a revision)
+ *     Revision: returns the next revision or NULL
+ *
+ * ASSUMPTIONS
+ *   The tag is valid, it has been validated by RCS_check_tag
+ *   The Tag does not contain RCS_MAGIC_BRANCH
+ */
+static char *
+RCS_getnext (RCSNode *rcs, const char *rev)
+{
+    Node *node = NULL;
+    RCSVers *vers = NULL;
+    char *retval = NULL;
+    const char *cmp = rev;
+    int dots = numdots (rev);
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (dots > 1)
+    {
+        node = findnode (rcs->versions, rev);
+       if (!node)
+           return NULL;
+
+       vers = node->data;
+
+       /* if there is a next version, find the node */
+       if (vers->next != NULL)
+       {
+           node = findnode (rcs->versions, vers->next);
+           if (node)
+               return xstrdup (((RCSVers *)node->data)->version);
+       }
+    }
+    else if (rcs->head)
+    {
+       node = findnode (rcs->versions, rcs->head);
+       if (node)
+       {
+           RCSVers *next = NULL;
+           vers = NULL;
+           while (node != NULL)
+           {
+               vers = next;
+               next = node->data;
+               if (next && STREQ (next->version, cmp))
+               {
+                   if (vers)
+                       retval = xstrdup (vers->version);
+                   break;
+               }
+                  
+               /* if there is a next version, find the node */
+               if (next->next != NULL)
+                   node = findnode (rcs->versions, next->next);
+               else
+                   node = NULL;
+           }
+       }
+    }
+
+    return retval;
+}
+
+
+
+/*
+ * Find the origin revision, which is the first revision
+ * on either the trunk or a branch if added there.
+ *
+ * ASSUMPTIONS
+ *   The tag is valid, it has been validated by RCS_check_tag
+ *   The Tag does not contain RCS_MAGIC_BRANCH
+ */
+static char *
+RCS_getorigin (RCSNode *rcs, const char *rev)
+{
+    char *trev;
+    int dots = numdots (rev);
+
+    if (!(dots & 1)) /* branch handling => turn into a head revision */
+    {
+        trev = RCS_branch_head (rcs, rev);
+       if (trev)
+       {
+           if (dots > (dots = numdots (trev)))
+           {
+               /* Got the root revision.  The first commit to the branch is
+                * defined as the origin, so return NULL.
+                */
+               free (trev);
+               return NULL;
+           }
+           /* Else, we have a head revision on the branch.  */
+       }
+       else
+           /* Error - branch not found...  */
+           return NULL;
+    }
+    else
+        trev = xstrdup (rev);
+
+    assert (dots == numdots (trev));
+
+    if (RCS_exist_rev (rcs, trev))
+    {
+       Node *rootnode = NULL;
+
+       while (dots > 1)
+       {
+          int len;
+          char *tmp = trev;
+          trev = xstrdup (tmp);
+
+          truncate_revnum_in_place (trev);
+          len = strlen (trev);
+          truncate_revnum_in_place (trev);
+
+          /* If a file was added on the trunk, and it is added on
+           * a branch in a second step, the '1.1.2.1' revision is
+           * dead, and timestamp of 1.1 and 1.1.2.1 are equal.
+           * Prevent returning this as root!
+           */
+          bool found = false;
+          rootnode = findnode (rcs->versions, trev);
+          if (rootnode)
+          {
+              RCSVers *vers = rootnode->data;
+              if (vers->branches)
+              {
+                  Node *head = vers->branches->list;
+                  Node *br;
+                  for (br = head->next; br != head; br = br->next)
+                  {
+                     /* check if br->key is on branch rev */
+                     if (!strncmp (tmp, br->key, len))
+                     {
+                         Node *bn = NULL;
+                         if (bn = findnode (rcs->versions, br->key))
+                         {
+                             RCSVers *cv = bn->data;
+                             if (cv->dead)
+                             {
+                                if (cv->next)
+                                {
+                                    free (trev);
+                                    if (STREQ (cv->version, rev))
+                                        trev = xstrdup (rev);
+                                    else
+                                    {
+                                        bn = findnode (rcs->versions,
+                                                       cv->next);
+                                        cv = bn->data;
+                                        trev = xstrdup (cv->version);
+                                    }
+                                    found = true;
+                                }
+                                else
+                                {
+                                    free (trev);
+                                    trev = NULL;
+                                }
+                             }
+                             else if (vers->dead)
+                             {
+                                /* root dead => stay on branch */
+                                free (trev);
+                                trev = xstrdup (cv->version);
+                                found = true;
+                             }
+                         }
+                     }
+                  }
+              }
+          }
+          if (found || !trev)
+              return trev;
+          else
+              dots = numdots (trev);
+          free (tmp);
+       }
+
+       RCSVers *vers;
+       char *prev = NULL;
+
+       if (!rootnode)
+           rootnode = findnode (rcs->versions, trev);
+       free (trev);
+
+       while (rootnode)
+       {
+           vers = rootnode->data;
+
+           /* if there is a next version, find the node */
+           if (vers->next != NULL)
+           {
+               prev = vers->version;
+               rootnode = findnode (rcs->versions, vers->next);
+           }
+           else if (vers->dead)
+               return xstrdup (prev);
+           else
+               return xstrdup (vers->version);
+       }
+    }
+    free (trev);
+    return NULL;
+}
+
+
+
+/*
+ * Find the branchpoint, no matter if rev points
+ * to a branch or a revision
+ *
+ * ASSUMPTIONS
+ *   The tag is valid, it has been validated by RCS_check_tag
+ *   The Tag does not contain RCS_MAGIC_BRANCH
+ */
+static char *
+RCS_getroot (RCSNode *rcs, const char *rev)
+{
+    char *retval = NULL;
+    RCSVers *vers = NULL;
+    int dots = numdots (rev);
+
+    if (dots > 1)
+    {
+        int len;
+       retval = xstrdup (rev);
+
+        if (dots & 1)
+       {
+           *strrchr (retval, '.') = '\0';
+           len = strlen (retval);
+           *strrchr (retval, '.') = '\0';
+       }
+       else
+       {
+           len = strlen (retval);
+           *strrchr (retval, '.') = '\0';
+       }
+
+       /* If a file was added on the trunk, and it is added on
+        * a branch in a second step, the '1.1.2.1' revision is
+        * dead, and timestamp of 1.1 and 1.1.2.1 are equal.
+        * Return 1.1.2.1 in this case!
+        */
+       if (rcs->flags & PARTIAL)
+           RCS_reparsercsfile (rcs, NULL, NULL);
+
+       Node *node = findnode(rcs->versions, retval);
+       if (node)
+       {
+           vers = node->data;
+           if (vers->branches)
+           {
+               char *rootdate = vers->date;
+               Node *head = vers->branches->list;
+               Node *br;
+               for (br = head->next; br != head; br = br->next)
+               {
+                    //check if br->key is on branch rev
+                    if (!strncmp (rev, br->key, len))
+                    {
+                        if (node = findnode (rcs->versions, br->key))
+                        {
+                           vers = node->data;
+                            if (vers->dead && !RCS_datecmp (rootdate, 
vers->date))
+                            {
+                               free (retval);
+                                if (STREQ (vers->version, rev))
+                                   return NULL;
+                                else
+                                   return xstrdup (vers->version);
+                            }
+                        }
+                        return retval;
+                    }
+               }
+           }
+            else
+            {
+                /* Branch tag exists, but no revisions are on this branch
+                 * Return the revision, it already is the root
+                 */
+                return retval;
+            }
+       }
+       free (retval);
+    }
+    return NULL;
+}
+
+
+
+/*
+ * Find the commitid, if prev is true return the
+ * previous revision
+ * If a revisions was initially added on a branch, there will
+ * be two revision with identical commitid. In this case,
+ * take the one that's not dead.
+ */
+static char *
+RCS_getcommitid (RCSNode *rcs, const char *commitid, bool prev)
+{
+    Node *p;
+    RCSVers *oldvers = NULL;
+    char *result = NULL;
+    char *cmp = xstrdup (commitid);
+
+    if (rcs->flags & PARTIAL)
+        RCS_reparsercsfile (rcs, NULL, NULL);
+
+    Node *head = rcs->versions->list;
+    for (p = head->next; p != head; p = p->next)
+    {
+        RCSVers *vers = (RCSVers *)p->data;
+
+       Node *info = findnode (vers->other_delta, "commitid");
+       if (info != NULL)
+           if (!strcmp (info->data, cmp))
+           {
+               RCSVers *next_vers = NULL;
+                bool maybe_duplicate = false;
+               if (!prev)
+                {
+                    if (result)
+                        free (result);
+                   result = xstrdup (vers->version);
+                    maybe_duplicate = vers->dead;
+                }
+                else
+                {
+                    if (p->next)
+                    {
+                        if (numdots (vers->version) == 1)
+                            next_vers = (RCSVers *)p->next->data;
+                    }
+                    else
+                        next_vers = oldvers;
+                    if (next_vers &&
+                          (RCS_datecmp (next_vers->date, vers->date) < 0))
+                    {
+                        result = xstrdup (next_vers->version);
+                        if (next_vers->dead)
+                        {
+                            info = findnode (next_vers->other_delta, 
"commitid");
+                            if (info)
+                            {
+                                free (cmp);
+                                cmp = xstrdup (info->data);
+                                maybe_duplicate = true;
+                                prev = false;
+                            }
+                        }
+                    }
+                    else
+                        result = RCS_getprevious (rcs, vers->version);
+                }
+                if (!maybe_duplicate)
+                    break;
+           }
+       oldvers = vers;
+    }
+    free (cmp);
+    return result;
+}
+
+
+
+/* Translate special/symbolic tags into their revision thus:
+ *
+ *   - If TAG starts numeric, rely on its formating up to the first
+ *     non-numberic-tag character ([^0-9.]) to resolve a base revision.
+ *   - Else, resolve everything before the first `.' as a symbolic tag to
+ *     determine the base tag. Convert magic branch numbers into their
+ *     physical equivalent.
+ *   - Resolve any remaining tag extensions (.trunk, .head, ...).
+ *
+ * RETURNS
+ *   NULL on error.
+ *   A newly malloc'd string containing the resolved revision, otherwise.
+ *
+ * ASSUMPTIONS
+ *   The Tag is valid, it has been validated by RCS_check_tag
+ */
+static char *
+translate_tag (RCSNode *rcs, const char *tag, bool keepmagic)
+{
+/*    printf("translate_tag: %s\n",tag); */
+    char c;
+    char *retval = NULL;
+    char *tmp, *tmpval;
+    bool dotstart = false;
+
+    assert (rcs && tag);
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (tag[0] == '@')
+    {
+        /* handle cvsnt compatibility stuff */
+        if (tag[1] == '<')
+           return RCS_getcommitid (rcs, tag + 2, true);
+       else
+           return RCS_getcommitid (rcs, tag + 1, false);
+    }
+
+    tmpval = xstrdup (tag);
+    if (isdigit ((unsigned char) *tmpval))
+    {
+        /* extract initial revision num */
+        char *p = tmpval;
+        do
+           ++p;
+       while (isdigit ((unsigned char) *p) || *p == '.');
+       tmp = p;
+       if (*p--) *p = '\0';
+       retval = xstrdup (tmpval);
+    }
+    else
+        tmp = tmpval;
+
+
+    char *token = strtok (tmp,".");
+    if (token)
+    {
+        bool force;
+       if (!keepmagic && retval && RCS_nodeisbranch (rcs, retval))
+       {
+           assert (*token); /* see => assumptions */
+           /* We have an initial numeric revision, check
+            * for magic rev numbers and convert to their
+            * physical equivalent if so.
+            */
+           char *p = RCS_whatbranch (rcs, retval);
+           free (retval);
+           retval = p;
+       }
+       do
+       {
+           assert (*token); /* see => assumptions */
+           force = false;
+           if (!retval)
+           {
+               /* there is no base revision and no initial numeric revision */
+               if (token == tmp)
+               {
+                   /* tag does not start with a dot */
+                    retval = translate_symtag (rcs, token);
+                   if (retval)
+                   {
+                       if (keepmagic && (token = strtok (NULL,".")))
+                       {
+                           /* The next token needs to be evaluated here because
+                            * we need to know whether magic revision numbers
+                            * need to be converted to their physical 
equivalent.
+                            * Set force flag to not lose this token
+                            */
+                           force = true;
+                           keepmagic = false;
+                       }
+                           
+                       if (!keepmagic && retval && RCS_nodeisbranch (rcs, 
retval))
+                       {
+                           /* convert magic rev numbers into their
+                            * physical equivalent
+                            */
+                           char *p = RCS_whatbranch (rcs, retval);
+                           free (retval);
+                           retval = p;
+                           if (!retval) force = false;
+                       }
+                   }
+               }
+               else if (STREQ (token, TAG_TRUNK))
+               {
+                   /* .trunk is a branch tag, but rcs->head is a revision.  */
+                   char *p;
+                   retval = RCS_head (rcs);
+
+                   if (retval)
+                   {
+                       /* A non-null return from RCS_head is guaranteed to not
+                        * specify a branch and to have at least one dot.
+                        */
+                       p = strrchr (retval, '.');
+                       *p = '\0';
+                   }
+               }
+               else if (STREQ (token, TAG_COMMITID))
+               {
+                   char *commitid = strtok (NULL,".");
+                   if (token = strtok (NULL,"."))
+                       /* The next token needs to be evaluated here because
+                        * RCS_getcommitid needs to know whether a previous
+                        * revision is requested (performance enhancement).
+                        * Set force flag to ensure the next token gets resolved
+                        * if it is not a '.prev' token.
+                        */
+                       force = true;
+                   if (commitid)
+                   {
+                       bool previous = (token && STREQ (token, TAG_PREVIOUS));
+                       retval = RCS_getcommitid (rcs, commitid, previous);
+                       if (previous || !retval)
+                           force = false;
+                   }
+               }
+               else if (!STREQ (token, TAG_DOTBASE))
+                   /* Does it make sense to output an error msg here?
+                    * Actualy this is more or less a help for debugging
+                    * since this only happens if the assumptions fail
+                    */
+                   error (1, 0, "Tag `%s': invalid head: `%s'", token, tag);
+
+               /* If no retval and no force flag => return NULL.
+                * Callers will generate the "TAG not found" message.
+                */
+           }
+           else
+            {
+               char *p = retval;
+               if (STREQ (token, TAG_DOTHEAD))
+                   retval = RCS_branch_head (rcs, p);
+               else if (STREQ (token, TAG_PREVIOUS))
+                   retval = RCS_getprevious (rcs, p);
+               else if (STREQ (token, TAG_ORIGIN))
+                   retval = RCS_getorigin (rcs, p);
+               else if (STREQ (token, TAG_ROOT))
+                   retval = RCS_getroot (rcs, p);
+               else if (STREQ (token, TAG_NEXT))
+                   retval = RCS_getnext (rcs, p);
+               else
+                   /* Does it make sense to output an error msg here?
+                    * Actualy this is more or less a help for debugging
+                    * since this only happens if the assumptions fail
+                    */
+                   error (1, 0,
+                          "Tag `%s': invalid extension: `%s'", token, tag);
+               free (p);
+           }
+       }
+       while (force || (retval && (token = strtok (NULL, "."))) );
+    }
+    free (tmpval);
+/*     printf("translate_tag: '%s' => '%s'\n",tag,retval); */
+    return retval;
+}
+
+
+
+/*
+ * Return the version associated with a particular symbolic tag.
+ * Returns NULL or a newly malloc'd string.
+ */
+static char *
+translate_symtag (RCSNode *rcs, const char *tag)
+{
+    if (rcs->symbols != NULL)
+    {
+       Node *p;
+
+       /* The symbols have already been converted into a list.  */
+       p = findnode (rcs->symbols, tag);
+       if (p == NULL)
+           return NULL;
+
+       return xstrdup (p->data);
+    }
+
+    if (rcs->symbols_data != NULL)
+    {
+       size_t len;
+       char *cp, *last;
+
+       /* Look through the RCS symbols information.  This is like
+           do_symbols, but we don't add the information to a list.  In
+           most cases, we will only be called once for this file, so
+           generating the list is unnecessary overhead.  */
+
+       len = strlen (tag);
+       cp = rcs->symbols_data;
+       /* Keeping track of LAST below isn't strictly necessary, now that tags
+        * should be parsed for validity before they are accepted, but tags
+        * with spaces used to cause the code below to loop indefintely, so
+        * I have corrected for that.  Now, in the event that I missed
+        * something, the server cannot be hung.  -DRP
+        */
+       last = NULL;
+       while ((cp = strchr (cp, tag[0])) != NULL)
+       {
+           if (cp == last) break;
+           if ((cp == rcs->symbols_data || whitespace (cp[-1]))
+               && strncmp (cp, tag, len) == 0
+               && cp[len] == ':')
+           {
+               char *v, *r;
+
+               /* We found the tag.  Return the version number.  */
+
+               cp += len + 1;
+               v = cp;
+               while (! whitespace (*cp) && *cp != '\0')
+                   ++cp;
+               r = xmalloc (cp - v + 1);
+               strncpy (r, v, cp - v);
+               r[cp - v] = '\0';
+               return r;
+           }
+
+           while (! whitespace (*cp) && *cp != '\0')
+               ++cp;
+           if (*cp == '\0')
+               break;
+           last = cp;
+       }
+    }
+
+    return NULL;
+}
+
+
+
+/*
+ * The argument ARG is the getopt remainder of the -k option specified on the
+ * command line.  This function returns malloc'ed space that can be used
+ * directly in calls to RCS V5, with the -k flag munged correctly.
+ */
+char *
+RCS_check_kflag (const char *arg)
+{
+    static const char *const  keyword_usage[] =
+    {
+      "%s %s: invalid RCS keyword expansion mode\n",
+      "Valid expansion modes include:\n",
+      "   -kkv\tGenerate keywords using the default form.\n",
+      "   -kkvl\tLike -kkv, except locker's name inserted.\n",
+      "   -kk\tGenerate only keyword names in keyword strings.\n",
+      "   -kv\tGenerate only keyword values in keyword strings.\n",
+      "   -ko\tGenerate the old keyword string (no changes from checked in 
file).\n",
+      "   -kb\tGenerate binary file unmodified (merges not allowed) (RCS 
5.7).\n",
+      "(Specify the --help global option for a list of other help options)\n",
+      NULL,
+    };
+    char const *const *cpp = NULL;
+
+    if (arg)
+    {
+       for (cpp = kflags; *cpp != NULL; cpp++)
+       {
+           if (STREQ (arg, *cpp))
+               break;
+       }
+    }
+
+    if (arg == NULL || *cpp == NULL)
+    {
+       usage (keyword_usage);
+    }
+
+    return Xasprintf ("-k%s", *cpp);
+}
+
+
+
+/*
+ * 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 (const char *tag)
+{
+    char *invalid = "$,.:;@";          /* invalid RCS tag characters */
+    const char *cp;
+
+    /*
+     * The first character must be an alphabetic letter. The remaining
+     * characters cannot be non-visible graphic characters, and must not be
+     * in the set of "invalid" RCS identifier characters.
+     */
+    if (isalpha ((unsigned char) *tag))
+    {
+       for (cp = tag; *cp; cp++)
+       {
+           if (!isgraph ((unsigned char) *cp))
+               error (1, 0, "tag `%s' has non-visible graphic characters",
+                      tag);
+           if (strchr (invalid, *cp))
+               error (1, 0, "tag `%s' must not contain the characters `%s'",
+                      tag, invalid);
+       }
+    }
+    else
+       error (1, 0, "tag `%s' must start with a letter", tag);
+}
+
+
+
+/*
+ * Do some consistency checks ...
+ * If a symbolic tag is found, return a newly allocated string
+ * Allow only:
+ * SYMTAG | SYMTAG[.prev]* | .trunk[.prev]* | HEAD | BASE | x.x.x[.prev]*
+ *
+ * ERRORS
+ *   fatal errors are generated for illegal syntax
+ */
+char *
+RCS_extract_tag (const char *tag, bool files)
+{
+    char *retval = NULL;
+    const char *p = tag;
+    bool first = true;
+
+    assert(p);
+
+    if (*p == '@')
+    {
+        if (*(p+1) == '<')
+       {
+           retval = xstrdup (p+1);
+           *retval = '@';
+       }
+       else
+           retval = xstrdup (p);
+       return retval;
+    }
+
+    if (strstr (p,".."))
+        error (1, 0, "\
+Numeric tag `%s' invalid.  Numeric tags should be of the form X[.X]...", tag);
+
+    bool dot = false;
+    while (p)
+    {
+        if (*p == '.')
+           dot = true;
+        else if (!isdigit ((unsigned char) *p))
+           break;
+       else if (dot && first)
+           error (1, 0, "\
+Numeric tag `%s' invalid. Numeric tags should be of the form X[.X]...", tag);
+       else
+       {
+           dot = false;
+           first = false;
+       }
+       ++p;
+    }
+
+    if ( (*p && !first && !dot) || tag[strlen (tag)-1] == '.')
+        error (1, 0, "\
+Tag `%s' invalid. Combined tags should be of the form X[.X]...", tag);
+
+    bool deptag = false;
+    bool multi = true;
+    char *prev = NULL;
+    char *tmp = xstrdup (p);
+    char *token = strtok(tmp,".");
+    if (token)
+    {
+       do
+       {
+           if (first)
+           {
+               if (!dot)
+               {
+                   if (STREQ (token, TAG_HEAD)
+                       || STREQ (token, TAG_BASE))
+                   {
+                       deptag = true;
+                       first = false;
+                   }
+                   else if (!STREQ (token, TAG_TRUNK)
+                       && !STREQ (token, TAG_COMMITID)
+                       && !STREQ (token, TAG_PREVIOUS)
+                       && !STREQ (token, TAG_NEXT)
+                       && !STREQ (token, TAG_DOTHEAD)
+                       && !STREQ (token, TAG_DOTBASE)
+                       && !STREQ (token, TAG_ORIGIN)
+                       && !STREQ (token, TAG_ROOT))
+                   {
+                      RCS_check_tag (token);
+                      retval = xstrdup (token);
+                      first = false;
+                   }
+                   else
+                       error (1, 0,"\
+Tag `%s' invalid. Reserved expression without leading dot: `%s'", tag, token);
+               }
+               else if (STREQ (token, TAG_TRUNK))
+               {
+                   first = false;
+               }
+               else if (STREQ (token, TAG_DOTBASE))
+               {
+                   first = false;
+                   multi = false;
+               }
+               else if (STREQ (token, TAG_COMMITID))
+               {
+                   token = strtok (NULL,".");
+                   if (token)
+                   {
+                       retval = Xasprintf ("@%s",token);
+                       first = false;
+                   }
+               }
+               else if (!files)
+                   error (1, 0,"\
+Tag `%s' invalid. Tag must not be relative: `.%s'", tag, token);
+               else if (STREQ (token, TAG_ORIGIN)
+                        || STREQ (token, TAG_DOTHEAD))
+               {
+                  first = false;
+                  prev = token;
+               }
+               else if (STREQ (token, TAG_PREVIOUS)
+                        || STREQ (token, TAG_NEXT)
+                        || STREQ (token, TAG_ROOT))
+               {
+                  first = false;
+               }
+               else
+                   error (1, 0,"\
+Tag `%s' invalid. Cannot resolve head: `.%s'", tag, token);
+           }
+           else if (deptag)
+               error (1, 0,"\
+Tag `%s' invalid. Deprecated prefix before extension: `%s'", tag, token);
+           else if (!multi)
+               error (1, 0,"\
+Tag `%s' invalid. Extension not allowed: `%s'", tag, token);
+           else if (STREQ (token, TAG_ORIGIN)
+                    || STREQ (token, TAG_DOTHEAD))
+           {
+               if (!prev || !STREQ (prev, token))
+                   prev = token;
+               else
+                   error (1, 0,"\
+Tag `%s' invalid. Duplicate extension: `%s'", tag, token);
+
+           }
+           else if ((!STREQ (token, TAG_PREVIOUS))
+                     && (!STREQ (token, TAG_NEXT))
+                     && (!STREQ (token, TAG_ROOT)))
+               error (1, 0,"\
+Tag `%s' invalid. Cannot resolve extension: `%s'", tag, token);
+           else
+               prev = NULL;
+       }
+       while (!first && (token = strtok (NULL, ".")) );
+    }
+    free (tmp);
+
+    return retval;
+}
+
+
+
+/*
+ * TRUE if argument has valid syntax for an RCS revision or 
+ * branch number.  All characters must be digits or dots, first 
+ * and last characters must be digits, and no two consecutive 
+ * characters may be dots.
+ *
+ * Intended for classifying things, so this function doesn't 
+ * call error.
+ */
+int 
+RCS_valid_rev (const char *rev)
+{
+   char last, c;
+   last = *rev++;
+   if (!isdigit ((unsigned char) last))
+       return 0;
+   while ((c = *rev++))   /* Extra parens placate -Wall gcc option */
+   {
+       if (c == '.')
+       {
+           if (last == '.')
+               return 0;
+           continue;
+       }
+       last = c;
+       if (!isdigit ((unsigned char) c))
+           return 0;
+   }
+   if (!isdigit ((unsigned char) last))
+       return 0;
+   return 1;
+}
+
+
+
+/*
+ * Return true if RCS revision with TAG is a dead revision.
+ */
+int
+RCS_isdead (RCSNode *rcs, const char *tag)
+{
+    Node *p;
+    RCSVers *version;
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    p = findnode (rcs->versions, tag);
+    if (p == NULL)
+       return 0;
+
+    version = 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 (RCSNode *rcs)
+{
+    /* Since RCS_parsercsfile_i now reads expand, don't need to worry
+       about RCS_reparsercsfile.  */
+    assert (rcs != NULL);
+    return rcs->expand;
+}
+
+
+
+/* Set keyword expansion mode to EXPAND.  For example "b" for binary.  */
+void
+RCS_setexpand (RCSNode *rcs, const char *expand)
+{
+    /* Since RCS_parsercsfile_i now reads expand, don't need to worry
+       about RCS_reparsercsfile.  */
+    assert (rcs != NULL);
+    if (rcs->expand != NULL)
+       free (rcs->expand);
+    rcs->expand = xstrdup (expand);
+}
+
+
+
+/* RCS keywords, and a matching enum.  */
+enum keyword
+{
+    KEYWORD_AUTHOR = 0,
+    KEYWORD_DATE,
+    KEYWORD_CVSHEADER,
+    KEYWORD_HEADER,
+    KEYWORD_ID,
+    KEYWORD_LOCKER,
+    KEYWORD_LOG,
+    KEYWORD_NAME,
+    KEYWORD_RCSFILE,
+    KEYWORD_REVISION,
+    KEYWORD_SOURCE,
+    KEYWORD_STATE,
+    KEYWORD_LOCALID
+};
+struct rcs_keyword
+{
+    const char *string;
+    size_t len;
+    enum keyword expandto;
+    bool expandit;
+};
+
+
+
+static inline struct rcs_keyword *
+new_keywords (void)
+{
+    struct rcs_keyword *new;
+    new = xcalloc (KEYWORD_LOCALID + 2, sizeof (struct rcs_keyword));
+
+#define KEYWORD_INIT(k, i, s) \
+       k[i].string = s; \
+       k[i].len = sizeof s - 1; \
+       k[i].expandto = i; \
+       k[i].expandit = true
+
+    KEYWORD_INIT (new, KEYWORD_AUTHOR, "Author");
+    KEYWORD_INIT (new, KEYWORD_DATE, "Date");
+    KEYWORD_INIT (new, KEYWORD_CVSHEADER, "CVSHeader");
+    KEYWORD_INIT (new, KEYWORD_HEADER, "Header");
+    KEYWORD_INIT (new, KEYWORD_ID, "Id");
+    KEYWORD_INIT (new, KEYWORD_LOCKER, "Locker");
+    KEYWORD_INIT (new, KEYWORD_LOG, "Log");
+    KEYWORD_INIT (new, KEYWORD_NAME, "Name");
+    KEYWORD_INIT (new, KEYWORD_RCSFILE, "RCSfile");
+    KEYWORD_INIT (new, KEYWORD_REVISION, "Revision");
+    KEYWORD_INIT (new, KEYWORD_SOURCE, "Source");
+    KEYWORD_INIT (new, KEYWORD_STATE, "State");
+
+    return new;
+}
+
+
+
+void
+free_keywords (void *keywords)
+{
+    free (keywords);
+}
+
+
+
+/* Convert an RCS date string into a readable string.  This is like
+   the RCS date2str function.  */
+static char *
+printable_date (const char *rcs_date)
+{
+    int year, mon, mday, hour, min, sec;
+    char buf[100];
+
+    (void) sscanf (rcs_date, SDATEFORM, &year, &mon, &mday, &hour, &min,
+                  &sec);
+    if (year < 1900)
+       year += 1900;
+    sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
+            hour, min, sec);
+    return xstrdup (buf);
+}
+
+
+
+/* Escape the characters in a string so that it can be included in an
+   RCS value.  */
+static char *
+escape_keyword_value (const char *value, int *free_value)
+{
+    char *ret, *t;
+    const char *s;
+
+    for (s = value; *s != '\0'; s++)
+    {
+       char c;
+
+       c = *s;
+       if (c == '\t'
+           || c == '\n'
+           || c == '\\'
+           || c == ' '
+           || c == '$')
+       {
+           break;
+       }
+    }
+
+    if (*s == '\0')
+    {
+       *free_value = 0;
+       return (char *) value;
+    }
+
+    ret = xmalloc (strlen (value) * 4 + 1);
+    *free_value = 1;
+
+    for (s = value, t = ret; *s != '\0'; s++, t++)
+    {
+       switch (*s)
+       {
+       default:
+           *t = *s;
+           break;
+       case '\t':
+           *t++ = '\\';
+           *t = 't';
+           break;
+       case '\n':
+           *t++ = '\\';
+           *t = 'n';
+           break;
+       case '\\':
+           *t++ = '\\';
+           *t = '\\';
+           break;
+       case ' ':
+           *t++ = '\\';
+           *t++ = '0';
+           *t++ = '4';
+           *t = '0';
+           break;
+       case '$':
+           *t++ = '\\';
+           *t++ = '0';
+           *t++ = '4';
+           *t = '4';
+           break;
+       }
+    }
+
+    *t = '\0';
+
+    return ret;
+}
+
+
+
+/* Expand RCS keywords in the memory buffer BUF of length LEN.  This
+   applies to file RCS and version VERS.  If NAME is not NULL, and is
+   not a numeric revision, then it is the symbolic tag used for the
+   checkout.  EXPAND indicates how to expand the keywords.  This
+   function sets *RETBUF and *RETLEN to the new buffer and length.
+   This function may modify the buffer BUF.  If BUF != *RETBUF, then
+   RETBUF is a newly allocated buffer.  */
+static void
+expand_keywords (RCSNode *rcs, RCSVers *ver, const char *name, const char *log,
+                size_t loglen, enum kflag expand, char *buf, size_t len,
+                char **retbuf, size_t *retlen)
+{
+    struct expand_buffer
+    {
+       struct expand_buffer *next;
+       char *data;
+       size_t len;
+       int free_data;
+    } *ebufs = NULL;
+    struct expand_buffer *ebuf_last = NULL;
+    size_t ebuf_len = 0;
+    char *locker;
+    char *srch, *srch_next;
+    size_t srch_len;
+    const struct rcs_keyword *keywords;
+
+    if (!config /* For `cvs init', config may not be set.  */
+       ||expand == KFLAG_O || expand == KFLAG_B)
+    {
+       *retbuf = buf;
+       *retlen = len;
+       return;
+    }
+
+    if (!config->keywords) config->keywords = new_keywords ();
+    keywords = config->keywords;
+
+    /* If we are using -kkvl, dig out the locker information if any.  */
+    locker = NULL;
+    if (expand == KFLAG_KVL)
+    {
+       Node *lock;
+       lock = findnode (RCS_getlocks(rcs), ver->version);
+       if (lock != NULL)
+           locker = xstrdup (lock->data);
+    }
+
+    /* RCS keywords look like $STRING$ or $STRING: VALUE$.  */
+    srch = buf;
+    srch_len = len;
+    while ((srch_next = memchr (srch, '$', srch_len)) != NULL)
+    {
+       char *s, *send;
+       size_t slen;
+       const struct rcs_keyword *keyword;
+       char *value;
+       int free_value;
+       char *sub;
+       size_t sublen;
+
+       srch_len -= (srch_next + 1) - srch;
+       srch = srch_next + 1;
+
+       /* Look for the first non alphabetic character after the '$'.  */
+       send = srch + srch_len;
+       for (s = srch; s < send; s++)
+           if (! isalpha ((unsigned char) *s))
+               break;
+
+       /* If the first non alphabetic character is not '$' or ':',
+           then this is not an RCS keyword.  */
+       if (s == send || (*s != '$' && *s != ':'))
+           continue;
+
+       /* See if this is one of the keywords.  */
+       slen = s - srch;
+       for (keyword = keywords; keyword->string != NULL; keyword++)
+       {
+           if (keyword->expandit
+               && keyword->len == slen
+               && strncmp (keyword->string, srch, slen) == 0)
+           {
+               break;
+           }
+       }
+       if (keyword->string == NULL)
+           continue;
+
+       /* If the keyword ends with a ':', then the old value consists
+           of the characters up to the next '$'.  If there is no '$'
+           before the end of the line, though, then this wasn't an RCS
+           keyword after all.  */
+       if (*s == ':')
+       {
+           for (; s < send; s++)
+               if (*s == '$' || *s == '\n')
+                   break;
+           if (s == send || *s != '$')
+               continue;
+       }
+
+       /* At this point we must replace the string from SRCH to S
+           with the expansion of the keyword KW.  */
+
+       /* Get the value to use.  */
+       free_value = 0;
+       if (expand == KFLAG_K)
+           value = NULL;
+       else
+       {
+           switch (keyword->expandto)
+           {
+           default:
+               assert (!"unreached");
+
+           case KEYWORD_AUTHOR:
+               value = ver->author;
+               break;
+
+           case KEYWORD_DATE:
+               value = printable_date (ver->date);
+               free_value = 1;
+               break;
+
+           case KEYWORD_CVSHEADER:
+           case KEYWORD_HEADER:
+           case KEYWORD_ID:
+           case KEYWORD_LOCALID:
+               {
+                   const char *path;
+                   int free_path;
+                   char *date;
+                   char *old_path;
+
+                   old_path = NULL;
+                   if (keyword->expandto == KEYWORD_HEADER)
+                       path = rcs->print_path;
+                   else if (keyword->expandto == KEYWORD_CVSHEADER)
+                       path = getfullCVSname (rcs->print_path, &old_path);
+                   else
+                       path = last_component (rcs->print_path);
+                   path = escape_keyword_value (path, &free_path);
+                   date = printable_date (ver->date);
+                   value = Xasprintf ("%s %s %s %s %s%s%s",
+                                      path, ver->version, date, ver->author,
+                                      ver->state,
+                                      locker != NULL ? " " : "",
+                                      locker != NULL ? locker : "");
+                   if (free_path)
+                       /* If free_path is set then we know we allocated path
+                        * and we can discard the const.
+                        */
+                       free ((char *)path);
+                   if (old_path)
+                       free (old_path);
+                   free (date);
+                   free_value = 1;
+               }
+               break;
+
+           case KEYWORD_LOCKER:
+               value = locker;
+               break;
+
+           case KEYWORD_LOG:
+           case KEYWORD_RCSFILE:
+               value = escape_keyword_value (last_component (rcs->print_path),
+                                             &free_value);
+               break;
+
+           case KEYWORD_NAME:
+               if (name != NULL && ! isdigit ((unsigned char) *name))
+                   value = (char *) name;
+               else
+                   value = NULL;
+               break;
+
+           case KEYWORD_REVISION:
+               value = ver->version;
+               break;
+
+           case KEYWORD_SOURCE:
+               value = escape_keyword_value (rcs->print_path, &free_value);
+               break;
+
+           case KEYWORD_STATE:
+               value = ver->state;
+               break;
+           }
+       }
+
+       sub = xmalloc (keyword->len
+                      + (value == NULL ? 0 : strlen (value))
+                      + 10);
+       if (expand == KFLAG_V)
+       {
+           /* Decrement SRCH and increment S to remove the $
+               characters.  */
+           --srch;
+           ++srch_len;
+           ++s;
+           sublen = 0;
+       }
+       else
+       {
+           strcpy (sub, keyword->string);
+           sublen = strlen (keyword->string);
+           if (expand != KFLAG_K)
+           {
+               sub[sublen] = ':';
+               sub[sublen + 1] = ' ';
+               sublen += 2;
+           }
+       }
+       if (value != NULL)
+       {
+           strcpy (sub + sublen, value);
+           sublen += strlen (value);
+       }
+       if (expand != KFLAG_V && expand != KFLAG_K)
+       {
+           sub[sublen] = ' ';
+           ++sublen;
+           sub[sublen] = '\0';
+       }
+
+       if (free_value)
+           free (value);
+
+       /* The Log keyword requires special handling.  This behaviour
+           is taken from RCS 5.7.  The special log message is what RCS
+           uses for ci -k.  */
+       if (keyword->expandto == KEYWORD_LOG
+           && (sizeof "checked in with -k by " <= loglen
+               || log == NULL
+               || strncmp (log, "checked in with -k by ",
+                           sizeof "checked in with -k by " - 1) != 0))
+       {
+           char *start;
+           char *leader;
+           size_t leader_len, leader_sp_len;
+           const char *logend;
+           const char *snl;
+           int cnl;
+           char *date;
+           const char *sl;
+
+           /* We are going to insert the trailing $ ourselves, before
+               the log message, so we must remove it from S, if we
+               haven't done so already.  */
+           if (expand != KFLAG_V)
+               ++s;
+
+           /* CVS never has empty log messages, but old RCS files might.  */
+           if (log == NULL)
+               log = "";
+
+           /* Find the start of the line.  */
+           start = srch;
+           leader_len = 0;
+           while (start > buf && start[-1] != '\n'
+                  && leader_len <= xsum (config->MaxCommentLeaderLength,
+                                         expand != KFLAG_V ? 1 : 0))
+           {
+               --start;
+               ++leader_len;
+           }
+
+           if (expand != KFLAG_V)
+               /* When automagically determined and !KFLAG_V, we wish to avoid
+                * including the leading `$' of the Log keyword in our leader.
+                */
+               --leader_len;
+
+           /* If the automagically determined leader exceeds the limit set in
+            * CVSROOT/config, try to use a fallback.
+            */
+           if (leader_len > config->MaxCommentLeaderLength)
+           {
+               if (config->UseArchiveCommentLeader && rcs->comment)
+               {
+                   leader = xstrdup (rcs->comment);
+                   leader_len = strlen (rcs->comment);
+               }
+               else
+               {
+                   error (0, 0,
+"Skipping `$" "Log$' keyword due to excessive comment leader.");
+                   continue;
+               }
+           }
+           else /* leader_len <= config->MaxCommentLeaderLength */
+           {
+               /* Copy the start of the line to use as a comment leader.  */
+               leader = xmalloc (leader_len);
+               memcpy (leader, start, leader_len);
+           }
+
+           leader_sp_len = leader_len;
+           while (leader_sp_len > 0 && isspace (leader[leader_sp_len - 1]))
+               --leader_sp_len;
+
+           /* RCS does some checking for an old style of Log here,
+              but we don't bother.  RCS issues a warning if it
+              changes anything.  */
+
+           /* Count the number of newlines in the log message so that
+              we know how many copies of the leader we will need.  */
+           cnl = 0;
+           logend = log + loglen;
+           for (snl = log; snl < logend; snl++)
+               if (*snl == '\n')
+                   ++cnl;
+
+           /* If the log message did not end in a newline, increment
+            * the newline count so we have space for the extra leader.
+            * Failure to do so results in a buffer overrun.
+            */
+           if (loglen && snl[-1] != '\n')
+               ++cnl;
+
+           date = printable_date (ver->date);
+           sub = xrealloc (sub,
+                           (sublen
+                            + sizeof "Revision"
+                            + strlen (ver->version)
+                            + strlen (date)
+                            + strlen (ver->author)
+                            + loglen
+                              /* Use CNL + 2 below:  One leader for each log
+                               * line, plus the Revision/Author/Date line,
+                               * plus a trailing blank line.
+                               */
+                            + (cnl + 2) * leader_len
+                            + 20));
+           if (expand != KFLAG_V)
+           {
+               sub[sublen] = '$';
+               ++sublen;
+           }
+           sub[sublen] = '\n';
+           ++sublen;
+           memcpy (sub + sublen, leader, leader_len);
+           sublen += leader_len;
+           sprintf (sub + sublen, "Revision %s  %s  %s\n",
+                    ver->version, date, ver->author);
+           sublen += strlen (sub + sublen);
+           free (date);
+
+           sl = log;
+           while (sl < logend)
+           {
+               if (*sl == '\n')
+               {
+                   memcpy (sub + sublen, leader, leader_sp_len);
+                   sublen += leader_sp_len;
+                   sub[sublen] = '\n';
+                   ++sublen;
+                   ++sl;
+               }
+               else
+               {
+                   const char *slnl;
+
+                   memcpy (sub + sublen, leader, leader_len);
+                   sublen += leader_len;
+                   for (slnl = sl; slnl < logend && *slnl != '\n'; ++slnl)
+                       ;
+                   if (slnl < logend)
+                       ++slnl;
+                   memcpy (sub + sublen, sl, slnl - sl);
+                   sublen += slnl - sl;
+                   if (slnl == logend && slnl[-1] != '\n')
+                   {
+                       /* There was no EOL at the end of the log message.  Add
+                        * one.
+                        */
+                       sub[sublen] = '\n';
+                       ++sublen;
+                   }
+                   sl = slnl;
+               }
+           }
+
+           memcpy (sub + sublen, leader, leader_sp_len);
+           sublen += leader_sp_len;
+
+           free (leader);
+       }
+
+       /* Now SUB contains a string which is to replace the string
+          from SRCH to S.  SUBLEN is the length of SUB.  */
+
+       if (srch + sublen == s)
+       {
+           memcpy (srch, sub, sublen);
+           free (sub);
+       }
+       else
+       {
+           struct expand_buffer *ebuf;
+
+           /* We need to change the size of the buffer.  We build a
+               list of expand_buffer structures.  Each expand_buffer
+               structure represents a portion of the final output.  We
+               concatenate them back into a single buffer when we are
+               done.  This minimizes the number of potentially large
+               buffer copies we must do.  */
+
+           if (ebufs == NULL)
+           {
+               ebufs = xmalloc (sizeof *ebuf);
+               ebufs->next = NULL;
+               ebufs->data = buf;
+               ebufs->free_data = 0;
+               ebuf_len = srch - buf;
+               ebufs->len = ebuf_len;
+               ebuf_last = ebufs;
+           }
+           else
+           {
+               assert (srch >= ebuf_last->data);
+               assert (srch <= ebuf_last->data + ebuf_last->len);
+               ebuf_len -= ebuf_last->len - (srch - ebuf_last->data);
+               ebuf_last->len = srch - ebuf_last->data;
+           }
+
+           ebuf = xmalloc (sizeof *ebuf);
+           ebuf->data = sub;
+           ebuf->len = sublen;
+           ebuf->free_data = 1;
+           ebuf->next = NULL;
+           ebuf_last->next = ebuf;
+           ebuf_last = ebuf;
+           ebuf_len += sublen;
+
+           ebuf = xmalloc (sizeof *ebuf);
+           ebuf->data = s;
+           ebuf->len = srch_len - (s - srch);
+           ebuf->free_data = 0;
+           ebuf->next = NULL;
+           ebuf_last->next = ebuf;
+           ebuf_last = ebuf;
+           ebuf_len += srch_len - (s - srch);
+       }
+
+       srch_len -= (s - srch);
+       srch = s;
+    }
+
+    if (locker != NULL)
+       free (locker);
+
+    if (ebufs == NULL)
+    {
+       *retbuf = buf;
+       *retlen = len;
+    }
+    else
+    {
+       char *ret;
+
+       ret = xmalloc (ebuf_len);
+       *retbuf = ret;
+       *retlen = ebuf_len;
+       while (ebufs != NULL)
+       {
+           struct expand_buffer *next;
+
+           memcpy (ret, ebufs->data, ebufs->len);
+           ret += ebufs->len;
+           if (ebufs->free_data)
+               free (ebufs->data);
+           next = ebufs->next;
+           free (ebufs);
+           ebufs = next;
+       }
+    }
+}
+
+
+
+/* Check out a revision from an RCS file.
+
+   If PFN is not NULL, then ignore WORKFILE and SOUT.  Call PFN zero
+   or more times with the contents of the file.  CALLERDAT is passed,
+   uninterpreted, to PFN.  (The current code will always call PFN
+   exactly once for a non empty file; however, the current code
+   assumes that it can hold the entire file contents in memory, which
+   is not a good assumption, and might change in the future).
+
+   Otherwise, if WORKFILE is not NULL, check out the revision to
+   WORKFILE.  However, if WORKFILE is not NULL, and noexec is set,
+   then don't do anything.
+
+   Otherwise, if WORKFILE is NULL, check out the revision to SOUT.  If
+   SOUT is RUN_TTY, then write the contents of the revision to
+   standard output.  When using SOUT, the output is generally a
+   temporary file; don't bother to get the file modes correct.  When
+   NOEXEC is set, WORKFILEs are not written but SOUTs are.
+
+   REV is the numeric revision to check out.  It may be NULL, which
+   means to check out the head of the default branch.
+
+   If NAMETAG is not NULL, and is not a numeric revision, then it is
+   the tag that should be used when expanding the RCS Name keyword.
+
+   OPTIONS is a string such as "-kb" or "-kv" for keyword expansion
+   options.  It may be NULL to use the default expansion mode of the
+   file, typically "-kkv".
+
+   On an error which prevented checking out the file, either print a
+   nonfatal error and return 1, or give a fatal error.  On success,
+   return 0.  */
+
+/* This function mimics the behavior of `rcs co' almost exactly.  The
+   chief difference is in its support for preserving file ownership,
+   permissions, and special files across checkin and checkout -- see
+   comments in RCS_checkin for some issues about this. -twp */
+int
+RCS_checkout (RCSNode *rcs, const char *workfile, const char *rev,
+              const char *nametag, const char *options, const char *sout,
+              RCSCHECKOUTPROC pfn, void *callerdat)
+{
+    int free_rev = 0;
+    enum kflag expand;
+    FILE *fp,
+        *ofp = NULL; /* Initialize since -Wall doesn't understand that
+                      * error (1, ...) does not return.
+                      */
+    struct stat sb;
+    struct rcsbuffer rcsbuf;
+    char *key;
+    char *value;
+    size_t len;
+    int free_value = 0;
+    char *log = NULL;
+    size_t loglen = 0;
+    Node *vp = NULL;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    uid_t rcs_owner = (uid_t) -1;
+    gid_t rcs_group = (gid_t) -1;
+    mode_t rcs_mode;
+    int change_rcs_owner_or_group = 0;
+    int change_rcs_mode = 0;
+    int special_file = 0;
+    unsigned long devnum_long;
+    dev_t devnum = 0;
+#endif
+
+    TRACE (TRACE_FUNCTION, "RCS_checkout (%s, %s, %s, %s, %s)",
+          rcs->path,
+          rev != NULL ? rev : "",
+          nametag != NULL ? nametag : "",
+          options != NULL ? options : "",
+          (pfn != NULL ? "(function)"
+           : (workfile != NULL ? workfile
+              : (sout != RUN_TTY ? sout
+                 : "(stdout)"))));
+
+    assert (rev == NULL || isdigit ((unsigned char) *rev));
+
+    if (noexec && !server_active && workfile != NULL)
+       return 0;
+
+    assert (sout == RUN_TTY || workfile == NULL);
+    assert (pfn == NULL || (sout == RUN_TTY && workfile == NULL));
+
+    /* Some callers, such as Checkin or remove_file, will pass us a
+       branch.  */
+    if (rev != NULL && (numdots (rev) & 1) == 0)
+    {
+       rev = RCS_getbranch (rcs, rev, 1);
+       if (rev == NULL)
+           error (1, 0, "internal error: bad branch tag in checkout");
+       free_rev = 1;
+    }
+
+    if (rev == NULL || STREQ (rev, rcs->head))
+    {
+       int gothead;
+
+       /* We want the head revision.  Try to read it directly.  */
+
+       if (rcs->flags & PARTIAL)
+           RCS_reparsercsfile (rcs, &fp, &rcsbuf);
+       else
+           rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf);
+
+       gothead = 0;
+       if (! rcsbuf_getrevnum (&rcsbuf, &key))
+           error (1, 0, "unexpected EOF reading %s", rcs->print_path);
+       while (rcsbuf_getkey (&rcsbuf, &key, &value))
+       {
+           if (STREQ (key, "log"))
+           {
+               if (log)
+               {
+                   error (0, 0,
+"Duplicate log keyword found for head revision in RCS file.");
+                   free (log);
+               }
+               log = rcsbuf_valcopy (&rcsbuf, value, 0, &loglen);
+           }
+           else if (STREQ (key, "text"))
+           {
+               gothead = 1;
+               break;
+           }
+       }
+
+       if (! gothead)
+       {
+           error (0, 0, "internal error: cannot find head text");
+           if (free_rev)
+               /* It's okay to discard the const when free_rev is set, because
+                * we know we allocated it in this function.
+                */
+               free ((char *)rev);
+           return 1;
+       }
+
+       rcsbuf_valpolish (&rcsbuf, value, 0, &len);
+
+       if (fstat (fileno (fp), &sb) < 0)
+           error (1, errno, "cannot fstat %s", rcs->path);
+
+       rcsbuf_cache (rcs, &rcsbuf);
+    }
+    else
+    {
+       struct rcsbuffer *rcsbufp;
+
+       /* It isn't the head revision of the trunk.  We'll need to
+          walk through the deltas.  */
+
+       fp = NULL;
+       if (rcs->flags & PARTIAL)
+           RCS_reparsercsfile (rcs, &fp, &rcsbuf);
+
+       if (fp == NULL)
+       {
+           /* If RCS_deltas didn't close the file, we could use fstat
+              here too.  Probably should change it thusly....  */
+           if (stat (rcs->path, &sb) < 0)
+               error (1, errno, "cannot stat %s", rcs->path);
+           rcsbufp = NULL;
+       }
+       else
+       {
+           if (fstat (fileno (fp), &sb) < 0)
+               error (1, errno, "cannot fstat %s", rcs->path);
+           rcsbufp = &rcsbuf;
+       }
+
+       RCS_deltas (rcs, fp, rcsbufp, rev, RCS_FETCH, &value, &len,
+                   &log, &loglen);
+       free_value = 1;
+    }
+
+    /* If OPTIONS is NULL or the empty string, then the old code would
+       invoke the RCS co program with no -k option, which means that
+       co would use the string we have stored in rcs->expand.  */
+    if ((options == NULL || options[0] == '\0') && rcs->expand == NULL)
+       expand = KFLAG_KV;
+    else
+    {
+       const char *ouroptions;
+       const char * const *cpp;
+
+       if (options != NULL && options[0] != '\0')
+       {
+           assert (options[0] == '-' && options[1] == 'k');
+           ouroptions = options + 2;
+       }
+       else
+           ouroptions = rcs->expand;
+
+       for (cpp = kflags; *cpp != NULL; cpp++)
+           if (STREQ (*cpp, ouroptions))
+               break;
+
+       if (*cpp != NULL)
+           expand = (enum kflag) (cpp - kflags);
+       else
+       {
+           error (0, 0,
+                  "internal error: unsupported substitution string -k%s",
+                  ouroptions);
+           expand = KFLAG_KV;
+       }
+    }
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    /* Handle special files and permissions, if that is desired. */
+    if (preserve_perms)
+    {
+       RCSVers *vers;
+       Node *info;
+
+       vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
+       if (vp == NULL)
+           error (1, 0, "internal error: no revision information for %s",
+                  rev == NULL ? rcs->head : rev);
+       vers = vp->data;
+
+       /* First we look for symlinks, which are simplest to handle. */
+       info = findnode (vers->other_delta, "symlink");
+       if (info != NULL)
+       {
+           char *dest;
+
+           if (pfn != NULL || (workfile == NULL && sout == RUN_TTY))
+               error (1, 0, "symbolic link %s:%s cannot be piped",
+                      rcs->path, vers->version);
+           if (workfile == NULL)
+               dest = sout;
+           else
+               dest = workfile;
+
+           /* Remove `dest', just in case.  It's okay to get ENOENT here,
+              since we just want the file not to be there.  (TODO: decide
+              whether it should be considered an error for `dest' to exist
+              at this point.  If so, the unlink call should be removed and
+              `symlink' should signal the error. -twp) */
+           if (CVS_UNLINK (dest) < 0 && !existence_error (errno))
+               error (1, errno, "cannot remove %s", dest);
+           if (symlink (info->data, dest) < 0)
+               error (1, errno, "cannot create symbolic link from %s to %s",
+                      dest, (char *)info->data);
+           if (free_value)
+               free (value);
+           if (free_rev)
+               /* It's okay to discard the const when free_rev is set, because
+                * we know we allocated it in this function.
+                */
+               free ((char *)rev);
+           return 0;
+       }
+
+       /* Next, we look at this file's hardlinks field, and see whether
+          it is linked to any other file that has been checked out.
+          If so, we don't do anything else -- just link it to that file.
+
+          If we are checking out a file to a pipe or temporary storage,
+          none of this should matter.  Hence the `workfile != NULL'
+          wrapper around the whole thing. -twp */
+
+       if (workfile != NULL)
+       {
+           List *links = vers->hardlinks;
+           if (links != NULL)
+           {
+               Node *uptodate_link;
+
+               /* For each file in the hardlinks field, check to see
+                  if it exists, and if so, if it has been checked out
+                  this iteration.  When walklist returns, uptodate_link
+                  should point to a hardlist node representing a file
+                  in `links' which has recently been checked out, or
+                  NULL if no file in `links' has yet been checked out. */
+
+               uptodate_link = NULL;
+               (void) walklist (links, find_checkedout_proc, &uptodate_link);
+               dellist (&links);
+
+               /* If we've found a file that `workfile' is supposed to be
+                  linked to, and it has been checked out since CVS was
+                  invoked, then simply link workfile to that file and return.
+
+                  If one of these conditions is not met, then
+                  workfile is the first one in its hardlink group to
+                  be checked out, and we must continue with a full
+                  checkout. */
+
+               if (uptodate_link != NULL)
+               {
+                   struct hardlink_info *hlinfo = uptodate_link->data;
+
+                   if (link (uptodate_link->key, workfile) < 0)
+                       error (1, errno, "cannot link %s to %s",
+                              workfile, uptodate_link->key);
+                   hlinfo->checked_out = 1;    /* probably unnecessary */
+                   if (free_value)
+                       free (value);
+                   if (free_rev)
+                       /* It's okay to discard the const when free_rev is set,
+                        * because we know we allocated it in this function.
+                        */
+                       free ((char *)rev);
+                   return 0;
+               }
+           }
+       }
+
+       info = findnode (vers->other_delta, "owner");
+       if (info != NULL)
+       {
+           change_rcs_owner_or_group = 1;
+           rcs_owner = (uid_t) strtoul (info->data, NULL, 10);
+       }
+       info = findnode (vers->other_delta, "group");
+       if (info != NULL)
+       {
+           change_rcs_owner_or_group = 1;
+           rcs_group = (gid_t) strtoul (info->data, NULL, 10);
+       }
+       info = findnode (vers->other_delta, "permissions");
+       if (info != NULL)
+       {
+           change_rcs_mode = 1;
+           rcs_mode = (mode_t) strtoul (info->data, NULL, 8);
+       }
+       info = findnode (vers->other_delta, "special");
+       if (info != NULL)
+       {
+           /* If the size of `devtype' changes, fix the sscanf call also */
+           char devtype[16];
+
+           if (sscanf (info->data, "%15s %lu",
+                       devtype, &devnum_long) < 2)
+               error (1, 0, "%s:%s has bad `special' newphrase %s",
+                      workfile, vers->version, (char *)info->data);
+           devnum = devnum_long;
+           if (STREQ (devtype, "character"))
+               special_file = S_IFCHR;
+           else if (STREQ (devtype, "block"))
+               special_file = S_IFBLK;
+           else
+               error (0, 0, "%s is a special file of unsupported type `%s'",
+                      workfile, (char *)info->data);
+       }
+    }
+#endif /* PRESERVE_PERMISSIONS_SUPPORT */
+
+    if (expand != KFLAG_O && expand != KFLAG_B)
+    {
+       char *newvalue;
+
+       /* Don't fetch the delta node again if we already have it. */
+       if (vp == NULL)
+       {
+           vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
+           if (vp == NULL)
+               error (1, 0, "internal error: no revision information for %s",
+                      rev == NULL ? rcs->head : rev);
+       }
+
+       expand_keywords (rcs, vp->data, nametag, log, loglen,
+                        expand, value, len, &newvalue, &len);
+
+       if (newvalue != value)
+       {
+           if (free_value)
+               free (value);
+           value = newvalue;
+           free_value = 1;
+       }
+    }
+
+    if (free_rev)
+       /* It's okay to discard the const when free_rev is set, because
+        * we know we allocated it in this function.
+        */
+       free ((char *)rev);
+
+    if (log != NULL)
+    {
+       free (log);
+       log = NULL;
+    }
+
+    if (pfn != NULL)
+    {
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       if (special_file)
+           error (1, 0, "special file %s cannot be piped to anything",
+                  rcs->path);
+#endif
+       /* The PFN interface is very simple to implement right now, as
+           we always have the entire file in memory.  */
+       if (len != 0)
+           pfn (callerdat, value, len);
+    }
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    else if (special_file)
+    {
+# ifdef HAVE_MKNOD
+       char *dest;
+
+       /* Can send either to WORKFILE or to SOUT, as long as SOUT is
+          not RUN_TTY. */
+       dest = workfile;
+       if (dest == NULL)
+       {
+           if (sout == RUN_TTY)
+               error (1, 0, "special file %s cannot be written to stdout",
+                      rcs->path);
+           dest = sout;
+       }
+
+       /* Unlink `dest', just in case.  It's okay if this provokes a
+          ENOENT error. */
+       if (CVS_UNLINK (dest) < 0 && existence_error (errno))
+           error (1, errno, "cannot remove %s", dest);
+       if (mknod (dest, special_file, devnum) < 0)
+           error (1, errno, "could not create special file %s",
+                  dest);
+# else
+       error (1, 0,
+"cannot create %s: unable to create special files on this system",
+workfile);
+# endif
+    }
+#endif
+    else
+    {
+       /* Not a special file: write to WORKFILE or SOUT. */
+       if (workfile == NULL)
+       {
+           if (sout == RUN_TTY)
+               ofp = stdout;
+           else
+           {
+               /* Symbolic links should be removed before replacement, so that
+                  `fopen' doesn't follow the link and open the wrong file. */
+               if (islink (sout))
+                   if (unlink_file (sout) < 0)
+                       error (1, errno, "cannot remove %s", sout);
+               ofp = CVS_FOPEN (sout, expand == KFLAG_B ? "wb" : "w");
+               if (ofp == NULL)
+                   error (1, errno, "cannot open %s", sout);
+           }
+       }
+       else
+       {
+           /* Output is supposed to go to WORKFILE, so we should open that
+              file.  Symbolic links should be removed first (see above). */
+           if (islink (workfile))
+               if (unlink_file (workfile) < 0)
+                   error (1, errno, "cannot remove %s", workfile);
+
+           ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
+
+           /* If the open failed because the existing workfile was not
+              writable, try to chmod the file and retry the open.  */
+           if (ofp == NULL && errno == EACCES
+               && isfile (workfile) && !iswritable (workfile))
+           {
+               xchmod (workfile, 1);
+               ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
+           }
+
+           if (ofp == NULL)
+           {
+               error (0, errno, "cannot open %s", workfile);
+               if (free_value)
+                   free (value);
+               return 1;
+           }
+       }
+
+       if (workfile == NULL && sout == RUN_TTY)
+       {
+           if (expand == KFLAG_B)
+               cvs_output_binary (value, len);
+           else
+           {
+               /* cvs_output requires the caller to check for zero
+                  length.  */
+               if (len > 0)
+                   cvs_output (value, len);
+           }
+       }
+       else
+       {
+           /* NT 4.0 is said to have trouble writing 2099999 bytes
+              (for example) in a single fwrite.  So break it down
+              (there is no need to be writing that much at once
+              anyway; it is possible that LARGEST_FWRITE should be
+              somewhat larger for good performance, but for testing I
+              want to start with a small value until/unless a bigger
+              one proves useful).  */
+#define LARGEST_FWRITE 8192
+           size_t nleft = len;
+           size_t nstep = (len < LARGEST_FWRITE ? len : LARGEST_FWRITE);
+           char *p = value;
+
+           while (nleft > 0)
+           {
+               if (fwrite (p, 1, nstep, ofp) != nstep)
+               {
+                   error (0, errno, "cannot write %s",
+                          (workfile != NULL
+                           ? workfile
+                           : (sout != RUN_TTY ? sout : "stdout")));
+                   if (free_value)
+                       free (value);
+                   return 1;
+               }
+               p += nstep;
+               nleft -= nstep;
+               if (nleft < nstep)
+                   nstep = nleft;
+           }
+       }
+    }
+
+    if (free_value)
+       free (value);
+
+    if (workfile != NULL)
+    {
+       int ret;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       if (!special_file && fclose (ofp) < 0)
+       {
+           error (0, errno, "cannot close %s", workfile);
+           return 1;
+       }
+
+       if (change_rcs_owner_or_group)
+       {
+           if (chown (workfile, rcs_owner, rcs_group) < 0)
+               error (0, errno, "could not change owner or group of %s",
+                      workfile);
+       }
+
+       ret = chmod (workfile,
+                    change_rcs_mode
+                    ? rcs_mode
+                    : sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
+#else
+       if (fclose (ofp) < 0)
+       {
+           error (0, errno, "cannot close %s", workfile);
+           return 1;
+       }
+
+       ret = chmod (workfile,
+                    sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
+#endif
+       if (ret < 0)
+       {
+           error (0, errno, "cannot change mode of file %s",
+                  workfile);
+       }
+    }
+    else if (sout != RUN_TTY)
+    {
+       if (
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+           !special_file &&
+#endif
+           fclose (ofp) < 0)
+       {
+           error (0, errno, "cannot close %s", sout);
+           return 1;
+       }
+    }
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    /* If we are in the business of preserving hardlinks, then
+       mark this file as having been checked out. */
+    if (preserve_perms && workfile != NULL)
+       update_hardlink_info (workfile);
+#endif
+
+    return 0;
+}
+
+
+
+/* Find the delta currently locked by the user.  From the `ci' man page:
+
+       "If rev is omitted, ci tries to  derive  the  new  revision
+        number  from  the  caller's  last lock.  If the caller has
+        locked the tip revision of a branch, the new  revision  is
+        appended  to  that  branch.   The  new  revision number is
+        obtained by incrementing the tip revision number.  If  the
+        caller  locked a non-tip revision, a new branch is started
+        at that revision by incrementing the highest branch number
+        at  that  revision.   The default initial branch and level
+        numbers are 1.
+
+        If rev is omitted and the caller has no lock, but owns the
+        file  and  locking is not set to strict, then the revision
+        is appended to the default branch (normally the trunk; see
+        the -b option of rcs(1))."
+
+   RCS_findlock_or_tip finds the unique revision locked by the caller
+   and returns its delta node.  If the caller has not locked any
+   revisions (and is permitted to commit to an unlocked delta, as
+   described above), return the tip of the default branch. */
+static RCSVers *
+RCS_findlock_or_tip (RCSNode *rcs)
+{
+    char *user = getcaller();
+    Node *lock, *p;
+    List *locklist;
+
+    /* Find unique delta locked by caller. This code is very similar
+       to the code in RCS_unlock -- perhaps it could be abstracted
+       into a RCS_findlock function. */
+    locklist = RCS_getlocks (rcs);
+    lock = NULL;
+    for (p = locklist->list->next; p != locklist->list; p = p->next)
+    {
+       if (STREQ (p->data, user))
+       {
+           if (lock != NULL)
+           {
+               error (0, 0, "\
+%s: multiple revisions locked by %s; please specify one", rcs->print_path, 
user);
+               return NULL;
+           }
+           lock = p;
+       }
+    }
+
+    if (lock != NULL)
+    {
+       /* Found an old lock, but check that the revision still exists. */
+       p = findnode (rcs->versions, lock->key);
+       if (p == NULL)
+       {
+           error (0, 0, "%s: can't unlock nonexistent revision %s",
+                  rcs->print_path,
+                  lock->key);
+           return NULL;
+       }
+       return p->data;
+    }
+
+    /* No existing lock.  The RCS rule is that this is an error unless
+       locking is nonstrict AND the file is owned by the current
+       user.  Trying to determine the latter is a portability nightmare
+       in the face of NT, VMS, AFS, and other systems with non-unix-like
+       ideas of users and owners.  In the case of CVS, we should never get
+       here (as long as the traditional behavior of making sure to call
+       RCS_lock persists).  Anyway, we skip the RCS error checks
+       and just return the default branch or head.  The reasoning is that
+       those error checks are to make users lock before a checkin, and we do
+       that in other ways if at all anyway (e.g. rcslock.pl).  */
+
+    p = findnode (rcs->versions, RCS_getbranch (rcs, rcs->branch, 0));
+    if (!p)
+    {
+       error (0, 0, "RCS file `%s' does not contain its default revision.",
+              rcs->path);
+       return NULL;
+    }
+
+    return p->data;
+}
+
+
+
+/* Revision number string, R, must contain a `.'.
+   Return a newly-malloc'd copy of the prefix of R up
+   to but not including the final `.'.  */
+static char *
+truncate_revnum (const char *r)
+{
+    size_t len;
+    char *new_r;
+    char *dot = strrchr (r, '.');
+
+    assert (dot);
+    len = dot - r;
+    new_r = xmalloc (len + 1);
+    memcpy (new_r, r, len);
+    new_r[len] = '\0';
+    return new_r;
+}
+
+
+
+/* Revision number strings, R and S, must each contain a `.'.
+   R and S must be writable and must have the same number of dots.
+   Truncate R and S for the comparison, then restored them to their
+   original state.
+   Return the result (see compare_revnums) of comparing R and S
+   ignoring differences in any component after the rightmost `.'.  */
+static int
+compare_truncated_revnums (char *r, char *s)
+{
+    char *r_dot = truncate_revnum_in_place (r);
+    char *s_dot = truncate_revnum_in_place (s);
+    int cmp;
+
+    assert (numdots (r) == numdots (s));
+
+    cmp = compare_revnums (r, s);
+
+    *r_dot = '.';
+    *s_dot = '.';
+
+    return cmp;
+}
+
+
+
+/* Return a malloc'd copy of the string representing the highest branch
+   number on BRANCHNODE.  If there are no branches on BRANCHNODE, return NULL.
+   FIXME: isn't the max rev always the last one?
+   If so, we don't even need a loop.  */
+static char *
+max_rev (const RCSVers *branchnode)
+{
+    Node *head;
+    Node *bp;
+    char *max;
+
+    if (branchnode->branches == NULL)
+    {
+        return NULL;
+    }
+
+    max = NULL;
+    head = branchnode->branches->list;
+    for (bp = head->next; bp != head; bp = bp->next)
+    {
+       if (max == NULL || compare_truncated_revnums (max, bp->key) < 0)
+       {
+           max = bp->key;
+       }
+    }
+    assert (max);
+
+    return truncate_revnum (max);
+}
+
+
+
+/* Create BRANCH in RCS's delta tree.  BRANCH may be either a branch
+   number or a revision number.  In the former case, create the branch
+   with the specified number; in the latter case, create a new branch
+   rooted at node BRANCH with a higher branch number than any others.
+   Return the number of the tip node on the new branch. */
+static char *
+RCS_addbranch (RCSNode *rcs, const char *branch)
+{
+    char *branchpoint, *newrevnum;
+    Node *nodep, *bp;
+    Node *marker;
+    RCSVers *branchnode;
+
+    assert (branch);
+
+    /* Append to end by default.  */
+    marker = NULL;
+
+    branchpoint = xstrdup (branch);
+    if ((numdots (branchpoint) & 1) == 0)
+    {
+       truncate_revnum_in_place (branchpoint);
+    }
+
+    /* Find the branch rooted at BRANCHPOINT. */
+    nodep = findnode (rcs->versions, branchpoint);
+    if (nodep == NULL)
+    {
+       error (0, 0, "%s: can't find branch point %s", rcs->print_path, 
branchpoint);
+       free (branchpoint);
+       return NULL;
+    }
+    free (branchpoint);
+    branchnode = nodep->data;
+
+    /* If BRANCH was a full branch number, make sure it is higher than MAX. */
+    if ((numdots (branch) & 1) == 1)
+    {
+       if (branchnode->branches == NULL)
+       {
+           /* We have to create the first branch on this node, which means
+              appending ".2" to the revision number. */
+           newrevnum = Xasprintf ("%s.2", branch);
+       }
+       else
+       {
+           char *max = max_rev (branchnode);
+           assert (max);
+           newrevnum = increment_revnum (max);
+           free (max);
+       }
+    }
+    else
+    {
+       newrevnum = xstrdup (branch);
+
+       if (branchnode->branches != NULL)
+       {
+           Node *head;
+           Node *bp;
+
+           /* Find the position of this new branch in the sorted list
+              of branches.  */
+           head = branchnode->branches->list;
+           for (bp = head->next; bp != head; bp = bp->next)
+           {
+               char *dot;
+               int found_pos;
+
+               /* The existing list must be sorted on increasing revnum.  */
+               assert (bp->next == head
+                       || compare_truncated_revnums (bp->key,
+                                                     bp->next->key) < 0);
+               dot = truncate_revnum_in_place (bp->key);
+               found_pos = (compare_revnums (branch, bp->key) < 0);
+               *dot = '.';
+
+               if (found_pos)
+               {
+                   break;
+               }
+           }
+           marker = bp;
+       }
+    }
+
+    newrevnum = xrealloc (newrevnum, strlen (newrevnum) + 3);
+    strcat (newrevnum, ".1");
+
+    /* Add this new revision number to BRANCHPOINT's branches list. */
+    if (branchnode->branches == NULL)
+       branchnode->branches = getlist();
+    bp = getnode();
+    bp->key = xstrdup (newrevnum);
+
+    /* Append to the end of the list by default, that is, just before
+       the header node, `list'.  */
+    if (marker == NULL)
+       marker = branchnode->branches->list;
+
+    {
+       int fail;
+       fail = insert_before (branchnode->branches, marker, bp);
+       assert (!fail);
+    }
+
+    return newrevnum;
+}
+
+
+
+/* Check in to RCSFILE with revision REV (which must be greater than
+   the largest revision) and message MESSAGE (which is checked for
+   validity).  If FLAGS & RCS_FLAGS_DEAD, check in a dead revision.
+   If FLAGS & RCS_FLAGS_QUIET, tell ci to be quiet.  If FLAGS &
+   RCS_FLAGS_MODTIME, use the working file's modification time for the
+   checkin time.  WORKFILE is the working file to check in from, or
+   NULL to use the usual RCS rules for deriving it from the RCSFILE.
+   If FLAGS & RCS_FLAGS_KEEPFILE, don't unlink the working file;
+   unlinking the working file is standard RCS behavior, but is rarely
+   appropriate for CVS.
+
+   UPDATE_DIR is used to print the path for the file.  This argument is
+   unnecessary when FLAGS & RCS_FLAGS_QUIET since the path won't be printed
+   anyhow.
+
+   This function should almost exactly mimic the behavior of `rcs ci'.  The
+   principal point of difference is the support here for preserving file
+   ownership and permissions in the delta nodes.  This is not a clean
+   solution -- precisely because it diverges from RCS's behavior -- but
+   it doesn't seem feasible to do this anywhere else in the code. [-twp]
+   
+   Return value is -1 for error (and errno is set to indicate the
+   error), positive for error (and an error message has been printed),
+   or zero for success.  */
+int
+RCS_checkin (RCSNode *rcs, const char *update_dir, const char *workfile_in,
+            const char *message, const char *rev, time_t citime, int flags)
+{
+    RCSVers *delta, *commitpt;
+    Deltatext *dtext;
+    Node *nodep;
+    char *tmpfile, *changefile;
+    int dargc = 0;
+    size_t darg_allocated = 0;
+    char **dargv = NULL;
+    size_t bufsize;
+    int status, checkin_quiet;
+    struct tm *ftm;
+    time_t modtime;
+    int adding_branch = 0;
+    char *workfile = xstrdup (workfile_in);
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    struct stat sb;
+#endif
+    Node *np;
+
+    commitpt = NULL;
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    /* Get basename of working file.  Is there a library function to
+       do this?  I couldn't find one. -twp */
+    if (workfile == NULL)
+    {
+       char *p;
+       int extlen = strlen (RCSEXT);
+       assert (rcs->path);
+       workfile = xstrdup (last_component (rcs->path));
+       p = workfile + (strlen (workfile) - extlen);
+       assert (strncmp (p, RCSEXT, extlen) == 0);
+       *p = '\0';
+    }
+
+    /* If the filename is a symbolic link, follow it and replace it
+       with the destination of the link.  We need to do this before
+       calling rcs_internal_lockfile, or else we won't put the lock in
+       the right place. */
+    resolve_symlink (&(rcs->path));
+
+    checkin_quiet = flags & RCS_FLAGS_QUIET;
+    if (!(checkin_quiet || really_quiet))
+    {
+       cvs_output (rcs->path, 0);
+       cvs_output ("  <--  ", 7);
+       if (update_dir && strlen (update_dir))
+       {
+           cvs_output (update_dir, 0);
+           cvs_output ("/", 1);
+       }
+       cvs_output (workfile, 0);
+       cvs_output ("\n", 1);
+    }
+
+    /* Create new delta node. */
+    delta = xmalloc (sizeof (RCSVers));
+    memset (delta, 0, sizeof (RCSVers));
+    delta->author = xstrdup (getcaller ());
+    if (flags & RCS_FLAGS_MODTIME)
+    {
+       struct stat ws;
+       if (stat (workfile, &ws) < 0)
+       {
+           error (1, errno, "cannot stat %s", workfile);
+       }
+       modtime = ws.st_mtime;
+    }
+    else if (flags & RCS_FLAGS_USETIME)
+       modtime = citime;
+    else
+       (void) time (&modtime);
+    ftm = gmtime (&modtime);
+    delta->date = Xasprintf (DATEFORM,
+                            ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+                            ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+                            ftm->tm_min, ftm->tm_sec);
+    if (flags & RCS_FLAGS_DEAD)
+    {
+       delta->state = xstrdup (RCSDEAD);
+       delta->dead = 1;
+    }
+    else
+       delta->state = xstrdup ("Exp");
+
+    delta->other_delta = getlist();
+
+    /* save the commit ID */
+    np = getnode();
+    np->type = RCSFIELD;
+    np->key = xstrdup ("commitid");
+    np->data = xstrdup(global_session_id);
+    addnode (delta->other_delta, np);
+
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    /* If permissions should be preserved on this project, then
+       save the permission info. */
+    if (preserve_perms)
+    {
+       Node *np;
+       char buf[64];   /* static buffer should be safe: see usage. -twp */
+
+       delta->other_delta = getlist();
+
+       if (lstat (workfile, &sb) < 0)
+           error (1, errno, "cannot lstat %s", workfile);
+
+       if (S_ISLNK (sb.st_mode))
+       {
+           np = getnode();
+           np->type = RCSFIELD;
+           np->key = xstrdup ("symlink");
+           np->data = Xreadlink (workfile, sb.st_size);
+           addnode (delta->other_delta, np);
+       }
+       else
+       {
+           (void) sprintf (buf, "%u", sb.st_uid);
+           np = getnode();
+           np->type = RCSFIELD;
+           np->key = xstrdup ("owner");
+           np->data = xstrdup (buf);
+           addnode (delta->other_delta, np);
+
+           (void) sprintf (buf, "%u", sb.st_gid);
+           np = getnode();
+           np->type = RCSFIELD;
+           np->key = xstrdup ("group");
+           np->data = xstrdup (buf);
+           addnode (delta->other_delta, np);
+           
+           (void) sprintf (buf, "%o", sb.st_mode & 07777);
+           np = getnode();
+           np->type = RCSFIELD;
+           np->key = xstrdup ("permissions");
+           np->data = xstrdup (buf);
+           addnode (delta->other_delta, np);
+
+           /* Save device number. */
+           switch (sb.st_mode & S_IFMT)
+           {
+               case S_IFREG: break;
+               case S_IFCHR:
+               case S_IFBLK:
+# ifdef HAVE_STRUCT_STAT_ST_RDEV
+                   np = getnode();
+                   np->type = RCSFIELD;
+                   np->key = xstrdup ("special");
+                   sprintf (buf, "%s %lu",
+                            ((sb.st_mode & S_IFMT) == S_IFCHR
+                             ? "character" : "block"),
+                            (unsigned long) sb.st_rdev);
+                   np->data = xstrdup (buf);
+                   addnode (delta->other_delta, np);
+# else
+                   error (0, 0,
+"can't preserve %s: unable to save device files on this system",
+workfile);
+# endif
+                   break;
+
+               default:
+                   error (0, 0, "special file %s has unknown type", workfile);
+           }
+
+           /* Save hardlinks. */
+           delta->hardlinks = list_linked_files_on_disk (workfile);
+       }
+    }
+#endif
+
+    /* Create a new deltatext node. */
+    dtext = xmalloc (sizeof (Deltatext));
+    memset (dtext, 0, sizeof (Deltatext));
+
+    dtext->log = make_message_rcsvalid (message);
+
+    /* If the delta tree is empty, then there's nothing to link the
+       new delta into.  So make a new delta tree, snarf the working
+       file contents, and just write the new RCS file. */
+    if (rcs->head == NULL)
+    {
+       char *newrev;
+       FILE *fout;
+
+       /* Figure out what the first revision number should be. */
+       if (rev == NULL || *rev == '\0')
+           newrev = xstrdup ("1.1");
+       else if (numdots (rev) == 0)
+       {
+           newrev = Xasprintf ("%s.1", rev);
+       }
+       else
+           newrev = xstrdup (rev);
+
+       /* Don't need to xstrdup NEWREV because it's already dynamic, and
+          not used for anything else.  (Don't need to free it, either.) */
+       rcs->head = newrev;
+       delta->version = xstrdup (newrev);
+       nodep = getnode();
+       nodep->type = RCSVERS;
+       nodep->delproc = rcsvers_delproc;
+       nodep->data = delta;
+       nodep->key = delta->version;
+       (void) addnode (rcs->versions, nodep);
+
+       dtext->version = xstrdup (newrev);
+       bufsize = 0;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       if (preserve_perms && !S_ISREG (sb.st_mode))
+           /* Pretend file is empty.  */
+           bufsize = 0;
+       else
+#endif
+       get_file (workfile, workfile,
+                 rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+                 &dtext->text, &bufsize, &dtext->len);
+
+       if (!(checkin_quiet || really_quiet))
+       {
+           cvs_output ("initial revision: ", 0);
+           cvs_output (rcs->head, 0);
+           cvs_output ("\n", 1);
+       }
+
+       /* We are probably about to invalidate any cached file.  */
+       rcsbuf_cache_close ();
+
+       fout = rcs_internal_lockfile (rcs->path);
+       RCS_putadmin (rcs, fout);
+       RCS_putdtree (rcs, rcs->head, fout);
+       RCS_putdesc (rcs, fout);
+       rcs->delta_pos = ftello (fout);
+       if (rcs->delta_pos == -1)
+           error (1, errno, "cannot ftello for %s", rcs->path);
+       putdeltatext (fout, dtext);
+       rcs_internal_unlockfile (fout, rcs->path);
+
+       if ((flags & RCS_FLAGS_KEEPFILE) == 0)
+       {
+           if (unlink_file (workfile) < 0)
+               /* FIXME-update-dir: message does not include update_dir.  */
+               error (0, errno, "cannot remove %s", workfile);
+       }
+
+       status = 0;
+       goto checkin_done;
+    }
+
+    /* Derive a new revision number.  From the `ci' man page:
+
+        "If rev  is  a revision number, it must be higher than the
+        latest one on the branch to which  rev  belongs,  or  must
+        start a new branch.
+
+        If  rev is a branch rather than a revision number, the new
+        revision is appended to that branch.  The level number  is
+        obtained  by  incrementing the tip revision number of that
+        branch.  If rev  indicates  a  non-existing  branch,  that
+        branch  is  created  with  the  initial  revision numbered
+        rev.1."
+
+       RCS_findlock_or_tip handles the case where REV is omitted.
+       RCS 5.7 also permits REV to be "$" or to begin with a dot, but
+       we do not address those cases -- every routine that calls
+       RCS_checkin passes it a numeric revision. */
+
+    if (rev == NULL || *rev == '\0')
+    {
+       /* Figure out where the commit point is by looking for locks.
+          If the commit point is at the tip of a branch (or is the
+          head of the delta tree), then increment its revision number
+          to obtain the new revnum.  Otherwise, start a new
+          branch. */
+       commitpt = RCS_findlock_or_tip (rcs);
+       if (commitpt == NULL)
+       {
+           status = 1;
+           goto checkin_done;
+       }
+       else if (commitpt->next == NULL
+                || STREQ (commitpt->version, rcs->head))
+           delta->version = increment_revnum (commitpt->version);
+       else
+           delta->version = RCS_addbranch (rcs, commitpt->version);
+    }
+    else
+    {
+       /* REV is either a revision number or a branch number.  Find the
+          tip of the target branch. */
+       char *branch, *tip, *newrev, *p;
+       int dots, isrevnum;
+
+       assert (isdigit ((unsigned char) *rev));
+
+       newrev = xstrdup (rev);
+       dots = numdots (newrev);
+       isrevnum = dots & 1;
+
+       branch = xstrdup (rev);
+       if (isrevnum)
+       {
+           p = strrchr (branch, '.');
+           *p = '\0';
+       }
+
+       /* Find the tip of the target branch.  If we got a one- or two-digit
+          revision number, this will be the head of the tree.  Exception:
+          if rev is a single-field revision equal to the branch number of
+          the trunk (usually "1") then we want to treat it like an ordinary
+          branch revision. */
+       if (dots == 0)
+       {
+           tip = xstrdup (rcs->head);
+           if (atoi (tip) != atoi (branch))
+           {
+               newrev = xrealloc (newrev, strlen (newrev) + 3);
+               strcat (newrev, ".1");
+               dots = isrevnum = 1;
+           }
+       }
+       else if (dots == 1)
+           tip = xstrdup (rcs->head);
+       else
+           tip = RCS_getbranch (rcs, branch, 1);
+
+       /* If the branch does not exist, and we were supplied an exact
+          revision number, signal an error.  Otherwise, if we were
+          given only a branch number, create it and set COMMITPT to
+          the branch point. */
+       if (tip == NULL)
+       {
+           if (isrevnum)
+           {
+               error (0, 0, "%s: can't find branch point %s",
+                      rcs->print_path, branch);
+               free (branch);
+               free (newrev);
+               status = 1;
+               goto checkin_done;
+           }
+           delta->version = RCS_addbranch (rcs, branch);
+           if (!delta->version)
+           {
+               free (branch);
+               free (newrev);
+               status = 1;
+               goto checkin_done;
+           }
+           adding_branch = 1;
+           p = strrchr (branch, '.');
+           *p = '\0';
+           tip = xstrdup (branch);
+       }
+       else
+       {
+           if (isrevnum)
+           {
+               /* NEWREV must be higher than TIP. */
+               if (compare_revnums (tip, newrev) >= 0)
+               {
+                   error (0, 0,
+                          "%s: revision %s too low; must be higher than %s",
+                          rcs->print_path,
+                          newrev, tip);
+                   free (branch);
+                   free (newrev);
+                   free (tip);
+                   status = 1;
+                   goto checkin_done;
+               }
+               delta->version = xstrdup (newrev);
+           }
+           else
+               /* Just increment the tip number to get the new revision. */
+               delta->version = increment_revnum (tip);
+       }
+
+       nodep = findnode (rcs->versions, tip);
+       commitpt = nodep->data;
+
+       free (branch);
+       free (newrev);
+       free (tip);
+    }
+
+    assert (delta->version != NULL);
+
+    /* If COMMITPT is locked by us, break the lock.  If it's locked
+       by someone else, signal an error. */
+    nodep = findnode (RCS_getlocks (rcs), commitpt->version);
+    if (nodep != NULL)
+    {
+       if (! STREQ (nodep->data, delta->author))
+       {
+           /* If we are adding a branch, then leave the old lock around.
+              That is sensible in the sense that when adding a branch,
+              we don't need to use the lock to tell us where to check
+              in.  It is fishy in the sense that if it is our own lock,
+              we break it.  However, this is the RCS 5.7 behavior (at
+              the end of addbranch in ci.c in RCS 5.7, it calls
+              removelock only if it is our own lock, not someone
+              else's).  */
+
+           if (!adding_branch)
+           {
+               error (0, 0, "%s: revision %s locked by %s",
+                      rcs->print_path,
+                      nodep->key, (char *)nodep->data);
+               status = 1;
+               goto checkin_done;
+           }
+       }
+       else
+           delnode (nodep);
+    }
+
+    dtext->version = xstrdup (delta->version);
+
+    /* Obtain the change text for the new delta.  If DELTA is to be the
+       new head of the tree, then its change text should be the contents
+       of the working file, and LEAFNODE's change text should be a diff.
+       Else, DELTA's change text should be a diff between LEAFNODE and
+       the working file. */
+
+    tmpfile = cvs_temp_name();
+    status = RCS_checkout (rcs, NULL, commitpt->version, NULL,
+                          ((rcs->expand != NULL
+                            && STREQ (rcs->expand, "b"))
+                           ? "-kb"
+                           : "-ko"),
+                          tmpfile,
+                          NULL, NULL);
+    if (status != 0)
+       error (1, 0,
+              "could not check out revision %s of `%s'",
+              commitpt->version, rcs->print_path);
+
+    bufsize = 0;
+    changefile = cvs_temp_name();
+
+    /* Diff options should include --binary if the RCS file has -kb set
+       in its `expand' field. */
+    run_add_arg_p (&dargc, &darg_allocated, &dargv, "-a");
+    run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n");
+    if (rcs->expand != NULL && STREQ (rcs->expand, "b"))
+       run_add_arg_p (&dargc, &darg_allocated, &dargv, "--binary");
+
+    if (STREQ (commitpt->version, rcs->head) &&
+       numdots (delta->version) == 1)
+    {
+       /* If this revision is being inserted on the trunk, the change text
+          for the new delta should be the contents of the working file ... */
+       bufsize = 0;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       if (preserve_perms && !S_ISREG (sb.st_mode))
+           /* Pretend file is empty.  */
+           ;
+       else
+#endif
+       get_file (workfile, workfile,
+                 rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+                 &dtext->text, &bufsize, &dtext->len);
+
+       /* ... and the change text for the old delta should be a diff. */
+       commitpt->text = xmalloc (sizeof (Deltatext));
+       memset (commitpt->text, 0, sizeof (Deltatext));
+
+       bufsize = 0;
+       switch (diff_exec (workfile, tmpfile, NULL, NULL,
+                          dargc, dargv, changefile))
+       {
+           case 0:
+           case 1:
+               break;
+           case -1:
+               /* FIXME-update-dir: message does not include update_dir.  */
+               error (1, errno, "error diffing %s", workfile);
+               break;
+           default:
+               /* FIXME-update-dir: message does not include update_dir.  */
+               error (1, 0, "error diffing %s", workfile);
+               break;
+       }
+
+       /* OK, the text file case here is really dumb.  Logically
+          speaking we want diff to read the files in text mode,
+          convert them to the canonical form found in RCS files
+          (which, we hope at least, is independent of OS--always
+          bare linefeeds), and then work with change texts in that
+          format.  However, diff_exec both generates change
+          texts and produces output for user purposes (e.g. patch.c),
+          and there is no way to distinguish between the two cases.
+          So we actually implement the text file case by writing the
+          change text as a text file, then reading it as a text file.
+          This should cause no harm, but doesn't strike me as
+          immensely clean.  */
+       get_file (changefile, changefile,
+                 rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+                 &commitpt->text->text, &bufsize, &commitpt->text->len);
+
+       /* If COMMITPT->TEXT->TEXT is NULL, it means that CHANGEFILE
+          was empty and that there are no differences between revisions.
+          In that event, we want to force RCS_rewrite to write an empty
+          string for COMMITPT's change text.  Leaving the change text
+          field set NULL won't work, since that means "preserve the original
+          change text for this delta." */
+       if (commitpt->text->text == NULL)
+       {
+           commitpt->text->text = xstrdup ("");
+           commitpt->text->len = 0;
+       }
+    }
+    else
+    {
+       /* This file is not being inserted at the head, but on a side
+          branch somewhere.  Make a diff from the previous revision
+          to the working file. */
+       switch (diff_exec (tmpfile, workfile, NULL, NULL,
+                          dargc, dargv, changefile))
+       {
+           case 0:
+           case 1:
+               break;
+           case -1:
+               /* FIXME-update-dir: message does not include update_dir.  */
+               error (1, errno, "error diffing %s", workfile);
+               break;
+           default:
+               /* FIXME-update-dir: message does not include update_dir.  */
+               error (1, 0, "error diffing %s", workfile);
+               break;
+       }
+       /* See the comment above, at the other get_file invocation,
+          regarding binary vs. text.  */
+       get_file (changefile, changefile, 
+                 rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+                 &dtext->text, &bufsize,
+                 &dtext->len);
+       if (dtext->text == NULL)
+       {
+           dtext->text = xstrdup ("");
+           dtext->len = 0;
+       }
+    }
+
+    run_arg_free_p (dargc, dargv);
+    free (dargv);
+
+    /* Update DELTA linkage.  It is important not to do this before
+       the very end of RCS_checkin; if an error arises that forces
+       us to abort checking in, we must not have malformed deltas
+       partially linked into the tree.
+
+       If DELTA and COMMITPT are on different branches, do nothing --
+       DELTA is linked to the tree through COMMITPT->BRANCHES, and we
+       don't want to change `next' pointers.
+
+       Otherwise, if the nodes are both on the trunk, link DELTA to
+       COMMITPT; otherwise, link COMMITPT to DELTA. */
+
+    if (numdots (commitpt->version) == numdots (delta->version))
+    {
+       if (STREQ (commitpt->version, rcs->head))
+       {
+           delta->next = rcs->head;
+           rcs->head = xstrdup (delta->version);
+       }
+       else
+           commitpt->next = xstrdup (delta->version);
+    }
+
+    /* Add DELTA to RCS->VERSIONS. */
+    if (rcs->versions == NULL)
+       rcs->versions = getlist();
+    nodep = getnode();
+    nodep->type = RCSVERS;
+    nodep->delproc = rcsvers_delproc;
+    nodep->data = delta;
+    nodep->key = delta->version;
+    (void) addnode (rcs->versions, nodep);
+       
+    /* Write the new RCS file, inserting the new delta at COMMITPT. */
+    if (!(checkin_quiet || really_quiet))
+    {
+       cvs_output ("new revision: ", 14);
+       cvs_output (delta->version, 0);
+       cvs_output ("; previous revision: ", 21);
+       cvs_output (commitpt->version, 0);
+       cvs_output ("\n", 1);
+    }
+
+    RCS_rewrite (rcs, dtext, commitpt->version);
+
+    if ((flags & RCS_FLAGS_KEEPFILE) == 0)
+    {
+       if (unlink_file (workfile) < 0)
+           /* FIXME-update-dir: message does not include update_dir.  */
+           error (1, errno, "cannot remove %s", workfile);
+    }
+    if (unlink_file (tmpfile) < 0)
+       error (0, errno, "cannot remove %s", tmpfile);
+    free (tmpfile);
+    if (unlink_file (changefile) < 0)
+       error (0, errno, "cannot remove %s", changefile);
+    free (changefile);
+
+ checkin_done:
+    free (workfile);
+
+    if (commitpt != NULL && commitpt->text != NULL)
+    {
+       freedeltatext (commitpt->text);
+       commitpt->text = NULL;
+    }
+
+    freedeltatext (dtext);
+    if (status != 0)
+    {
+       /* If delta has not been added to a List, then freeing the Node key
+        * won't free delta->version.
+        */
+       if (delta->version) free (delta->version);
+       free_rcsvers_contents (delta);
+    }
+
+    return status;
+}
+
+
+
+/* This structure is passed between RCS_cmp_file and cmp_file_buffer.  */
+struct cmp_file_data
+{
+    const char *filename;
+    FILE *fp;
+    int different;
+};
+
+/* Compare the contents of revision REV1 of RCS file RCS with the
+   contents of REV2 if given, otherwise, compare with the contents of
+   the file FILENAME.  OPTIONS is a string for the keyword
+   expansion options.  Return 0 if the contents of the revision are
+   the same as the contents of the file, 1 if they are different.  */
+int
+RCS_cmp_file (RCSNode *rcs, const char *rev1, char **rev1_cache,
+              const char *rev2, const char *options, const char *filename)
+{
+    int binary;
+
+    TRACE (TRACE_FUNCTION, "RCS_cmp_file( %s, %s, %s, %s, %s )",
+           rcs->path ? rcs->path : "(null)",
+          rev1 ? rev1 : "(null)", rev2 ? rev2 : "(null)",
+          options ? options : "(null)", filename ? filename : "(null)");
+
+    if (options != NULL && options[0] != '\0')
+       binary = STREQ (options, "-kb");
+    else
+    {
+       char *expand;
+
+       expand = RCS_getexpand (rcs);
+       if (expand != NULL && STREQ (expand, "b"))
+           binary = 1;
+       else
+           binary = 0;
+    }
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    /* If CVS is to deal properly with special files (when
+       PreservePermissions is on), the best way is to check out the
+       revision to a temporary file and call `xcmp' on the two disk
+       files.  xcmp needs to handle non-regular files properly anyway,
+       so calling it simplifies RCS_cmp_file.  We *could* just yank
+       the delta node out of the version tree and look for device
+       numbers, but writing to disk and calling xcmp is a better
+       abstraction (therefore probably more robust). -twp */
+
+    if (preserve_perms)
+    {
+       char *tmp;
+       int retcode;
+
+       tmp = cvs_temp_name();
+       retcode = RCS_checkout(rcs, NULL, rev, NULL, options, tmp, NULL, NULL);
+       if (retcode != 0)
+           return 1;
+
+       retcode = xcmp (tmp, filename);
+       if (CVS_UNLINK (tmp) < 0)
+           error (0, errno, "cannot remove %s", tmp);
+       free (tmp);
+       return retcode;
+    }
+    else
+#endif
+    {
+       FILE *fp;
+       struct cmp_file_data data;
+       const char *use_file1;
+       char *tmpfile = NULL;
+
+       if (rev2 != NULL)
+       {
+           /* Open & cache rev1 */
+           tmpfile = cvs_temp_name();
+           if (RCS_checkout (rcs, NULL, rev1, NULL, options, tmpfile,
+                             NULL, NULL))
+               error (1, errno,
+                      "cannot check out revision %s of %s",
+                      rev1, rcs->print_path);
+           use_file1 = tmpfile;
+           if (rev1_cache != NULL)
+               *rev1_cache = tmpfile;
+       }
+       else
+           use_file1 = filename;
+
+        fp = CVS_FOPEN (use_file1, binary ? FOPEN_BINARY_READ : "r");
+       if (fp == NULL)
+           /* FIXME-update-dir: should include update_dir in message.  */
+           error (1, errno, "cannot open file %s for comparing", use_file1);
+       
+        data.filename = use_file1;
+        data.fp = fp;
+        data.different = 0;
+       
+        if (RCS_checkout (rcs, NULL, rev2 ? rev2 : rev1, NULL, options,
+                          RUN_TTY, cmp_file_buffer, &data ))
+               error (1, errno,
+                      "cannot check out revision %s of %s",
+                      rev2 ? rev2 : rev1, rcs->print_path);
+
+        /* If we have not yet found a difference, make sure that we are at
+           the end of the file.  */
+        if (!data.different)
+        {
+           if (getc (fp) != EOF)
+               data.different = 1;
+        }
+       
+        fclose (fp);
+       if (rev1_cache == NULL && tmpfile)
+       {
+           if (CVS_UNLINK (tmpfile ) < 0)
+               error (0, errno, "cannot remove %s", tmpfile);
+           free (tmpfile);
+       }
+
+        return data.different;
+    }
+}
+
+
+
+/* This is a subroutine of RCS_cmp_file.  It is passed to
+   RCS_checkout.  */
+#define CMP_BUF_SIZE (8 * 1024)
+
+static void
+cmp_file_buffer (void *callerdat, const char *buffer, size_t len)
+{
+    struct cmp_file_data *data = callerdat;
+    char *filebuf;
+
+    /* If we've already found a difference, we don't need to check
+       further.  */
+    if (data->different)
+       return;
+
+    filebuf = xmalloc (len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len);
+
+    while (len > 0)
+    {
+       size_t checklen;
+
+       checklen = len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len;
+       if (fread (filebuf, 1, checklen, data->fp) != checklen)
+       {
+           if (ferror (data->fp))
+               error (1, errno, "cannot read file %s for comparing",
+                      data->filename);
+           data->different = 1;
+           free (filebuf);
+           return;
+       }
+
+       if (memcmp (filebuf, buffer, checklen) != 0)
+       {
+           data->different = 1;
+           free (filebuf);
+           return;
+       }
+
+       buffer += checklen;
+       len -= checklen;
+    }
+
+    free (filebuf);
+}
+
+
+
+/* For RCS file RCS, make symbolic tag TAG point to revision REV.
+   This validates that TAG is OK for a user to use.  Return value is
+   -1 for error (and errno is set to indicate the error), positive for
+   error (and an error message has been printed), or zero for success.  */
+int
+RCS_settag (RCSNode *rcs, const char *tag, const char *rev)
+{
+    List *symbols;
+    Node *node;
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    /* FIXME: This check should be moved to RCS_check_tag.  There is no
+       reason for it to be here.  */
+    if (STREQ (tag, TAG_BASE)
+       || STREQ (tag, TAG_HEAD)
+       || *tag == '.')
+    {
+       /* Print the name of the tag might be considered redundant
+          with the caller, which also prints it.  Perhaps this helps
+          clarify why the tag name is considered reserved, I don't
+          know.  */
+       error (0, 0, "Attempt to add reserved tag name %s", tag);
+       return 1;
+    }
+
+    /* A revision number of NULL means use the head or default branch.
+       If rev is not NULL, it may be a symbolic tag or branch number;
+       expand it to the correct numeric revision or branch head. */
+    if (rev == NULL)
+       rev = rcs->branch ? rcs->branch : rcs->head;
+
+    /* At this point rcs->symbol_data may not have been parsed.
+       Calling RCS_symbols will force it to be parsed into a list
+       which we can easily manipulate.  */
+    symbols = RCS_symbols (rcs);
+    if (symbols == NULL)
+    {
+       symbols = getlist ();
+       rcs->symbols = symbols;
+    }
+    node = findnode (symbols, tag);
+    if (node != NULL)
+    {
+       free (node->data);
+       node->data = xstrdup (rev);
+    }
+    else
+    {
+       node = getnode ();
+       node->key = xstrdup (tag);
+       node->data = xstrdup (rev);
+       (void)addnode_at_front (symbols, node);
+    }
+
+    return 0;
+}
+
+
+
+/* Delete the symbolic tag TAG from the RCS file RCS.  Return 0 if
+   the tag was found (and removed), or 1 if it was not present.  (In
+   either case, the tag will no longer be in RCS->SYMBOLS.) */
+int
+RCS_deltag (RCSNode *rcs, const char *tag)
+{
+    List *symbols;
+    Node *node;
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    symbols = RCS_symbols (rcs);
+    if (symbols == NULL)
+       return 1;
+
+    node = findnode (symbols, tag);
+    if (node == NULL)
+       return 1;
+
+    delnode (node);
+
+    return 0;
+}
+
+
+
+/* Set the default branch of RCS to REV.  */
+int
+RCS_setbranch (RCSNode *rcs, const char *rev)
+{
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (rev && ! *rev)
+       rev = NULL;
+
+    if (rev == NULL && rcs->branch == NULL)
+       return 0;
+    if (rev != NULL && rcs->branch != NULL && STREQ (rev, rcs->branch))
+       return 0;
+
+    if (rcs->branch != NULL)
+       free (rcs->branch);
+    rcs->branch = xstrdup (rev);
+
+    return 0;
+}
+
+
+
+/* Lock revision REV.  LOCK_QUIET is 1 to suppress output.  FIXME:
+   Most of the callers only call us because RCS_checkin still tends to
+   like a lock (a relic of old behavior inherited from the RCS ci
+   program).  If we clean this up, only "cvs admin -l" will still need
+   to call RCS_lock.  */
+
+/* FIXME-twp: if a lock owned by someone else is broken, should this
+   send mail to the lock owner?  Prompt user?  It seems like such an
+   obscure situation for CVS as almost not worth worrying much
+   about. */
+int
+RCS_lock (RCSNode *rcs, const char *rev, int lock_quiet)
+{
+    List *locks;
+    Node *p;
+    char *user;
+    char *xrev = NULL;
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    locks = RCS_getlocks (rcs);
+    if (locks == NULL)
+       locks = rcs->locks = getlist();
+    user = getcaller();
+
+    /* A revision number of NULL means lock the head or default branch. */
+    if (rev == NULL)
+       xrev = RCS_head (rcs);
+    else
+       xrev = RCS_gettag (rcs, rev, 1, NULL);
+
+    /* Make sure that the desired revision exists.  Technically,
+       we can update the locks list without even checking this,
+       but RCS 5.7 did this.  And it can't hurt. */
+    if (xrev == NULL || findnode (rcs->versions, xrev) == NULL)
+    {
+       if (!lock_quiet)
+           error (0, 0, "%s: revision %s absent", rcs->print_path, rev);
+       free (xrev);
+       return 1;
+    }
+
+    /* Is this rev already locked? */
+    p = findnode (locks, xrev);
+    if (p != NULL)
+    {
+       if (STREQ (p->data, user))
+       {
+           /* We already own the lock on this revision, so do nothing. */
+           free (xrev);
+           return 0;
+       }
+
+#if 0
+       /* Well, first of all, "rev" below should be "xrev" to avoid
+          core dumps.  But more importantly, should we really be
+          breaking the lock unconditionally?  What CVS 1.9 does (via
+          RCS) is to prompt "Revision 1.1 is already locked by fred.
+          Do you want to break the lock? [ny](n): ".  Well, we don't
+          want to interact with the user (certainly not at the
+          server/protocol level, and probably not in the command-line
+          client), but isn't it more sensible to give an error and
+          let the user run "cvs admin -u" if they want to break the
+          lock?  */
+
+       /* Break the lock. */       
+       if (!lock_quiet)
+       {
+           cvs_output (rev, 0);
+           cvs_output (" unlocked\n", 0);
+       }
+       delnode (p);
+#else
+       error (1, 0, "Revision %s is already locked by %s",
+               xrev, (char *)p->data);
+#endif
+    }
+
+    /* Create a new lock. */
+    p = getnode();
+    p->key = xrev;     /* already xstrdupped */
+    p->data = xstrdup (getcaller());
+    (void)addnode_at_front (locks, p);
+
+    if (!lock_quiet)
+    {
+       cvs_output (xrev, 0);
+       cvs_output (" locked\n", 0);
+    }
+
+    return 0;
+}
+
+
+
+/* Unlock revision REV.  UNLOCK_QUIET is 1 to suppress output.  FIXME:
+   Like RCS_lock, this can become a no-op if we do the checkin
+   ourselves.
+
+   If REV is not null and is locked by someone else, break their
+   lock and notify them.  It is an open issue whether RCS_unlock
+   queries the user about whether or not to break the lock. */
+int
+RCS_unlock (RCSNode *rcs, char *rev, int unlock_quiet)
+{
+    Node *lock;
+    List *locks;
+    char *user;
+    char *xrev = NULL;
+
+    user = getcaller();
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    /* If rev is NULL, unlock the revision held by the caller; if more
+       than one, make the user specify the revision explicitly.  This
+       differs from RCS which unlocks the latest revision (first in
+       rcs->locks) held by the caller. */
+    if (rev == NULL)
+    {
+       Node *p;
+
+       /* No-ops: attempts to unlock an empty tree or an unlocked file. */
+       if (rcs->head == NULL)
+       {
+           if (!unlock_quiet)
+               cvs_outerr ("can't unlock an empty tree\n", 0);
+           return 0;
+       }
+
+       locks = RCS_getlocks (rcs);
+       if (locks == NULL)
+       {
+           if (!unlock_quiet)
+               cvs_outerr ("No locks are set.\n", 0);
+           return 0;
+       }
+
+       lock = NULL;
+       for (p = locks->list->next; p != locks->list; p = p->next)
+       {
+           if (STREQ (p->data, user))
+           {
+               if (lock != NULL)
+               {
+                   if (!unlock_quiet)
+                       error (0, 0, "\
+%s: multiple revisions locked by %s; please specify one", rcs->print_path, 
user);
+                   return 1;
+               }
+               lock = p;
+           }
+       }
+       if (lock == NULL)
+       {
+           if (!unlock_quiet)
+               error (0, 0, "No locks are set for %s.\n", user);
+           return 0;   /* no lock found, ergo nothing to do */
+       }
+       xrev = xstrdup (lock->key);
+    }
+    else
+    {
+       xrev = RCS_gettag (rcs, rev, 1, NULL);
+       if (xrev == NULL)
+       {
+           error (0, 0, "%s: revision %s absent", rcs->print_path, rev);
+           return 1;
+       }
+    }
+
+    lock = findnode (RCS_getlocks (rcs), xrev);
+    if (lock == NULL)
+    {
+       /* This revision isn't locked. */
+       free (xrev);
+       return 0;
+    }
+
+    if (! STREQ (lock->data, user))
+    {
+        /* If the revision is locked by someone else, notify
+          them.  Note that this shouldn't ever happen if RCS_unlock
+          is called with a NULL revision, since that means "whatever
+          revision is currently locked by the caller." */
+       char *repos, *workfile;
+       if (!unlock_quiet)
+           error (0, 0, "\
+%s: revision %s locked by %s; breaking lock", rcs->print_path, xrev,
+                  (char *)lock->data);
+       repos = xstrdup (rcs->path);
+       workfile = strrchr (repos, '/');
+       *workfile++ = '\0';
+       notify_do ('C', workfile, NULL, user, NULL, NULL, repos);
+       free (repos);
+    }
+
+    delnode (lock);
+    if (!unlock_quiet)
+    {
+       cvs_output (xrev, 0);
+       cvs_output (" unlocked\n", 0);
+    }
+
+    free (xrev);
+    return 0;
+}
+
+
+
+/* Add USER to the access list of RCS.  Do nothing if already present.
+   FIXME-twp: check syntax of USER to make sure it's a valid id. */
+
+void
+RCS_addaccess (RCSNode *rcs, char *user)
+{
+    char *access, *a;
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (rcs->access == NULL)
+       rcs->access = xstrdup (user);
+    else
+    {
+       access = xstrdup (rcs->access);
+       for (a = strtok (access, " "); a != NULL; a = strtok (NULL, " "))
+       {
+           if (STREQ (a, user))
+           {
+               free (access);
+               return;
+           }
+       }
+       free (access);
+       rcs->access = xrealloc (rcs->access,
+                               strlen (rcs->access) + strlen (user) + 2);
+       strcat (rcs->access, " ");
+       strcat (rcs->access, user);
+    }
+}
+
+
+
+/* Remove USER from the access list of RCS. */
+void
+RCS_delaccess (RCSNode *rcs, char *user)
+{
+    char *p, *s;
+    int ulen;
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (rcs->access == NULL)
+       return;
+
+    if (user == NULL)
+    {
+        free (rcs->access);
+        rcs->access = NULL;
+        return;
+    }
+
+    p = rcs->access;
+    ulen = strlen (user);
+    while (p != NULL)
+    {
+       if (strncmp (p, user, ulen) == 0 && (p[ulen] == '\0' || p[ulen] == ' '))
+           break;
+       p = strchr (p, ' ');
+       if (p != NULL)
+           ++p;
+    }
+
+    if (p == NULL)
+       return;
+
+    s = p + ulen;
+    while (*s != '\0')
+       *p++ = *s++;
+    *p = '\0';
+}
+
+
+
+char *
+RCS_getaccess (RCSNode *rcs)
+{
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    return rcs->access;
+}
+
+
+
+/* Return a nonzero value if the revision specified by ARG is found.  */
+static int
+findtag (Node *node, void *arg)
+{
+    char *rev = arg;
+
+    if (STREQ (node->data, rev))
+       return 1;
+    else
+       return 0;
+}
+
+
+
+/* Delete revisions between REV1 and REV2.  The changes between the two
+   revisions must be collapsed, and the result stored in the revision
+   immediately preceding the lower one.  Return 0 for successful completion,
+   1 otherwise.
+
+   Solution: check out the revision preceding REV1 and the revision
+   following REV2.  Use call_diff to find aggregate diffs between
+   these two revisions, and replace the delta text for the latter one
+   with the new aggregate diff.  Alternatively, we could write a
+   function that takes two change texts and combines them to produce a
+   new change text, without checking out any revs or calling diff.  It
+   would be hairy, but so, so cool.
+
+   If INCLUSIVE is set, then TAG1 and TAG2, if non-NULL, tell us to
+   delete that revision as well (cvs admin -o tag1:tag2).  If clear,
+   delete up to but not including that revision (cvs admin -o tag1::tag2).
+   This does not affect TAG1 or TAG2 being NULL; the meaning of the start
+   point in ::tag2 and :tag2 is the same and likewise for end points.  */
+int
+RCS_delete_revs (RCSNode *rcs, char *tag1, char *tag2, int inclusive)
+{
+    char *next;
+    Node *nodep;
+    RCSVers *revp = NULL;
+    RCSVers *beforep;
+    int status, found;
+    int save_noexec;
+
+    char *branchpoint = NULL;
+    char *rev1 = NULL;
+    char *rev2 = NULL;
+    int rev1_inclusive = inclusive;
+    int rev2_inclusive = inclusive;
+    char *before = NULL;
+    char *after = NULL;
+    char *beforefile = NULL;
+    char *afterfile = NULL;
+    char *outfile = NULL;
+
+    if (tag1 == NULL && tag2 == NULL)
+       return 0;
+
+    /* Assume error status until everything is finished. */
+    status = 1;
+
+    /* Make sure both revisions exist. */
+    if (tag1 != NULL)
+    {
+       rev1 = RCS_gettag (rcs, tag1, 1, NULL);
+       if (rev1 == NULL || (nodep = findnode (rcs->versions, rev1)) == NULL)
+       {
+           error (0, 0, "%s: Revision %s doesn't exist.", rcs->print_path, 
tag1);
+           goto delrev_done;
+       }
+    }
+    if (tag2 != NULL)
+    {
+       rev2 = RCS_gettag (rcs, tag2, 1, NULL);
+       if (rev2 == NULL || (nodep = findnode (rcs->versions, rev2)) == NULL)
+       {
+           error (0, 0, "%s: Revision %s doesn't exist.", rcs->print_path, 
tag2);
+           goto delrev_done;
+       }
+    }
+
+    /* If rev1 is on the trunk and rev2 is NULL, rev2 should be
+       RCS->HEAD.  (*Not* RCS_head(rcs), which may return rcs->branch
+       instead.)  We need to check this special case early, in order
+       to make sure that rev1 and rev2 get ordered correctly. */
+    if (rev2 == NULL && numdots (rev1) == 1)
+    {
+       rev2 = xstrdup (rcs->head);
+       rev2_inclusive = 1;
+    }
+
+    if (rev2 == NULL)
+       rev2_inclusive = 1;
+
+    if (rev1 != NULL && rev2 != NULL)
+    {
+       /* A range consisting of a branch number means the latest revision
+          on that branch. */
+       if (RCS_isbranch (rcs, rev1) && STREQ (rev1, rev2))
+       {
+           char *tmp = RCS_getbranch (rcs, rev1, 0);
+           free (rev1);
+           free (rev2);
+           rev1 = rev2 = tmp;
+       }
+       else
+       {
+           /* Make sure REV1 and REV2 are ordered correctly (in the
+              same order as the next field).  For revisions on the
+              trunk, REV1 should be higher than REV2; for branches,
+              REV1 should be lower.  */
+           /* Shouldn't we just be giving an error in the case where
+              the user specifies the revisions in the wrong order
+              (that is, always swap on the trunk, never swap on a
+              branch, in the non-error cases)?  It is not at all
+              clear to me that users who specify -o 1.4:1.2 really
+              meant to type -o 1.2:1.4, and the out of order usage
+              has never been documented, either by cvs.texinfo or
+              rcs(1).  */
+           char *temp;
+           int temp_inclusive;
+           if (numdots (rev1) == 1)
+           {
+               if (compare_revnums (rev1, rev2) <= 0)
+               {
+                   temp = rev2;
+                   rev2 = rev1;
+                   rev1 = temp;
+
+                   temp_inclusive = rev2_inclusive;
+                   rev2_inclusive = rev1_inclusive;
+                   rev1_inclusive = temp_inclusive;
+               }
+           }
+           else if (compare_revnums (rev1, rev2) > 0)
+           {
+               temp = rev2;
+               rev2 = rev1;
+               rev1 = temp;
+
+               temp_inclusive = rev2_inclusive;
+               rev2_inclusive = rev1_inclusive;
+               rev1_inclusive = temp_inclusive;
+           }
+       }
+    }
+
+    /* Basically the same thing; make sure that the ordering is what we
+       need.  */
+    if (rev1 == NULL)
+    {
+       assert (rev2 != NULL);
+       if (numdots (rev2) == 1)
+       {
+           /* Swap rev1 and rev2.  */
+           int temp_inclusive;
+
+           rev1 = rev2;
+           rev2 = NULL;
+
+           temp_inclusive = rev2_inclusive;
+           rev2_inclusive = rev1_inclusive;
+           rev1_inclusive = temp_inclusive;
+       }
+    }
+
+    /* Put the revision number preceding the first one to delete into
+       BEFORE (where "preceding" means according to the next field).
+       If the first revision to delete is the first revision on its
+       branch (e.g. 1.3.2.1), BEFORE should be the node on the trunk
+       at which the branch is rooted.  If the first revision to delete
+       is the head revision of the trunk, set BEFORE to NULL.
+
+       Note that because BEFORE may not be on the same branch as REV1,
+       it is not very handy for navigating the revision tree.  It's
+       most useful just for checking out the revision preceding REV1. */
+    before = NULL;
+    branchpoint = RCS_getbranchpoint (rcs, rev1 != NULL ? rev1 : rev2);
+    if (rev1 == NULL)
+    {
+       rev1 = xstrdup (branchpoint);
+       if (numdots (branchpoint) > 1)
+       {
+           char *bp;
+           bp = strrchr (branchpoint, '.');
+           while (*--bp != '.')
+               ;
+           *bp = '\0';
+           /* Note that this is exclusive, always, because the inclusive
+              flag doesn't affect the meaning when rev1 == NULL.  */
+           before = xstrdup (branchpoint);
+           *bp = '.';
+       }
+    }
+    else if (! STREQ (rev1, branchpoint))
+    {
+       /* Walk deltas from BRANCHPOINT on, looking for REV1. */
+       nodep = findnode (rcs->versions, branchpoint);
+       revp = nodep->data;
+       while (revp->next != NULL && ! STREQ (revp->next, rev1))
+       {
+           revp = nodep->data;
+           nodep = findnode (rcs->versions, revp->next);
+       }
+       if (revp->next == NULL)
+       {
+           error (0, 0, "%s: Revision %s doesn't exist.", rcs->print_path, 
rev1);
+           goto delrev_done;
+       }
+       if (rev1_inclusive)
+           before = xstrdup (revp->version);
+       else
+       {
+           before = rev1;
+           nodep = findnode (rcs->versions, before);
+           rev1 = xstrdup (((RCSVers *)nodep->data)->next);
+       }
+    }
+    else if (!rev1_inclusive)
+    {
+       before = rev1;
+       nodep = findnode (rcs->versions, before);
+       rev1 = xstrdup (((RCSVers *)nodep->data)->next);
+    }
+    else if (numdots (branchpoint) > 1)
+    {
+       /* Example: rev1 is "1.3.2.1", branchpoint is "1.3.2.1".
+          Set before to "1.3".  */
+       char *bp;
+       bp = strrchr (branchpoint, '.');
+       while (*--bp != '.')
+           ;
+       *bp = '\0';
+       before = xstrdup (branchpoint);
+       *bp = '.';
+    }
+
+    /* If any revision between REV1 and REV2 is locked or is a branch point,
+       we can't delete that revision and must abort. */
+    after = NULL;
+    next = rev1;
+    found = 0;
+    while (!found && next != NULL)
+    {
+       nodep = findnode (rcs->versions, next);
+       revp = nodep->data;
+
+       if (rev2 != NULL)
+           found = STREQ (revp->version, rev2);
+       next = revp->next;
+
+       if ((!found && next != NULL) || rev2_inclusive || rev2 == NULL)
+       {
+           if (findnode (RCS_getlocks (rcs), revp->version))
+           {
+               error (0, 0, "%s: can't remove locked revision %s",
+                      rcs->print_path,
+                      revp->version);
+               goto delrev_done;
+           }
+           if (revp->branches != NULL)
+           {
+               error (0, 0, "%s: can't remove branch point %s",
+                      rcs->print_path,
+                      revp->version);
+               goto delrev_done;
+           }
+
+           /* Doing this only for the :: syntax is for compatibility.
+              See cvs.texinfo for somewhat more discussion.  */
+           if (!inclusive
+               && walklist (RCS_symbols (rcs), findtag, revp->version))
+           {
+               /* We don't print which file this happens to on the theory
+                  that the caller will print the name of the file in a
+                  more useful fashion (fullname not rcs->path).  */
+               error (0, 0, "cannot remove revision %s because it has tags",
+                      revp->version);
+               goto delrev_done;
+           }
+
+           /* It's misleading to print the `deleting revision' output
+              here, since we may not actually delete these revisions.
+              But that's how RCS does it.  Bleah.  Someday this should be
+              moved to the point where the revs are actually marked for
+              deletion. -twp */
+           cvs_output ("deleting revision ", 0);
+           cvs_output (revp->version, 0);
+           cvs_output ("\n", 1);
+       }
+    }
+
+    if (rev2 == NULL)
+       ;
+    else if (found)
+    {
+       if (rev2_inclusive)
+           after = xstrdup (next);
+       else
+           after = xstrdup (revp->version);
+    }
+    else if (!inclusive)
+    {
+       /* In the case of an empty range, for example 1.2::1.2 or
+          1.2::1.3, we want to just do nothing.  */
+       status = 0;
+       goto delrev_done;
+    }
+    else
+    {
+       /* This looks fishy in the cases where tag1 == NULL or tag2 == NULL.
+          Are those cases really impossible?  */
+       assert (tag1 != NULL);
+       assert (tag2 != NULL);
+
+       error (0, 0, "%s: invalid revision range %s:%s", rcs->print_path,
+              tag1, tag2);
+       goto delrev_done;
+    }
+
+    if (after == NULL && before == NULL)
+    {
+       /* The user is trying to delete all revisions.  While an
+          RCS file without revisions makes sense to RCS (e.g. the
+          state after "rcs -i"), CVS has never been able to cope with
+          it.  So at least for now we just make this an error.
+
+          We don't include rcs->path in the message since "cvs admin"
+          already printed "RCS file:" and the name.  */
+       error (1, 0, "attempt to delete all revisions");
+    }
+
+    /* The conditionals at this point get really hairy.  Here is the
+       general idea:
+
+       IF before != NULL and after == NULL
+         THEN don't check out any revisions, just delete them
+       IF before == NULL and after != NULL
+         THEN only check out after's revision, and use it for the new deltatext
+       ELSE
+         check out both revisions and diff -n them.  This could use
+        RCS_exec_rcsdiff with some changes, like being able
+        to suppress diagnostic messages and to direct output. */
+
+    if (after != NULL)
+    {
+       char *diffbuf;
+       size_t bufsize, len;
+
+#if defined (WOE32) && !defined (__CYGWIN32__)
+       /* FIXME: This is an awful kludge, but at least until I have
+          time to work on it a little more and test it, I'd rather
+          give a fatal error than corrupt the file.  I think that we
+          need to use "-kb" and "--binary" and "rb" to get_file
+          (probably can do it always, not just for binary files, if
+          we are consistent between the RCS_checkout and the diff).  */
+       {
+           char *expand = RCS_getexpand (rcs);
+           if (expand != NULL && STREQ (expand, "b"))
+               error (1, 0,
+                  "admin -o not implemented yet for binary on this system");
+       }
+#endif /* WOE32 */
+
+       afterfile = cvs_temp_name();
+       status = RCS_checkout (rcs, NULL, after, NULL, "-ko", afterfile,
+                              NULL, NULL);
+       if (status > 0)
+           goto delrev_done;
+
+       if (before == NULL)
+       {
+           /* We are deleting revisions from the head of the tree,
+              so must create a new head. */
+           diffbuf = NULL;
+           bufsize = 0;
+           get_file (afterfile, afterfile, "r", &diffbuf, &bufsize, &len);
+
+           save_noexec = noexec;
+           noexec = 0;
+           if (unlink_file (afterfile) < 0)
+               error (0, errno, "cannot remove %s", afterfile);
+           noexec = save_noexec;
+
+           free (afterfile);
+           afterfile = NULL;
+
+           free (rcs->head);
+           rcs->head = xstrdup (after);
+       }
+       else
+       {
+           int dargc = 0;
+           size_t darg_allocated = 0;
+           char **dargv = NULL;
+
+           beforefile = cvs_temp_name();
+           status = RCS_checkout (rcs, NULL, before, NULL, "-ko", beforefile,
+                                  NULL, NULL);
+           if (status > 0)
+               goto delrev_done;
+
+           outfile = cvs_temp_name();
+           run_add_arg_p (&dargc, &darg_allocated, &dargv, "-a");
+           run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n");
+           status = diff_exec (beforefile, afterfile, NULL, NULL,
+                               dargc, dargv, outfile);
+           run_arg_free_p (dargc, dargv);
+           free (dargv);
+
+           if (status == 2)
+           {
+               /* Not sure we need this message; will diff_exec already
+                  have printed an error?  */
+               error (0, 0, "%s: could not diff", rcs->print_path);
+               status = 1;
+               goto delrev_done;
+           }
+
+           diffbuf = NULL;
+           bufsize = 0;
+           get_file (outfile, outfile, "r", &diffbuf, &bufsize, &len);
+       }
+
+       /* Save the new change text in after's delta node. */
+       nodep = findnode (rcs->versions, after);
+       revp = nodep->data;
+
+       assert (revp->text == NULL);
+
+       revp->text = xmalloc (sizeof (Deltatext));
+       memset (revp->text, 0, sizeof (Deltatext));
+       revp->text->version = xstrdup (revp->version);
+       revp->text->text = diffbuf;
+       revp->text->len = len;
+
+       /* If DIFFBUF is NULL, it means that OUTFILE is empty and that
+          there are no differences between the two revisions.  In that
+          case, we want to force RCS_copydeltas to write an empty string
+          for the new change text (leaving the text field set NULL
+          means "preserve the original change text for this delta," so
+          we don't want that). */
+       if (revp->text->text == NULL)
+           revp->text->text = xstrdup ("");
+    }
+
+    /* Walk through the revisions (again) to mark each one as
+       outdated.  (FIXME: would it be safe to use the `dead' field for
+       this?  Doubtful.) */
+    for (next = rev1;
+        next != NULL && (after == NULL || ! STREQ (next, after));
+        next = revp->next)
+    {
+       nodep = findnode (rcs->versions, next);
+       revp = nodep->data;
+       revp->outdated = 1;
+    }
+
+    /* Update delta links.  If BEFORE == NULL, we're changing the
+       head of the tree and don't need to update any `next' links. */
+    if (before != NULL)
+    {
+       /* If REV1 is the first node on its branch, then BEFORE is its
+          root node (on the trunk) and we have to update its branches
+          list.  Otherwise, BEFORE is on the same branch as AFTER, and
+          we can just change BEFORE's `next' field to point to AFTER.
+          (This should be safe: since findnode manages its lists via
+          the `hashnext' and `hashprev' fields, rather than `next' and
+          `prev', mucking with `next' and `prev' should not corrupt the
+          delta tree's internal structure.  Much. -twp) */
+
+       if (rev1 == NULL)
+           /* beforep's ->next field already should be equal to after,
+              which I think is always NULL in this case.  */
+           ;
+       else if (STREQ (rev1, branchpoint))
+       {
+           nodep = findnode (rcs->versions, before);
+           revp = nodep->data;
+           nodep = revp->branches->list->next;
+           while (nodep != revp->branches->list &&
+                  ! STREQ (nodep->key, rev1))
+               nodep = nodep->next;
+           assert (nodep != revp->branches->list);
+           if (after == NULL)
+               delnode (nodep);
+           else
+           {
+               free (nodep->key);
+               nodep->key = xstrdup (after);
+           }
+       }
+       else
+       {
+           nodep = findnode (rcs->versions, before);
+           beforep = nodep->data;
+           free (beforep->next);
+           beforep->next = xstrdup (after);
+       }
+    }
+
+    status = 0;
+
+ delrev_done:
+    if (rev1 != NULL)
+       free (rev1);
+    if (rev2 && rev2 != rev1)
+       free (rev2);
+    if (branchpoint != NULL)
+       free (branchpoint);
+    if (before != NULL)
+       free (before);
+    if (after != NULL)
+       free (after);
+
+    save_noexec = noexec;
+    noexec = 0;
+    if (beforefile != NULL)
+    {
+       if (unlink_file (beforefile) < 0)
+           error (0, errno, "cannot remove %s", beforefile);
+       free (beforefile);
+    }
+    if (afterfile != NULL)
+    {
+       if (unlink_file (afterfile) < 0)
+           error (0, errno, "cannot remove %s", afterfile);
+       free (afterfile);
+    }
+    if (outfile != NULL)
+    {
+       if (unlink_file (outfile) < 0)
+           error (0, errno, "cannot remove %s", outfile);
+       free (outfile);
+    }
+    noexec = save_noexec;
+
+    return status;
+}
+
+
+
+/*
+ * TRUE if there exists a symbolic tag "tag" in file.
+ */
+int 
+RCS_exist_tag (RCSNode *rcs, char *tag)
+{
+
+    assert (rcs != NULL);
+
+    if (findnode (RCS_symbols (rcs), tag))
+    return 1;
+    return 0;
+
+}
+
+
+
+/*
+ * TRUE if RCS revision number "rev" exists.
+ * This includes magic branch revisions, not found in rcs->versions, 
+ * but only in rcs->symbols, requiring a list walk to find them.
+ * Take advantage of list walk callback function already used by 
+ * RCS_delete_revs, above.
+ */
+int
+RCS_exist_rev (RCSNode *rcs, char *rev)
+{
+
+    assert (rcs != NULL);
+
+    if (rcs->flags & PARTIAL)
+       RCS_reparsercsfile (rcs, NULL, NULL);
+
+    if (findnode(rcs->versions, rev) != 0)
+       return 1;
+
+    if (walklist (RCS_symbols(rcs), findtag, rev) != 0)
+       return 1;
+
+    return 0;
+
+}
+
+
+
+
+/* RCS_deltas and friends.  Processing of the deltas in RCS files.  */
+struct line
+{
+    /* Text of this line.  Part of the same malloc'd block as the struct
+       line itself (we probably should use the "struct hack" (char text[1])
+       and save ourselves sizeof (char *) bytes).  Does not include \n;
+       instead has_newline indicates the presence or absence of \n.  */
+    char *text;
+    /* Length of this line, not counting \n if has_newline is true.  */
+    size_t len;
+    /* Version in which it was introduced.  */
+    RCSVers *vers;
+    /* Nonzero if this line ends with \n.  This will always be true
+       except possibly for the last line.  */
+    int has_newline;
+    /* Number of pointers to this struct line.  */
+    int refcount;
+};
+
+struct linevector
+{
+    /* How many lines in use for this linevector?  */
+    unsigned int nlines;
+    /* How many lines allocated for this linevector?  */
+    unsigned int lines_alloced;
+    /* Pointer to array containing a pointer to each line.  */
+    struct line **vector;
+};
+
+
+
+/* Initialize *VEC to be a linevector with no lines.  */
+static void
+linevector_init (struct linevector *vec)
+{
+    vec->lines_alloced = 0;
+    vec->nlines = 0;
+    vec->vector = NULL;
+}
+
+
+
+/* Given some text TEXT, add each of its lines to VEC before line POS
+   (where line 0 is the first line).  The last line in TEXT may or may
+   not be \n terminated.
+   Set the version for each of the new lines to VERS.  This
+   function returns non-zero for success.  It returns zero if the line
+   number is out of range.
+
+   Each of the lines in TEXT are copied to space which is managed with
+   the linevector (and freed by linevector_free).  So the caller doesn't
+   need to keep TEXT around after the call to this function.  */
+static int
+linevector_add (struct linevector *vec, const char *text, size_t len,
+               RCSVers *vers, unsigned int pos)
+{
+    const char *textend;
+    unsigned int i;
+    unsigned int nnew;
+    const char *p;
+    const char *nextline_text;
+    size_t nextline_len;
+    int nextline_newline;
+    struct line *q;
+
+    if (len == 0)
+       return 1;
+
+    textend = text + len;
+
+    /* Count the number of lines we will need to add.  */
+    nnew = 1;
+    for (p = text; p < textend; ++p)
+       if (*p == '\n' && p + 1 < textend)
+           ++nnew;
+
+    /* Expand VEC->VECTOR if needed.  */
+    if (vec->nlines + nnew >= vec->lines_alloced)
+    {
+       if (vec->lines_alloced == 0)
+           vec->lines_alloced = 10;
+       while (vec->nlines + nnew >= vec->lines_alloced)
+           vec->lines_alloced *= 2;
+       vec->vector = xnrealloc (vec->vector,
+                                vec->lines_alloced, sizeof (*vec->vector));
+    }
+
+    /* Make room for the new lines in VEC->VECTOR.  */
+    for (i = vec->nlines + nnew - 1; i >= pos + nnew; --i)
+       vec->vector[i] = vec->vector[i - nnew];
+
+    if (pos > vec->nlines)
+       return 0;
+
+    /* Actually add the lines, to VEC->VECTOR.  */
+    i = pos;
+    nextline_text = text;
+    nextline_newline = 0;
+    for (p = text; p < textend; ++p)
+       if (*p == '\n')
+       {
+           nextline_newline = 1;
+           if (p + 1 == textend)
+               /* If there are no characters beyond the last newline, we
+                  don't consider it another line.  */
+               break;
+           nextline_len = p - nextline_text;
+           q = xmalloc (sizeof (struct line) + nextline_len);
+           q->vers = vers;
+           q->text = (char *)q + sizeof (struct line);
+           q->len = nextline_len;
+           q->has_newline = nextline_newline;
+           q->refcount = 1;
+           memcpy (q->text, nextline_text, nextline_len);
+           vec->vector[i++] = q;
+
+           nextline_text = (char *)p + 1;
+           nextline_newline = 0;
+       }
+    nextline_len = p - nextline_text;
+    q = xmalloc (sizeof (struct line) + nextline_len);
+    q->vers = vers;
+    q->text = (char *)q + sizeof (struct line);
+    q->len = nextline_len;
+    q->has_newline = nextline_newline;
+    q->refcount = 1;
+    memcpy (q->text, nextline_text, nextline_len);
+    vec->vector[i] = q;
+
+    vec->nlines += nnew;
+
+    return 1;
+}
+
+
+
+/* Remove NLINES lines from VEC at position POS (where line 0 is the
+   first line).  */
+static void
+linevector_delete (struct linevector *vec, unsigned int pos,
+                  unsigned int nlines)
+{
+    unsigned int i;
+    unsigned int last;
+
+    last = vec->nlines - nlines;
+    for (i = pos; i < pos + nlines; ++i)
+    {
+       if (--vec->vector[i]->refcount == 0)
+           free (vec->vector[i]);
+    }
+    for (i = pos; i < last; ++i)
+       vec->vector[i] = vec->vector[i + nlines];
+    vec->nlines -= nlines;
+}
+
+
+
+/* Copy FROM to TO, copying the vectors but not the lines pointed to.  */
+static void
+linevector_copy (struct linevector *to, struct linevector *from)
+{
+    unsigned int ln;
+
+    for (ln = 0; ln < to->nlines; ++ln)
+    {
+       if (--to->vector[ln]->refcount == 0)
+           free (to->vector[ln]);
+    }
+    if (from->nlines > to->lines_alloced)
+    {
+       if (to->lines_alloced == 0)
+           to->lines_alloced = 10;
+       while (from->nlines > to->lines_alloced)
+           to->lines_alloced *= 2;
+       to->vector = xnrealloc (to->vector,
+                               to->lines_alloced,
+                               sizeof (*to->vector));
+    }
+    memcpy (to->vector, from->vector,
+           xtimes (from->nlines, sizeof (*to->vector)));
+    to->nlines = from->nlines;
+    for (ln = 0; ln < to->nlines; ++ln)
+       ++to->vector[ln]->refcount;
+}
+
+
+
+/* Free storage associated with linevector.  */
+static void
+linevector_free (struct linevector *vec)
+{
+    unsigned int ln;
+
+    if (vec->vector != NULL)
+    {
+       for (ln = 0; ln < vec->nlines; ++ln)
+           if (--vec->vector[ln]->refcount == 0)
+               free (vec->vector[ln]);
+
+       free (vec->vector);
+    }
+}
+
+
+
+/* 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 const char *
+month_printname (const 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 months[mnum - 1];
+}
+
+
+
+/* Apply changes to the line vector LINES.  DIFFBUF is a buffer of
+   length DIFFLEN holding the change text from an RCS file (the output
+   of diff -n).  NAME is used in error messages.  The VERS field of
+   any line added is set to ADDVERS.  The VERS field of any line
+   deleted is set to DELVERS, unless DELVERS is NULL, in which case
+   the VERS field of deleted lines is unchanged.  The function returns
+   non-zero if the change text is applied successfully.  It returns
+   zero if the change text does not appear to apply to LINES (e.g., a
+   line number is invalid).  If the change text is improperly
+   formatted (e.g., it is not the output of diff -n), the function
+   calls error with a status of 1, causing the program to exit.  */
+static int
+apply_rcs_changes (struct linevector *lines, const char *diffbuf,
+                  size_t difflen, const char *name, RCSVers *addvers,
+                  RCSVers *delvers)
+{
+    const char *p;
+    const char *q;
+    int op;
+    /* The RCS format throws us for a loop in that the deltafrags (if
+       we define a deltafrag as an add or a delete) need to be applied
+       in reverse order.  So we stick them into a linked list.  */
+    struct deltafrag {
+       enum {FRAG_ADD, FRAG_DELETE} type;
+       unsigned long pos;
+       unsigned long nlines;
+       const char *new_lines;
+       size_t len;
+       struct deltafrag *next;
+    };
+    struct deltafrag *dfhead;
+    struct deltafrag *df;
+    int err;
+
+    dfhead = NULL;
+    for (p = diffbuf; p != NULL && p < diffbuf + difflen; )
+    {
+       op = *p++;
+       if (op != 'a' && op != 'd')
+           /* Can't just skip over the deltafrag, because the value
+              of op determines the syntax.  */
+           error (1, 0, "unrecognized operation '\\x%x' in %s",
+                  op, name);
+       df = xmalloc (sizeof (struct deltafrag));
+       df->next = dfhead;
+       dfhead = df;
+       df->pos = strtoul (p, (char **) &q, 10);
+
+       if (p == q)
+           error (1, 0, "number expected in %s", name);
+       p = q;
+       if (*p++ != ' ')
+           error (1, 0, "space expected in %s", name);
+       df->nlines = strtoul (p, (char **) &q, 10);
+       if (p == q)
+           error (1, 0, "number expected in %s", name);
+       p = q;
+       if (*p++ != '\012')
+           error (1, 0, "linefeed expected in %s", name);
+
+       if (op == 'a')
+       {
+           unsigned int i;
+
+           df->type = FRAG_ADD;
+           i = df->nlines;
+           /* The text we want is the number of lines specified, or
+              until the end of the value, whichever comes first (it
+              will be the former except in the case where we are
+              adding a line which does not end in newline).  */
+           for (q = p; i != 0; ++q)
+               if (*q == '\n')
+                   --i;
+               else if (q == diffbuf + difflen)
+               {
+                   if (i != 1)
+                       error (1, 0, "premature end of change in %s", name);
+                   else
+                       break;
+               }
+
+           /* Stash away a pointer to the text we are adding.  */
+           df->new_lines = p;
+           df->len = q - p;
+
+           p = q;
+       }
+       else
+       {
+           /* Correct for the fact that line numbers in RCS files
+              start with 1.  */
+           --df->pos;
+
+           assert (op == 'd');
+           df->type = FRAG_DELETE;
+       }
+    }
+
+    err = 0;
+    for (df = dfhead; df != NULL;)
+    {
+       unsigned int ln;
+
+       /* Once an error is encountered, just free the rest of the list and
+        * return.
+        */
+       if (!err)
+           switch (df->type)
+           {
+           case FRAG_ADD:
+               if (! linevector_add (lines, df->new_lines, df->len, addvers,
+                                     df->pos))
+                   err = 1;
+               break;
+           case FRAG_DELETE:
+               if (df->pos > lines->nlines
+                   || df->pos + df->nlines > lines->nlines)
+                   return 0;
+               if (delvers != NULL)
+                   for (ln = df->pos; ln < df->pos + df->nlines; ++ln)
+                       lines->vector[ln]->vers = delvers;
+               linevector_delete (lines, df->pos, df->nlines);
+               break;
+           }
+
+       df = df->next;
+       free (dfhead);
+       dfhead = df;
+    }
+
+    return !err;
+}
+
+
+
+/* Apply an RCS change text to a buffer.  The function name starts
+   with rcs rather than RCS because this does not take an RCSNode
+   argument.  NAME is used in error messages.  TEXTBUF is the text
+   buffer to change, and TEXTLEN is the size.  DIFFBUF and DIFFLEN are
+   the change buffer and size.  The new buffer is returned in *RETBUF
+   and *RETLEN.  The new buffer is allocated by xmalloc.
+
+   Return 1 for success.  On failure, call error and return 0.  */
+int
+rcs_change_text (const char *name, char *textbuf, size_t textlen,
+                const char *diffbuf, size_t difflen, char **retbuf,
+                size_t *retlen)
+{
+    struct linevector lines;
+    int ret;
+
+    *retbuf = NULL;
+    *retlen = 0;
+
+    linevector_init (&lines);
+
+    if (! linevector_add (&lines, textbuf, textlen, NULL, 0))
+       error (1, 0, "cannot initialize line vector");
+
+    if (! apply_rcs_changes (&lines, diffbuf, difflen, name, NULL, NULL))
+    {
+       error (0, 0, "invalid change text in %s", name);
+       ret = 0;
+    }
+    else
+    {
+       char *p;
+       size_t n;
+       unsigned int ln;
+
+       n = 0;
+       for (ln = 0; ln < lines.nlines; ++ln)
+           /* 1 for \n */
+           n += lines.vector[ln]->len + 1;
+
+       p = xmalloc (n);
+       *retbuf = p;
+
+       for (ln = 0; ln < lines.nlines; ++ln)
+       {
+           memcpy (p, lines.vector[ln]->text, lines.vector[ln]->len);
+           p += lines.vector[ln]->len;
+           if (lines.vector[ln]->has_newline)
+               *p++ = '\n';
+       }
+
+       *retlen = p - *retbuf;
+       assert (*retlen <= n);
+
+       ret = 1;
+    }
+
+    linevector_free (&lines);
+
+    return ret;
+}
+
+
+
+/* Walk the deltas in RCS to get to revision VERSION.
+
+   If OP is RCS_ANNOTATE, then write annotations using cvs_output.
+
+   If OP is RCS_FETCH, then put the contents of VERSION into a
+   newly-malloc'd array and put a pointer to it in *TEXT.  Each line
+   is \n terminated; the caller is responsible for converting text
+   files if desired.  The total length is put in *LEN.
+
+   If FP is non-NULL, it should be a file descriptor open to the file
+   RCS with file position pointing to the deltas.  We close the file
+   when we are done.
+
+   If LOG is non-NULL, then *LOG is set to the log message of VERSION,
+   and *LOGLEN is set to the length of the log message.
+
+   On error, give a fatal error.  */
+void
+RCS_deltas (RCSNode *rcs, FILE *fp, struct rcsbuffer *rcsbuf,
+            const char *version, enum rcs_delta_op op, char **text,
+            size_t *len, char **log, size_t *loglen)
+{
+    struct rcsbuffer rcsbuf_local;
+    char *branchversion;
+    char *cpversion;
+    char *key;
+    char *value;
+    size_t vallen;
+    RCSVers *vers;
+    RCSVers *prev_vers;
+    RCSVers *trunk_vers;
+    char *next;
+    int ishead, isnext, isversion, onbranch;
+    Node *node;
+    struct linevector headlines;
+    struct linevector curlines;
+    struct linevector trunklines;
+    int foundhead;
+
+    assert (version);
+
+    if (fp == NULL)
+    {
+       rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf_local);
+       rcsbuf = &rcsbuf_local;
+    }
+
+   if (log) *log = NULL;
+
+    ishead = 1;
+    vers = NULL;
+    prev_vers = NULL;
+    trunk_vers = NULL;
+    next = NULL;
+    onbranch = 0;
+    foundhead = 0;
+
+    linevector_init (&curlines);
+    linevector_init (&headlines);
+    linevector_init (&trunklines);
+
+    /* We set BRANCHVERSION to the version we are currently looking
+       for.  Initially, this is the version on the trunk from which
+       VERSION branches off.  If VERSION is not a branch, then
+       BRANCHVERSION is just VERSION.  */
+    branchversion = xstrdup (version);
+    cpversion = strchr (branchversion, '.');
+    if (cpversion != NULL)
+        cpversion = strchr (cpversion + 1, '.');
+    if (cpversion != NULL)
+        *cpversion = '\0';
+
+    do {
+       if (! rcsbuf_getrevnum (rcsbuf, &key))
+           error (1, 0, "unexpected EOF reading RCS file %s", rcs->print_path);
+
+       if (next != NULL && ! STREQ (next, key))
+       {
+           /* This is not the next version we need.  It is a branch
+               version which we want to ignore.  */
+           isnext = 0;
+           isversion = 0;
+       }
+       else
+       {
+           isnext = 1;
+
+           /* look up the revision */
+           node = findnode (rcs->versions, key);
+           if (node == NULL)
+               error (1, 0,
+                      "mismatch in rcs file %s between deltas and deltatexts 
(%s)",
+                      rcs->print_path, key);
+
+           /* Stash the previous version.  */
+           prev_vers = vers;
+
+           vers = node->data;
+           next = vers->next;
+
+           /* Compare key and trunkversion now, because key points to
+              storage controlled by rcsbuf_getkey.  */
+           if (STREQ (branchversion, key))
+               isversion = 1;
+           else
+               isversion = 0;
+       }
+
+       while (1)
+       {
+           if (! rcsbuf_getkey (rcsbuf, &key, &value))
+               error (1, 0, "%s does not appear to be a valid rcs file",
+                      rcs->print_path);
+
+           if (log != NULL
+               && isversion
+               && STREQ (key, "log")
+               && STREQ (branchversion, version))
+           {
+               if (*log != NULL)
+               {
+                   error (0, 0, "Duplicate `log' keyword in RCS file (`%s').",
+                          rcs->print_path);
+                   free (*log);
+               }
+               *log = rcsbuf_valcopy (rcsbuf, value, 0, loglen);
+           }
+
+           if (STREQ (key, "text"))
+           {
+               rcsbuf_valpolish (rcsbuf, value, 0, &vallen);
+               if (ishead)
+               {
+                   if (! linevector_add (&curlines, value, vallen, NULL, 0))
+                       error (1, 0, "invalid rcs file %s", rcs->print_path);
+
+                   ishead = 0;
+               }
+               else if (isnext)
+               {
+                   if (! apply_rcs_changes (&curlines, value, vallen,
+                                            rcs->path,
+                                            onbranch ? vers : NULL,
+                                            onbranch ? NULL : prev_vers))
+                       error (1, 0, "invalid change text in %s", 
rcs->print_path);
+               }
+               break;
+           }
+       }
+
+       if (isversion)
+       {
+           /* This is either the version we want, or it is the
+               branchpoint to the version we want.  */
+           if (STREQ (branchversion, version))
+           {
+               /* This is the version we want.  */
+               linevector_copy (&headlines, &curlines);
+               foundhead = 1;
+               if (onbranch)
+               {
+                   /* We have found this version by tracking up a
+                       branch.  Restore back to the lines we saved
+                       when we left the trunk, and continue tracking
+                       down the trunk.  */
+                   onbranch = 0;
+                   vers = trunk_vers;
+                   next = vers->next;
+                   linevector_copy (&curlines, &trunklines);
+               }
+           }
+           else
+           {
+               Node *p;
+
+               /* We need to look up the branch.  */
+               onbranch = 1;
+
+               if (numdots (branchversion) < 2)
+               {
+                   unsigned int ln;
+
+                   /* We are leaving the trunk; save the current
+                       lines so that we can restore them when we
+                       continue tracking down the trunk.  */
+                   trunk_vers = vers;
+                   linevector_copy (&trunklines, &curlines);
+
+                   /* Reset the version information we have
+                       accumulated so far.  It only applies to the
+                       changes from the head to this version.  */
+                   for (ln = 0; ln < curlines.nlines; ++ln)
+                       curlines.vector[ln]->vers = NULL;
+               }
+
+               /* The next version we want is the entry on
+                   VERS->branches which matches this branch.  For
+                   example, suppose VERSION is 1.21.4.3 and
+                   BRANCHVERSION was 1.21.  Then we look for an entry
+                   starting with "1.21.4" and we'll put it (probably
+                   1.21.4.1) in NEXT.  We'll advance BRANCHVERSION by
+                   two dots (in this example, to 1.21.4.3).  */
+
+               if (vers->branches == NULL)
+                   error (1, 0, "missing expected branches in %s",
+                          rcs->print_path);
+               if (!cpversion)
+                   error (1, 0, "Invalid revision number in `%s'.",
+                          rcs->print_path);
+               *cpversion = '.';
+               ++cpversion;
+               cpversion = strchr (cpversion, '.');
+               if (cpversion == NULL)
+                   error (1, 0, "version number confusion in %s",
+                          rcs->print_path);
+               for (p = vers->branches->list->next;
+                    p != vers->branches->list;
+                    p = p->next)
+                   if (strncmp (p->key, branchversion,
+                                cpversion - branchversion) == 0)
+                       break;
+               if (p == vers->branches->list)
+                   error (1, 0, "missing expected branch in %s",
+                          rcs->print_path);
+
+               next = p->key;
+
+               cpversion = strchr (cpversion + 1, '.');
+               if (cpversion != NULL)
+                   *cpversion = '\0';
+           }
+       }
+       if (op == RCS_FETCH && foundhead)
+           break;
+    } while (next != NULL);
+
+    free (branchversion);
+
+    rcsbuf_cache (rcs, rcsbuf);
+
+    if (! foundhead)
+        error (1, 0, "could not find desired version %s in %s",
+              version, rcs->print_path);
+
+    /* Now print out or return the data we have just computed.  */
+    switch (op)
+    {
+       case RCS_ANNOTATE:
+           {
+               unsigned int ln;
+
+               for (ln = 0; ln < headlines.nlines; ++ln)
+               {
+                   char *buf;
+                   /* 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;
+
+                   buf = Xasprintf ("%-12s (%-*.*s ",
+                                    prvers->version,
+                                    annotate_width, annotate_width,
+                                    prvers->author);
+                   cvs_output (buf, 0);
+                   free (buf);
+
+                   /* Now output the date.  */
+                   ym = strchr (prvers->date, '.');
+                   if (ym == NULL)
+                   {
+                       cvs_output ("??", 0);
+                       cvs_output ("-???", 0);
+                       cvs_output ("-??", 0);
+                   }
+                   else
+                   {
+                       md = strchr (ym + 1, '.');
+                       if (md == NULL)
+                           cvs_output ("??", 0);
+                       else
+                           cvs_output (md + 1, 2);
+
+                       cvs_output ("-", 1);
+                       cvs_output (month_printname (ym + 1), 0);
+                       cvs_output ("-", 1);
+                       /* Only output the last two digits of the year.  Our 
output
+                          lines are long enough as it is without printing the
+                          century.  */
+                       cvs_output (ym - 2, 2);
+                   }
+                   cvs_output ("): ", 0);
+                   if (headlines.vector[ln]->len != 0)
+                       cvs_output (headlines.vector[ln]->text,
+                                   headlines.vector[ln]->len);
+                   cvs_output ("\n", 1);
+               }
+           }
+           break;
+       case RCS_FETCH:
+           {
+               char *p;
+               size_t n;
+               unsigned int ln;
+
+               assert (text != NULL);
+               assert (len != NULL);
+
+               n = 0;
+               for (ln = 0; ln < headlines.nlines; ++ln)
+                   /* 1 for \n */
+                   n += headlines.vector[ln]->len + 1;
+               p = xmalloc (n);
+               *text = p;
+               for (ln = 0; ln < headlines.nlines; ++ln)
+               {
+                   memcpy (p, headlines.vector[ln]->text,
+                           headlines.vector[ln]->len);
+                   p += headlines.vector[ln]->len;
+                   if (headlines.vector[ln]->has_newline)
+                       *p++ = '\n';
+               }
+               *len = p - *text;
+               assert (*len <= n);
+           }
+           break;
+    }
+
+    linevector_free (&curlines);
+    linevector_free (&headlines);
+    linevector_free (&trunklines);
+
+    return;
+}
+
+
+
+/* Read the information for a single delta from the RCS buffer RCSBUF,
+   whose name is RCSFILE.  *KEYP and *VALP are either NULL, or the
+   first key/value pair to read, as set by rcsbuf_getkey. Return NULL
+   if there are no more deltas.  Store the key/value pair which
+   terminated the read in *KEYP and *VALP.  */
+static RCSVers *
+getdelta (struct rcsbuffer *rcsbuf, char *rcsfile, char **keyp, char **valp)
+{
+    RCSVers *vnode;
+    char *key, *value, *cp;
+    Node *kv;
+
+    /* Get revision number if it wasn't passed in. This uses
+       rcsbuf_getkey because it doesn't croak when encountering
+       unexpected input.  As a result, we have to play unholy games
+       with `key' and `value'. */
+    if (*keyp != NULL)
+    {
+       key = *keyp;
+       value = *valp;
+    }
+    else
+    {
+       if (! rcsbuf_getkey (rcsbuf, &key, &value))
+           error (1, 0, "%s: unexpected EOF", rcsfile);
+    }
+
+    /* Make sure that it is a revision number and not a cabbage 
+       or something. */
+    for (cp = key;
+        (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
+        cp++)
+       /* do nothing */ ;
+    /* Note that when comparing with RCSDATE, we are not massaging
+       VALUE from the string found in the RCS file.  This is OK since
+       we know exactly what to expect.  */
+    if (*cp != '\0' || strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) != 0)
+    {
+       *keyp = key;
+       *valp = value;
+       return NULL;
+    }
+
+    vnode = xmalloc (sizeof (RCSVers));
+    memset (vnode, 0, sizeof (RCSVers));
+
+    vnode->version = xstrdup (key);
+
+    /* Grab the value of the date from value.  Note that we are not
+       massaging VALUE from the string found in the RCS file.  */
+    cp = value + (sizeof RCSDATE) - 1; /* skip the "date" keyword */
+    while (whitespace (*cp))           /* take space off front of value */
+       cp++;
+
+    vnode->date = xstrdup (cp);
+
+    /* Get author field.  */
+    if (! rcsbuf_getkey (rcsbuf, &key, &value))
+    {
+       error (1, 0, "unexpected end of file reading %s", rcsfile);
+    }
+    if (! STREQ (key, "author"))
+       error (1, 0, "\
+unable to parse %s; `author' not in the expected place", rcsfile);
+    vnode->author = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
+
+    /* Get state field.  */
+    if (! rcsbuf_getkey (rcsbuf, &key, &value))
+    {
+       error (1, 0, "unexpected end of file reading %s", rcsfile);
+    }
+    if (! STREQ (key, "state"))
+       error (1, 0, "\
+unable to parse %s; `state' not in the expected place", rcsfile);
+    vnode->state = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
+    /* The value is optional, according to rcsfile(5).  */
+    if (value != NULL && STREQ (value, RCSDEAD))
+    {
+       vnode->dead = 1;
+    }
+
+    /* Note that "branches" and "next" are in fact mandatory, according
+       to doc/RCSFILES.  */
+
+    /* fill in the branch list (if any branches exist) */
+    if (! rcsbuf_getkey (rcsbuf, &key, &value))
+    {
+       error (1, 0, "unexpected end of file reading %s", rcsfile);
+    }
+    if (STREQ (key, RCSDESC))
+    {
+       *keyp = key;
+       *valp = value;
+       /* Probably could/should be a fatal error.  */
+       error (0, 0, "warning: 'branches' keyword missing from %s", rcsfile);
+       return vnode;
+    }
+    if (value != NULL)
+    {
+       vnode->branches = getlist ();
+       /* Note that we are not massaging VALUE from the string found
+           in the RCS file.  */
+       do_branches (vnode->branches, value);
+    }
+
+    /* fill in the next field if there is a next revision */
+    if (! rcsbuf_getkey (rcsbuf, &key, &value))
+    {
+       error (1, 0, "unexpected end of file reading %s", rcsfile);
+    }
+    if (STREQ (key, RCSDESC))
+    {
+       *keyp = key;
+       *valp = value;
+       /* Probably could/should be a fatal error.  */
+       error (0, 0, "warning: 'next' keyword missing from %s", rcsfile);
+       return vnode;
+    }
+    if (value != NULL)
+       vnode->next = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
+
+    /*
+     * XXX - this is where we put the symbolic link stuff???
+     * (into newphrases in the deltas).
+     */
+    while (1)
+    {
+       if (! rcsbuf_getkey (rcsbuf, &key, &value))
+           error (1, 0, "unexpected end of file reading %s", rcsfile);
+
+       /* The `desc' keyword is the end of the deltas. */
+       if (strcmp (key, RCSDESC) == 0)
+           break;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+
+       /* The `hardlinks' value is a group of words, which must
+          be parsed separately and added as a list to vnode->hardlinks. */
+       if (strcmp (key, "hardlinks") == 0)
+       {
+           char *word;
+
+           vnode->hardlinks = getlist();
+           while ((word = rcsbuf_valword (rcsbuf, &value)) != NULL)
+           {
+               Node *n = getnode();
+               n->key = word;
+               addnode (vnode->hardlinks, n);
+           }
+           continue;
+       }
+#endif
+
+       /* Enable use of repositories created by certain obsolete
+          versions of CVS.  This code should remain indefinately;
+          there is no procedure for converting old repositories, and
+          checking for it is harmless.  */
+       if (STREQ (key, RCSDEAD))
+       {
+           vnode->dead = 1;
+           if (vnode->state != NULL)
+               free (vnode->state);
+           vnode->state = xstrdup (RCSDEAD);
+           continue;
+       }
+       /* if we have a new revision number, we're done with this delta */
+       for (cp = key;
+            (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
+            cp++)
+           /* do nothing */ ;
+       /* Note that when comparing with RCSDATE, we are not massaging
+          VALUE from the string found in the RCS file.  This is OK
+          since we know exactly what to expect.  */
+       if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
+           break;
+
+       /* At this point, key and value represent a user-defined field
+          in the delta node. */
+       if (vnode->other_delta == NULL)
+           vnode->other_delta = getlist ();
+       kv = getnode ();
+       kv->type = rcsbuf_valcmp (rcsbuf) ? RCSCMPFLD : RCSFIELD;
+       kv->key = xstrdup (key);
+       kv->data = rcsbuf_valcopy (rcsbuf, value, kv->type == RCSFIELD, NULL);
+       if (addnode (vnode->other_delta, kv) != 0)
+       {
+           /* Complaining about duplicate keys in newphrases seems
+              questionable, in that we don't know what they mean and
+              doc/RCSFILES has no prohibition on several newphrases
+              with the same key.  But we can't store more than one as
+              long as we store them in a List *.  */
+           error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
+                  key, rcsfile);
+           freenode (kv);
+       }
+    }
+
+    /* Return the key which caused us to fail back to the caller.  */
+    *keyp = key;
+    *valp = value;
+
+    return vnode;
+}
+
+
+
+static void
+freedeltatext (Deltatext *d)
+{
+    if (d->version != NULL)
+       free (d->version);
+    if (d->log != NULL)
+       free (d->log);
+    if (d->text != NULL)
+       free (d->text);
+    if (d->other != NULL)
+       dellist (&d->other);
+    free (d);
+}
+
+static Deltatext *
+RCS_getdeltatext (RCSNode *rcs, FILE *fp, struct rcsbuffer *rcsbuf)
+{
+    char *num;
+    char *key, *value;
+    Node *p;
+    Deltatext *d;
+
+    /* Get the revision number. */
+    if (! rcsbuf_getrevnum (rcsbuf, &num))
+    {
+       /* If num == NULL, it means we reached EOF naturally.  That's
+          fine. */
+       if (num == NULL)
+           return NULL;
+       else
+           error (1, 0, "%s: unexpected EOF", rcs->print_path);
+    }
+
+    p = findnode (rcs->versions, num);
+    if (p == NULL)
+       error (1, 0, "mismatch in rcs file %s between deltas and deltatexts 
(%s)",
+              rcs->print_path, num);
+
+    d = xmalloc (sizeof (Deltatext));
+    d->version = xstrdup (num);
+
+    /* Get the log message. */
+    if (! rcsbuf_getkey (rcsbuf, &key, &value))
+       error (1, 0, "%s, delta %s: unexpected EOF", rcs->print_path, num);
+    if (! STREQ (key, "log"))
+       error (1, 0, "%s, delta %s: expected `log', got `%s'",
+              rcs->print_path, num, key);
+    d->log = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
+
+    /* Get random newphrases. */
+    d->other = getlist();
+    while (1)
+    {
+       if (! rcsbuf_getkey (rcsbuf, &key, &value))
+           error (1, 0, "%s, delta %s: unexpected EOF", rcs->print_path, num);
+
+       if (STREQ (key, "text"))
+           break;
+
+       p = getnode();
+       p->type = rcsbuf_valcmp (rcsbuf) ? RCSCMPFLD : RCSFIELD;
+       p->key = xstrdup (key);
+       p->data = rcsbuf_valcopy (rcsbuf, value, p->type == RCSFIELD, NULL);
+       if (addnode (d->other, p) < 0)
+       {
+           error (0, 0, "warning: %s, delta %s: duplicate field `%s'",
+                  rcs->print_path, num, key);
+       }
+    }
+
+    /* Get the change text. We already know that this key is `text'. */
+    d->text = rcsbuf_valcopy (rcsbuf, value, 0, &d->len);
+
+    return d;
+}
+
+
+
+/* RCS output functions, for writing RCS format files from RCSNode
+   structures.
+
+   For most of this work, RCS 5.7 uses an `aprintf' function which aborts
+   program upon error.  Instead, these functions check the output status
+   of the stream right before closing it, and aborts if an error condition
+   is found.  The RCS solution is probably the better one: it produces
+   more overhead, but will produce a clearer diagnostic in the case of
+   catastrophic error.  In either case, however, the repository will probably
+   not get corrupted. */
+static int
+putsymbol_proc (Node *symnode, void *fparg)
+{
+    FILE *fp = fparg;
+
+    /* A fiddly optimization: this code used to just call fprintf, but
+       in an old repository with hundreds of tags this can get called
+       hundreds of thousands of times when doing a cvs tag.  Since
+       tagging is a relatively common operation, and using putc and
+       fputs is just as comprehensible, the change is worthwhile.  */
+    putc ('\n', fp);
+    putc ('\t', fp);
+    fputs (symnode->key, fp);
+    putc (':', fp);
+    fputs (symnode->data, fp);
+    return 0;
+}
+
+
+
+/* putlock_proc is like putsymbol_proc, but key and data are reversed. */
+static int
+putlock_proc (Node *symnode, void *fp)
+{
+    return fprintf (fp, "\n\t%s:%s", (char *)symnode->data, symnode->key);
+}
+
+
+
+static int
+putrcsfield_proc (Node *node, void *vfp)
+{
+    FILE *fp = vfp;
+
+    /* Some magic keys used internally by CVS start with `;'. Skip them. */
+    if (node->key[0] == ';')
+       return 0;
+
+    fprintf (fp, "\n%s\t", node->key);
+    if (node->data != NULL)
+    {
+       /* If the field's value contains evil characters,
+          it must be stringified. */
+       /* FIXME: This does not quite get it right.  "7jk8f" is not a valid
+          value for a value in a newpharse, according to doc/RCSFILES,
+          because digits are not valid in an "id".  We might do OK by
+          always writing strings (enclosed in @@).  Would be nice to
+          explicitly mention this one way or another in doc/RCSFILES.
+          A case where we are wrong in a much more clear-cut way is that
+          we let through non-graphic characters such as whitespace and
+          control characters.  */
+
+       if (node->type == RCSCMPFLD || strpbrk (node->data, "$,.:;@") == NULL)
+           fputs (node->data, fp);
+       else
+       {
+           putc ('@', fp);
+           expand_at_signs (node->data, (off_t) strlen (node->data), fp);
+           putc ('@', fp);
+       }
+    }
+
+    /* desc, log and text fields should not be terminated with semicolon;
+       all other fields should be. */
+    if (! STREQ (node->key, "desc") &&
+       ! STREQ (node->key, "log") &&
+       ! STREQ (node->key, "text"))
+    {
+       putc (';', fp);
+    }
+    return 0;
+}
+
+
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+
+/* Save a filename in a `hardlinks' RCS field.  NODE->KEY will contain
+   a full pathname, but currently only basenames are stored in the RCS
+   node.  Assume that the filename includes nasty characters and
+   @-escape it. */
+
+static int
+puthardlink_proc (node, vfp)
+    Node *node;
+    void *vfp;
+{
+    FILE *fp = vfp;
+    char *basename = strrchr (node->key, '/');
+
+    if (basename == NULL)
+       basename = node->key;
+    else
+       ++basename;
+
+    putc ('\t', fp);
+    putc ('@', fp);
+    (void) expand_at_signs (basename, strlen (basename), fp);
+    putc ('@', fp);
+
+    return 0;
+}
+
+#endif /* PRESERVE_PERMISSIONS_SUPPORT */
+
+
+
+/* Output the admin node for RCS into stream FP. */
+static void
+RCS_putadmin (RCSNode *rcs, FILE *fp)
+{
+    fprintf (fp, "%s\t%s;\n", RCSHEAD, rcs->head ? rcs->head : "");
+    if (rcs->branch)
+       fprintf (fp, "%s\t%s;\n", RCSBRANCH, rcs->branch);
+
+    fputs ("access", fp);
+    if (rcs->access)
+    {
+       char *p, *s;
+       s = xstrdup (rcs->access);
+       for (p = strtok (s, " \n\t"); p != NULL; p = strtok (NULL, " \n\t"))
+           fprintf (fp, "\n\t%s", p);
+       free (s);
+    }
+    fputs (";\n", fp);
+
+    fputs (RCSSYMBOLS, fp);
+    /* If we haven't had to convert the symbols to a list yet, don't
+       force a conversion now; just write out the string.  */
+    if (rcs->symbols == NULL && rcs->symbols_data != NULL)
+    {
+       fputs ("\n\t", fp);
+       fputs (rcs->symbols_data, fp);
+    }
+    else
+       walklist (RCS_symbols (rcs), putsymbol_proc, fp);
+    fputs (";\n", fp);
+
+    fputs ("locks", fp);
+    if (rcs->locks_data)
+       fprintf (fp, "\t%s", rcs->locks_data);
+    else if (rcs->locks)
+       walklist (rcs->locks, putlock_proc, fp);
+    if (rcs->strict_locks)
+       fprintf (fp, "; strict");
+    fputs (";\n", fp);
+
+    if (rcs->comment)
+    {
+       fprintf (fp, "comment\t@");
+       expand_at_signs (rcs->comment, (off_t) strlen (rcs->comment), fp);
+       fputs ("@;\n", fp);
+    }
+    if (rcs->expand && ! STREQ (rcs->expand, "kv"))
+       fprintf (fp, "address@hidden@;\n", RCSEXPAND, rcs->expand);
+
+    walklist (rcs->other, putrcsfield_proc, fp);
+
+    putc ('\n', fp);
+}
+
+
+
+static void
+putdelta (RCSVers *vers, FILE *fp)
+{
+    Node *bp, *start;
+
+    /* Skip if no revision was supplied, or if it is outdated (cvs admin -o) */
+    if (vers == NULL || vers->outdated)
+       return;
+
+    fprintf (fp, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches",
+            vers->version,
+            RCSDATE, vers->date,
+            "author", vers->author,
+            "state", vers->state ? vers->state : "");
+
+    if (vers->branches != NULL)
+    {
+       start = vers->branches->list;
+       for (bp = start->next; bp != start; bp = bp->next)
+           fprintf (fp, "\n\t%s", bp->key);
+    }
+
+    fprintf (fp, ";\nnext\t%s;", vers->next ? vers->next : "");
+
+    walklist (vers->other_delta, putrcsfield_proc, fp);
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    if (vers->hardlinks)
+    {
+       fprintf (fp, "\nhardlinks");
+       walklist (vers->hardlinks, puthardlink_proc, fp);
+       putc (';', fp);
+    }
+#endif
+    putc ('\n', fp);
+}
+
+
+
+static void
+RCS_putdtree (RCSNode *rcs, char *rev, FILE *fp)
+{
+    RCSVers *versp;
+    Node *p, *branch;
+
+    /* Previously, this function used a recursive implementation, but
+       if the trunk has a huge number of revisions and the program
+       stack is not big, a stack overflow could occur, so this
+       nonrecursive version was developed to be more safe. */
+    Node *branchlist, *onebranch;
+    List *branches;
+    List *onebranchlist;
+
+    if (rev == NULL)
+       return;
+
+    branches = getlist();
+
+    for (; rev != NULL;)
+    {
+       /* Find the delta node for this revision. */
+       p = findnode (rcs->versions, rev);
+       if (p == NULL)
+       {
+           error (1, 0,
+                  "error parsing repository file %s, file may be corrupt.", 
+                  rcs->path);
+       }
+ 
+       versp = p->data;
+
+       /* Print the delta node and go for its `next' node.  This
+          prints the trunk. If there are any branches printed on this
+          revision, mark we have some. */
+       putdelta (versp, fp);
+       /* Store branch information into branch list so to write its
+          trunk afterwards */
+       if (versp->branches != NULL)
+       {
+           branch = getnode();
+           branch->data = versp->branches;
+
+           addnode(branches, branch);
+       }
+
+       rev = versp->next;
+    }
+
+    /* If there are any branches printed on this revision,
+       print those trunks as well. */
+    branchlist = branches->list;
+    for (branch = branchlist->next;
+        branch != branchlist;
+        branch = branch->next)
+    {
+       onebranchlist = (List *)(branch->data);
+       onebranch = onebranchlist->list;
+       for (p = onebranch->next; p != onebranch; p = p->next)
+           RCS_putdtree (rcs, p->key, fp);
+
+       branch->data = NULL; /* so to prevent its freeing on dellist */
+    }
+
+    dellist(&branches);
+}
+
+
+
+static void
+RCS_putdesc (RCSNode *rcs, FILE *fp)
+{
+    fprintf (fp, "\n\n%s\n@", RCSDESC);
+    if (rcs->desc != NULL)
+    {
+       off_t len = (off_t) strlen (rcs->desc);
+       if (len > 0)
+       {
+           expand_at_signs (rcs->desc, len, fp);
+           if (rcs->desc[len-1] != '\n')
+               putc ('\n', fp);
+       }
+    }
+    fputs ("@\n", fp);
+}
+
+
+
+static void
+putdeltatext (FILE *fp, Deltatext *d)
+{
+    fprintf (fp, "\n\n%s\nlog\n@", d->version);
+    if (d->log != NULL)
+    {
+       int loglen = strlen (d->log);
+       expand_at_signs (d->log, (off_t) loglen, fp);
+       if (d->log[loglen-1] != '\n')
+           putc ('\n', fp);
+    }
+    putc ('@', fp);
+
+    walklist (d->other, putrcsfield_proc, fp);
+
+    fputs ("\ntext\n@", fp);
+    if (d->text != NULL)
+       expand_at_signs (d->text, (off_t) d->len, fp);
+    fputs ("@\n", fp);
+}
+
+
+
+/* TODO: the whole mechanism for updating deltas is kludgey... more
+   sensible would be to supply all the necessary info in a `newdeltatext'
+   field for RCSVers nodes. -twp */
+
+/* Copy delta text nodes from FIN to FOUT.  If NEWDTEXT is non-NULL, it
+   is a new delta text node, and should be added to the tree at the
+   node whose revision number is INSERTPT.  (Note that trunk nodes are
+   written in decreasing order, and branch nodes are written in
+   increasing order.) */
+static void
+RCS_copydeltas (RCSNode *rcs, FILE *fin, struct rcsbuffer *rcsbufin,
+               FILE *fout, Deltatext *newdtext, char *insertpt)
+{
+    int actions;
+    RCSVers *dadmin;
+    Node *np;
+    int insertbefore, found;
+    char *bufrest;
+    int nls;
+    size_t buflen;
+#ifndef HAVE_MMAP
+    char buf[8192];
+    int got;
+#endif
+
+    /* Count the number of versions for which we have to do some
+       special operation.  */
+    actions = walklist (rcs->versions, count_delta_actions, NULL);
+
+    /* Make a note of whether NEWDTEXT should be inserted
+       before or after its INSERTPT. */
+    insertbefore = (newdtext != NULL && numdots (newdtext->version) == 1);
+
+    while (actions != 0 || newdtext != NULL)
+    {
+       Deltatext *dtext;
+
+       dtext = RCS_getdeltatext (rcs, fin, rcsbufin);
+
+       /* We shouldn't hit EOF here, because that would imply that
+           some action was not taken, or that we could not insert
+           NEWDTEXT.  */
+       if (dtext == NULL)
+           error (1, 0, "internal error: EOF too early in RCS_copydeltas");
+
+       found = (insertpt != NULL && STREQ (dtext->version, insertpt));
+       if (found && insertbefore)
+       {
+           putdeltatext (fout, newdtext);
+           newdtext = NULL;
+           insertpt = NULL;
+       }
+
+       np = findnode (rcs->versions, dtext->version);
+       dadmin = np->data;
+
+       /* If this revision has been outdated, just skip it. */
+       if (dadmin->outdated)
+       {
+           freedeltatext (dtext);
+           --actions;
+           continue;
+       }
+          
+       /* Update the change text for this delta.  New change text
+          data may come from cvs admin -m, cvs admin -o, or cvs ci. */
+       if (dadmin->text != NULL)
+       {
+           if (dadmin->text->log != NULL || dadmin->text->text != NULL)
+               --actions;
+           if (dadmin->text->log != NULL)
+           {
+               free (dtext->log);
+               dtext->log = dadmin->text->log;
+               dadmin->text->log = NULL;
+           }
+           if (dadmin->text->text != NULL)
+           {
+               free (dtext->text);
+               dtext->text = dadmin->text->text;
+               dtext->len = dadmin->text->len;
+               dadmin->text->text = NULL;
+           }
+       }
+       putdeltatext (fout, dtext);
+       freedeltatext (dtext);
+
+       if (found && !insertbefore)
+       {
+           putdeltatext (fout, newdtext);
+           newdtext = NULL;
+           insertpt = NULL;
+       }
+    }
+
+    /* Copy the rest of the file directly, without bothering to
+       interpret it.  The caller will handle error checking by calling
+       ferror.
+
+       We just wrote a newline to the file, either in putdeltatext or
+       in the caller.  However, we may not have read the corresponding
+       newline from the file, because rcsbuf_getkey returns as soon as
+       it finds the end of the '@' string for the desc or text key.
+       Therefore, we may read three newlines when we should really
+       only write two, and we check for that case here.  This is not
+       an semantically important issue; we only do it to make our RCS
+       files look traditional.  */
+
+    nls = 3;
+
+    rcsbuf_get_buffered (rcsbufin, &bufrest, &buflen);
+    if (buflen > 0)
+    {
+       if (bufrest[0] != '\n'
+           || strncmp (bufrest, "\n\n\n", buflen < 3 ? buflen : 3) != 0)
+       {
+           nls = 0;
+       }
+       else
+       {
+           if (buflen < 3)
+               nls -= buflen;
+           else
+           {
+               ++bufrest;
+               --buflen;
+               nls = 0;
+           }
+       }
+
+       fwrite (bufrest, 1, buflen, fout);
+    }
+#ifndef HAVE_MMAP
+    /* This bit isn't necessary when using mmap since the entire file
+     * will already be available via the RCS buffer.  Besides, the
+     * mmap code doesn't always keep the file pointer up to date, so
+     * this adds some data twice.
+     */
+    while ((got = fread (buf, 1, sizeof buf, fin)) != 0)
+    {
+       if (nls > 0
+           && got >= nls
+           && buf[0] == '\n'
+           && strncmp (buf, "\n\n\n", nls) == 0)
+       {
+           fwrite (buf + 1, 1, got - 1, fout);
+       }
+       else
+       {
+           fwrite (buf, 1, got, fout);
+       }
+
+       nls = 0;
+    }
+#endif /* HAVE_MMAP */
+}
+
+
+
+/* A helper procedure for RCS_copydeltas.  This is called via walklist
+   to count the number of RCS revisions for which some special action
+   is required.  */
+static int
+count_delta_actions (Node *np, void *ignore)
+{
+    RCSVers *dadmin = np->data;
+
+    if (dadmin->outdated)
+       return 1;
+
+    if (dadmin->text != NULL
+       && (dadmin->text->log != NULL || dadmin->text->text != NULL))
+    {
+       return 1;
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Clean up temporary files.
+ *
+ * NOTES
+ *   This function needs to be reentrant since a call to exit() can cause a
+ *   call to this function, which can then be interrupted by a signal, which
+ *   can cause a second call to this function.
+ *
+ * RETURNS
+ *   Nothing.
+ */
+static void
+rcs_cleanup (void)
+{
+    TRACE (TRACE_FUNCTION, "rcs_cleanup()");
+
+    /* FIXME: Do not perform buffered I/O from an interrupt handler like
+     * this (via error).  However, I'm leaving the error-calling code there
+     * in the hope that on the rare occasion the error call is actually made
+     * (e.g., a fluky I/O error or permissions problem prevents the deletion
+     * of a just-created file) reentrancy won't be an issue.
+     */
+
+    /* We don't want to be interrupted during calls which set globals to NULL,
+     * but we know that by the time we reach this function, interrupts have
+     * already been blocked.
+     */
+    if (rcs_lockfile != NULL)
+    {
+       /* Use a tmp var since any of these functions could call exit, causing
+        * us to be called a second time.
+        */
+       char *tmp = rcs_lockfile;
+       rcs_lockfile = NULL;
+       if (rcs_lockfd >= 0)
+       {
+           if (close (rcs_lockfd) != 0)
+               error (0, errno, "error closing lock file %s", tmp);
+           rcs_lockfd = -1;
+       }
+
+       /* Note that the checks for existence_error are because we can be
+        * called from a signal handler, so we don't know whether the
+        * files got created.
+        */
+       if (unlink_file (tmp) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", tmp);
+    }
+}
+
+
+
+/* RCS_internal_lockfile and RCS_internal_unlockfile perform RCS-style
+   locking on the specified RCSFILE: for a file called `foo,v', open
+   for writing a file called `,foo,'.
+
+   Note that we what do here is quite different from what RCS does.
+   RCS creates the ,foo, file before it reads the RCS file (if it
+   knows that it will be writing later), so that it actually serves as
+   a lock.  We don't; instead we rely on CVS writelocks.  This means
+   that if someone is running RCS on the file at the same time they
+   are running CVS on it, they might lose (we read the file,
+   then RCS writes it, then we write it, clobbering the
+   changes made by RCS).  I believe the current sentiment about this
+   is "well, don't do that".
+
+   A concern has been expressed about whether adopting the RCS
+   strategy would slow us down.  I don't think so, since we need to
+   write the ,foo, file anyway (unless perhaps if O_EXCL is slower or
+   something).
+
+   These do not perform quite the same function as the RCS -l option
+   for locking files: they are intended to prevent competing RCS
+   processes from stomping all over each other's laundry.  Hence,
+   they are `internal' locking functions.
+
+   If there is an error, give a fatal error; if we return we always
+   return a non-NULL value.  */
+static FILE *
+rcs_internal_lockfile (char *rcsfile)
+{
+    struct stat rstat;
+    FILE *fp;
+    static int first_call = 1;
+
+    if (first_call)
+    {
+       first_call = 0;
+       /* Clean up if we get a signal or exit.  */
+       cleanup_register (rcs_cleanup);
+    }
+
+    /* Get the lock file name: `,file,' for RCS file `file,v'. */
+    assert (rcs_lockfile == NULL);
+    assert (rcs_lockfd < 0);
+    rcs_lockfile = rcs_lockfilename (rcsfile);
+
+    /* Use the existing RCS file mode, or read-only if this is a new
+       file.  (Really, this is a lie -- if this is a new file,
+       RCS_checkin uses the permissions from the working copy.  For
+       actually creating the file, we use 0444 as a safe default mode.) */
+    if (stat (rcsfile, &rstat) < 0)
+    {
+       if (existence_error (errno))
+           rstat.st_mode = S_IRUSR | S_IRGRP | S_IROTH;
+       else
+           error (1, errno, "cannot stat %s", rcsfile);
+    }
+
+    /* Try to open exclusively.  POSIX.1 guarantees that O_EXCL|O_CREAT
+       guarantees an exclusive open.  According to the RCS source, with
+       NFS v2 we must also throw in O_TRUNC and use an open mask that makes
+       the file unwriteable.  For extensive justification, see the comments for
+       rcswriteopen() in rcsedit.c, in RCS 5.7.  This is kind of pointless
+       in the CVS case; see comment at the start of this file concerning
+       general ,foo, file strategy.
+
+       There is some sentiment that with NFSv3 and such, that one can
+       rely on O_EXCL these days.  This might be true for unix (I
+       don't really know), but I am still pretty skeptical in the case
+       of the non-unix systems.  */
+    rcs_lockfd = open (rcs_lockfile,
+                      OPEN_BINARY | O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
+                      S_IRUSR | S_IRGRP | S_IROTH);
+
+    if (rcs_lockfd < 0)
+    {
+       error (1, errno, "could not open lock file `%s'", rcs_lockfile);
+    }
+
+    /* Force the file permissions, and return a stream object. */
+    /* Because we change the modes later, we don't worry about
+       this in the non-HAVE_FCHMOD case.  */
+#ifdef HAVE_FCHMOD
+    if (fchmod (rcs_lockfd, rstat.st_mode) < 0)
+       error (1, errno, "cannot change mode for %s", rcs_lockfile);
+#endif
+    fp = fdopen (rcs_lockfd, FOPEN_BINARY_WRITE);
+    if (fp == NULL)
+       error (1, errno, "cannot fdopen %s", rcs_lockfile);
+
+    return fp;
+}
+
+
+
+static void
+rcs_internal_unlockfile (FILE *fp, char *rcsfile)
+{
+    assert (rcs_lockfile != NULL);
+    assert (rcs_lockfd >= 0);
+
+    /* Abort if we could not write everything successfully to LOCKFILE.
+       This is not a great error-handling mechanism, but should prevent
+       corrupting the repository. */
+
+    if (ferror (fp))
+       /* Using errno here may well be misleanding since the most recent
+          call that set errno may not have anything whatsoever to do with
+          the error that set the flag, but it's better than nothing.  The
+          real solution is to check each call to fprintf rather than waiting
+          until the end like this.  */
+       error (1, errno, "error writing to lock file %s", rcs_lockfile);
+
+    /* Flush and sync the file, or the user may be told the commit completed,
+     * while a server crash/power failure could still cause the data to be
+     * lost.
+     *
+     * Invoking rename(",<file>," , "<file>,v") on Linux and almost all UNIXs
+     * only flushes the inode for the target file to disk, it does not
+     * guarantee flush of the kernel buffers allocated for the ,<file>,.
+     * Depending upon the load on the machine, the Linux kernel's flush daemon
+     * process may not flush for a while.  In the meantime the CVS transaction
+     * could have been declared committed to the end CVS user (CVS process has
+     * returned the final "OK").  If the machine crashes prior to syncing the
+     * changes to disk, the committed transaction can be lost.
+     */
+    if (fflush (fp) != 0)
+       error (1, errno, "error flushing file `%s' to kernel buffers",
+              rcs_lockfile);
+#ifdef HAVE_FSYNC
+    if (fsync (rcs_lockfd) < 0)
+       error (1, errno, "error fsyncing file `%s'", rcs_lockfile);
+#endif
+
+    if (fclose (fp) == EOF)
+       error (1, errno, "error closing lock file %s", rcs_lockfile);
+    rcs_lockfd = -1;
+
+    rename_file (rcs_lockfile, rcsfile);
+
+    {
+       /* Use a temporary to make sure there's no interval
+          (after rcs_lockfile has been freed but before it's set to NULL)
+          during which the signal handler's use of rcs_lockfile would
+          reference freed memory.  */
+       char *tmp = rcs_lockfile;
+       rcs_lockfile = NULL;
+       free (tmp);
+    }
+}
+
+
+
+static char *
+rcs_lockfilename (const char *rcsfile)
+{
+    char *lockfile, *lockp;
+    const char *rcsbase, *rcsp, *rcsend;
+    int rcslen;
+
+    /* Create the lockfile name. */
+    rcslen = strlen (rcsfile);
+    lockfile = xmalloc (rcslen + 10);
+    rcsbase = last_component (rcsfile);
+    rcsend = rcsfile + rcslen - sizeof(RCSEXT);
+    for (lockp = lockfile, rcsp = rcsfile; rcsp < rcsbase; ++rcsp)
+       *lockp++ = *rcsp;
+    *lockp++ = ',';
+    while (rcsp <= rcsend)
+       *lockp++ = *rcsp++;
+    *lockp++ = ',';
+    *lockp = '\0';
+
+    return lockfile;
+}
+
+
+
+/* Rewrite an RCS file.  The basic idea here is that the caller should
+   first call RCS_reparsercsfile, then munge the data structures as
+   desired (via RCS_delete_revs, RCS_settag, &c), then call RCS_rewrite.  */
+void
+RCS_rewrite (RCSNode *rcs, Deltatext *newdtext, char *insertpt)
+{
+    FILE *fin, *fout;
+    struct rcsbuffer rcsbufin;
+
+    if (noexec)
+       return;
+
+    /* Make sure we're operating on an actual file and not a symlink.  */
+    resolve_symlink (&(rcs->path));
+
+    fout = rcs_internal_lockfile (rcs->path);
+
+    RCS_putadmin (rcs, fout);
+    RCS_putdtree (rcs, rcs->head, fout);
+    RCS_putdesc (rcs, fout);
+
+    /* Open the original RCS file and seek to the first delta text. */
+    rcsbuf_cache_open (rcs, rcs->delta_pos, &fin, &rcsbufin);
+
+    /* Update delta_pos to the current position in the output file.
+       Do NOT move these statements: they must be done after fin has
+       been positioned at the old delta_pos, but before any delta
+       texts have been written to fout.
+     */
+    rcs->delta_pos = ftello (fout);
+    if (rcs->delta_pos == -1)
+       error (1, errno, "cannot ftello in RCS file %s", rcs->path);
+
+    RCS_copydeltas (rcs, fin, &rcsbufin, fout, newdtext, insertpt);
+
+    /* We don't want to call rcsbuf_cache here, since we're about to
+       delete the file.  */
+    rcsbuf_close (&rcsbufin);
+    if (ferror (fin))
+       /* The only case in which using errno here would be meaningful
+          is if we happen to have left errno unmolested since the call
+          which produced the error (e.g. fread).  That is pretty
+          fragile even if it happens to sometimes be true.  The real
+          solution is to make sure that all the code which reads
+          from fin checks for errors itself (some does, some doesn't).  */
+       error (0, 0, "warning: ferror set while rewriting RCS file `%s'", 
rcs->path);
+    if (fclose (fin) < 0)
+       error (0, errno, "warning: closing RCS file `%s'", rcs->path);
+
+    rcs_internal_unlockfile (fout, rcs->path);
+}
+
+
+
+/* Abandon changes to an RCS file. */
+void
+RCS_abandon (RCSNode *rcs)
+{
+    free_rcsnode_contents (rcs);
+    rcs->symbols_data = NULL;
+    rcs->expand = NULL;
+    rcs->access = NULL;
+    rcs->locks_data = NULL;
+    rcs->comment = NULL;
+    rcs->desc = NULL;
+    rcs->flags |= PARTIAL;
+}
+
+
+
+/*
+ * For a given file with full pathname PATH and revision number REV,
+ * produce a file label suitable for passing to diff.  The default
+ * file label as used by RCS 5.7 looks like this:
+ *
+ *     FILENAME <tab> YYYY/MM/DD <sp> HH:MM:SS <tab> REVNUM
+ *
+ * The date and time used are the revision's last checkin date and time.
+ * If REV is NULL, use the working copy's mtime instead.
+ *
+ * /dev/null is not statted but assumed to have been created on the Epoch.
+ * At least using the POSIX.2 definition of patch, this should cause creation
+ * of files on platforms such as Windoze where the null IO device isn't named
+ * /dev/null to be parsed by patch properly.
+ */
+char *
+make_file_label (const char *path, const char *rev, RCSNode *rcs)
+{
+    char datebuf[MAXDATELEN + 1];
+    char *label;
+
+    if (rev)
+    {
+       char date[MAXDATELEN + 1];
+       /* revs cannot be attached to /dev/null ... duh. */
+       assert (strcmp(DEVNULL, path));
+       RCS_getrevtime (rcs, rev, datebuf, 0);
+       (void) date_to_internet (date, datebuf);
+       label = Xasprintf ("-L%s\t%s\t%s", path, date, rev);
+    }
+    else
+    {
+       struct stat sb;
+       struct tm *wm;
+
+       if (strcmp(DEVNULL, path))
+       {
+           const char *file = last_component (path);
+           if (stat (file, &sb) < 0)
+               /* Assume that if the stat fails,then the later read for the
+                * diff will too.
+                */
+               error (1, errno, "could not get info for `%s'", path);
+           wm = gmtime (&sb.st_mtime);
+       }
+       else
+       {
+           time_t t = 0;
+           wm = gmtime(&t);
+       }
+
+       (void) tm_to_internet (datebuf, wm);
+       label = Xasprintf ("-L%s\t%s", path, datebuf);
+    }
+    return label;
+}
+
+
+
+/*
+ * Set up a local/custom RCS keyword for expansion.
+ *
+ * INPUTS
+ *   infopath          Path to file being parsed, for error messages.
+ *   ln                        Line number of INFOPATH being processed, for 
error
+ *                     messages.
+ *   keywords_in
+ *   arg
+ *
+ * OUTPUTS
+ *   keywords_in
+ */
+void
+RCS_setlocalid (const char *infopath, unsigned int ln,
+               void **keywords_in, const char *arg)
+{
+    char *copy, *next, *key, *s;
+    struct rcs_keyword *keywords;
+    enum keyword save_expandto;
+
+    if (!*keywords_in)
+       *keywords_in = new_keywords ();
+    keywords = *keywords_in;
+
+    copy = xstrdup (arg);
+    next = copy;
+    key = strtok (next, "=");
+
+    /*
+     * Validate key
+     */
+    for (s = key; *s != '\0'; s++)
+    {
+       if (! isalpha ((unsigned char) *s))
+       {
+           if (!parse_error (infopath, ln))
+                   error (0, 0,
+"%s [%u]: LocalKeyword ignored: Bad character `%c' in key `%s'",
+                          primary_root_inverse_translate (infopath),
+                          ln, *s, key);
+           free (copy);
+           return;
+       }
+    }
+
+    save_expandto = keywords[KEYWORD_LOCALID].expandto;
+
+    /* options? */
+    while ((key = strtok (NULL, ",")) != NULL) {
+       if (!strcmp(key, keywords[KEYWORD_ID].string))
+           keywords[KEYWORD_LOCALID].expandto = KEYWORD_ID;
+       else if (!strcmp(key, keywords[KEYWORD_HEADER].string))
+           keywords[KEYWORD_LOCALID].expandto = KEYWORD_HEADER;
+       else if (!strcmp(key, keywords[KEYWORD_CVSHEADER].string))
+           keywords[KEYWORD_LOCALID].expandto = KEYWORD_CVSHEADER;
+       else
+       {
+           keywords[KEYWORD_LOCALID].expandto = save_expandto;
+           if (!parse_error (infopath, ln))
+               error (0, 0,
+"%s [%u]: LocalKeyword ignored: Unknown LocalId mode: `%s'",
+                      primary_root_inverse_translate (infopath),
+                      ln, key);
+           free (copy);
+           return;
+       }
+    }
+
+    keywords[KEYWORD_LOCALID].string = xstrdup (next);
+    keywords[KEYWORD_LOCALID].len = strlen (next);
+    keywords[KEYWORD_LOCALID].expandit = 1;
+
+    free (copy);
+}
+
+
+
+void
+RCS_setincexc (void **keywords_in, const char *arg)
+{
+    char *key;
+    char *copy, *next;
+    bool include = false;
+    struct rcs_keyword *keyword;
+    struct rcs_keyword *keywords;
+
+    if (!*keywords_in)
+       *keywords_in = new_keywords ();
+    keywords = *keywords_in;
+
+    copy = xstrdup(arg);
+    next = copy;
+    switch (*next++) {
+       case 'e':
+           include = false;
+           break;
+       case 'i':
+           include = true;
+           break;
+       default:
+           free(copy);
+           return;
+    }
+
+    if (include)
+       for (keyword = keywords; keyword->string != NULL; keyword++)
+       {
+           keyword->expandit = false;
+       }
+
+    key = strtok(next, ",");
+    while (key) {
+       for (keyword = keywords; keyword->string != NULL; keyword++) {
+           if (strcmp (keyword->string, key) == 0)
+               keyword->expandit = include;
+       }
+       key = strtok(NULL, ",");
+    }
+    free(copy);
+    return;
+}
+
+
+
+#define ATTIC "/" CVSATTIC
+static char *
+getfullCVSname(char *CVSname, char **pathstore)
+{
+    if (current_parsed_root->directory) {
+       int rootlen;
+       char *c = NULL;
+       int alen = sizeof(ATTIC) - 1;
+
+       *pathstore = xstrdup(CVSname);
+       if ((c = strrchr(*pathstore, '/')) != NULL) {
+           if (c - *pathstore >= alen) {
+               if (!strncmp(c - alen, ATTIC, alen)) {
+                   while (*c != '\0') {
+                       *(c - alen) = *c;
+                       c++;
+                   }
+                   *(c - alen) = '\0';
+               }
+           }
+       }
+
+       rootlen = strlen(current_parsed_root->directory);
+       if (!strncmp(*pathstore, current_parsed_root->directory, rootlen) &&
+           (*pathstore)[rootlen] == '/')
+           CVSname = (*pathstore + rootlen + 1);
+       else
+           CVSname = (*pathstore);
+    }
+    return CVSname;
+}
Index: ccvs/src/rcs.h
diff -u /dev/null ccvs/src/rcs.h:1.83.2.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/rcs.h      Tue Jan 17 15:41:23 2006
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * RCS source control definitions needed by rcs.c and friends
+ */
+
+/* Strings which indicate a conflict if they occur at the start of a line.  */
+#define        RCS_MERGE_PAT_1 "<<<<<<< "
+#define        RCS_MERGE_PAT_2 "=======\n"
+#define        RCS_MERGE_PAT_3 ">>>>>>> "
+
+#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 */
+
+/* All the "char *" fields in RCSNode, Deltatext, and RCSVers are
+   '\0'-terminated (except "text" in Deltatext).  This means that we
+   can't deal with fields containing '\0', which is a limitation that
+   RCS does not have.  Would be nice to fix this some day.  */
+
+struct rcsnode
+{
+    /* Reference count for this structure.  Used to deal with the
+       fact that there might be a pointer from the Vers_TS or might
+       not.  Callers who increment this field are responsible for
+       calling freercsnode when they are done with their reference.  */
+    int refcount;
+
+    /* Flags (INATTIC, PARTIAL, &c), see above.  */
+    int flags;
+
+    /* File name of the RCS file.  This is not necessarily the name
+       as specified by the user, but it is a name which can be passed to
+       system calls and a name which is OK to print in error messages
+       (the various names might differ in case).  */
+    char *path;
+
+    /* Use when printing paths.  */
+    char *print_path;
+
+    /* Value for head keyword from RCS header, or NULL if empty.  HEAD may only
+     * be empty in a valid RCS file when the file has no revisions, a state
+     * that should not be able to occur with CVS.
+     */
+    char *head;
+
+    /* Value for branch keyword from RCS header, or NULL if omitted.  */
+    char *branch;
+
+    /* Raw data on symbolic revisions.  The first time that RCS_symbols is
+       called, we parse these into ->symbols, and free ->symbols_data.  */
+    char *symbols_data;
+
+    /* Value for expand keyword from RCS header, or NULL if omitted.  */
+    char *expand;
+
+    /* List of nodes, the key of which is the symbolic name and the data
+       of which is the numeric revision that it corresponds to (malloc'd).  */
+    List *symbols;
+
+    /* List of nodes (type RCSVERS), the key of which the numeric revision
+       number, and the data of which is an RCSVers * for the revision.  */
+    List *versions;
+
+    /* Value for access keyword from RCS header, or NULL if empty.
+       FIXME: RCS_delaccess would also seem to use "" for empty.  We
+       should pick one or the other.  */
+    char *access;
+
+    /* Raw data on locked revisions.  The first time that RCS_getlocks is
+       called, we parse these into ->locks, and free ->locks_data.  */
+    char *locks_data;
+
+    /* List of nodes, the key of which is the numeric revision and the
+       data of which is the user that it corresponds to (malloc'd).  */
+    List *locks;
+
+    /* Set for the strict keyword from the RCS header.  */
+    int strict_locks;
+
+    /* Value for the comment keyword from RCS header (comment leader), or
+       NULL if omitted.  */
+    char *comment;
+
+    /* Value for the desc field in the RCS file, or NULL if empty.  */
+    char *desc;
+
+    /* File offset of the first deltatext node, so we can seek there.  */
+    off_t delta_pos;
+
+    /* Newphrases from the RCS header.  List of nodes, the key of which
+       is the "id" which introduces the newphrase, and the value of which
+       is the value from the newphrase.  */
+    List *other;
+};
+
+typedef struct rcsnode RCSNode;
+
+struct deltatext {
+    char *version;
+
+    /* Log message, or NULL if we do not intend to change the log message
+       (that is, RCS_copydeltas should just use the log message from the
+       file).  */
+    char *log;
+
+    /* Change text, or NULL if we do not intend to change the change text
+       (that is, RCS_copydeltas should just use the change text from the
+       file).  Note that it is perfectly valid to have log be NULL and
+       text non-NULL, or vice-versa.  */
+    char *text;
+    size_t len;
+
+    /* Newphrase fields from deltatext nodes.  FIXME: duplicates the
+       other field in the rcsversnode, I think.  */
+    List *other;
+};
+typedef struct deltatext Deltatext;
+
+struct rcsversnode
+{
+    /* Duplicate of the key by which this structure is indexed.  */
+    char *version;
+
+    char *date;
+    char *author;
+    char *state;
+    char *next;
+    int dead;
+    int outdated;
+    Deltatext *text;
+    List *branches;
+    /* Newphrase fields from deltatext nodes.  Also contains ";add" and
+       ";delete" magic fields (see rcs.c, log.c).  I think this is
+       only used by log.c (where it looks up "log").  Duplicates the
+       other field in struct deltatext, I think.  */
+    List *other;
+    /* Newphrase fields from delta nodes.  */
+    List *other_delta;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    /* Hard link information for each revision. */
+    List *hardlinks;
+#endif
+};
+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
+
+/* The type of a function passed to RCS_checkout.  */
+typedef void (*RCSCHECKOUTPROC) (void *, const char *, size_t);
+
+struct rcsbuffer;
+
+/* What RCS_deltas is supposed to do.  */
+enum rcs_delta_op {RCS_ANNOTATE, RCS_FETCH};
+
+/*
+ * exported interfaces
+ */
+RCSNode *RCS_parse (const char *file, const char *repos);
+RCSNode *RCS_parsercsfile (const char *rcsfile);
+void RCS_fully_parse (RCSNode *);
+void RCS_reparsercsfile (RCSNode *, FILE **, struct rcsbuffer *);
+extern int RCS_setattic (RCSNode *, int);
+
+char *RCS_check_kflag (const char *arg);
+char *RCS_getdate (RCSNode * rcs, const char *date, int force_tag_match);
+char *RCS_gettag (RCSNode * rcs, const char *symtag, int force_tag_match,
+                 int *simple_tag);
+int RCS_exist_rev (RCSNode *rcs, char *rev);
+int RCS_exist_tag (RCSNode *rcs, char *tag);
+char *RCS_tag2rev (RCSNode *rcs, char *tag);
+char *RCS_getversion (RCSNode *rcs, const char *tag, const char *date,
+                     int force_tag_match, int *simple_tag);
+char *RCS_magicrev (RCSNode *rcs, char *rev);
+int RCS_isbranch (RCSNode *rcs, const char *rev);
+bool RCS_nodeisbranch (RCSNode *rcs, const char *tag);
+char *RCS_whatbranch (RCSNode *rcs, const char *tag);
+char *RCS_head (RCSNode * rcs);
+int RCS_datecmp (const char *date1, const char *date2);
+time_t RCS_getrevtime (RCSNode * rcs, const char *rev, char *date, int fudge);
+List *RCS_symbols (RCSNode *rcs);
+void RCS_check_tag (const char *tag);
+char *RCS_extract_tag (const char *tag, bool files);
+int RCS_valid_rev (const char *rev);
+List *RCS_getlocks (RCSNode *rcs);
+void freercsnode (RCSNode ** rnodep);
+char *RCS_getbranch (RCSNode *rcs, const char *tag, int force_tag_match);
+char *RCS_branch_head (RCSNode *rcs, const char *tag);
+
+int RCS_isdead (RCSNode *, const char *);
+char *RCS_getexpand (RCSNode *);
+void RCS_setexpand (RCSNode *, const char *);
+int RCS_checkout (RCSNode *, const char *, const char *, const char *,
+                  const char *, const char *, RCSCHECKOUTPROC, void *);
+int RCS_checkin (RCSNode *rcs, const char *update_dir, const char *workfile,
+                const char *message, const char *rev, time_t citime,
+                int flags);
+int RCS_cmp_file (RCSNode *, const char *, char **, const char *, const char *,
+                 const char * );
+int RCS_settag (RCSNode *, const char *, const char *);
+int RCS_deltag (RCSNode *, const char *);
+int RCS_setbranch (RCSNode *, const char *);
+int RCS_lock (RCSNode *, const char *, int);
+int RCS_unlock (RCSNode *, char *, int);
+int RCS_delete_revs (RCSNode *, char *, char *, int);
+void RCS_addaccess (RCSNode *, char *);
+void RCS_delaccess (RCSNode *, char *);
+char *RCS_getaccess (RCSNode *);
+void RCS_rewrite (RCSNode *, Deltatext *, char *);
+void RCS_abandon (RCSNode *);
+int rcs_change_text (const char *, char *, size_t, const char *,
+                    size_t, char **, size_t *);
+void RCS_deltas (RCSNode *, FILE *, struct rcsbuffer *, const char *,
+                enum rcs_delta_op, char **, size_t *,
+                char **, size_t *);
+void RCS_setincexc (void **, const char *arg);
+void RCS_setlocalid (const char *, unsigned int, void **, const char *arg);
+char *make_file_label (const char *, const char *, RCSNode *);
+bool RCS_is_symbolic (const char *);
+bool RCS_is_relative (const char *);
+
+extern bool preserve_perms;
+extern int annotate_width;
+
+/* From import.c.  */
+extern int add_rcs_file (const char *, const char *, const char *,
+                         const char *, const char *, const char *,
+                         const char *, int, char **, const char *, size_t,
+                         FILE *, bool);
+void free_keywords (void *keywords);
Index: ccvs/src/sanity.sh
diff -u /dev/null ccvs/src/sanity.sh:1.1108.2.1
--- /dev/null   Tue Jan 17 15:41:24 2006
+++ ccvs/src/sanity.sh  Tue Jan 17 15:41:23 2006
@@ -0,0 +1,36907 @@
+#! /bin/sh
+:
+#      sanity.sh -- a growing testsuite for cvs.
+#
+# The copyright notice said: "Copyright (C) 1992, 1993 Cygnus Support"
+# I'm not adding new copyright notices for new years as our recent 
+# practice has been to include copying terms without copyright notices.
+#
+# 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.
+#
+# Original Author: K. Richard Pixley
+
+# usage:
+usage ()
+{
+    echo "Usage: `basename $0` --help"
+    echo "Usage: `basename $0` [--eklr] [-c CONFIG-FILE] [-f FROM-TEST] \\"
+    echo "                 [-h HOSTNAME] [-s CVS-FOR-CVS-SERVER] CVS-TO-TEST 
\\"
+    echo "                 [TESTS-TO-RUN...]"
+}
+
+exit_usage ()
+{
+    usage 1>&2
+    exit 2
+}
+
+exit_help ()
+{
+    usage
+    echo
+    echo "-H|--help    display this text"
+    echo "-c CONFIG-FILE"
+    echo "--config=CONFIG_FILE"
+    echo "             use an alternate test suite config file (defaults to"
+    echo "             \`sanity.config.sh' in the same directory as"
+    echo "             CVS-TO-TEST is found in)"
+    echo "-e|--skipfail Treat tests that would otherwise be nonfatally skipped"
+    echo "              for reasons like missing tools as failures, exiting"
+    echo "              with an error message.  Also treat warnings as"
+    echo "             failures."
+    echo "-f FROM-TEST"
+    echo "--from-test=FROM-TEST"
+    echo "             run TESTS-TO-RUN, skipping all tests in the list before"
+    echo "             FROM-TEST"
+    echo "-h HOSTNAME"
+    echo "--hostname HOSTNAME"
+    echo "              Use :ext:HOSTNAME to run remote tests rather than"
+    echo "              :fork:.  Implies --remote and assumes that \$TESTDIR"
+    echo "              resolves to the same directory on both the client and"
+    echo "              the server."
+    echo "-k|--keep    try to keep directories created by individual tests"
+    echo "             around, exiting after the first test which supports"
+    echo "             --keep"
+    echo "-l|--link-root"
+    echo "             test CVS using a symlink to a real CVSROOT"
+    echo "-n|--noredirect"
+    echo "              test a secondary/primary CVS server (writeproxy)"
+    echo "              configuration with the Redirect response disabled"
+    echo "              (implies --proxy)."
+    echo "-p|--proxy   test a secondary/primary CVS server (writeproxy)"
+    echo "              configuration (implies --remote)."
+    echo "-r|--remote  test client/server, as opposed to local, CVS"
+    echo "-s CVS-FOR-CVS-SERVER"
+    echo "--server=CVS-FOR-CVS-SERVER"
+    echo "             use CVS-FOR-CVS-SERVER as the path to the CVS SERVER"
+    echo "             executable to be tested (defaults to CVS-TO-TEST and"
+    echo "             implies --remote)"
+    echo
+    echo "CVS-TO-TEST  the path to the CVS executable to be tested; used as"
+    echo "             the path to the CVS client when CVS-FOR-CVS-SERVER is"
+    echo "             specified"
+    echo "TESTS-TO-RUN the names of the tests to run (defaults to all tests)"
+    exit 2
+}
+
+checklongoptarg()
+{
+    if test "x$1" != xoptional && test -z "$OPTARG"; then
+       echo "option \`--$LONGOPT' requires an argument" >&2
+       exit_usage
+    fi
+}
+
+# See TODO list at end of file.
+
+# required to make this script work properly.
+unset CVSREAD
+
+# We want to invoke a predictable set of i18n behaviors, not whatever
+# the user running this script might have set.
+# In particular:
+#   'sort' and tabs and spaces (LC_COLLATE).
+#   Messages from getopt (LC_MESSAGES) (in the future, CVS itself might 
+#     also alter its messages based on LC_MESSAGES).
+LANG=C
+export LANG
+LC_ALL=C
+export LC_ALL
+
+# And a few tests want a predictable umask.
+umask 0002
+
+#
+# Initialize the test counts.
+#
+passed=0
+skipped=0
+warnings=0
+
+
+
+#
+# read our options
+#
+unset configfile
+unset fromtest
+unset remotehost
+unset rootoptions
+keep=false
+linkroot=false
+noredirect=false
+proxy=false
+remote=false
+servercvs=false
+skipfail=false
+while getopts Hc:ef:h:klnprs:-: option ; do
+    # convert the long opts to short opts
+    if test x$option = x-;  then
+       # remove any argument
+       if echo "$OPTARG" |grep = >/dev/null; then
+           LONGOPT=`echo "$OPTARG" |sed 's/=.*$//'`
+           OPTARG=`echo "$OPTARG" |sed -e 's/^.*=//'`
+       else
+           LONGOPT=$OPTARG
+           OPTARG=
+       fi
+       # Convert LONGOPT to lower case
+       LONGOPT=`echo "$LONGOPT" |sed 
'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+       case "$LONGOPT" in
+           c|co|con|conf|confi|config)
+               option=c
+               checklongoptarg
+               ;;
+           f|fr|fro|from|from-|from-t|from-te|from-tes|from-test)
+               option=f
+               checklongoptarg
+               ;;
+           h)
+               echo "\`--h' is ambiguous.  Could mean \`--help' or 
\`--hostname'" >&2
+               exit_usage
+               ;;
+           he|hel|help)
+               option=H
+               OPTARG=
+               ;;
+           ho|hos|host|hostn|hostna|hostnam|hostname)
+               option=h
+               checklongoptarg
+               ;;
+           k|ke|kee|keep)
+               option=k
+               OPTARG=
+               ;;
+           l|li|lin|link|link-|link-r]|link-ro|link-roo|link-root)
+               option=l
+               OPTARG=
+               ;;
+           n|no|nor|nore|nored|noredi|noredir|noredire|noredirec|noredirect)
+               option=n
+               OPTARG=
+               ;;
+           p|pr|pro|prox|proxy)
+               option=p
+               OPTARG=
+               ;;
+           r|re|rem|remo|remot|remote)
+               option=r
+               OPTARG=
+               ;;
+           s)
+               echo "\`--s' is ambiguous.  Could mean \`--server' or 
\`--skipfail'" >&2
+               exit_usage
+               ;;
+           se|ser|serv|serve|server)
+               option=s
+               checklongoptarg
+               ;;
+           sk|ski|skip|skipf|skipfa|skipfai|skipfail)
+               option=e
+               OPTARG=
+               ;;
+           *)
+               option=\?
+               OPTARG=
+       esac
+    fi
+    case "$option" in
+       c)
+           configfile="$OPTARG"
+           ;;
+       e)
+           skipfail=:
+           ;;
+       f)
+           fromtest="$OPTARG"
+           ;;
+       h)
+           # Set a remotehost to run the remote tests on via :ext:
+           # Implies `-r' and assumes that $TESTDIR resolves to the same
+           # directory on the client and the server.
+           remotehost="$OPTARG"
+           remote=:
+           ;;
+       H)
+           exit_help
+           ;;
+       k)
+           # The -k (keep) option will eventually cause all the tests to
+           # leave around the contents of the /tmp directory; right now only
+           # some implement it.  Not originally intended to be useful with
+           # more than one test, but this should work if each test uses a
+           # uniquely named dir (use the name of the test).
+           keep=:
+           ;;
+       l)
+           linkroot=:
+           ;;
+        n)
+           proxy=:
+           noredirect=:
+           remote=:
+           ;;
+        p)
+           proxy=:
+           remote=:
+           ;;
+       r)
+           remote=:
+           ;;
+        s)
+           servercvs="$OPTARG"
+           remote=:
+           ;;
+       \?)
+           exit_usage
+           ;;
+    esac
+done
+
+# boot the arguments we used above
+while test $OPTIND -gt 1 ; do
+    shift
+    OPTIND=`expr $OPTIND - 1`
+done
+
+# Use full path for CVS executable, so that CVS_SERVER gets set properly
+# for remote.
+case $1 in
+"")
+  exit_usage
+  ;;
+/*)
+  testcvs=$1
+  ;;
+*)
+  testcvs=`pwd`/$1
+  ;;
+esac
+shift
+
+# Verify that $testcvs looks like CVS.
+# we can't use test -x since BSD 4.3 doesn't support it.
+if test ! -f $testcvs || test ! -r $testcvs; then
+  echo "No such file or file not readable: $testcvs" >&2
+  exit 1
+fi
+if $testcvs --version </dev/null 2>/dev/null |
+     grep '^Concurrent Versions System' >/dev/null 2>&1; then :; else
+  echo "Not a CVS executable: $testcvs" >&2
+  exit 1
+fi
+
+# If $remotehost is set, warn if $TESTDIR isn't since we are pretty sure
+# that its default value of `/tmp/cvs-sanity' will not resolve to the same
+# directory on two different machines.
+if test -n "$remotehost" && test -z "$TESTDIR"; then
+    echo "WARNING: CVS server hostname is set and \$TESTDIR is not.  If" >&2
+    echo "$remotehost is not the local machine, then it is unlikely that" >&2
+    echo "the default value assigned to \$TESTDIR will resolve to the same" >&2
+    echo "directory on both this client and the CVS server." >&2
+fi
+
+# Read our config file if we can find it.
+#
+# The config file should always be located in the same directory as the CVS
+# executable, unless we are testing an executable outside of the build
+# directory.  In this case, we echo a warning and attempt to assume the most
+# portable configuration.
+if test -z "$configfile"; then
+       configfile=`dirname $testcvs`/sanity.config.sh
+fi
+if test -r "$configfile"; then
+       . "$configfile"
+else
+       echo "WARNING: Failed to locate test suite config file" >&2
+       echo "         \`$configfile'." >&2
+fi
+
+
+
+# Set a default value for $CVS_RSH. The sanity.config.sh file will
+# have the configured value in the RSH_DFLT variable.
+#
+: ${CVS_RSH=${RSH_DFLT:-ssh}}; export CVS_RSH
+
+if test -n "$remotehost"; then
+    # Verify that $CVS_RSH $remotehost works.
+    result=`$CVS_RSH $remotehost 'echo test'`
+    if test $? != 0 || test "x$result" != "xtest"; then
+       echo "\`$CVS_RSH $remotehost' failed." >&2
+       exit 1
+    fi
+fi
+
+case "$servercvs" in
+"")
+  exit_usage
+  ;;
+false)
+  ;;
+/*)
+  ;;
+*)
+  servercvs=`pwd`/$servercvs
+  ;;
+esac
+
+if test false != $servercvs; then
+  # Allow command line to override $CVS_SERVER
+  CVS_SERVER=$servercvs
+else
+  # default $CVS_SERVER to ${testcvs}
+  : ${CVS_SERVER=$testcvs}
+  # With the previous command, effectively defaults $servercvs to $CVS_SERVER,
+  # then $testcvs
+  servercvs=$CVS_SERVER
+fi
+export CVS_SERVER
+servercvs_orig=$servercvs
+
+# Fail in client/server mode if our ${servercvs} does not contain server
+# support.
+if $remote; then
+  if test -n "$remotehost"; then
+    if $CVS_RSH $remotehost "test ! -f ${servercvs} || test ! -r ${servercvs}"
+    then
+      echo "No such file or file not readable: $remotehost:${testcvs}" >&2
+      exit 1
+    fi
+    if $CVS_RSH $remotehost "${servercvs} --version </dev/null 2>/dev/null |
+         grep '^Concurrent Versions System' >/dev/null 2>&1"; then :; else
+      echo "Not a CVS executable: $remotehost:${servercvs}" >&2
+      exit 1
+    fi
+    if $CVS_RSH $remotehost "${servercvs} --version </dev/null |
+         grep '^Concurrent.*(.*server)$' >/dev/null 2>&1"; then :; else
+      echo "CVS executable \`$remotehost:${servercvs}' does not contain server 
support." >&2
+      exit 1
+    fi
+  else
+    if test ! -f ${servercvs} || test ! -r ${servercvs}; then
+      echo "No such file or file not readable: ${testcvs}" >&2
+      exit 1
+    fi
+    if ${servercvs} --version </dev/null 2>/dev/null |
+         grep '^Concurrent Versions System' >/dev/null 2>&1; then :; else
+      echo "Not a CVS executable: ${servercvs}" >&2
+      exit 1
+    fi
+    if ${servercvs} --version </dev/null |
+         grep '^Concurrent.*(.*server)$' >/dev/null 2>&1; then :; else
+      echo "CVS executable \`${servercvs}' does not contain server support." 
>&2
+      exit 1
+    fi
+  fi
+fi
+
+# Fail in client/server mode if our ${testcvs} does not contain client
+# support.
+if $remote; then
+  if ${testcvs} --version </dev/null |
+       grep '^Concurrent.*(client.*)$' >/dev/null 2>&1; then :; else
+    echo "CVS executable \`${testcvs}' does not contain client support." >&2
+    exit 1
+  fi
+fi
+
+# For the "fork" tests.
+if ${testcvs} --version </dev/null |
+     grep '^Concurrent.*(.*server)$' >/dev/null 2>&1
+then
+  testcvs_server_support=:
+else
+  testcvs_server_support=false
+fi
+
+
+
+dokeep() 
+{ 
+    if ${keep}; then
+      echo "Keeping ${TESTDIR} for test case \`${what}' and exiting due to 
--keep"
+      exit 0
+    fi
+}
+
+
+
+###
+### GUTS
+###
+
+# "debugger"
+#set -x
+
+echo 'This test should produce no other output than this message, and a final 
"OK".'
+echo '(Note that the test can take an hour or more to run and periodically 
stops'
+echo 'for as long as one minute.  Do not assume there is a problem just 
because'
+echo 'nothing seems to happen for a long time.  If you cannot live without'
+echo "running status, try the command: \`tail -f check.log' from another 
window.)"
+
+# Regexp to match what the CVS client 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.
+CPROG=`basename ${testcvs} |sed 's/\.exe$//'`
+# And the regexp for the CVS server when we have one.  In local mode, this
+# defaults to $CPROG since $servercvs already did.
+# FIXCVS: There are a few places in error messages where CVS suggests a command
+# and outputs $SPROG as the suggested executable.  This could hopefully use
+# MT (tagged text - see doc/cvs-client.texi) to request that the client print
+# its own name.
+SPROG=`basename ${servercvs} |sed 's/\.exe$//'`
+
+
+# Match the hostname
+hostname="[-_.a-zA-Z0-9]*"
+
+# Regexp to match a commitid
+commitid="[a-zA-Z0-9]*"
+
+# Regexp to match the name of a temporary file (from cvs_temp_name).
+# This appears in certain diff output.
+tempfile="cvs[-a-zA-Z0-9.%_]*"
+# $tempname set after $TMPDIR, below.
+
+# Regexp to match a date in RFC822 format (as amended by RFC1123).
+RFCDATE="[a-zA-Z0-9 ][a-zA-Z0-9 ]* [0-9:][0-9:]* -0000"
+RFCDATE_EPOCH="1 Jan 1970 00:00:00 -0000"
+
+# Special times used in touch -t commands and the regular expresions
+# to match them. Now that the tests set TZ=UTC0, it
+# should be easier to be more exact in their regexp.
+TOUCH1971="197107040343"
+# This date regexp was 1971/07/0[3-5] [0-9][0-9]:43:[0-9][0-9]
+ISO8601DATE1971="1971-07-04 03:43:[0-9][0-9] [+-]0000"
+
+TOUCH2034="203412251801"
+# This date regexp was 2034/12/2[4-6] [0-9][0-9]:01:[0-9][0-9]
+ISO8601DATE2034="2034-12-25 18:01:[0-9][0-9] [+-]0000"
+
+# Used in admin tests for exporting RCS files.
+# The RAWRCSDATE..... format is for internal ,v files and
+# the ISO8601DATE..... format is to allow for a regular expression in
+# 'cvs log' output patterns. The tests that use this set of specific
+# ${ISO8601DATE.....} variables also force TZ=UTC0 for the test.
+RAWRCSDATE2000A="2000.11.24.15.58.37"
+RAWRCSDATE1996A="96.11.24.15.57.41"
+RAWRCSDATE1996B="96.11.24.15.56.05"
+ISO8601DATE2000A="2000-11-24 15:58:37 [+-]0000"
+ISO8601DATE1996A="1996-11-24 15:57:41 [+-]0000"
+ISO8601DATE1996B="1996-11-24 15:56:05 [+-]0000"
+
+# Regexp to match the date in cvs log command output
+# This format has been enhanced in the future to accept either
+# old-style cvs log output dates or new-style ISO8601 timezone
+# information similar to the ISODATE format. The RCSKEYDATE is
+# similar, but uses '/' instead of '-' to sepearate year/month/day
+# and does not include the optional timezone offset.
+ISO8601DATE="[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9] 
[0-2][0-9]:[0-6][0-9]:[0-6][0-9] [-+][0-1][0-9][0-6][0-9]"
+
+# Regexp to match the dates found in rcs keyword strings
+RCSKEYDATE="[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9] 
[0-9][0-9]:[0-9][0-9]:[0-9][0-9]"
+
+# Regexp to match the date in the delta section of rcs format files.
+# Dates in very old RCS files may not have included the century.
+RCSDELTADATE="[0-9][0-9]*\.[0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]"
+
+# Regexp to match a date in standard Unix format as used by rdiff
+# FIXCVS: There's no reason for rdiff to use a different date format
+# than diff does
+DATE="[a-zA-Z]* [a-zA-Z]* [ 1-3][0-9] [0-9:]* [0-9]*"
+# ISO 8601 format "yyyy-mm-dd hh:mm -0000"
+ISODATE="[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9] 
[+-][0-9][0-9][0-9][0-9]"
+# %p format is not well defined (nil) and hex digits are common. Using
+# ..* is a bad idea as the tests take a very long time to run due to
+# the complexity of the expressions.  If you run into any other characters
+# that are used in a %p format, add them here.
+PFMT="[0-9a-zA-Z()][0-9a-zA-Z()]*"
+
+# Which directories should Which and find_tool search for executables?
+SEARCHPATH=$PATH:/usr/local/bin:/usr/contrib/bin:/usr/contrib:/usr/gnu/bin:/local/bin:/local/gnu/bin:/gnu/bin:/sw/bin:/usr/pkg/bin
+
+# Do not assume that `type -p cmd` is portable
+# Usage: Which [-a] [-x|-f|-r] prog [$SEARCHPATH:/with/directories:/to/search]
+Which() {
+  # Optional first argument for file type, defaults to -x.
+  # Second argument is the file or directory to be found.
+  # Third argument is the PATH to search.
+  # By default, print only the first file that matches,
+  # -a will cause all matches to be printed.
+  notevery=:
+  if [ "x$1" = "x-a" ]; then notevery=false; shift; fi
+  case "$1" in
+    -*) t=$1; shift ;;
+    *) t=-x ;;
+  esac
+  case "$1" in
+    # FIXME: Someday this may need to be fixed
+    # to deal better with C:\some\path\to\ssh values...
+    /*) test $t $1 && echo $1 ;;
+    *) for d in `IFS=:; echo ${2-$SEARCHPATH}`
+       do
+         test $t $d/$1 && { echo $d/$1; if $notevery; then break; fi; }
+       done
+       ;;
+  esac
+}
+
+
+# On cygwin32, we may not have /bin/sh.
+if test -r /bin/sh; then
+  TESTSHELL="/bin/sh"
+else
+  TESTSHELL=`Which -f sh`
+  if test ! -r "$TESTSHELL"; then
+    TESTSHELL="/bin/sh"
+  fi
+fi
+
+# 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.
+test -f check.plog && mv check.plog check.plog~
+test -f check.log && mv check.log check.plog
+
+# Create the log file so check.log can be tailed almost immediately after
+# this script is started.  Otherwise it can take up to a minute or two before
+# the log file gets created when $remotehost is specified on some systems,
+# which makes for a lot of failed `tail -f' attempts.
+touch check.log
+
+# Workaround any X11Forwarding by ssh. Otherwise this text:
+#   Warning: No xauth data; using fake authentication data for X11 forwarding.
+# has been known to end up in the test results below
+# causing the test to fail.
+[ -n "$DISPLAY" ] && unset DISPLAY
+  
+# The default value of /tmp/cvs-sanity for TESTDIR is dubious,
+# because it loses if two people/scripts try to run the tests
+# at the same time.  Some possible solutions:
+# 1.  Use /tmp/cvs-test$$.  One disadvantage is that the old
+#     cvs-test* directories would pile up, because they wouldn't
+#     necessarily get removed.
+# 2.  Have everyone/everything running the testsuite set
+#     TESTDIR to some appropriate directory.
+# 3.  Have the default value of TESTDIR be some variation of
+#     `pwd`/cvs-sanity.  The biggest problem here is that we have
+#     been fairly careful to test that CVS prints in messages the
+#     actual pathnames that we pass to it, rather than a different
+#     pathname for the same directory, as may come out of `pwd`.
+#     So this would be lost if everything was `pwd`-based.  I suppose
+#     if we wanted to get baroque we could start making symlinks
+#     to ensure the two are different.
+if test -n "$remotehost"; then
+        # We need to set $tmp on the server since $TMPDIR is compared against
+       # messages generated by the server.
+       tmp=`$CVS_RSH $remotehost 'cd /tmp; /bin/pwd || pwd' 2>/dev/null`
+       if test $? != 0; then
+           echo "$CVS_RSH $remotehost failed." >&2
+           exit 1
+       fi
+else
+       tmp=`(cd /tmp; /bin/pwd || pwd) 2>/dev/null`
+fi
+
+# Now:
+#      1) Set TESTDIR if it's not set already
+#      2) Remove any old test remnants
+#      3) Create $TESTDIR
+#      4) Normalize TESTDIR with `cd && (/bin/pwd || pwd)`
+#         (This will match CVS output later)
+: ${TESTDIR=$tmp/cvs-sanity}
+# clean any old remnants (we need the chmod because some tests make
+# directories read-only)
+if test -d $TESTDIR; then
+    chmod -R a+wx $TESTDIR
+    rm -rf $TESTDIR
+fi
+# These exits are important.  The first time I tried this, if the `mkdir && cd`
+# failed then the build directory would get blown away.  Some people probably
+# wouldn't appreciate that.
+mkdir $TESTDIR || exit 1
+cd $TESTDIR || exit 1
+# Ensure $TESTDIR is absolute
+if echo "$TESTDIR" |grep '^[^/]'; then
+    # Don't resolve this unless we have to.  This keeps symlinks intact.  This
+    # is important at least when testing using -h $remotehost, because the same
+    # value for $TESTDIR must resolve to the same directory on the client and
+    # the server and we likely used Samba, and possibly symlinks, to do this.
+    TESTDIR=`(/bin/pwd || pwd) 2>/dev/null`
+fi
+
+if test -z "$TESTDIR" || echo "$TESTDIR" |grep '^[^/]'; then
+    echo "Unable to resolve TESTDIR to an absolute directory." >&2
+    exit 1
+fi
+cd $TESTDIR
+
+
+
+: ${TIMING=false}
+if $remote; then
+    # Now override our CVS_RSH in order to forward variables which affect the
+    # test suite through.  This always needs to be done when $remotehost is
+    # set, needs to be done in $proxy mode for the crerepos tests, and needs to
+    # be done in $remote mode for the writeproxy-ssh tests.
+    if $TIMING; then
+       time="/usr/bin/time -ao'$TESTDIR/time.out'"
+    else
+       time=
+    fi
+    cat >$TESTDIR/ssh-wrapper-env <<EOF
+#! $TESTSHELL
+while [ \$# -gt 0 ]
+do
+  case "\$1" in
+    *=*)
+      eval "\$1"
+      var=\`echo "\$1" | sed 's/^\\(.*\\)=.*\$/\\1/'\`
+      export \$var
+      ;;
+    *) break;;
+  esac
+  shift
+done
+exec \${1+"\$@"}
+EOF
+    chmod a+x $TESTDIR/ssh-wrapper-env
+    cat >$TESTDIR/ssh-wrapper <<EOF
+#! $TESTSHELL
+hostname=\$1
+shift
+exec \
+$CVS_RSH \
+        \$hostname \
+        $TESTDIR/ssh-wrapper-env \
+        "CVS_SERVER='\$CVS_SERVER'" \
+        "CVS_SERVER_SLEEP='\$CVS_SERVER_SLEEP'" \
+        "CVS_PARENT_SERVER_SLEEP='\$CVS_PARENT_SERVER_SLEEP'" \
+        "CVS_SERVER_LOG='\$CVS_SERVER_LOG'" \
+        "CVS_SECONDARY_LOG='\$CVS_SECONDARY_LOG'" \
+        "TMPDIR='\$TMPDIR'" \
+        "CVS_RSH='$TESTDIR/ssh-wrapper'" \
+        "CVSUMASK='\$CVSUMASK'" \
+        "CVS_PID='\$CVS_PID'" \
+        $time \
+        \${1+"\$@"}
+EOF
+    chmod a+x $TESTDIR/ssh-wrapper
+    CVS_RSH=$TESTDIR/ssh-wrapper
+fi # $remotehost
+
+
+
+# Now set $TMPDIR if the user hasn't overridden it.
+#
+# We use a $TMPDIR under $TESTDIR by default so that two tests may be run at
+# the same time without bumping heads without requiring the user to specify
+# more than $TESTDIR.  See the test for leftover cvs-serv* directories near the
+# end of this script at the end of "The big loop".
+: ${TMPDIR=$TESTDIR/tmp}
+export TMPDIR
+if test -d $TMPDIR; then :; else
+    mkdir $TMPDIR
+fi
+
+
+# Regexp to match the the full path to a temporary file (from cvs_temp_name).
+# This appears in certain diff output.
+tempname=$TMPDIR/$tempfile
+
+# Make sure various tools work the way we expect, or try to find
+# versions that do.
+: ${AWK=awk}
+: ${DIFF=diff}
+: ${EXPR=expr}
+: ${ID=id}
+: ${TR=tr}
+
+# Keep track of tools that are found, but do NOT work as we hope
+# in order to avoid them in future
+badtools=
+set_bad_tool ()
+{
+   badtools=$badtools:$1
+}
+is_bad_tool ()
+{
+   case ":$badtools:" in *:$1:*) return 0 ;; *) return 1 ; esac
+}
+
+version_test ()
+{
+  vercmd=$1
+  verbad=:
+  if RES=`$vercmd --version </dev/null 2>&1`; then
+    if test "X$RES" != "X--version" && test "X$RES" != "X" ; then
+      echo "$RES"
+      verbad=false
+    fi
+  fi
+  if $verbad; then
+    echo "The command \`$vercmd' does not support the --version option."
+  fi
+  # It does not really matter that --version is not supported
+  return 0
+}
+
+# Try to find a tool that satisfies all of the tests.
+# Usage: list:of:colon:separated:alternatives test1 test2 test3 test4...
+# Example: find_tool awk:gawk:nawk awk_tooltest1 awk_tooltest2
+find_tool ()
+{
+  dTn=$1
+  default_TOOL=$2
+  echo find_tool: ${1+"$@"} >>$LOGFILE
+  cmds="`IFS=:; echo $2`"; shift; shift; tooltests="address@hidden"
+  if test -z "$tooltests"; then tooltests=version_test; fi
+  clist=; for cmd in $cmds; do clist="$clist `Which -a $cmd`"; done
+  # Make sure the default tool is just the first real command name
+  for default_TOOL in $clist `IFS=:; echo $default_TOOL`; do break; done
+  TOOL=""
+  TEST_MARGINALS=0
+  for trytool in $clist ; do
+    pass=:
+    MARGINALS=0
+    for tooltest in $tooltests; do
+      result=`eval $tooltest $trytool 2>&1`
+      rc=$?
+      echo "Running $tooltest $trytool" >>$LOGFILE
+      if test -n "$result"; then
+       echo "$result" >>$LOGFILE
+      fi
+      if test "$rc" = "0"; then
+        echo "PASS: $tooltest $trytool" >>$LOGFILE
+      elif test "$rc" = "77"; then
+        echo "MARGINAL: $tooltest $trytool; rc=$rc" >>$LOGFILE
+       MARGINALS=`expr $MARGINALS + 1`
+       pass=false
+      else
+        set_bad_tool $trytool
+        echo "FAIL: $tooltest $trytool; rc=$rc" >>$LOGFILE
+       pass=false
+      fi
+    done
+    if $pass; then
+      echo $trytool
+      return 0
+    fi
+    if test $MARGINALS -gt 0 \
+       && (test -z "$TOOL" || test $MARGINALS -lt $TEST_MARGINALS); then
+      if is_bad_tool $trytool; then
+       # Ignore tools with some MARGINAL results and some FAIL
+       :
+      else
+       TOOL=$trytool
+       TEST_MARGINALS=$MARGINALS
+      fi
+    fi
+  done
+  if test -n "$TOOL"; then
+    echo "Notice: The default version of $dTn (\`$default_TOOL')" >>$LOGFILE
+    echo "is defective.  Using \`$TOOL' and hoping for the best." >>$LOGFILE
+    echo "Notice: The default version of $dTn (\`$default_TOOL')" >&2
+    echo "is defective.  Using \`$TOOL' and hoping for the best." >&2
+    echo $TOOL
+  else
+    echo $default_TOOL
+  fi
+}
+
+id_tool_test ()
+{
+  id=$1
+  if $id -u >/dev/null 2>&1 && $id -un >/dev/null 2>&1; then
+    return 0
+  else
+    echo "Running these tests requires an \`id' program that understands the"
+    echo "-u and -n flags.  Make sure that such an id (GNU, or many but not"
+    echo "all vendor-supplied versions) is in your path."
+    return 1
+  fi
+}
+
+ID=`find_tool id id version_test id_tool_test`
+echo "Using ID=$ID" >>$LOGFILE
+
+# You can't run CVS as root; print a nice error message here instead
+# of somewhere later, after making a mess.
+for pass in false :; do
+  case "`$ID -u 2>/dev/null`" in
+    "0")
+      echo "Test suite does not work correctly when run as root" >&2
+      exit 1
+      ;;
+
+    *)
+      break
+      ;;
+  esac
+done
+
+
+
+# Test if diff supports the -u option, falling back on -c, then no arguments.
+#
+# Set $diff_u to `$1 -u' if $1 -u works, `$1 -c' if not and $1 -c
+# works, and `$1' otherwise.
+#
+# $diff_u is intended to be used for tests expecting no differences, since the
+# non-matching output is going to vary depending on what version of diff is
+# found.  Used in tests which expect no differences, output will always mean
+# errors and will make the error log more verbose, and correspondingly more
+# readable by a human, regardless of the source.
+diff_u_test()
+{
+  diff=$1
+  touch sanity.1 sanity.2
+  if output=`$diff -u sanity.1 sanity.2 2>&1` && test -z "$output"; then
+    diff_u="$diff -u"
+    retval=0
+  elif output=`$diff -c sanity.1 sanity.2 2>&1` && test -z "$output"; then
+    diff_u="$diff -c"
+    retval=77
+  elif output=`$diff sanity.1 sanity.2 2>&1` && test -z "$output"; then
+    echo "A diff that supports the -u or -c options and which does not output"
+    echo "text when files are identical can make the output of some of this"
+    echo "script's tests more readable on failure."
+    diff_u=$diff
+    retval=77
+  else
+    echo "This test suite requires either \`diff' or \`cmp' to run."
+    retval=1
+  fi
+  rm sanity.1 sanity.2
+  return $retval
+}
+
+
+
+# Test if diff supports the -u, --recursive, && --exclude options.
+diff_recursive_test()
+{
+  diff=$1
+  mkdir sanitydir.1
+  mkdir sanitydir.2
+  mkdir sanitydir.1/CVS
+  mkdir sanitydir.2/CVS
+  touch sanitydir.1/sanity.1 sanitydir.1/sanity.2 sanitydir.1/CVS/fileX \
+        sanitydir.2/sanity.1 sanitydir.2/sanity.2 sanitydir.2/CVS/fileY
+
+  if $diff -u --recursive --exclude=CVS sanitydir.1 sanitydir.2 \
+          >/dev/null 2>&1; then
+    retval=0
+  else
+    echo "GNU diff can make the output of some tests more readable."
+    retval=77
+  fi
+  rm -r sanitydir.1 sanitydir.2
+  return $retval
+}
+
+DIFF=`find_tool diff $DIFF:gdiff:cmp \
+               version_test diff_u_test diff_recursive_test`
+# Make sure $diff_u is set based on the tool find_tool returned.
+diff_u_test $DIFF
+
+
+
+# Cause NextStep 3.3 users to lose in a more graceful fashion.
+expr_tooltest1 ()
+{
+expr=$1
+if $expr 'abc
+def' : 'abc
+def' >/dev/null; then
+  # good, it works
+  return 0
+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.'
+  return 1
+fi
+}
+
+# Warn SunOS, SysVr3.2, etc., users that they may be partially losing
+# if we can't find a GNU expr to ease their troubles...
+expr_tooltest2 ()
+{
+expr=$1
+if $expr 'a
+b' : 'a
+c' >/dev/null; then
+  echo 'Warning: you are using a version of expr that does not correctly'
+  echo 'match multi-line patterns.  Some tests may spuriously pass or fail.'
+  echo 'You may wish to make sure GNU expr is in your path.'
+  return 1
+else
+  return 0
+fi
+}
+
+expr_create_bar ()
+{
+echo 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' >${TESTDIR}/foo
+cat ${TESTDIR}/foo ${TESTDIR}/foo ${TESTDIR}/foo ${TESTDIR}/foo >${TESTDIR}/bar
+cat ${TESTDIR}/bar ${TESTDIR}/bar ${TESTDIR}/bar ${TESTDIR}/bar >${TESTDIR}/foo
+cat ${TESTDIR}/foo ${TESTDIR}/foo ${TESTDIR}/foo ${TESTDIR}/foo >${TESTDIR}/bar
+rm -f ${TESTDIR}/foo
+}
+
+expr_tooltest3 ()
+{
+expr=$1
+# More SunOS lossage...
+test ! -f ${TESTDIR}/bar && expr_create_bar
+if $expr "`cat ${TESTDIR}/bar`" : "`cat ${TESTDIR}/bar`" >/dev/null; then
+  : good, it works
+else
+  echo 'Warning: you are using a version of expr that does not correctly'
+  echo 'match large patterns.  Some tests may spuriously pass or fail.'
+  echo 'You may wish to make sure GNU expr is in your path.'
+  return 1
+fi
+if $expr "`cat ${TESTDIR}/bar`x" : "`cat ${TESTDIR}/bar`y" >/dev/null; then
+  echo 'Warning: you are using a version of expr that does not correctly'
+  echo 'match large patterns.  Some tests may spuriously pass or fail.'
+  echo 'You may wish to make sure GNU expr is in your path.'
+  return 1
+fi
+# good, it works
+return 0
+}
+
+# That we should have to do this is total bogosity, but GNU expr
+# version 1.9.4-1.12 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="$"
+expr_set_ENDANCHOR ()
+{
+expr=$1
+ENDANCHOR="$"
+if $expr 'abc
+def' : 'abc$' >/dev/null; then
+  ENDANCHOR='\'\'
+   echo "Notice: An ENDANCHOR of dollar does not work."
+   echo "Using a workaround for GNU expr versions 1.9.4 thru 1.12"
+fi
+return 0
+}
+
+# Work around another GNU expr (version 1.10-1.12) 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='.*'
+expr_set_DOTSTAR ()
+{
+expr=$1
+DOTSTAR='.*'
+if $expr 'abc
+def' : "a${DOTSTAR}f" >/dev/null; then
+  : good, it works
+else
+  DOTSTAR='\(.\|
+\)*'
+  echo "Notice: DOTSTAR changed from sane \`.*' value to \`$DOTSTAR\`"
+  echo "to workaround GNU expr version 1.10 thru 1.12 bug where \`.'"
+  echo "does not match a newline."
+fi
+return 0
+}
+
+# Now that we have DOTSTAR, make sure it works with big matches
+expr_tooltest_DOTSTAR ()
+{
+expr=$1
+test ! -f ${TESTDIR}/bar && expr_create_bar
+if $expr "`cat ${TESTDIR}/bar`" : "${DOTSTAR}xyzABC${DOTSTAR}$" >/dev/null; 
then
+  # good, it works
+  return 0
+else
+  echo 'Warning: you are using a version of expr that does not correctly'
+  echo 'match large patterns.  Some tests may spuriously pass or fail.'
+  echo 'You may wish to make sure GNU expr is in your path.'
+  return 77
+fi
+}
+
+EXPR=`find_tool expr ${EXPR}:gexpr \
+  version_test expr_tooltest1 expr_tooltest2 expr_tooltest3 \
+expr_set_ENDANCHOR expr_set_DOTSTAR expr_tooltest_DOTSTAR`
+
+# Set the ENDANCHOR and DOTSTAR for the chosen expr version.
+expr_set_ENDANCHOR ${EXPR} >/dev/null
+expr_tooltest_DOTSTAR ${EXPR} >/dev/null
+
+echo "Using EXPR=$EXPR" >>$LOGFILE
+echo "Using ENDANCHOR=$ENDANCHOR" >>$LOGFILE
+echo "Using DOTSTAR=$DOTSTAR" >>$LOGFILE
+
+# Cleanup
+rm -f ${TESTDIR}/bar
+
+# 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
+
+# Now test the username to make sure it contains only valid characters
+username=`$ID -un`
+if $EXPR "${username}" : "${username}" >/dev/null; then
+  : good, it works
+else
+  echo "Test suite does not work correctly when run by a username" >&2
+  echo "containing regular expression meta-characters." >&2
+  exit 1
+fi
+
+# Also check for exactly the length of the username
+userlen=`echo $username | wc -c`
+userlen=`expr $userlen - 1`
+
+# Only 8 characters of $username appear in some output.
+if test `echo $username |wc -c` -gt 8; then
+  username8=`echo $username |sed 's/^\(........\).*/\1/'`
+else
+  username8=$username
+fi
+
+# Only 1 character of $username appear in some output.
+if test `echo $username |wc -c` -gt 1; then
+  username1=`echo $username |sed 's/^\(.\).*/\1/'`
+else
+  username1=$username
+fi
+
+# Rarely, we need to match any username, not just the name of the user
+# running this test.  This variable usually shouldn't be used.  $username
+# contains the name of the user actually running this test.
+#
+# I believe this only ever actually gets compared to usernames created by this
+# test.  It used to be compared to the username of the user running this test,
+# but this hasn't been true for a long time.  Regardless, I tried to get the
+# allowed character set right, based on a list in a private email from Mark
+# Baushke, basically the allowed names from Linux systems (plus `.', which is
+# only allowed on Gentoo Linux as of 2005-09-13).
+anyusername="[_a-zA-Z0-9][-_.$a-zA-Z0-9]*"
+
+# now make sure that tr works on NULs
+tr_tooltest1 ()
+{
+tr=$1
+if $EXPR `echo "123" | $tr '2' '\0'` : "123" >/dev/null 2>&1; then
+  echo 'Warning: you are using a version of tr which does not correctly'
+  echo 'handle NUL bytes.  Some tests may spuriously pass or fail.'
+  echo 'You may wish to make sure GNU tr is in your path.'
+  return 77
+fi
+# good, it works
+return 0
+}
+
+TR=`find_tool tr ${TR}:gtr version_test tr_tooltest1`
+echo "Using TR=$TR" >>$LOGFILE
+
+# MacOS X (10.2.8) has a /bin/ls that does not work correctly in that
+# it will return true even if the wildcard argument does not match any
+# files.
+ls_tooltest ()
+{
+ls=$1
+# Force cleanup
+if test -d $TESTDIR/ls-test; then
+    chmod -R a+wx $TESTDIR/ls-test
+    rm -rf $TESTDIR/ls-test
+fi
+if $ls $TESTDIR/ls-test >/dev/null 2>&1; then
+  echo "Notice: \`$ls' is defective."
+  echo 'This is a version of ls which does not correctly'
+  echo 'return false for files that do not exist. Some tests may'
+  echo 'spuriously pass or fail.'
+  echo 'You may wish to put a an ls from GNU coreutils into your path.'
+  return 77
+else
+  return 0
+fi
+}
+LS=`find_tool ls ls:gls version_test ls_tooltest`
+echo "Using LS=$LS" >>$LOGFILE
+
+# Awk testing
+
+awk_tooltest1 ()
+{
+awk=$1
+$awk 'BEGIN {printf("one\ntwo\nthree\nfour\nfive\nsix")}' </dev/null >abc
+if $EXPR "`cat abc`" : \
+'one
+two
+three
+four
+five
+six'; then
+  rm abc
+  return 0
+else
+  rm abc
+  echo "Notice: awk BEGIN clause or printf is not be working properly."
+  return 1
+fi
+}
+
+# Format item %c check
+awk_tooltest2 ()
+{
+awk=$1
+$awk 'BEGIN { printf "%c%c%c", 2, 3, 4 }' </dev/null \
+  | ${TR} '\002\003\004' '123' >abc
+if $EXPR "`cat abc`" : "123" ; then
+  : good, found it
+else
+  echo "Notice: awk format %c string may not be working properly."
+  rm abc
+  return 77
+fi
+rm abc
+return 0
+}
+
+AWK=`find_tool awk gawk:nawk:awk version_test awk_tooltest1 awk_tooltest2`
+echo "Using AWK=$AWK" >>$LOGFILE
+
+
+###
+### Functions used by tests.
+###
+
+# Execute a command on the repository, syncing when done if necessary.
+#
+# Syntax is as `eval'.
+modify_repo ()
+{
+    eval "$*"
+    if $proxy; then
+       # And now resync the secondary.
+       $TESTDIR/sync-secondary "repo modification" modify_repo ALL "$@"
+    fi
+}
+
+# Restore changes to CVSROOT admin files.
+restore_adm ()
+{
+    modify_repo rm -rf $CVSROOT_DIRNAME/CVSROOT
+    modify_repo cp -Rp $TESTDIR/CVSROOT.save $CVSROOT_DIRNAME/CVSROOT
+}
+
+# Test that $RSYNC supports the options we need or try to find a
+# replacement. If $RSYNC works or we replace it, and return 0.
+# Otherwise, set $skipreason and return 77.
+require_rsync ()
+{
+  rsyncworks=false
+  # rsync is NOT a GNU tool, so do NOT use find_tool for name munging.
+  for rsync in ${RSYNC} `Which -a rsync`;
+  do
+
+    if is_bad_tool `Which $rsync` ; then continue ; fi
+    # Make some data to test rsync on.
+    mkdir $TESTDIR/rsync-test
+    mkdir $TESTDIR/rsync-test/Attic && touch $TESTDIR/rsync-test/Attic/6
+    mkdir $TESTDIR/rsync-test/otherdir && touch $TESTDIR/rsync-test/otherdir/7
+    for file in 1 2 3 4 5; do
+      touch $TESTDIR/rsync-test/$file
+    done
+  
+    if test -f "$rsync" && test -r "$rsync" \
+      && $rsync -rglop --delete $TESTDIR/rsync-test/ $TESTDIR/rsync-test-copy \
+             >/dev/null 2>&1 \
+      && $rsync -rglop --delete --include Attic --exclude '*/' \
+             $TESTDIR/rsync-test/ $TESTDIR/rsync-test-copy2 \
+             >/dev/null 2>&1 \
+      && test -f $TESTDIR/rsync-test/5 \
+      && mv $TESTDIR/rsync-test/5 $TESTDIR/rsync-test/Attic/5 \
+      && test -f $TESTDIR/rsync-test-copy/Attic/6 \
+      && $rsync -rglop --delete $TESTDIR/rsync-test/ $TESTDIR/rsync-test-copy \
+             >/dev/null 2>&1 \
+      && $rsync -rglop --delete --include Attic --exclude '*/' \
+             $TESTDIR/rsync-test/ $TESTDIR/rsync-test-copy2 \
+             >/dev/null 2>&1 \
+      && test ! -f $TESTDIR/rsync-test-copy/5 \
+      && test ! -f $TESTDIR/rsync-test-copy2/5 \
+      && test -f $TESTDIR/rsync-test-copy2/Attic/5 \
+      && test ! -f $TESTDIR/rsync-test-copy2/otherdir/7
+    then
+      # good, it works
+      rsyncworks=:
+      RSYNC=$rsync
+    else
+      # Only use Which because of ${RSYNC} in the for loop.
+      set_bad_tool `Which $rsync`
+    fi
+  
+    rm -rf $TESTDIR/rsync-test $TESTDIR/rsync-test-copy \
+       $TESTDIR/rsync-test-copy2
+
+    if $rsyncworks; then
+      return 0
+    else
+      (echo $rsync failed to work properly;\
+       echo "$rsync --version"; $rsync --version) >>$LOGFILE 2>&1
+    fi
+  done
+
+  unset RSYNC
+  skipreason="unusable or no rsync found"
+  return 77
+}
+
+# Test that $1 works as a remote shell.  If so, set $host, $CVS_RSH, &
+# $save_CVS_RSH to match and return 0.  Otherwise, set $skipreason and return
+# 77.
+require_rsh ()
+{
+  host=${remotehost-"`hostname`"}
+  result=`$1 $host 'echo test'`
+  rc=$?
+  if test $? != 0 || test "x$result" != "xtest"; then
+    skipreason="\`$1 $host' failed rc=$rc result=$result"
+    return 77
+  fi
+
+  save_CVS_RSH=$CVS_RSH
+  CVS_RSH=$1; export CVS_RSH
+  return 0
+}
+
+# Find a usable SSH.  When a usable ssh is found, set $host, $CVS_RSH, and
+# $save_CVS_RSH and return 0.  Otherwise, set $skipreason and return 77.
+require_ssh ()
+{
+  case "$CVS_RSH" in
+    *ssh*|*putty*)
+      tryssh=`Which $CVS_RSH`
+      if [ ! -n "$tryssh" ]; then
+       skipreason="Unable to find CVS_RSH=$CVS_RSH executable"
+       return 77
+      elif [ ! -x "$tryssh" ]; then
+       skipreason="Unable to execute $tryssh program"
+       return 77
+      fi
+      ;;
+    *)
+      # Look in the user's PATH for "ssh"
+      tryssh=`Which ssh`
+      if test ! -r "$tryssh"; then
+       skipreason="Unable to find ssh program"
+       return 77
+      fi
+      ;;
+  esac
+
+  require_rsh "$tryssh"
+  return $?
+}
+
+pass ()
+{
+  echo "PASS: $1" >>${LOGFILE}
+  passed=`expr $passed + 1`
+}
+
+# Like skip(), but don't fail when $skipfail is set.
+skip_always ()
+{
+  echo "SKIP: $1${2+ ($2)}" >>$LOGFILE
+  skipped=`expr $skipped + 1`
+}
+
+skip ()
+{
+  if $skipfail; then
+    # exits
+    fail "$1${2+ ($2)}"
+  fi
+
+  skip_always ${1+"$@"}
+}
+
+# Convenience function for skipping tests run only in remote mode.
+remoteonly ()
+{
+  skip_always $1 "only tested in remote mode"
+}
+
+# Convenience function for skipping tests not run in proxy mode.
+notproxy ()
+{
+  skip_always $1 "not tested in proxy mode"
+}
+
+# Convenience function for skipping tests not run in proxy mode.
+notnoredirect ()
+{
+  skip_always $1 "not tested in proxy-noredirect mode"
+}
+
+warn ()
+{
+  if $skipfail; then
+    fail "$1${2+ ($2)}"
+  else
+    echo "WARNING: $1${2+ ($2)}" >>$LOGFILE
+  fi
+  warnings=`expr $warnings + 1`
+}
+
+fail ()
+{
+  echo "FAIL: $1" | tee -a ${LOGFILE}
+  echo "*** Please see the \`TESTS' and \`check.log' files for more 
information." >&2
+  # This way the tester can go and see what remnants were left
+  exit 1
+}
+
+verify_tmp_empty ()
+{
+  # Test our temp directory for cvs-serv* directories and cvsXXXXXX temp
+  # files.  We would like to not leave any behind.
+  if $remote && $LS $TMPDIR/cvs-serv* >/dev/null 2>&1; then
+    # A true value means ls found files/directories with these names.
+    # Give the server some time to finish, then retry.
+    sleep 1
+    if $LS $TMPDIR/cvs-serv* >/dev/null 2>&1; then
+      warn "$1" "Found cvs-serv* directories in $TMPDIR."
+      # The above will exit if $skipfail
+      rm -rf $TMPDIR/cvs-serv*
+    fi
+  fi
+  if $LS $TMPDIR/cvs?????? >/dev/null 2>&1; then
+    # A true value means ls found files/directories with these names.
+    warn "$1" "Found cvsXXXXXX temp files in $TMPDIR."
+    # The above will exit if $skipfail
+    rm -f ls $TMPDIR/cvs??????
+  fi
+}
+
+# See dotest and dotest_fail for explanation (this is the parts
+# of the implementation common to the two).
+dotest_internal ()
+{
+  if $EXPR "`cat ${TESTDIR}/dotest.tmp`" : "$3${ENDANCHOR}" >/dev/null; then
+    # Why, I hear you ask, do we write this to the logfile
+    # even when the test passes?  The reason is that the test
+    # may give us the regexp which we were supposed to match,
+    # but sometimes it may be useful to look at the exact
+    # text which was output.  For example, suppose one wants
+    # to grep for a particular warning, and make _sure_ that
+    # CVS never hits it (even in cases where the tests might
+    # match it with .*).  Or suppose one wants to see the exact
+    # date format output in a certain case (where the test will
+    # surely use a somewhat non-specific pattern).
+    cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+    pass "$1"
+    verify_tmp_empty "$1"
+  # expr can't distinguish between "zero characters matched" and "no match",
+  # so special-case it.
+  elif test -z "$3" && test ! -s ${TESTDIR}/dotest.tmp; then
+    pass "$1"
+    verify_tmp_empty "$1"
+  elif test x"$4" != x; then
+    if $EXPR "`cat ${TESTDIR}/dotest.tmp`" : "$4${ENDANCHOR}" >/dev/null; then
+      cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+      pass "$1"
+      verify_tmp_empty "$1"
+    else
+      echo "** expected: " >>${LOGFILE}
+      echo "$3" >>${LOGFILE}
+      echo "$3" > ${TESTDIR}/dotest.ex1
+      echo "** or: " >>${LOGFILE}
+      echo "$4" >>${LOGFILE}
+      echo "$4" > ${TESTDIR}/dotest.ex2
+      echo "** got: " >>${LOGFILE}
+      cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+      fail "$1"
+    fi
+  else
+    echo "** expected: " >>${LOGFILE}
+    echo "$3" >>${LOGFILE}
+    echo "$3" > ${TESTDIR}/dotest.exp
+    echo "** got: " >>${LOGFILE}
+    cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+    fail "$1"
+  fi
+}
+
+dotest_all_in_one ()
+{
+  if $EXPR "`cat ${TESTDIR}/dotest.tmp`" : \
+         "`cat ${TESTDIR}/dotest.exp`" >/dev/null; then
+    return 0
+  fi
+  return 1
+}
+
+# WARNING: this won't work with REs that match newlines....
+#
+dotest_line_by_line ()
+{
+  line=1
+  while [ $line -le `wc -l <${TESTDIR}/dotest.tmp` ] ; do
+    if $EXPR "`sed -n ${line}p ${TESTDIR}/dotest.tmp`" : \
+       "`sed -n ${line}p ${TESTDIR}/dotest.exp`" >/dev/null; then
+      :
+    elif test -z "`sed -n ${line}p ${TESTDIR}/dotest.tmp`" &&
+       test -z "`sed -n ${line}p ${TESTDIR}/dotest.exp`"; then
+      :
+    else
+      echo "Line $line:" >> ${LOGFILE}
+      echo "**** expected: " >>${LOGFILE}
+      sed -n ${line}p ${TESTDIR}/dotest.exp >>${LOGFILE}
+      echo "**** got: " >>${LOGFILE}
+      sed -n ${line}p ${TESTDIR}/dotest.tmp >>${LOGFILE}
+      unset line
+      return 1
+    fi
+    line=`expr $line + 1`
+  done
+  unset line
+  return 0
+}
+
+# If you are having trouble telling which line of a multi-line
+# expression is not being matched, replace calls to dotest_internal()
+# with calls to this function:
+#
+dotest_internal_debug ()
+{
+  if test -z "$3"; then
+    if test -s ${TESTDIR}/dotest.tmp; then
+      echo "** expected: " >>${LOGFILE}
+      echo "$3" >>${LOGFILE}
+      echo "$3" > ${TESTDIR}/dotest.exp
+      rm -f ${TESTDIR}/dotest.ex2
+      echo "** got: " >>${LOGFILE}
+      cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+      fail "$1"
+    else
+      pass "$1"
+      verify_tmp_empty "$1"
+    fi
+  else
+    echo "$3" > ${TESTDIR}/dotest.exp
+    if dotest_line_by_line "$1" "$2"; then
+      pass "$1"
+      verify_tmp_empty "$1"
+    else
+      if test x"$4" != x; then
+       mv ${TESTDIR}/dotest.exp ${TESTDIR}/dotest.ex1
+       echo "$4" > ${TESTDIR}/dotest.exp
+       if dotest_line_by_line "$1" "$2"; then
+         pass "$1"
+         verify_tmp_empty "$1"
+       else
+         mv ${TESTDIR}/dotest.exp ${TESTDIR}/dotest.ex2
+         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
+}
+
+# This function allows the test output to be filtered before being verified.
+# The dotest_* functions all call this function, which runs the command
+# in the env var $TEST_FILTER on its argument if $TEST_FILTER is set.  If
+# $TEST_FILTER is not set, this function does nothing.
+#
+# I found this primarily useful when running the test suite on a CVS
+# executable linked with memory and function profilers which can generate
+# spurious output.
+run_filter ()
+{
+  if test -n "$TEST_FILTER"; then
+    # Make sure there is an EOL
+    echo >>$1
+    sed '${/^$/d}' <$1 >$1.filter1
+    # Run the filter
+    eval "$TEST_FILTER" <$1.filter1 >$1.filter2
+    $diff_u $1 $1.filter2 \
+           >$1.diff
+    mv $1.filter2 $1
+    rm $1.filter1
+  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 ()
+{
+  rm -f $TESTDIR/dotest.ex? 2>&1
+  eval "$2" >$TESTDIR/dotest.tmp 2>&1
+  status=$?
+  run_filter $TESTDIR/dotest.tmp
+  if test "$status" != 0; then
+    cat $TESTDIR/dotest.tmp >>$LOGFILE
+    echo "exit status was $status" >>${LOGFILE}
+    fail "$1"
+  fi
+  dotest_internal "$@"
+}
+
+# Like dotest except only 2 args and result must exactly match stdin
+dotest_lit ()
+{
+  rm -f $TESTDIR/dotest.ex? 2>&1
+  eval "$2" >$TESTDIR/dotest.tmp 2>&1
+  status=$?
+  run_filter $TESTDIR/dotest.tmp
+  if test "$status" != 0; then
+    cat $TESTDIR/dotest.tmp >>$LOGFILE
+    echo "exit status was $status" >>$LOGFILE
+    fail "$1"
+  fi
+  cat >$TESTDIR/dotest.exp
+  if cmp $TESTDIR/dotest.exp $TESTDIR/dotest.tmp >/dev/null 2>&1; then
+    pass "$1"
+    verify_tmp_empty "$1"
+  else
+    echo "** expected: " >>$LOGFILE
+    cat $TESTDIR/dotest.exp >>$LOGFILE
+    echo "** got: " >>$LOGFILE
+    cat $TESTDIR/dotest.tmp >>$LOGFILE
+    fail "$1"
+  fi
+}
+
+# Like dotest except exitstatus should be nonzero.
+dotest_fail ()
+{
+  rm -f $TESTDIR/dotest.ex? 2>&1
+  eval "$2" >$TESTDIR/dotest.tmp 2>&1
+  status=$?
+  run_filter $TESTDIR/dotest.tmp
+  if test "$status" = 0; then
+    cat $TESTDIR/dotest.tmp >>$LOGFILE
+    echo "exit status was $status" >>$LOGFILE
+    fail "$1"
+  fi
+  dotest_internal "$@"
+}
+
+# Like dotest except output is sorted.
+dotest_sort ()
+{
+  rm -f $TESTDIR/dotest.ex? 2>&1
+  eval "$2" >$TESTDIR/dotest.tmp1 2>&1
+  status=$?
+  run_filter $TESTDIR/dotest.tmp1
+  if test "$status" != 0; then
+    cat $TESTDIR/dotest.tmp1 >>$LOGFILE
+    echo "exit status was $status" >>$LOGFILE
+    fail "$1"
+  fi
+  $TR '        ' ' ' < $TESTDIR/dotest.tmp1 | sort > $TESTDIR/dotest.tmp
+  dotest_internal "$@"
+}
+
+# Like dotest_fail except output is sorted.
+dotest_fail_sort ()
+{
+  rm -f $TESTDIR/dotest.ex? 2>&1
+  eval "$2" >$TESTDIR/dotest.tmp1 2>&1
+  status=$?
+  run_filter $TESTDIR/dotest.tmp1
+  if test "$status" = 0; then
+    cat $TESTDIR/dotest.tmp1 >>$LOGFILE
+    echo "exit status was $status" >>$LOGFILE
+    fail "$1"
+  fi
+  $TR '        ' ' ' < $TESTDIR/dotest.tmp1 | sort > $TESTDIR/dotest.tmp
+  dotest_internal "$@"
+}
+
+# A function for fetching the timestamp of a revison of a file
+getrlogdate () {
+    ${testcvs} -n rlog -N ${1+"$@"} |
+    while read token value; do
+       case "$token" in
+       date:)
+           echo $value | sed "s,;.*,,"
+           break;
+            ;;
+       esac
+    done
+}
+
+# Avoid picking up any stray .cvsrc, etc., from the user running the tests
+mkdir home
+HOME=$TESTDIR/home; export HOME
+
+# Make sure this variable is not defined to anything that would
+# change the format of rcs dates.  Otherwise people using e.g.,
+# RCSINIT=-zLT get lots of spurious failures.
+RCSINIT=; export RCSINIT
+
+# 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
+       # Basic/miscellaneous functionality
+       tests="version basica basicb basicc basic1 deep basic2 ls"
+       tests="$tests parseroot parseroot2 parseroot3 files spacefiles"
+       tests="${tests} commit-readonly commit-add-missing"
+       tests="${tests} status"
+       # Branching, tagging, removing, adding, multiple directories
+       tests="${tests} rdiff rdiff-short"
+       tests="${tests} rdiff2 diff diffnl death death2"
+       tests="${tests} rm-update-message rmadd rmadd2 rmadd3 resurrection"
+       tests="${tests} dirs dirs2 branches branches2 branches3"
+       tests="${tests} branches4 tagc tagf tag-space"
+       tests="${tests} rcslib multibranch import importb importc importX"
+       tests="$tests importX2 import-CVS import-quirks"
+       tests="${tests} update-p import-after-initial branch-after-import"
+       tests="${tests} join join2 join3 join4 join5 join6 join7"
+       tests="${tests} join-readonly-conflict join-admin join-admin-2"
+       tests="${tests} join-rm"
+       tests="${tests} new newb conflicts conflicts2 conflicts3"
+       tests="${tests} clean"
+       tests="${tests} keywordexpand"
+       tests="${tests} tag-ext"
+       # Checking out various places (modules, checkout -d, &c)
+       tests="${tests} modules modules2 modules3 modules4 modules5 modules6"
+       tests="${tests} modules7 mkmodules co-d"
+       tests="${tests} cvsadm emptydir abspath abspath2 toplevel toplevel2"
+        tests="${tests} rstar-toplevel trailingslashes checkout_repository"
+       # Log messages, error messages.
+       tests="${tests} mflag editor env errmsg1 errmsg2 adderrmsg opterrmsg"
+       tests="${tests} errmsg3"
+       tests="${tests} close-stdout"
+       tests="$tests debug-log-nonfatal"
+       # Watches, binary files, history browsing, &c.
+       tests="${tests} devcom devcom2 devcom3 watch4 watch5 watch6-0 watch6"
+        tests="${tests} edit-check"
+       tests="${tests} unedit-without-baserev"
+       tests="${tests} ignore ignore-on-branch binfiles binfiles2 binfiles3"
+       tests="${tests} mcopy binwrap binwrap2"
+       tests="${tests} binwrap3 mwrap info taginfo posttag"
+       tests="$tests config config2 config3 config4 compression"
+       tests="${tests} serverpatch log log2 logopt ann ann-id"
+       # Repository Storage (RCS file format, CVS lock files, creating
+       # a repository without "cvs init", &c).
+       tests="${tests} crerepos rcs rcs2 rcs3 rcs4 rcs5"
+       tests="$tests lockfiles backuprecover"
+       tests="${tests} sshstdio"
+       # More history browsing, &c.
+       tests="${tests} history"
+       tests="${tests} big modes modes2 modes3 stamps"
+       # PreservePermissions stuff: permissions, symlinks et al.
+       # tests="${tests} perms symlinks symlinks2 hardlinks"
+       # More tag and branch tests, keywords.
+       tests="${tests} sticky keyword keywordlog keywordname keyword2"
+       tests="${tests} head tagdate multibranch2 tag8k"
+       # "cvs admin", reserved checkouts.
+       tests="${tests} admin reserved"
+       # Nuts and bolts of diffing/merging (diff library, &c)
+       tests="${tests} diffmerge1 diffmerge2"
+       # Release of multiple directories
+       tests="${tests} release"
+       tests="${tests} recase"
+       # Multiple root directories and low-level protocol tests.
+       tests="${tests} multiroot multiroot2 multiroot3 multiroot4"
+       tests="${tests} rmroot reposmv pserver server server2 client"
+       tests="${tests} dottedroot fork commit-d template"
+       tests="${tests} writeproxy writeproxy-noredirect writeproxy-ssh"
+       tests="${tests} writeproxy-ssh-noredirect"
+else
+       tests="$*"
+fi
+
+# Now check the -f argument for validity.
+if test -n "$fromtest"; then
+       # Don't allow spaces - they are our delimiters in tests
+       count=0
+       for sub in $fromtest; do
+         count=`expr $count + 1`
+       done
+       if test $count != 1; then
+               echo "No such test \`$fromtest'." >&2
+               exit 2
+       fi
+       # make sure it is in $tests
+       case " $tests " in
+               *" $fromtest "*)
+                       ;;
+               *)
+                       echo "No such test \`$fromtest'." >&2
+                       exit 2
+                       ;;
+       esac
+fi
+
+
+
+if diff_recursive_test $DIFF >/dev/null 2>&1; then
+  directory_cmp ()
+  {
+    $DIFF -u --recursive --exclude=CVS $1 $2
+  }
+else
+  # a simple function to compare directory contents
+  #
+  # Returns: 0 for same, 1 for different
+  #
+  directory_cmp ()
+  {
+    OLDPWD=`pwd`
+    DIR_1=$1
+    DIR_2=$2
+
+    cd $DIR_1
+    find . -print | fgrep -v /CVS | sort > $TESTDIR/dc$$d1
+
+    # go back where we were to avoid symlink hell...
+    cd $OLDPWD
+    cd $DIR_2
+    find . -print | fgrep -v /CVS | sort > $TESTDIR/dc$$d2
+
+    if $diff_u $TESTDIR/dc$$d1 $TESTDIR/dc$$d2
+    then
+      :
+    else
+      echo "directory_cmp: file lists of \`$DIR_1' & \`$DIR_2' differ" >&2
+      return 1
+    fi
+    cd $OLDPWD
+    while read a
+    do
+      if test -f $DIR_1/"$a" ; then
+       cmp $DIR_1/"$a" $DIR_2/"$a"
+       if test $? -ne 0 ; then
+         return 1
+       fi
+      fi
+    done < $TESTDIR/dc$$d1
+    rm -f $TESTDIR/dc$$*
+    return 0
+  }
+fi
+
+
+
+#
+# The following 4 functions are used by the diffmerge1 test case.  They set up,
+# respectively, the four versions of the files necessary:
+#
+#      1.  Ancestor revisions.
+#      2.  "Your" changes.
+#      3.  "My" changes.
+#      4.  Expected merge result.
+#
+
+# Create ancestor revisions for diffmerge1
+diffmerge_create_older_files() {
+         # This test case was supplied by Noah Friedman:
+         cat >testcase01 <<EOF
+// Button.java
+
+package random.application;
+
+import random.util.*;
+
+public class Button
+{
+  /* Instantiates a Button with origin (0, 0) and zero width and height.
+   * You must call an initializer method to properly initialize the Button.
+   */
+  public Button ()
+  {
+    super ();
+
+    _titleColor = Color.black;
+    _disabledTitleColor = Color.gray;
+    _titleFont = Font.defaultFont ();
+  }
+
+  /* Convenience constructor for instantiating a Button with
+   * bounds x, y, width, and height.  Equivalent to
+   *     foo = new Button ();
+   *     foo.init (x, y, width, height);
+   */
+  public Button (int x, int y, int width, int height)
+  {
+    this ();
+    init (x, y, width, height);
+  }
+}
+EOF
+
+         # This test case was supplied by Jacob Burckhardt:
+         cat >testcase02 <<EOF
+a
+a
+a
+a
+a
+EOF
+
+         # This test case was supplied by Karl Tomlinson who also wrote the
+         # patch which lets CVS correctly handle this and several other cases:
+         cat >testcase03 <<EOF
+x
+s
+a
+b
+s
+y
+EOF
+
+         # This test case was supplied by Karl Tomlinson:
+         cat >testcase04 <<EOF
+s
+x
+m
+m
+x
+s
+v
+s
+x
+m
+m
+x
+s
+EOF
+
+         # This test case was supplied by Karl Tomlinson:
+         cat >testcase05 <<EOF
+s
+x
+m
+m
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+s
+s
+s
+s
+s
+s
+s
+s
+s
+s
+v
+EOF
+
+         # This test case was supplied by Jacob Burckhardt:
+         cat >testcase06 <<EOF
+g
+
+
+
+
+
+
+
+
+
+
+
+i
+EOF
+
+         # This test is supposed to verify that the horizon lines are the same
+         # for both 2-way diffs, but unfortunately, it does not fail with the
+         # old version of cvs.  However, Karl Tomlinson still thought it would
+         # be good to test it anyway:
+         cat >testcase07 <<EOF
+h
+f
+
+
+
+
+
+
+
+
+
+g
+r
+
+
+
+i
+
+
+
+
+
+
+
+
+
+
+i
+EOF
+
+         # This test case was supplied by Jacob Burckhardt:
+         cat >testcase08 <<EOF
+Both changes move this line to the end of the file.
+
+no
+changes
+here
+
+First change will delete this line.
+
+First change will also delete this line.
+
+    no
+    changes
+    here
+
+Second change will change it here.
+
+        no
+        changes
+        here
+EOF
+
+         # This test case was supplied by Jacob Burckhardt.  Note that I do not
+         # think cvs has ever failed with this case, but I include it anyway,
+         # since I think it is a hard case.  It is hard because Peter Miller's
+         # fmerge utility fails on it:
+         cat >testcase09 <<EOF
+m
+a
+{
+}
+b
+{
+}
+EOF
+
+         # This test case was supplied by Martin Dorey and simplified by Jacob
+         # Burckhardt:
+         cat >testcase10 <<EOF
+
+    petRpY ( MtatRk );
+    fV ( MtatRk != Zy ) UDTXUK_DUUZU ( BGKT_ZFDK_qjT_HGTG );
+
+    MtatRk = MQfr_GttpfIRte_MtpeaL ( &acI, jfle_Uecopd_KRLIep * 
jfle_Uecopd_MfJe_fY_nEtek );
+    OjZy MtatRk = Uead_GttpfIRte_MtpeaL ( &acI, jfle_Uecopd_MfJe_fY_nEtek, 
nRVVep );
+
+    Bloke_GttpfIRte_MtpeaL ( &acI );
+MTGTXM Uead_Ktz_qjT_jfle_Uecopd ( fYt Y, uofd *nRVVep )
+{
+    fV ( Y < 16 )
+    {
+        petRpY ( Uead_Mectopk ( noot_Uecopd.qVtHatabcY0 * 
noot_Uecopd.MectopkFepBlRktep +
+                                                      Y * 
jfle_Uecopd_MfJe_fY_Mectopk,
+                                jfle_Uecopd_MfJe_fY_Mectopk,
+                                nRVVep ) );
+    }
+    elke
+    {
+        petRpY ( Uead_Mectopk ( noot_Uecopd.qVtqfppHatabcY0 * 
noot_Uecopd.MectopkFepBlRktep +
+                                                 ( Y - 16 ) * 
jfle_Uecopd_MfJe_fY_Mectopk,
+                                jfle_Uecopd_MfJe_fY_Mectopk,
+                                nRVVep ) );
+    }
+
+}
+
+
+/****************************************************************************
+*                                                                           *
+*   Uead Mectopk ( Uelatfue to tze cRppeYt raptftfoY )                      *
+*                                                                           *
+****************************************************************************/
+
+MTGTXM Uead_Mectopk ( RfYt64 Mtapt_Mectop, RfYt64 KRL_Mectopk, uofd *nRVVep )
+{
+MTGTXM MtatRk = Zy;
+
+    MtatRk = Uead_HfkQ ( FaptftfoY_TaIle.Uelatfue_Mectop + Mtapt_Mectop, 
KRL_Mectopk, nRVVep );
+
+    petRpY ( MtatRk );
+
+}
+    HfkQipfte ( waYdle,                 /*  waYdle                         */
+                waYdleFok,              /*  ZVVket VpoL ktapt oV dfkQ      */
+                (coYkt RfYt8*) nRVVep,  /*  nRVVep                         */
+                0,                      /*  MRrepVlRoRk KfxoYfkL           */
+                beYgtz                  /*  nEtek to Apfte                 */
+              );
+
+    petRpY ( Zy );
+}
+EOF
+}
+
+# Create "your" revisions for diffmerge1
+diffmerge_create_your_files() {
+         # remove the Button() method
+         cat >testcase01 <<\EOF
+// Button.java
+
+package random.application;
+
+import random.util.*;
+
+public class Button
+{
+  /* Instantiates a Button with origin (0, 0) and zero width and height.
+   * You must call an initializer method to properly initialize the Button.
+   */
+  public Button ()
+  {
+    super ();
+
+    _titleColor = Color.black;
+    _disabledTitleColor = Color.gray;
+    _titleFont = Font.defaultFont ();
+  }
+}
+EOF
+
+         cat >testcase02 <<\EOF
+y
+a
+a
+a
+a
+EOF
+
+         cat >testcase03 <<\EOF
+x
+s
+a
+b
+s
+b
+s
+y
+EOF
+
+         cat >testcase04 <<\EOF
+s
+m
+s
+v
+s
+m
+s
+EOF
+
+         cat >testcase05 <<\EOF
+v
+s
+m
+s
+s
+s
+s
+s
+s
+s
+s
+s
+s
+v
+EOF
+
+         # Test case 6 and test case 7 both use the same input files, but they
+         # order the input files differently.  In one case, a certain file is
+         # used as the older file, but in the other test case, that same file
+         # is used as the file which has changes.  I could have put echo
+         # commands here, but since the echo lines would be the same as those
+         # in the previous function, I decided to save space and avoid 
repeating
+         # several lines of code.  Instead, I merely swap the files:
+         mv testcase07 tmp
+         mv testcase06 testcase07
+         mv tmp testcase06
+
+         # Make the date newer so that cvs thinks that the files are changed:
+         touch testcase06 testcase07
+
+         cat >testcase08 <<\EOF
+no
+changes
+here
+
+First change has now added this in.
+
+    no
+    changes
+    here
+
+Second change will change it here.
+
+        no
+        changes
+        here
+
+Both changes move this line to the end of the file.
+EOF
+
+         cat >testcase09 <<\EOF
+
+m
+a
+{
+}
+b
+{
+}
+c
+{
+}
+EOF
+
+         cat >testcase10 <<\EOF
+
+    fV ( BzQkV_URYYfYg ) (*jfle_Uecopdk)[0].jfle_Uecopd_KRLIep = ZpfgfYal_jUK;
+
+    petRpY ( MtatRk );
+    fV ( MtatRk != Zy ) UDTXUK_DUUZU ( BGKT_ZFDK_qjT_HGTG );
+
+    fV ( jfle_Uecopd_KRLIep < 16 )
+    {
+        MtatRk = Uead_Ktz_qjT_jfle_Uecopd ( jfle_Uecopd_KRLIep, (uofd*)nRVVep 
);
+    }
+    elke
+    {
+        MtatRk = ZreY_GttpfIRte_MtpeaL ( qjT_jfle_Uecopdk, 
qjT_jfle_Uecopd_BoRYt, HGTG_TvFD, KXbb, KXbb, &acI );
+        fV ( MtatRk != Zy ) UDTXUK_DUUZU ( BGKT_ZFDK_qjT_HGTG );
+
+        MtatRk = MQfr_GttpfIRte_MtpeaL ( &acI, jfle_Uecopd_KRLIep * 
jfle_Uecopd_MfJe_fY_nEtek );
+        OjZy MtatRk = Uead_GttpfIRte_MtpeaL ( &acI, jfle_Uecopd_MfJe_fY_nEtek, 
nRVVep );
+
+    Bloke_GttpfIRte_MtpeaL ( &acI );
+MTGTXM Uead_Ktz_qjT_jfle_Uecopd ( fYt Y, uofd *nRVVep )
+{
+MTGTXM MtatRk = Zy;
+
+    fV ( Y < 16 )
+    {
+        petRpY ( Uead_Mectopk ( noot_Uecopd.qVtHatabcY0 * 
noot_Uecopd.MectopkFepBlRktep +
+                                                      Y * 
jfle_Uecopd_MfJe_fY_Mectopk,
+                                jfle_Uecopd_MfJe_fY_Mectopk,
+                                nRVVep ) );
+    }
+    elke
+    {
+        petRpY ( Uead_Mectopk ( noot_Uecopd.qVtqfppHatabcY0 * 
noot_Uecopd.MectopkFepBlRktep +
+                                                 ( Y - 16 ) * 
jfle_Uecopd_MfJe_fY_Mectopk,
+                                jfle_Uecopd_MfJe_fY_Mectopk,
+                                nRVVep ) );
+    }
+
+    petRpY ( MtatRk );
+
+}
+
+
+/****************************************************************************
+*                                                                           *
+*   Uead Mectopk ( Uelatfue to tze cRppeYt raptftfoY )                      *
+*                                                                           *
+****************************************************************************/
+
+MTGTXM Uead_Mectopk ( RfYt64 Mtapt_Mectop, RfYt64 KRL_Mectopk, uofd *nRVVep )
+{
+MTGTXM MtatRk = Zy;
+
+    MtatRk = Uead_HfkQ ( FaptftfoY_TaIle.Uelatfue_Mectop + Mtapt_Mectop, 
KRL_Mectopk, nRVVep );
+
+    petRpY ( MtatRk );
+
+}
+    HfkQipfte ( waYdle,                 /*  waYdle                         */
+                waYdleFok,              /*  ZVVket VpoL ktapt oV dfkQ      */
+                (coYkt RfYt8*) nRVVep,  /*  nRVVep                         */
+                0,                      /*  MRrepVlRoRk KfxoYfkL           */
+                beYgtz                  /*  nEtek to Apfte                 */
+              );
+
+    petRpY ( Zy );
+}
+
+EOF
+}
+
+# Create "my" revisions for diffmerge1
+diffmerge_create_my_files() {
+          # My working copy still has the Button() method, but I
+         # comment out some code at the top of the class.
+         cat >testcase01 <<\EOF
+// Button.java
+
+package random.application;
+
+import random.util.*;
+
+public class Button
+{
+  /* Instantiates a Button with origin (0, 0) and zero width and height.
+   * You must call an initializer method to properly initialize the Button.
+   */
+  public Button ()
+  {
+    super ();
+
+    // _titleColor = Color.black;
+    // _disabledTitleColor = Color.gray;
+    // _titleFont = Font.defaultFont ();
+  }
+
+  /* Convenience constructor for instantiating a Button with
+   * bounds x, y, width, and height.  Equivalent to
+   *     foo = new Button ();
+   *     foo.init (x, y, width, height);
+   */
+  public Button (int x, int y, int width, int height)
+  {
+    this ();
+    init (x, y, width, height);
+  }
+}
+EOF
+
+         cat >testcase02 <<\EOF
+a
+a
+a
+a
+m
+EOF
+
+         cat >testcase03 <<\EOF
+x
+s
+c
+s
+b
+s
+y
+EOF
+
+         cat >testcase04 <<\EOF
+v
+s
+x
+m
+m
+x
+s
+v
+s
+x
+m
+m
+x
+s
+v
+EOF
+
+         # Note that in test case 5, there are no changes in the "mine"
+         # section, which explains why there is no command here which writes to
+         # file testcase05.
+
+         # no changes for testcase06
+
+         # The two branches make the same changes:
+         cp ../yours/testcase07 .
+
+         cat >testcase08 <<\EOF
+no
+changes
+here
+
+First change will delete this line.
+
+First change will also delete this line.
+
+    no
+    changes
+    here
+
+Second change has now changed it here.
+
+        no
+        changes
+        here
+
+Both changes move this line to the end of the file.
+EOF
+
+         cat >testcase09 <<\EOF
+m
+a
+{
+}
+b
+{
+}
+c
+{
+}
+EOF
+
+         cat >testcase10 <<\EOF
+
+    petRpY ( MtatRk );
+    fV ( MtatRk != Zy ) UDTXUK_DUUZU ( BGKT_ZFDK_qjT_HGTG );
+
+    MtatRk = MQfr_GttpfIRte_MtpeaL ( &acI, jfle_Uecopd_KRLIep * 
jfle_Uecopd_MfJe_fY_nEtek );
+    OjZy MtatRk = Uead_GttpfIRte_MtpeaL ( &acI, jfle_Uecopd_MfJe_fY_nEtek, 
nRVVep );
+
+    Bloke_GttpfIRte_MtpeaL ( &acI );
+MTGTXM Uead_Ktz_qjT_jfle_Uecopd ( fYt Y, uofd *nRVVep )
+{
+    fV ( Y < 16 )
+    {
+        petRpY ( Uead_Mectopk ( noot_Uecopd.qVtHatabcY0 * 
noot_Uecopd.MectopkFepBlRktep +
+                                                      Y * 
jfle_Uecopd_MfJe_fY_Mectopk,
+                                jfle_Uecopd_MfJe_fY_Mectopk,
+                                nRVVep ) );
+    }
+    elke
+    {
+        petRpY ( Uead_Mectopk ( noot_Uecopd.qVtqfppHatabcY0 * 
noot_Uecopd.MectopkFepBlRktep +
+                                                 ( Y - 16 ) * 
jfle_Uecopd_MfJe_fY_Mectopk,
+                                jfle_Uecopd_MfJe_fY_Mectopk,
+                                nRVVep ) );
+    }
+
+}
+
+
+/****************************************************************************
+*                                                                           *
+*   Uead Mectopk ( Uelatfue to tze cRppeYt raptftfoY )                      *
+*                                                                           *
+****************************************************************************/
+
+MTGTXM Uead_Mectopk ( RfYt64 Mtapt_Mectop, RfYt64 KRL_Mectopk, uofd *nRVVep )
+{
+MTGTXM MtatRk = Zy;
+
+    MtatRk = Uead_HfkQ ( FaptftfoY_TaIle.Uelatfue_Mectop + Mtapt_Mectop, 
KRL_Mectopk, nRVVep );
+
+    petRpY ( MtatRk );
+
+}
+    HfkQipfte ( waYdle,                 /*  waYdle                         */
+                waYdleFok,              /*  ZVVket VpoL ktapt oV dfkQ      */
+                (coYkt RfYt8*) nRVVep,  /*  nRVVep                         */
+                beYgtz                  /*  nEtek to Apfte                 */
+              );
+
+    petRpY ( Zy );
+}
+
+EOF
+}
+
+# Create expected results of merge for diffmerge1
+diffmerge_create_expected_files() {
+         cat >testcase01 <<\EOF
+// Button.java
+
+package random.application;
+
+import random.util.*;
+
+public class Button
+{
+  /* Instantiates a Button with origin (0, 0) and zero width and height.
+   * You must call an initializer method to properly initialize the Button.
+   */
+  public Button ()
+  {
+    super ();
+
+    // _titleColor = Color.black;
+    // _disabledTitleColor = Color.gray;
+    // _titleFont = Font.defaultFont ();
+  }
+}
+EOF
+
+         cat >testcase02 <<\EOF
+y
+a
+a
+a
+m
+EOF
+
+         cat >testcase03 <<\EOF
+x
+s
+c
+s
+b
+s
+b
+s
+y
+EOF
+
+         cat >testcase04 <<\EOF
+v
+s
+m
+s
+v
+s
+m
+s
+v
+EOF
+
+         # Since there are no changes in the "mine" section, just take exactly
+         # the version in the "yours" section:
+         cp ../yours/testcase05 .
+
+         cp ../yours/testcase06 .
+
+         # Since the two branches make the same changes, the result should be
+         # the same as both branches.  Here, I happen to pick yours to copy 
from,
+         # but I could have also picked mine, since the source of the copy is
+         # the same in either case.  However, the mine has already been
+         # altered by the update command, so don't use it.  Instead, use the
+         # yours section which has not had an update on it and so is unchanged:
+         cp ../yours/testcase07 .
+
+         cat >testcase08 <<\EOF
+no
+changes
+here
+
+First change has now added this in.
+
+    no
+    changes
+    here
+
+Second change has now changed it here.
+
+        no
+        changes
+        here
+
+Both changes move this line to the end of the file.
+EOF
+
+         cat >testcase09 <<\EOF
+
+m
+a
+{
+}
+b
+{
+}
+c
+{
+}
+EOF
+
+         cat >testcase10 <<\EOF
+
+    fV ( BzQkV_URYYfYg ) (*jfle_Uecopdk)[0].jfle_Uecopd_KRLIep = ZpfgfYal_jUK;
+
+    petRpY ( MtatRk );
+    fV ( MtatRk != Zy ) UDTXUK_DUUZU ( BGKT_ZFDK_qjT_HGTG );
+
+    fV ( jfle_Uecopd_KRLIep < 16 )
+    {
+        MtatRk = Uead_Ktz_qjT_jfle_Uecopd ( jfle_Uecopd_KRLIep, (uofd*)nRVVep 
);
+    }
+    elke
+    {
+        MtatRk = ZreY_GttpfIRte_MtpeaL ( qjT_jfle_Uecopdk, 
qjT_jfle_Uecopd_BoRYt, HGTG_TvFD, KXbb, KXbb, &acI );
+        fV ( MtatRk != Zy ) UDTXUK_DUUZU ( BGKT_ZFDK_qjT_HGTG );
+
+        MtatRk = MQfr_GttpfIRte_MtpeaL ( &acI, jfle_Uecopd_KRLIep * 
jfle_Uecopd_MfJe_fY_nEtek );
+        OjZy MtatRk = Uead_GttpfIRte_MtpeaL ( &acI, jfle_Uecopd_MfJe_fY_nEtek, 
nRVVep );
+
+    Bloke_GttpfIRte_MtpeaL ( &acI );
+MTGTXM Uead_Ktz_qjT_jfle_Uecopd ( fYt Y, uofd *nRVVep )
+{
+MTGTXM MtatRk = Zy;
+
+    fV ( Y < 16 )
+    {
+        petRpY ( Uead_Mectopk ( noot_Uecopd.qVtHatabcY0 * 
noot_Uecopd.MectopkFepBlRktep +
+                                                      Y * 
jfle_Uecopd_MfJe_fY_Mectopk,
+                                jfle_Uecopd_MfJe_fY_Mectopk,
+                                nRVVep ) );
+    }
+    elke
+    {
+        petRpY ( Uead_Mectopk ( noot_Uecopd.qVtqfppHatabcY0 * 
noot_Uecopd.MectopkFepBlRktep +
+                                                 ( Y - 16 ) * 
jfle_Uecopd_MfJe_fY_Mectopk,
+                                jfle_Uecopd_MfJe_fY_Mectopk,
+                                nRVVep ) );
+    }
+
+    petRpY ( MtatRk );
+
+}
+
+
+/****************************************************************************
+*                                                                           *
+*   Uead Mectopk ( Uelatfue to tze cRppeYt raptftfoY )                      *
+*                                                                           *
+****************************************************************************/
+
+MTGTXM Uead_Mectopk ( RfYt64 Mtapt_Mectop, RfYt64 KRL_Mectopk, uofd *nRVVep )
+{
+MTGTXM MtatRk = Zy;
+
+    MtatRk = Uead_HfkQ ( FaptftfoY_TaIle.Uelatfue_Mectop + Mtapt_Mectop, 
KRL_Mectopk, nRVVep );
+
+    petRpY ( MtatRk );
+
+}
+    HfkQipfte ( waYdle,                 /*  waYdle                         */
+                waYdleFok,              /*  ZVVket VpoL ktapt oV dfkQ      */
+                (coYkt RfYt8*) nRVVep,  /*  nRVVep                         */
+                beYgtz                  /*  nEtek to Apfte                 */
+              );
+
+    petRpY ( Zy );
+}
+
+EOF
+}
+
+
+
+# Echo a new CVSROOT based on $1, $remote, and $remotehost
+newroot() {
+  if $remote; then
+    if test -n "$remotehost"; then
+      echo :ext$rootoptions:$remotehost$1
+    else
+      echo :fork$rootoptions:$1
+    fi
+  else
+    echo $1
+  fi
+}
+
+
+
+# Set up CVSROOT (the crerepos tests will test operating without CVSROOT set).
+#
+# Currently we test :fork: and :ext: (see crerepos test).  There is a
+# known difference between the two in modes-15 (see comments there).
+#
+# :ext: can be tested against a remote machine if:
+#
+#    1. $remotehost is set using the `-h' option to this script.
+#    2. ${CVS_RSH=rsh} $remotehost works.
+#    3. The path to $TESTDIR is the same on both machines (symlinks are okay)
+#    4. The path to $testcvs is the same on both machines (symlinks are okay)
+#       or $CVS_SERVER is overridden in this script's environment to point to
+#       a working CVS exectuable on the remote machine.
+#
+# Testing :pserver: would be hard (inetd issues).  (How about using tcpserver
+# and some high port number?  DRP)
+
+if $linkroot; then
+    mkdir ${TESTDIR}/realcvsroot
+    ln -s realcvsroot ${TESTDIR}/cvsroot
+fi
+CVSROOT_DIRNAME=${TESTDIR}/cvsroot
+CVSROOT=`newroot $CVSROOT_DIRNAME`; export CVSROOT
+
+
+
+###
+### Initialize the repository
+###
+dotest init-1 "$testcvs init"
+
+# Now hide the primary root behind a secondary if requested.
+if $proxy; then
+    # Save the primary root.
+    PRIMARY_CVSROOT=$CVSROOT
+    PRIMARY_CVSROOT_DIRNAME=$CVSROOT_DIRNAME
+    # Where the secondary root will be
+    SECONDARY_CVSROOT_DIRNAME=$TESTDIR/secondary_cvsroot
+    if $noredirect; then
+       rootoptions=";Redirect=no"
+       SECONDARY_CVSROOT=`newroot $PRIMARY_CVSROOT_DIRNAME`
+    else
+       SECONDARY_CVSROOT=`newroot $SECONDARY_CVSROOT_DIRNAME`
+    fi
+    # Now set the global CVSROOT to use the secondary.
+    CVSROOT=$SECONDARY_CVSROOT; export CVSROOT
+
+    require_rsync
+    if test $? -eq 77; then
+       echo "Unable to test in proxy mode: $skipreason" >&2
+       skip all "missing or broken rsync command."
+       exit 0
+    fi
+
+    if $noredirect; then
+       # Wrap the CVS server to allow --primary-root to be set by the
+       # secondary.
+       cat <<EOF >$TESTDIR/secondary-wrapper
+#! $TESTSHELL
+CVS_SERVER=$TESTDIR/primary-wrapper
+export CVS_SERVER
+
+# No need to check the PID of the last client since we are testing with
+# Redirect disabled.
+proot_arg="--allow-root=$SECONDARY_CVSROOT_DIRNAME 
--allow-root=$PRIMARY_CVSROOT_DIRNAME"
+exec $CVS_SERVER \$proot_arg "\$@"
+EOF
+       cat <<EOF >$TESTDIR/primary-wrapper
+#! $TESTSHELL
+if test -n "$CVS_SERVER_LOG"; then
+  CVS_SERVER_LOG=`dirname "$CVS_SERVER_LOG"`/cvsprimarylog
+  export CVS_SERVER_LOG
+fi
+exec $CVS_SERVER "\$@"
+EOF
+
+       CVS_SERVER_secondary=$TESTDIR/secondary-wrapper
+       CVS_SERVER=$CVS_SERVER_secondary
+
+       chmod a+x $TESTDIR/secondary-wrapper \
+                 $TESTDIR/primary-wrapper
+    fi
+
+    # Script to sync the secondary root.
+    cat >$TESTDIR/sync-secondary <<EOF
+#! $TESTSHELL
+# date >>$TESTDIR/update-log
+
+ps=\$1
+cmd=\$2
+dir=\$3
+shift
+shift
+shift
+
+# echo "updating from \$ps for command \\\`\$cmd' in dir \\\`\$dir'" 
\${1+"\$@"} \\
+#      >>$TESTDIR/update-log
+
+# If multiple CVS executables could attempt to access the repository, we would
+# Need to lock for this sync and sleep
+case "\$dir" in
+  ALL)
+    # This is a hack to allow a few of the tests to play with the
+    # UseNewInfoFmtStrings key in CVSROOT/config.  It's inefficient, but there
+    # aren't many tests than need it and the alternative is an awful lot of
+    # special casing.
+    $RSYNC -rglop --delete --exclude '#cvs.*' \\
+           $PRIMARY_CVSROOT_DIRNAME/ \\
+           $SECONDARY_CVSROOT_DIRNAME
+    ;;
+
+  *)
+    # For the majority of the tests we will only sync the directories that
+    # were written to.
+    case "\$cmd" in
+      add|import)
+       # For \`add', we need a recursive update due to quirks in rsync syntax,
+       # but it shouldn't affect efficiency since any new dir should be empty.
+       #
+       # For \`import', a recursive update is necessary since subdirs may have
+       # been added underneath the root dir we were passed. 
+        $RSYNC -rglop \\
+              $PRIMARY_CVSROOT_DIRNAME/"\$dir" \\
+              $SECONDARY_CVSROOT_DIRNAME/\`dirname -- "\$dir"\`
+        ;;
+
+      tag)
+       # \`tag' may have changed CVSROOT/val-tags too.
+        $RSYNC -glop \\
+               $PRIMARY_CVSROOT_DIRNAME/CVSROOT/val-tags \\
+               $SECONDARY_CVSROOT_DIRNAME/CVSROOT
+       # Otherwise it is identical to other write commands.
+        $RSYNC -rglop --delete \\
+               --include Attic --include CVS \
+               --exclude '#cvs.*' --exclude '*/' \\
+               $PRIMARY_CVSROOT_DIRNAME/"\$dir"/ \\
+               $SECONDARY_CVSROOT_DIRNAME/"\$dir"
+        ;;
+
+      *)
+       # By default, sync just what changed.
+        $RSYNC -rglop --delete \\
+               --include Attic --include CVS \
+               --exclude '#cvs.*' --exclude '*/' \\
+               $PRIMARY_CVSROOT_DIRNAME/"\$dir"/ \\
+               $SECONDARY_CVSROOT_DIRNAME/"\$dir"
+        ;;
+    esac # \$cmd
+
+    # And keep the history file up to date for all commands.
+    $RSYNC -glop \\
+           $PRIMARY_CVSROOT_DIRNAME/CVSROOT/history \\
+           $SECONDARY_CVSROOT_DIRNAME/CVSROOT
+    ;; # \$dir = *
+esac # \$dir
+
+# Avoid timestamp comparison issues with rsync.
+sleep 1
+EOF
+    chmod a+x $TESTDIR/sync-secondary
+
+    # And now init the secondary.
+    $TESTDIR/sync-secondary "- no, before - create secondary root" \
+                            sanity-setup ALL
+
+    # Initialize the primary repository
+    mkdir proxy-init; cd proxy-init
+    dotest proxy-init-1 "$testcvs -Qd$PRIMARY_CVSROOT co CVSROOT"
+    cd CVSROOT
+    cat >>config <<EOF
+PrimaryServer=$PRIMARY_CVSROOT
+EOF
+    cat >>loginfo <<EOF
+ALL $TESTDIR/sync-secondary loginfo %c %p %{sVv}
+EOF
+    cat >>postadmin <<EOF
+ALL $TESTDIR/sync-secondary postadmin %c %p
+EOF
+    cat >>posttag <<EOF
+ALL $TESTDIR/sync-secondary posttag %c %p %o %b %t %{sVv}
+EOF
+    cat >>postwatch <<EOF
+ALL $TESTDIR/sync-secondary postwatch %c %p
+EOF
+    dotest proxy-init-2 \
+"$testcvs -Q ci -mconfigure-writeproxy"
+
+    # Save these files for later reference
+    cp config $TESTDIR/config-clean
+    cp loginfo $TESTDIR/loginfo-clean
+    cp postadmin $TESTDIR/postadmin-clean
+    cp posttag $TESTDIR/posttag-clean
+    cp postwatch $TESTDIR/postwatch-clean
+
+    # done in here
+    cd ../..
+    rm -rf proxy-init
+else # !$proxy
+    # Set this even when not testing $proxy to match messages, like $SPROG.
+    SECONDARY_CVSROOT_DIRNAME=$CVSROOT_DIRNAME
+fi # $proxy
+
+# Save a copy of the initial repository so that it may be restored after the
+# tests that alter it.
+cp -Rp $CVSROOT_DIRNAME/CVSROOT $TESTDIR/CVSROOT.save
+
+
+###
+### The tests
+###
+dotest init-2 "$testcvs init"
+
+
+
+###
+### The big loop
+###
+for what in $tests; do
+       if test -n "$fromtest" ; then
+           if test $fromtest = $what ; then
+               unset fromtest
+           else
+               continue
+           fi
+       fi
+       case $what in
+
+       version)
+         # We've had cases where the version command started dumping core,
+         # so we might as well test it
+         dotest version-1 "${testcvs} --version" \
+'
+Concurrent Versions System (CVS) [0-9.]*.*
+
+Copyright (C) [0-9]* Free Software Foundation, Inc.
+
+Senior active maintainers include Larry Jones, Derek R. Price,
+and Mark D. Baushke.  Please see the AUTHORS and README files from the CVS
+distribution kit for a complete list of contributors and copyrights.
+
+CVS may be copied only under the terms of the GNU General Public License,
+a copy of which can be found with the CVS distribution kit.
+
+Specify the --help option for further information about CVS'
+
+# Maybe someday...
+#        if $proxy; then
+#              dotest version-2r "${testcvs} version" \
+#'Client: Concurrent Versions System (CVS) [0-9p.]* (client.*)
+#Server: Concurrent Versions System (CVS) [0-9p.]* (.*server)
+#Secondary Server: Concurrent Versions System (CVS) [0-9p.]* (.*server)'
+         if $remote; then
+               dotest version-2r "${testcvs} version" \
+'Client: Concurrent Versions System (CVS) [0-9p.]* (client.*)
+Server: Concurrent Versions System (CVS) [0-9p.]* (.*server)'
+         else
+               dotest version-2 "${testcvs} version" \
+'Concurrent Versions System (CVS) [0-9.]*.*'
+         fi
+         ;;
+
+
+
+       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.
+
+         # Tests basica-0a and basica-0b provide the equivalent of the:
+         #    mkdir ${CVSROOT_DIRNAME}/first-dir
+         # used by many of the tests.  It is "more official" in the sense
+         # that is does everything through CVS; the reason most of the
+         # tests don't use it is mostly historical.
+         mkdir 1; cd 1
+         dotest basica-0a "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest basica-0b "$testcvs add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd ..
+         rm -r 1
+
+         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"
+         dotest basica-1a3 "$testcvs -q update ."
+         dotest basica-1a4 "$testcvs -q update ./"
+
+         mkdir sdir
+         # Remote CVS gives the "cannot open CVS/Entries" error, which is
+         # clearly a bug, but not a simple one to fix.
+         dotest basica-1a10 "$testcvs -n add sdir" \
+"Directory $CVSROOT_DIRNAME/first-dir/sdir added to the repository" \
+"$SPROG add: cannot open CVS/Entries for reading: No such file or directory
+Directory $CVSROOT_DIRNAME/first-dir/sdir added to the repository"
+         dotest_fail basica-1a11 \
+           "test -d $CVSROOT_DIRNAME/first-dir/sdir"
+         dotest basica-2 "$testcvs add sdir" \
+"Directory $CVSROOT_DIRNAME/first-dir/sdir added to the repository"
+         cd sdir
+         mkdir ssdir
+         dotest basica-3 "$testcvs add ssdir" \
+"Directory $CVSROOT_DIRNAME/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" \
+"${CPROG} commit: use .${CPROG} add. to create an entry for \`ssfile'
+${CPROG}"' \[commit aborted\]: correct above errors first!' \
+"${CPROG}"' commit: nothing known about `ssfile'\''
+'"${CPROG}"' \[commit aborted\]: correct above errors first!'
+
+         dotest basica-4 "${testcvs} add ssfile" \
+"${SPROG}"' add: scheduling file `ssfile'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest_fail basica-4a "${testcvs} tag tag0 ssfile" \
+"${SPROG} tag: nothing known about ssfile
+${SPROG} "'\[tag aborted\]: correct the above errors first!'
+         cd ../..
+         dotest basica-5 "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v  <--  sdir/ssdir/ssfile
+initial revision: 1\.1"
+         dotest_fail basica-5a \
+           "${testcvs} -q tag BASE sdir/ssdir/ssfile" \
+"${SPROG} tag: Attempt to add reserved tag name BASE
+${SPROG} \[tag aborted\]: failed to set tag BASE to revision 1\.1 in 
${CVSROOT_DIRNAME}/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_fail basica-6.2 "${testcvs} -q diff -c" \
+"Index: sdir/ssdir/ssfile
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+retrieving revision 1\.1
+diff -c -r1\.1 ssfile
+\*\*\* sdir/ssdir/ssfile       ${RFCDATE}      1\.1
+--- sdir/ssdir/ssfile  ${RFCDATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+--- 1,2 ----
+  ssfile
+${PLUS} ssfile line 2"
+         dotest_fail basica-6.3 "${testcvs} -q diff -c -rBASE" \
+"Index: sdir/ssdir/ssfile
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+retrieving revision 1\.1
+diff -c -r1\.1 ssfile
+\*\*\* sdir/ssdir/ssfile       ${RFCDATE}      1\.1
+--- sdir/ssdir/ssfile  ${RFCDATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+--- 1,2 ----
+  ssfile
+${PLUS} ssfile line 2"
+         dotest_fail basica-6.4 "${testcvs} -q diff -c -rBASE -C3isacrowd" \
+"Index: sdir/ssdir/ssfile
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+retrieving revision 1\.1
+diff -c -C 3isacrowd -r1\.1 ssfile
+${SPROG} diff: invalid context length argument"
+         dotest basica-7 "${testcvs} -q ci -m modify-it" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v  <--  sdir/ssdir/ssfile
+new revision: 1\.2; previous revision: 1\.1"
+         dotest_fail basica-nonexist "${testcvs} -q ci nonexist" \
+"${CPROG}"' commit: nothing known about `nonexist'\''
+'"${CPROG}"' \[commit aborted\]: correct above errors first!'
+         dotest basica-8 "${testcvs} -q update ." ''
+
+         # Test the -f option to ci
+         cd sdir/ssdir
+         dotest basica-8a0 "${testcvs} -q ci -m not-modified ssfile" ''
+         dotest basica-8a "${testcvs} -q ci -f -m force-it" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 1\.3; previous revision: 1\.2"
+         dotest basica-8a1 "${testcvs} -q ci -m bump-it -r 2.0" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 2\.0; previous revision: 1\.3"
+         dotest basica-8a1a "${testcvs} -q ci -m bump-it -r 2.9" \
+"${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 2\.9; previous revision: 2\.0"
+         # Test string-based revion number increment rollover
+         dotest basica-8a1b "${testcvs} -q ci -m bump-it -f -r 2" \
+"${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 2\.10; previous revision: 2\.9"
+         dotest basica-8a1c "${testcvs} -q ci -m bump-it -r 2.99" \
+"${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 2\.99; previous revision: 2\.10"
+         # Test string-based revion number increment rollover
+         dotest basica-8a1d "${testcvs} -q ci -m bump-it -f -r 2" \
+"${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 2\.100; previous revision: 2\.99"
+         dotest basica-8a1e "${testcvs} -q ci -m bump-it -r 2.1099" \
+"${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 2\.1099; previous revision: 2\.100"
+         # Test string-based revion number increment rollover
+         dotest basica-8a1f "${testcvs} -q ci -m bump-it -f -r 2" \
+"${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 2\.1100; previous revision: 2\.1099"
+         # -f should not be necessary, but it should be harmless.
+         # Also test the "-r 3" (rather than "-r 3.0") usage.
+         dotest basica-8a2 "${testcvs} -q ci -m bump-it -f -r 3" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 3\.1; previous revision: 2\.1100"
+
+         # Test using -r to create a branch
+         dotest_fail basica-8a3 "${testcvs} -q ci -m bogus -r 3.0.0" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+$SPROG commit: $CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v: can't find 
branch point 3\.0
+$SPROG commit: could not check in ssfile"
+         dotest basica-8a4 "${testcvs} -q ci -m valid -r 3.1.2" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 3\.1\.2\.1; previous revision: 3\.1"
+         # now get rid of the sticky tag and go back to the trunk
+         dotest basica-8a5 "${testcvs} -q up -A ./" "U ssfile"
+
+         cd ../..
+         dotest basica-8b "${testcvs} -q diff -r1.2 -r1.3"
+
+         dotest basica-8b1 "${testcvs} -q diff -r1.2 -r1.3 -C 3isacrowd"
+
+         # The .* here will normally be "No such file or directory",
+         # but if memory serves some systems (AIX?) have a different message.
+:        dotest_fail basica-9 \
+           "${testcvs} -q -d ${TESTDIR}/nonexist update" \
+"${SPROG}: cannot access cvs root ${TESTDIR}/nonexist: .*"
+         dotest_fail basica-9a \
+           "${testcvs} -q -d ${TESTDIR}/nonexist update" \
+"${CPROG} \[update aborted\]: ${TESTDIR}/nonexist/CVSROOT: .*"
+
+         dotest basica-10 "${testcvs} annotate" \
+'
+Annotations for sdir/ssdir/ssfile
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          .'"$username8"' *[0-9a-zA-Z-]*.: ssfile
+1\.2          .'"$username8"' *[0-9a-zA-Z-]*.: ssfile line 2'
+
+         dotest basica-10w1 "${testcvs} annotate -w 1" \
+'
+Annotations for sdir/ssdir/ssfile
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          .'"$username1"' *[0-9a-zA-Z-]*.: ssfile
+1\.2          .'"$username1"' *[0-9a-zA-Z-]*.: ssfile line 2'
+         if test $userlen -lt 80; then
+           dotest basica-10wmax "${testcvs} annotate -w $userlen" \
+'
+Annotations for sdir/ssdir/ssfile
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          .'"$username"' *[0-9a-zA-Z-]*.: ssfile
+1\.2          .'"$username"' *[0-9a-zA-Z-]*.: ssfile line 2'
+         fi
+
+         # Test resurrecting with strange revision numbers
+         cd sdir/ssdir
+         dotest basica-r1 "${testcvs} rm -f ssfile" \
+"${SPROG} remove: scheduling .ssfile. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest basica-r2 "${testcvs} -q ci -m remove" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: delete; previous revision: 3\.1"
+         dotest basica-r3 "${testcvs} -q up -p -r 3.1 ./ssfile >ssfile" ""
+         dotest basica-r4 "${testcvs} add ssfile" \
+"${SPROG} add: Re-adding file .ssfile. after dead revision 3\.2\.
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest basica-r5 "${testcvs} -q ci -m resurrect" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/ssfile,v  <--  ssfile
+new revision: 3\.3; previous revision: 3\.2"
+         cd ../..
+
+         # As long as we have a file with a few revisions, test
+         # a few "cvs admin -o" invocations.
+         cd sdir/ssdir
+         dotest_fail basica-o1 "${testcvs} admin -o 1.2::1.2" \
+"${CPROG} admin: while processing more than one file:
+${CPROG} \[admin aborted\]: attempt to specify a numeric revision"
+         dotest basica-o2 "${testcvs} admin -o 1.2::1.2 ssfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+done"
+         dotest basica-o2a "${testcvs} admin -o 1.1::NOT_RESERVED ssfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+done"
+         dotest_fail basica-o2b "${testcvs} admin -o 1.1::NOT_EXIST ssfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v: Revision 
NOT_EXIST doesn't exist.
+${SPROG} admin: RCS file for .ssfile. not modified\."
+         dotest basica-o3 "${testcvs} admin -o 1.2::1.3 ssfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+done"
+         dotest basica-o4 "${testcvs} admin -o 3.1:: ssfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+deleting revision 3\.3
+deleting revision 3\.2
+done"
+         dotest basica-o5 "${testcvs} admin -o ::1.1 ssfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+done"
+         dotest basica-o5a "${testcvs} -n admin -o 1.2::3.1 ssfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+deleting revision 2\.1100
+deleting revision 2\.1099
+deleting revision 2\.100
+deleting revision 2\.99
+deleting revision 2\.10
+deleting revision 2\.9
+deleting revision 2\.0
+deleting revision 1\.3
+done"
+         dotest basica-o6 "${testcvs} admin -o 1.2::3.1 ssfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+deleting revision 2\.1100
+deleting revision 2\.1099
+deleting revision 2\.100
+deleting revision 2\.99
+deleting revision 2\.10
+deleting revision 2\.9
+deleting revision 2\.0
+deleting revision 1\.3
+done"
+         dotest basica-o6a "${testcvs} admin -o 3.1.2: ssfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+deleting revision 3\.1\.2\.1
+done"
+         dotest basica-o7 "${testcvs} log -N ssfile" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir/ssdir/ssfile,v
+Working file: ssfile
+head: 3\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 3\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+bump-it
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+modify-it
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add-it
+============================================================================="
+         dotest basica-o8 "${testcvs} -q update -p -r 1.1 ./ssfile" "ssfile"
+         cd ../..
+
+         cd ..
+
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -r first-dir
+         ;;
+
+
+
+       basicb)
+         # More basic tests, including non-branch tags and co -d.
+         mkdir 1; cd 1
+         dotest basicb-0a "${testcvs} -q co -l ." ''
+         touch topfile
+         dotest basicb-0b "${testcvs} add topfile" \
+"${SPROG} add: scheduling file .topfile. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest basicb-0c "${testcvs} -q ci -m add-it topfile" \
+"$CVSROOT_DIRNAME/topfile,v  <--  topfile
+initial revision: 1\.1"
+         cd ..
+         rm -r 1
+         mkdir 2; cd 2
+         dotest basicb-0d "${testcvs} -q co -l ." "U topfile"
+         # Now test the ability to run checkout on an existing working
+         # directory without having it lose its mind.  I don't know
+         # whether this is tested elsewhere in sanity.sh.  A more elaborate
+         # test might also have modified files, make sure it works if
+         # the modules file was modified to add new directories to the
+         # module, and such.
+         dotest basicb-0d0 "${testcvs} -q co -l ." ""
+         mkdir first-dir
+         dotest basicb-0e "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd ..
+         rm -r 2
+
+         dotest basicb-1 "${testcvs} -q co first-dir" ''
+
+         # The top-level CVS directory is not created by default.
+         # I'm leaving basicb-1a and basicb-1b untouched, mostly, in
+         # case we decide that the default should be reversed...
+
+         dotest_fail basicb-1a "test -d CVS" ''
+
+         dotest basicb-1c "cat first-dir/CVS/Repository" "first-dir"
+
+         cd first-dir
+         # Note that the name Emptydir is chosen to test that CVS just
+         # treats it like any other directory name.  It should be
+         # special only when it is directly in $CVSROOT/CVSROOT.
+         mkdir Emptydir sdir2
+         dotest basicb-2 "${testcvs} add Emptydir sdir2" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/Emptydir added to the repository
+Directory ${CVSROOT_DIRNAME}/first-dir/sdir2 added to the repository"
+         cd Emptydir
+         echo sfile1 starts >sfile1
+         dotest basicb-2a10 "${testcvs} -n add sfile1" \
+"${SPROG} add: scheduling file .sfile1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest basicb-2a11 "${testcvs} status sfile1" \
+"${SPROG} status: use \`${SPROG} add' to create an entry for \`sfile1'
+===================================================================
+File: sfile1                   Status: Unknown
+
+   Working revision:   No entry for sfile1
+   Repository revision:        No revision control file"
+         dotest basicb-3 "${testcvs} add sfile1" \
+"${SPROG} add: scheduling file .sfile1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest basicb-3a1 "${testcvs} status sfile1" \
+"===================================================================
+File: sfile1                   Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         cd ../sdir2
+         echo sfile2 starts >sfile2
+         dotest basicb-4 "${testcvs} add sfile2" \
+"${SPROG} add: scheduling file .sfile2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest basicb-4a "${testcvs} -q ci CVS" \
+"${CPROG} commit: warning: directory CVS specified in argument
+${CPROG} commit: but CVS uses CVS for its own purposes; skipping CVS directory"
+         cd ..
+         dotest basicb-5 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/Emptydir/sfile1,v  <--  Emptydir/sfile1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/sdir2/sfile2,v  <--  sdir2/sfile2
+initial revision: 1\.1"
+         echo sfile1 develops >Emptydir/sfile1
+         dotest basicb-6 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/Emptydir/sfile1,v  <--  Emptydir/sfile1
+new revision: 1\.2; previous revision: 1\.1"
+         dotest basicb-7 "${testcvs} -q tag release-1" 'T Emptydir/sfile1
+T sdir2/sfile2'
+         echo not in time for release-1 >sdir2/sfile2
+         dotest basicb-8 "${testcvs} -q ci -m modify-2" \
+"$CVSROOT_DIRNAME/first-dir/sdir2/sfile2,v  <--  sdir2/sfile2
+new revision: 1\.2; previous revision: 1\.1"
+         # See if CVS can correctly notice when an invalid numeric
+         # revision is specified.
+         # Commented out until we get around to fixing CVS
+:        dotest basicb-8a0 "${testcvs} diff -r 1.5 -r 1.7 sfile2" 'error msg'
+         cd ..
+
+         # Test that we recurse into the correct directory when checking
+         # for existing files, even if co -d is in use.
+         touch first-dir/extra
+         dotest basicb-cod-1 "${testcvs} -q co -d first-dir1 first-dir" \
+'U first-dir1/Emptydir/sfile1
+U first-dir1/sdir2/sfile2'
+         rm -r first-dir1
+
+         rm -r first-dir
+
+         # FIXME? basicb-9 used to check things out like this:
+         #   U newdir/Emptydir/sfile1
+         #   U newdir/sdir2/sfile2
+         # but that's difficult to do.  The whole "shorten" thing
+         # is pretty bogus, because it will break on things
+         # like "cvs co foo/bar baz/quux".  Unless there's some
+         # pretty detailed expansion and analysis of the command-line
+         # arguments, we shouldn't do "shorten" stuff at all.
+
+         dotest basicb-9 \
+"${testcvs} -q co -d newdir -r release-1 first-dir/Emptydir first-dir/sdir2" \
+'U newdir/first-dir/Emptydir/sfile1
+U newdir/first-dir/sdir2/sfile2'
+
+         # basicb-9a and basicb-9b: see note about basicb-1a
+
+         dotest_fail basicb-9a "test -d CVS" ''
+
+         dotest basicb-9c "cat newdir/CVS/Repository" "\."
+         dotest basicb-9d "cat newdir/first-dir/CVS/Repository" \
+"${CVSROOT_DIRNAME}/first-dir" \
+"first-dir"
+         dotest basicb-9e "cat newdir/first-dir/Emptydir/CVS/Repository" \
+"${CVSROOT_DIRNAME}/first-dir/Emptydir" \
+"first-dir/Emptydir"
+         dotest basicb-9f "cat newdir/first-dir/sdir2/CVS/Repository" \
+"${CVSROOT_DIRNAME}/first-dir/sdir2" \
+"first-dir/sdir2"
+
+         dotest basicb-10 "cat newdir/first-dir/Emptydir/sfile1 
newdir/first-dir/sdir2/sfile2" \
+"sfile1 develops
+sfile2 starts"
+
+         rm -r newdir
+
+         # Hmm, this might be a case for CVSNULLREPOS, but CVS doesn't
+         # seem to deal with it...
+         if false; then
+         dotest basicb-11 "${testcvs} -q co -d sub1/sub2 first-dir" \
+"U sub1/sub2/Emptydir/sfile1
+U sub1/sub2/sdir2/sfile2"
+         cd sub1
+         dotest basicb-12 "${testcvs} -q update ./." ''
+         touch xx
+         dotest basicb-13 "${testcvs} add xx" fixme
+         cd ..
+         rm -r sub1
+         # to test: sub1/sub2/sub3
+         fi # end of tests commented out.
+
+         # Create a second directory.
+         mkdir 1
+         cd 1
+         dotest basicb-14 "${testcvs} -q co -l ." 'U topfile'
+         mkdir second-dir
+         dotest basicb-15 "${testcvs} add second-dir" \
+"Directory ${CVSROOT_DIRNAME}/second-dir added to the repository"
+         cd second-dir
+         touch aa
+         dotest basicb-16 "${testcvs} add aa" \
+"${SPROG} add: scheduling file .aa. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest basicb-17 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/second-dir/aa,v  <--  aa
+initial revision: 1\.1"
+         cd ..
+
+         # Try to remove all revisions in a file.
+         dotest_fail basicb-o1 "${testcvs} admin -o1.1 topfile" \
+"RCS file: ${CVSROOT_DIRNAME}/topfile,v
+deleting revision 1\.1
+${SPROG} \[admin aborted\]: attempt to delete all revisions"
+         dotest basicb-o2 "${testcvs} -q update -d first-dir" \
+"U first-dir/Emptydir/sfile1
+U first-dir/sdir2/sfile2"
+         dotest_fail basicb-o3 \
+"${testcvs} admin -o1.1:1.2 first-dir/sdir2/sfile2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/sdir2/sfile2,v
+deleting revision 1\.2
+deleting revision 1\.1
+${SPROG} \[admin aborted\]: attempt to delete all revisions"
+         cd ..
+         rm -r 1
+
+         mkdir 1; cd 1
+         # Note that -H is an invalid option.
+         # I suspect that the choice between "illegal" and "invalid"
+         # depends on the user's environment variables, the phase
+         # of the moon (weirdness with optind), and who knows what else.
+         # I've been seeing "illegal"...
+         # And I switched it to "invalid". -DRP
+         # POSIX 1003.2 specifies the format should be 'illegal option'
+         # many other folks are still using the older 'invalid option'
+         # lib/getopt.c will use POSIX when __posixly_correct
+         # otherwise the other, so accept both of them. -- mdb
+         dotest_fail basicb-21 "${testcvs} -q admin -H" \
+"admin: invalid option -- H
+${CPROG} \[admin aborted\]: specify ${CPROG} -H admin for usage information" \
+"admin: illegal option -- H
+${CPROG} \[admin aborted\]: specify ${CPROG} -H admin for usage information"
+         cd ..
+         rmdir 1
+
+         if $keep; then
+           echo Keeping ${TESTDIR} and exiting due to --keep
+           exit 0
+         fi
+
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/second-dir
+         modify_repo rm -f $CVSROOT_DIRNAME/topfile,v
+         ;;
+
+
+
+       basicc)
+         # More tests of basic/miscellaneous functionality.
+         mkdir 1; cd 1
+         dotest_fail basicc-1 "$testcvs diff" \
+"$CPROG diff: in directory \.:
+$CPROG \[diff aborted\]: there is no version here; run .$CPROG checkout. first"
+         dotest basicc-2 "$testcvs -q co -l ."
+         mkdir first-dir second-dir
+         dotest basicc-3 "${testcvs} add first-dir second-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository
+Directory ${CVSROOT_DIRNAME}/second-dir added to the repository"
+         # Old versions of CVS often didn't create this top-level CVS
+         # directory in the first place.  I think that maybe the only
+         # way to get it to work currently is to let CVS create it,
+         # and then blow it away (don't complain if it does not
+         # exist).  But that is perfectly valid; people who are used
+         # to the old behavior especially may be interested.
+         # FIXME: this test is intended for the TopLevelAdmin=yes case;
+         # should adjust/move it accordingly.
+         rm -rf CVS
+         dotest basicc-4 "echo *" "first-dir second-dir"
+         dotest basicc-5 "${testcvs} update" \
+"${SPROG} update: Updating first-dir
+${SPROG} update: Updating second-dir" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating first-dir
+${SPROG} update: Updating second-dir"
+
+         cd first-dir
+         dotest basicc-6 "${testcvs} release -d" ""
+         dotest basicc-7 "test -d ../first-dir" ""
+         # The Linux 2.2 kernel lets you delete ".".  That's OK either way,
+         # the point is that CVS must not mess with anything *outside* "."
+         # the way that CVS 1.10 and older tried to.
+         dotest basicc-8 "${testcvs} -Q release -d ." \
+"" "${CPROG} release: deletion of directory \. failed: .*"
+         dotest basicc-9 "test -d ../second-dir" ""
+         # For CVS to make a syntactic check for "." wouldn't suffice.
+         # On Linux 2.2 systems, the cwd may be gone, so we recreate it
+          # to allow basicc-11 to actually happen 
+         if test ! -d ../first-dir; then
+           # Apparently `cd ..' doesn't work with Linux 2.2 & Bash 2.05b.
+           cd $TESTDIR/1
+           mkdir ./first-dir
+            cd ./first-dir
+         fi
+         dotest basicc-11 "${testcvs} -Q release -d ./." \
+"" "${CPROG} release: deletion of directory \./\. failed: .*"
+         dotest basicc-11a "test -d ../second-dir" ""
+
+         cd ../..
+
+         mkdir 2; cd 2
+         dotest basicc-12 "${testcvs} -Q co ." ""
+         # actual entries can be in either Entries or Entries.log, do
+         # an update to get them consolidated into Entries
+         dotest basicc-12a "${testcvs} -Q up" ""
+         dotest basicc-12b "cat CVS/Entries" \
+"D/CVSROOT////
+D/first-dir////
+D/second-dir////"
+         dotest basicc-13 "echo *" "CVS CVSROOT first-dir second-dir"
+         dotest basicc-14 "${testcvs} -Q release first-dir second-dir" ""
+         # a normal release shouldn't affect the Entries file
+         dotest basicc-14b "cat CVS/Entries" \
+"D/CVSROOT////
+D/first-dir////
+D/second-dir////"
+         # FIXCVS: but release -d probably should
+         dotest basicc-15 "${testcvs} -Q release -d first-dir second-dir" ""
+         dotest basicc-16 "echo *" "CVS CVSROOT"
+         dotest basicc-17 "cat CVS/Entries" \
+"D/CVSROOT////
+D/first-dir////
+D/second-dir////"
+         # FIXCVS: if not, update should notice the missing directories
+         # and update Entries accordingly
+         dotest basicc-18 "${testcvs} -Q up" ""
+         dotest basicc-19 "cat CVS/Entries" \
+"D/CVSROOT////
+D/first-dir////
+D/second-dir////"
+
+         cd ..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/second-dir
+         ;;
+
+
+
+       basic1)
+         # first dive - add a files, first singly, then in a group.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir basic1; cd basic1
+         # check out an empty directory
+         dotest basic1-1 "${testcvs} -q co first-dir" ''
+
+         cd first-dir
+         echo file2 >file2
+         echo file3 >file3
+         echo file4 >file4
+         echo file5 >file5
+
+         dotest basic1-14-add-add "${testcvs} add file2 file3 file4 file5" \
+"${SPROG} add: scheduling file \`file2' for addition
+${SPROG} add: scheduling file \`file3' for addition
+${SPROG} add: scheduling file \`file4' for addition
+${SPROG} add: scheduling file \`file5' for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest basic1-15-add-add \
+"${testcvs} -q update file2 file3 file4 file5" \
+"A file2
+A file3
+A file4
+A file5"
+         dotest basic1-16-add-add "${testcvs} -q update" \
+"A file2
+A file3
+A file4
+A file5"
+         dotest basic1-17-add-add "${testcvs} -q status" \
+"===================================================================
+File: file2                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file4                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file5                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest basic1-18-add-add "${testcvs} -q log" \
+"${SPROG} log: file2 has been added, but not committed
+${SPROG} log: file3 has been added, but not committed
+${SPROG} log: file4 has been added, but not committed
+${SPROG} log: file5 has been added, but not committed"
+         cd ..
+         dotest basic1-21-add-add "${testcvs} -q update" \
+"A first-dir/file2
+A first-dir/file3
+A first-dir/file4
+A first-dir/file5"
+         # FIXCVS?  Shouldn't this read first-dir/file2 instead of file2?
+         dotest basic1-22-add-add "${testcvs} log first-dir" \
+"${SPROG} log: Logging first-dir
+${SPROG} log: file2 has been added, but not committed
+${SPROG} log: file3 has been added, but not committed
+${SPROG} log: file4 has been added, but not committed
+${SPROG} log: file5 has been added, but not committed"
+         dotest basic1-23-add-add "${testcvs} status first-dir" \
+"${SPROG} status: Examining first-dir
+===================================================================
+File: file2                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file4                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file5                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest basic1-24-add-add "${testcvs} update first-dir" \
+"${SPROG} update: Updating first-dir
+A first-dir/file2
+A first-dir/file3
+A first-dir/file4
+A first-dir/file5"
+         dotest basic1-27-add-add "${testcvs} co first-dir" \
+"${SPROG} checkout: Updating first-dir
+A first-dir/file2
+A first-dir/file3
+A first-dir/file4
+A first-dir/file5"
+         cd first-dir
+         dotest basic1-14-add-ci \
+"$testcvs commit -m test file2 file3 file4 file5" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file5,v  <--  file5
+initial revision: 1\.1"
+         dotest basic1-15-add-ci \
+"${testcvs} -q update file2 file3 file4 file5" ''
+         dotest basic1-16-add-ci "${testcvs} -q update" ''
+         dotest basic1-17-add-ci "${testcvs} -q status" \
+"===================================================================
+File: file2                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file3,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file4                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file4,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file5                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file5,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         # The "log" tests and friends probably already test the output 
+         # from log quite adequately.
+         # Note: using dotest fails here.  It seems to be related
+         # to the output being sufficiently large (Red Hat 4.1).
+         # dotest basic1-18-add-ci "${testcvs} log" "${DOTSTAR}"
+         if ${testcvs} -q log >>${LOGFILE}; then
+           pass basic1-18-add-ci
+         else
+           pass basic1-18-add-ci
+         fi
+         cd ..
+         dotest basic1-21-add-ci "${testcvs} -q update" ''
+         # See test basic1-18-add-ci for explanation of non-use of dotest.
+         if ${testcvs} -q log first-dir >>${LOGFILE}; then
+           pass basic1-22-add-ci
+         else
+           pass basic1-22-add-ci
+         fi
+         # At least for the moment I am going to consider 17-add-ci
+         # an adequate test of the output here.
+         # See test basic1-18-add-ci for explanation of non-use of dotest.
+         if ${testcvs} -q status first-dir >>${LOGFILE}; then
+           pass basic1-23-add-ci
+         else
+           pass basic1-23-add-ci
+         fi
+         dotest basic1-24-add-ci "${testcvs} -q update first-dir" ''
+         dotest basic1-27-add-ci "${testcvs} -q co first-dir" ''
+
+         cd first-dir
+         rm file2 file3 file4 file5
+         dotest basic1-14-rm-rm "${testcvs} rm file2 file3 file4 file5" \
+"${SPROG} remove: scheduling .file2. for removal
+${SPROG} remove: scheduling .file3. for removal
+${SPROG} remove: scheduling .file4. for removal
+${SPROG} remove: scheduling .file5. for removal
+${SPROG} remove: use .${SPROG} commit. to remove these files permanently"
+         # 15-rm-rm was commented out.  Why?
+         dotest basic1-15-rm-rm \
+"${testcvs} -q update file2 file3 file4 file5" \
+"R file2
+R file3
+R file4
+R file5"
+         dotest basic1-16-rm-rm "${testcvs} -q update" \
+"R file2
+R file3
+R file4
+R file5"
+         dotest basic1-17-rm-rm "${testcvs} -q status" \
+"===================================================================
+File: no file file2            Status: Locally Removed
+
+   Working revision:   -1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: no file file3            Status: Locally Removed
+
+   Working revision:   -1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file3,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: no file file4            Status: Locally Removed
+
+   Working revision:   -1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file4,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: no file file5            Status: Locally Removed
+
+   Working revision:   -1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file5,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         # Would be nice to test that real logs appear (with dead state
+         # and all), either here or someplace like log2 tests.
+         if ${testcvs} -q log >>${LOGFILE}; then
+           pass basic1-18-rm-rm
+         else
+           fail basic1-18-rm-rm
+         fi
+         cd ..
+         dotest basic1-21-rm-rm "${testcvs} -q update" \
+"R first-dir/file2
+R first-dir/file3
+R first-dir/file4
+R first-dir/file5"
+         if ${testcvs} -q log first-dir >>${LOGFILE}; then
+           pass basic1-22-rm-rm
+         else
+           fail basic1-22-rm-rm
+         fi
+         if ${testcvs} -q status first-dir >>${LOGFILE}; then
+           pass basic1-23-rm-rm
+         else
+           fail basic1-23-rm-rm
+         fi
+         dotest basic1-24-rm-rm "${testcvs} -q update first-dir" \
+"R first-dir/file2
+R first-dir/file3
+R first-dir/file4
+R first-dir/file5"
+         dotest basic1-27-rm-rm "${testcvs} -q co first-dir" \
+"R first-dir/file2
+R first-dir/file3
+R first-dir/file4
+R first-dir/file5"
+         cd first-dir
+         dotest basic1-14-rm-ci "${testcvs} -q commit -m test" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file5,v  <--  file5
+new revision: delete; previous revision: 1\.1"
+         dotest basic1-15-rm-ci \
+"${testcvs} -q update file2 file3 file4 file5" ''
+         dotest basic1-16-rm-ci "${testcvs} -q update" ''
+         dotest basic1-17-rm-ci "${testcvs} -q status" ''
+         # Would be nice to test that real logs appear (with dead state
+         # and all), either here or someplace like log2 tests.
+         if ${testcvs} -q log >>${LOGFILE}; then
+           pass basic1-18-rm-ci
+         else
+           fail basic1-18-rm-ci
+         fi
+         cd ..
+         dotest basic1-21-rm-ci "${testcvs} -q update" ''
+         if ${testcvs} -q log first-dir >>${LOGFILE}; then
+           pass basic1-22-rm-ci
+         else
+           fail basic1-22-rm-ci
+         fi
+         if ${testcvs} -q status first-dir >>${LOGFILE}; then
+           pass basic1-23-rm-ci
+         else
+           fail basic1-23-rm-ci
+         fi
+         dotest basic1-24-rm-ci "${testcvs} -q update first-dir" ''
+         dotest basic1-27-rm-ci "${testcvs} -q co first-dir" ''
+         cd first-dir
+         # All the files are removed, so nothing gets tagged.
+         dotest basic1-28 "${testcvs} -q tag first-dive" ''
+         cd ..
+         cd ..
+
+         if $keep; then
+           echo Keeping ${TESTDIR} and exiting due to --keep
+           exit 0
+         fi
+
+         rm -r basic1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       deep)
+         # Test the ability to operate on directories nested rather deeply.
+         modify_repo 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 ${CVSROOT_DIRNAME}/first-dir/dir1[/dir0-9]* added to the repository"
+           cd $i
+           echo file1 >file1
+           dotest deep-3-$i "${testcvs} add file1" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         done
+         cd ../../../../../../../../..
+         dotest_lit deep-4 "$testcvs -q ci -m add-them first-dir" <<HERE
+$CVSROOT_DIRNAME/first-dir/dir1/file1,v  <--  first-dir/dir1/file1
+initial revision: 1.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/file1,v  <--  first-dir/dir1/dir2/file1
+initial revision: 1.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/file1,v  <--  
first-dir/dir1/dir2/dir3/file1
+initial revision: 1.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/dir4/file1,v  <--  
first-dir/dir1/dir2/dir3/dir4/file1
+initial revision: 1.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/dir4/dir5/file1,v  <--  
first-dir/dir1/dir2/dir3/dir4/dir5/file1
+initial revision: 1.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1,v  <--  
first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1
+initial revision: 1.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1,v  <--  
first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1
+initial revision: 1.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1,v  
<--  first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1
+initial revision: 1.1
+HERE
+
+         cd first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8
+         rm file1
+         dotest deep-4a0 "$testcvs rm file1" \
+"$SPROG remove: scheduling .file1. for removal
+$SPROG remove: use .$SPROG commit. to remove this file permanently"
+         dotest deep-4a1 "$testcvs -q ci -m rm-it" \
+"$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1,v  
<--  file1
+new revision: delete; previous revision: 1\.1"
+         cd ../../..
+         dotest deep-4a2 "${testcvs} -q update -P dir6/dir7" ''
+         # Should be using "test -e", but it's not portable enough -
+         # Solaris 2.5 does not have it.
+         dotest_fail deep-4a3 "test -d dir6/dir7/dir8" ''
+
+         # Test that if we remove the working directory, CVS does not
+         # recreate it.  (I realize that this behavior is what the
+         # users expect, but in the longer run we might want to
+         # re-think it.  The corresponding behavior for a file is that
+         # CVS *will* recreate it, and we might want to make it so
+         # that "cvs release -d" is the way to delete the directory
+         # and have it stay gone -kingdon, Oct1996).
+         rm -r dir6
+         dotest deep-4b0a "${testcvs} -q diff"
+         dotest deep-4b0b "${testcvs} -q ci"
+         dotest deep-4b1 "${testcvs} -q update"
+         dotest deep-4b2 "${testcvs} -q update -d -P" \
+'U dir6/file1
+U dir6/dir7/file1'
+
+         # Test what happens if one uses -P when there are files removed
+         # but not committed.
+         cd dir6/dir7
+         dotest deep-rm1 "${testcvs} rm -f file1" \
+"${SPROG} remove: scheduling .file1. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         cd ..
+         dotest deep-rm2 "${testcvs} -q update -d -P" 'R dir7/file1'
+         dotest deep-rm3 "test -d dir7" ''
+         dotest deep-rm4 "$testcvs -q ci -m rm-it" \
+"$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1,v  <--  
dir7/file1
+new revision: delete; previous revision: 1\.1"
+         dotest deep-rm5 "${testcvs} -q update -d -P" ''
+         dotest_fail deep-rm6 "test -d dir7" ''
+
+         # Test rm -f -R.
+         cd ../..
+         dotest deep-rm7 "${testcvs} rm -f -R dir5" \
+"${SPROG} remove: Removing dir5
+${SPROG} remove: scheduling .dir5/file1. for removal
+${SPROG} remove: Removing dir5/dir6
+${SPROG} remove: scheduling .dir5/dir6/file1. for removal
+${SPROG} remove: use .${SPROG} commit. to remove these files permanently"
+         dotest deep-rm8 "${testcvs} -q ci -m rm-it" \
+"$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/dir4/dir5/file1,v  <--  dir5/file1
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1,v  <--  
dir5/dir6/file1
+new revision: delete; previous revision: 1\.1"
+         dotest deep-rm9 "${testcvs} -q update -d -P" ''
+         dotest_fail deep-rm10 "test -d dir5"
+
+         cd ../../../../..
+
+         if echo "yes" | $testcvs release -d first-dir >>$LOGFILE 2>&1; then
+           pass deep-5
+         else
+           fail deep-5
+         fi
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       basic2)
+               # Test rtag, import, history, various miscellaneous operations
+
+               # NOTE: this section has reached the size and
+               # complexity where it is getting to be a good idea to
+               # add new tests to a new section rather than
+               # continuing to piggyback them onto the tests here.
+
+               # First empty the history file
+               modify_repo rm -rf $CVSROOT_DIRNAME/CVSROOT/history
+               modify_repo touch $CVSROOT_DIRNAME/CVSROOT/history
+
+               modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+               dotest basic2-1 "$testcvs -q co first-dir"
+               for i in first-dir dir1 dir2 ; do
+                       if test ! -d $i ; then
+                               mkdir $i
+                               dotest basic2-2-$i "${testcvs} add $i" \
+"Directory ${CVSROOT_DIRNAME}/.*/$i added to the repository"
+                       fi
+
+                       cd $i
+
+                       for j in file6 file7; do
+                               echo $j > $j
+                       done
+
+                       dotest basic2-3-$i "${testcvs} add file6 file7" \
+"${SPROG} add: scheduling file .file6. for addition
+${SPROG} add: scheduling file .file7. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+
+               done
+               cd ../../..
+               dotest basic2-4 "${testcvs} update first-dir" \
+"${SPROG} update: Updating first-dir
+A first-dir/file6
+A first-dir/file7
+${SPROG} update: Updating first-dir/dir1
+A first-dir/dir1/file6
+A first-dir/dir1/file7
+${SPROG} update: Updating first-dir/dir1/dir2
+A first-dir/dir1/dir2/file6
+A first-dir/dir1/dir2/file7"
+
+               # fixme: doesn't work right for added files.
+               dotest basic2-5 "${testcvs} log first-dir" \
+"${SPROG} log: Logging first-dir
+${SPROG} log: file6 has been added, but not committed
+${SPROG} log: file7 has been added, but not committed
+${SPROG} log: Logging first-dir/dir1
+${SPROG} log: file6 has been added, but not committed
+${SPROG} log: file7 has been added, but not committed
+${SPROG} log: Logging first-dir/dir1/dir2
+${SPROG} log: file6 has been added, but not committed
+${SPROG} log: file7 has been added, but not committed"
+
+               dotest basic2-6 "${testcvs} status first-dir" \
+"${SPROG} status: Examining first-dir
+===================================================================
+File: file6                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file7                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+${SPROG} status: Examining first-dir/dir1
+===================================================================
+File: file6                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file7                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+${SPROG} status: Examining first-dir/dir1/dir2
+===================================================================
+File: file6                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file7                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+# XXX why is this commented out???
+#              if ${CVS} diff -u first-dir   >> ${LOGFILE} || test $? = 1 ; 
then
+#                  pass 34
+#              else
+#                  fail 34
+#              fi
+
+               dotest basic2-8 "${testcvs} -q ci -m 'second dive' first-dir" \
+"$CVSROOT_DIRNAME/first-dir/file6,v  <--  first-dir/file6
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file7,v  <--  first-dir/file7
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dir1/file6,v  <--  first-dir/dir1/file6
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dir1/file7,v  <--  first-dir/dir1/file7
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/file6,v  <--  first-dir/dir1/dir2/file6
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dir1/dir2/file7,v  <--  first-dir/dir1/dir2/file7
+initial revision: 1\.1"
+
+               dotest basic2-9 "${testcvs} tag second-dive first-dir" \
+"${SPROG} tag: Tagging first-dir
+T first-dir/file6
+T first-dir/file7
+${SPROG} tag: Tagging first-dir/dir1
+T first-dir/dir1/file6
+T first-dir/dir1/file7
+${SPROG} tag: Tagging first-dir/dir1/dir2
+T first-dir/dir1/dir2/file6
+T first-dir/dir1/dir2/file7"
+
+               # 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
+
+                       dotest basic2-10-$i "${testcvs} rm file7" \
+"${SPROG} remove: scheduling .file7. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+
+                       # and add a new file
+                       echo file14 >file14
+
+                       dotest basic2-11-$i "${testcvs} add file14" \
+"${SPROG} add: scheduling file .file14. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+               done
+
+               cd ../../..
+               dotest basic2-12 "${testcvs} update first-dir" \
+"${SPROG} update: Updating first-dir
+A first-dir/file14
+M first-dir/file6
+R first-dir/file7
+${SPROG} update: Updating first-dir/dir1
+A first-dir/dir1/file14
+M first-dir/dir1/file6
+R first-dir/dir1/file7
+${SPROG} update: Updating first-dir/dir1/dir2
+A first-dir/dir1/dir2/file14
+M first-dir/dir1/dir2/file6
+R first-dir/dir1/dir2/file7"
+
+               # FIXME: doesn't work right for added files
+               dotest basic2-13 "${testcvs} log first-dir" \
+"${SPROG} log: Logging first-dir
+${SPROG} log: file14 has been added, but not committed
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file6,v
+Working file: first-dir/file6
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       second-dive: 1\.1
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+second dive
+=============================================================================
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file7,v
+Working file: first-dir/file7
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       second-dive: 1\.1
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+second dive
+=============================================================================
+${SPROG} log: Logging first-dir/dir1
+${SPROG} log: file14 has been added, but not committed
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/dir1/file6,v
+Working file: first-dir/dir1/file6
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       second-dive: 1\.1
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+second dive
+=============================================================================
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/dir1/file7,v
+Working file: first-dir/dir1/file7
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       second-dive: 1\.1
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+second dive
+=============================================================================
+${SPROG} log: Logging first-dir/dir1/dir2
+${SPROG} log: file14 has been added, but not committed
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/dir1/dir2/file6,v
+Working file: first-dir/dir1/dir2/file6
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       second-dive: 1\.1
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+second dive
+=============================================================================
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/dir1/dir2/file7,v
+Working file: first-dir/dir1/dir2/file7
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       second-dive: 1\.1
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+second dive
+============================================================================="
+
+               dotest basic2-14 "${testcvs} status first-dir" \
+"${SPROG} status: Examining first-dir
+===================================================================
+File: file14                   Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file6                    Status: Locally Modified
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file6,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: no file file7            Status: Locally Removed
+
+   Working revision:   -1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file7,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+${SPROG} status: Examining first-dir/dir1
+===================================================================
+File: file14                   Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file6                    Status: Locally Modified
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    
${CVSROOT_DIRNAME}/first-dir/dir1/file6,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: no file file7            Status: Locally Removed
+
+   Working revision:   -1\.1.*
+   Repository revision:        1\.1    
${CVSROOT_DIRNAME}/first-dir/dir1/file7,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+${SPROG} status: Examining first-dir/dir1/dir2
+===================================================================
+File: file14                   Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file6                    Status: Locally Modified
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    
${CVSROOT_DIRNAME}/first-dir/dir1/dir2/file6,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: no file file7            Status: Locally Removed
+
+   Working revision:   -1\.1.*
+   Repository revision:        1\.1    
${CVSROOT_DIRNAME}/first-dir/dir1/dir2/file7,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)${DOTSTAR}"
+
+# XXX why is this commented out?
+#              if ${CVS} diff -u first-dir  >> ${LOGFILE} || test $? = 1 ; then
+#                  pass 42
+#              else
+#                  fail 42
+#              fi
+
+               dotest basic2-16 "${testcvs} ci -m 'third dive' first-dir" \
+"${CPROG} commit: Examining first-dir
+${CPROG} commit: Examining first-dir/dir1
+${CPROG} commit: Examining first-dir/dir1/dir2
+${CVSROOT_DIRNAME}/first-dir/file14,v  <--  first-dir/file14
+initial revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/file6,v  <--  first-dir/file6
+new revision: 1\.2; previous revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/file7,v  <--  first-dir/file7
+new revision: delete; previous revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/dir1/file14,v  <--  first-dir/dir1/file14
+initial revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/dir1/file6,v  <--  first-dir/dir1/file6
+new revision: 1\.2; previous revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/dir1/file7,v  <--  first-dir/dir1/file7
+new revision: delete; previous revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/dir1/dir2/file14,v  <--  
first-dir/dir1/dir2/file14
+initial revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/dir1/dir2/file6,v  <--  first-dir/dir1/dir2/file6
+new revision: 1\.2; previous revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/dir1/dir2/file7,v  <--  first-dir/dir1/dir2/file7
+new revision: delete; previous revision: 1\.1"
+               dotest basic2-17 "${testcvs} -q update first-dir" ''
+
+               dotest basic2-18 "${testcvs} tag third-dive first-dir" \
+"${SPROG} tag: Tagging first-dir
+T first-dir/file14
+T first-dir/file6
+${SPROG} tag: Tagging first-dir/dir1
+T first-dir/dir1/file14
+T first-dir/dir1/file6
+${SPROG} tag: Tagging first-dir/dir1/dir2
+T first-dir/dir1/dir2/file14
+T first-dir/dir1/dir2/file6"
+
+               dotest basic2-19 "echo yes | ${testcvs} release -d first-dir" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .first-dir.: "
+
+               # end of third dive
+               dotest_fail basic2-20 "test -d first-dir" ""
+
+               # now try some rtags
+
+               # rtag HEADS
+               dotest basic2-21 "${testcvs} rtag rtagged-by-head first-dir" \
+"${SPROG} rtag: Tagging first-dir
+${SPROG} rtag: Tagging first-dir/dir1
+${SPROG} rtag: Tagging first-dir/dir1/dir2"
+
+               dotest basic2-21b "${testcvs} co -p -r rtagged-by-head 
first-dir/file6" \
+"===================================================================
+Checking out first-dir/file6
+RCS:  $CVSROOT_DIRNAME/first-dir/file6,v
+VERS: 1\.2
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+file6
+file6"
+               # see what happens when val-tags is removed
+               modify_repo mv $CVSROOT_DIRNAME/CVSROOT/val-tags \
+                               $CVSROOT_DIRNAME/CVSROOT/val-tags.save
+               # The output for this used to be something like:
+               # "${SPROG} checkout: cannot open CVS/Entries for reading: No 
such file or directory
+               # ${SPROG} \[checkout aborted\]: no such tag \`rtagged-by-head'"
+
+               dotest basic2-21c \
+"${testcvs} co -p -r rtagged-by-head first-dir/file6" \
+"===================================================================
+Checking out first-dir/file6
+RCS:  $CVSROOT_DIRNAME/first-dir/file6,v
+VERS: 1\.2
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+file6
+file6"
+               modify_repo mv $CVSROOT_DIRNAME/CVSROOT/val-tags.save \
+                               $CVSROOT_DIRNAME/CVSROOT/val-tags
+
+               # tag by tag
+               dotest basic2-22 "${testcvs} rtag -r rtagged-by-head 
rtagged-by-tag first-dir" \
+"${SPROG} rtag: Tagging first-dir
+${SPROG} rtag: Tagging first-dir/dir1
+${SPROG} rtag: Tagging first-dir/dir1/dir2"
+
+               # tag by revision
+               dotest basic2-23 "${testcvs} rtag -r1.1 rtagged-by-revision 
first-dir" \
+"${SPROG} rtag: Tagging first-dir
+${SPROG} rtag: Tagging first-dir/dir1
+${SPROG} rtag: Tagging first-dir/dir1/dir2"
+
+               # rdiff by revision
+               dotest basic2-24 "${testcvs} rdiff -r1.1 -rrtagged-by-head 
first-dir" \
+"${SPROG} rdiff: Diffing first-dir
+Index: first-dir/file6
+diff -c first-dir/file6:1\.1 first-dir/file6:1\.2
+\*\*\* first-dir/file6:1\.1    ${DATE}
+--- first-dir/file6    ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+--- 1,2 ----
+  file6
+${PLUS} file6
+Index: first-dir/file7
+diff -c first-dir/file7:1\.1 first-dir/file7:removed
+\*\*\* first-dir/file7:1.1     ${DATE}
+--- first-dir/file7    ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- file7
+--- 0 ----
+${SPROG} rdiff: Diffing first-dir/dir1
+Index: first-dir/dir1/file6
+diff -c first-dir/dir1/file6:1\.1 first-dir/dir1/file6:1\.2
+\*\*\* first-dir/dir1/file6:1\.1       ${DATE}
+--- first-dir/dir1/file6       ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+--- 1,2 ----
+  file6
+${PLUS} file6
+Index: first-dir/dir1/file7
+diff -c first-dir/dir1/file7:1\.1 first-dir/dir1/file7:removed
+\*\*\* first-dir/dir1/file7:1\.1       ${DATE}
+--- first-dir/dir1/file7       ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- file7
+--- 0 ----
+${SPROG} rdiff: Diffing first-dir/dir1/dir2
+Index: first-dir/dir1/dir2/file6
+diff -c first-dir/dir1/dir2/file6:1\.1 first-dir/dir1/dir2/file6:1\.2
+\*\*\* first-dir/dir1/dir2/file6:1\.1  ${DATE}
+--- first-dir/dir1/dir2/file6  ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+--- 1,2 ----
+  file6
+${PLUS} file6
+Index: first-dir/dir1/dir2/file7
+diff -c first-dir/dir1/dir2/file7:1\.1 first-dir/dir1/dir2/file7:removed
+\*\*\* first-dir/dir1/dir2/file7:1\.1  ${DATE}
+--- first-dir/dir1/dir2/file7  ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- file7
+--- 0 ----"
+               dotest basic2-24a "${testcvs} rdiff -l -r1.1 -rrtagged-by-head 
first-dir" \
+"${SPROG} rdiff: Diffing first-dir
+Index: first-dir/file6
+diff -c first-dir/file6:1\.1 first-dir/file6:1\.2
+\*\*\* first-dir/file6:1\.1    ${DATE}
+--- first-dir/file6    ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+--- 1,2 ----
+  file6
+${PLUS} file6
+Index: first-dir/file7
+diff -c first-dir/file7:1\.1 first-dir/file7:removed
+\*\*\* first-dir/file7:1.1     ${DATE}
+--- first-dir/file7    ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- file7
+--- 0 ----"
+               # now export by rtagged-by-head and rtagged-by-tag and compare.
+               dotest basic2-25 "${testcvs} export -r rtagged-by-head -d 1dir 
first-dir" \
+"${SPROG} export: Updating 1dir
+U 1dir/file14
+U 1dir/file6
+${SPROG} export: Updating 1dir/dir1
+U 1dir/dir1/file14
+U 1dir/dir1/file6
+${SPROG} export: Updating 1dir/dir1/dir2
+U 1dir/dir1/dir2/file14
+U 1dir/dir1/dir2/file6"
+               dotest_fail basic2-25a "test -d 1dir/CVS"
+               dotest_fail basic2-25b "test -d 1dir/dir1/CVS"
+               dotest_fail basic2-25c "test -d 1dir/dir1/dir2/CVS"
+
+               dotest basic2-26 "${testcvs} export -r rtagged-by-tag 
first-dir" \
+"${SPROG} export: Updating first-dir
+U first-dir/file14
+U first-dir/file6
+${SPROG} export: Updating first-dir/dir1
+U first-dir/dir1/file14
+U first-dir/dir1/file6
+${SPROG} export: Updating first-dir/dir1/dir2
+U first-dir/dir1/dir2/file14
+U first-dir/dir1/dir2/file6"
+               dotest_fail basic2-26a "test -d first-dir/CVS"
+               dotest_fail basic2-26b "test -d first-dir/dir1/CVS"
+               dotest_fail basic2-26c "test -d first-dir/dir1/dir2/CVS"
+
+               dotest basic2-27 "directory_cmp 1dir first-dir"
+               rm -r 1dir first-dir
+
+               # checkout by revision vs export by rtagged-by-revision and 
compare.
+               mkdir export-dir
+               dotest basic2-28 "${testcvs} export -rrtagged-by-revision -d 
export-dir first-dir" \
+"${SPROG} export: Updating export-dir
+U export-dir/file14
+U export-dir/file6
+U export-dir/file7
+${SPROG} export: Updating export-dir/dir1
+U export-dir/dir1/file14
+U export-dir/dir1/file6
+U export-dir/dir1/file7
+${SPROG} export: Updating export-dir/dir1/dir2
+U export-dir/dir1/dir2/file14
+U export-dir/dir1/dir2/file6
+U export-dir/dir1/dir2/file7"
+               dotest_fail basic2-28a "test -d export-dir/CVS"
+               dotest_fail basic2-28b "test -d export-dir/dir1/CVS"
+               dotest_fail basic2-28c "test -d export-dir/dir1/dir2/CVS"
+
+               dotest basic2-29 "${testcvs} co -r1.1 first-dir" \
+"${SPROG} checkout: Updating first-dir
+U first-dir/file14
+U first-dir/file6
+U first-dir/file7
+${SPROG} checkout: Updating first-dir/dir1
+U first-dir/dir1/file14
+U first-dir/dir1/file6
+U first-dir/dir1/file7
+${SPROG} checkout: Updating first-dir/dir1/dir2
+U first-dir/dir1/dir2/file14
+U first-dir/dir1/dir2/file6
+U first-dir/dir1/dir2/file7"
+
+               # 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 -))
+
+               dotest basic2-30 "directory_cmp first-dir export-dir"
+
+               # interrupt, while we've got a clean 1.1 here, let's import it
+               # into a couple of other modules.
+               cd export-dir
+               dotest_sort basic2-31 \
+"$testcvs import -m first-import second-dir first-immigration immigration1 
immigration1_0" \
+"
+
+N second-dir/dir1/dir2/file14
+N second-dir/dir1/dir2/file6
+N second-dir/dir1/dir2/file7
+N second-dir/dir1/file14
+N second-dir/dir1/file6
+N second-dir/dir1/file7
+N second-dir/file14
+N second-dir/file6
+N second-dir/file7
+No conflicts created by this import
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/second-dir/dir1
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/second-dir/dir1/dir2"
+               cd ..
+
+               dotest basic2-32 "${testcvs} export -r HEAD second-dir" \
+"${SPROG} export: Updating second-dir
+U second-dir/file14
+U second-dir/file6
+U second-dir/file7
+${SPROG} export: Updating second-dir/dir1
+U second-dir/dir1/file14
+U second-dir/dir1/file6
+U second-dir/dir1/file7
+${SPROG} export: Updating second-dir/dir1/dir2
+U second-dir/dir1/dir2/file14
+U second-dir/dir1/dir2/file6
+U second-dir/dir1/dir2/file7"
+
+               dotest basic2-33 "directory_cmp first-dir second-dir"
+
+               rm -r second-dir
+
+               rm -r 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
+               dotest basic2-34 "${testcvs} update -A -l *file*" \
+"U file6
+${SPROG} update: \`file7' is no longer in the repository"
+
+               # If we don't delete the tag first, cvs won't retag it.
+               # This would appear to be a feature.
+               dotest basic2-35 "${testcvs} tag -l -d rtagged-by-revision" \
+"${SPROG} tag: Untagging \.
+D file14
+D file6"
+               dotest basic2-36 "${testcvs} tag -l rtagged-by-revision" \
+"${SPROG} tag: Tagging \.
+T file14
+T file6"
+
+               cd ..
+               mv first-dir 1dir
+               mv first-dir.cpy first-dir
+               cd first-dir
+
+               dotest basic2-37 "${testcvs} -q diff -u" ''
+
+               dotest basic2-38 "${testcvs} update" \
+"${SPROG} update: Updating .
+${SPROG} update: Updating dir1
+${SPROG} update: Updating dir1/dir2"
+
+               cd ..
+
+               #### FIXME: is this expected to work???  Need to investigate
+               #### and fix or remove the test.
+#              dotest basic2-39 "directory_cmp 1dir first-dir"
+
+               rm -r 1dir first-dir
+
+               # Test the cvs history command.
+               #
+               # Just skip these in write proxy mode for now.  We should only
+               # see write commands and maybe the last few reads in the
+               # secondary history file the way we currently sync, but I'm not
+               # going to try and test this yet.
+               if $proxy; then :; else
+
+               # The reason that there are two patterns rather than using
+               # \(${TESTDIR}\|<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.
+
+               dotest basic2-64 "${testcvs} his -x TOFWUPCGMAR -a" \
+"O [0-9-]* [0-9:]* ${PLUS}0000 ${username} first-dir           =first-dir= 
${TESTDIR}/\*
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file6     first-dir           
== ${TESTDIR}
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file7     first-dir           
== ${TESTDIR}
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file6     first-dir/dir1      
== ${TESTDIR}
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file7     first-dir/dir1      
== ${TESTDIR}
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file6     first-dir/dir1/dir2 
== ${TESTDIR}
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file7     first-dir/dir1/dir2 
== ${TESTDIR}
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file14    first-dir           
== ${TESTDIR}
+M [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file6     first-dir           
== ${TESTDIR}
+R [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file7     first-dir           
== ${TESTDIR}
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file14    first-dir/dir1      
== ${TESTDIR}
+M [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file6     first-dir/dir1      
== ${TESTDIR}
+R [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file7     first-dir/dir1      
== ${TESTDIR}
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file14    first-dir/dir1/dir2 
== ${TESTDIR}
+M [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file6     first-dir/dir1/dir2 
== ${TESTDIR}
+R [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file7     first-dir/dir1/dir2 
== ${TESTDIR}
+F [0-9-]* [0-9:]* ${PLUS}0000 ${username}                     =first-dir= 
${TESTDIR}/\*
+T [0-9-]* [0-9:]* ${PLUS}0000 ${username} first-dir \[rtagged-by-head:A\]
+T [0-9-]* [0-9:]* ${PLUS}0000 ${username} first-dir 
\[rtagged-by-tag:rtagged-by-head\]
+T [0-9-]* [0-9:]* ${PLUS}0000 ${username} first-dir 
\[rtagged-by-revision:1\.1\]
+O [0-9-]* [0-9:]* ${PLUS}0000 ${username} \[1\.1\] first-dir           
=first-dir= ${TESTDIR}/\*
+U [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file6     first-dir           
== ${TESTDIR}/first-dir
+W [0-9-]* [0-9:]* ${PLUS}0000 ${username}     file7     first-dir           == 
${TESTDIR}/first-dir" \
+"O [0-9-]* [0-9:]* ${PLUS}0000 ${username} first-dir           =first-dir= 
<remote>/\*
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file6     first-dir           
== <remote>
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file7     first-dir           
== <remote>
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file6     first-dir/dir1      
== <remote>
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file7     first-dir/dir1      
== <remote>
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file6     first-dir/dir1/dir2 
== <remote>
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file7     first-dir/dir1/dir2 
== <remote>
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file14    first-dir           
== <remote>
+M [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file6     first-dir           
== <remote>
+R [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file7     first-dir           
== <remote>
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file14    first-dir/dir1      
== <remote>
+M [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file6     first-dir/dir1      
== <remote>
+R [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file7     first-dir/dir1      
== <remote>
+A [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.1 file14    first-dir/dir1/dir2 
== <remote>
+M [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file6     first-dir/dir1/dir2 
== <remote>
+R [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file7     first-dir/dir1/dir2 
== <remote>
+F [0-9-]* [0-9:]* ${PLUS}0000 ${username}                     =first-dir= 
<remote>/\*
+T [0-9-]* [0-9:]* ${PLUS}0000 ${username} first-dir \[rtagged-by-head:A\]
+T [0-9-]* [0-9:]* ${PLUS}0000 ${username} first-dir 
\[rtagged-by-tag:rtagged-by-head\]
+T [0-9-]* [0-9:]* ${PLUS}0000 ${username} first-dir 
\[rtagged-by-revision:1\.1\]
+O [0-9-]* [0-9:]* ${PLUS}0000 ${username} \[1\.1\] first-dir           
=first-dir= <remote>/\*
+P [0-9-]* [0-9:]* ${PLUS}0000 ${username} 1\.2 file6     first-dir           
== <remote>
+W [0-9-]* [0-9:]* ${PLUS}0000 ${username}     file7     first-dir           == 
<remote>"
+         fi
+
+         dokeep
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/second-dir
+         ;;
+
+
+
+       ls)
+         # Test the ls & rls commands.  There are some tests of
+         # Interaction of ls, rls, and branches in branches2.
+         mkdir ls; cd ls
+         dotest ls-init-1 "$testcvs -Q co -dtop ."
+         cd top
+         dotest ls-1 "$testcvs ls CVSROOT" \
+"checkoutlist
+commitinfo
+config
+cvswrappers
+loginfo
+modules
+notify
+postadmin
+postproxy
+posttag
+postwatch
+preproxy
+rcsinfo
+taginfo
+verifymsg"
+         dotest ls-2 "$testcvs ls -R" \
+"\.:
+CVSROOT
+
+CVSROOT:
+checkoutlist
+commitinfo
+config
+cvswrappers
+loginfo
+modules
+notify
+postadmin
+postproxy
+posttag
+postwatch
+preproxy
+rcsinfo
+taginfo
+verifymsg"
+         # This used to cause a fatal error.
+         modify_repo mkdir $CVSROOT_DIRNAME/notcheckedout
+         dotest ls-3 "$testcvs ls -RP" \
+"\.:
+CVSROOT
+notcheckedout
+
+CVSROOT:
+checkoutlist
+commitinfo
+config
+cvswrappers
+loginfo
+modules
+notify
+postadmin
+postproxy
+posttag
+postwatch
+preproxy
+rcsinfo
+taginfo
+verifymsg"
+
+         # Make sure the previous command did not create the notcheckedout
+         # directory.
+         dotest_fail ls-4 "test -d notcheckedout"
+
+         dotest ls-5 "$testcvs ls -R" \
+"\.:
+CVSROOT
+notcheckedout
+
+CVSROOT:
+checkoutlist
+commitinfo
+config
+cvswrappers
+loginfo
+modules
+notify
+postadmin
+postproxy
+posttag
+postwatch
+preproxy
+rcsinfo
+taginfo
+verifymsg
+
+notcheckedout:"
+         dotest_fail ls-6 "test -d notcheckedout"
+
+         # Several test for ls -d, which shows dead revisions
+
+         # Set up the dead files
+         mkdir cemetery
+         dotest ls-d-init-1 "$testcvs -Q add cemetery"
+         cd cemetery
+         touch dead living
+         dotest ls-d-init-2 "$testcvs -Q add dead living"
+         dotest ls-d-init-3 "$testcvs -Q ci -mm dead living"
+         dotest ls-d-init-4 "$testcvs -Q tag -b branch"
+         dotest ls-d-init-5 "$testcvs -Q up -A"
+         rm dead
+         dotest ls-d-init-6 "$testcvs -Q rm dead"
+         dotest ls-d-init-7 "$testcvs -Q ci -mm dead"
+         dotest ls-d-init-8 "$testcvs -Q up -r branch"
+         rm dead
+         dotest ls-d-init-9 "$testcvs -Q rm dead"
+         dotest ls-d-init-10 "$testcvs -Q ci -mm dead"
+
+         # Possible output
+         output_living="living"
+         output_dead="dead
+living"
+
+         # The basic test is to make sure that dead revisions are shown if and
+         # only if -d is speficified (and that live revisions are always
+         # shown).  The following test cases cover all combinations of these
+         # factors:
+         #
+         #    + Working directory is on branch or trunk
+         #    + ls or rls
+         #    + implicit branch, explicit trunk, or explicit branch
+         #    + -d present or absent
+
+         # Working directory on trunk
+         $testcvs -Q up -A
+
+         ## ls
+         dotest ls-d-1 "$testcvs ls" "$output_living"
+         dotest ls-d-2 "$testcvs ls -d" "$output_dead"
+
+         dotest ls-d-3 "$testcvs ls -rHEAD" "$output_living"
+         dotest ls-d-4 "$testcvs ls -drHEAD" "$output_dead"
+
+         dotest ls-d-5 "$testcvs ls -rbranch" "$output_living"
+         dotest ls-d-6 "$testcvs ls -drbranch" "$output_dead"
+
+         ## rls
+         dotest ls-d-7 "$testcvs rls cemetery" \
+"$SPROG rls: Listing module: \`cemetery'
+$output_living"
+         dotest ls-d-8 "$testcvs rls -d cemetery" \
+"$SPROG rls: Listing module: \`cemetery'
+$output_dead"
+
+         dotest ls-d-9 "$testcvs -q rls -rHEAD cemetery" "$output_living"
+         dotest ls-d-10 "$testcvs -q rls -drHEAD cemetery" "$output_dead"
+
+         dotest ls-d-11 "$testcvs -q rls -rbranch cemetery" "$output_living"
+         dotest ls-d-12 "$testcvs -q rls -drbranch cemetery" "$output_dead"
+
+         # Working directory on branch
+         $testcvs -Q up -r branch
+
+         ## ls
+         dotest ls-d-13 "$testcvs ls" "$output_living"
+         dotest ls-d-14 "$testcvs ls -d" "$output_dead"
+
+         dotest ls-d-15 "$testcvs ls -r HEAD" "$output_living"
+         dotest ls-d-16 "$testcvs ls -d -r HEAD" "$output_dead"
+
+         dotest ls-d-17 "$testcvs ls -r branch" "$output_living"
+         dotest ls-d-18 "$testcvs ls -d -r branch" "$output_dead"
+
+         ## rls
+         dotest ls-d-19 "$testcvs -q rls cemetery" "$output_living"
+         dotest ls-d-20 "$testcvs -q rls -d cemetery" "$output_dead"
+
+         dotest ls-d-21 "$testcvs -q rls -rHEAD cemetery" "$output_living"
+         dotest ls-d-22 "$testcvs -q rls -drHEAD cemetery" "$output_dead"
+
+         dotest ls-d-23 "$testcvs -q rls -rbranch cemetery" "$output_living"
+         dotest ls-d-24 "$testcvs -q rls -drbranch cemetery" "$output_dead"
+
+         # Some tests to cover specifying a file name as an option
+         # Combinations of factors:
+         #
+         #  + file in CVS/Entries or not
+         #  + current directory or subdirectory
+         #  + file dead or not
+
+         # Switch back to the trunk
+         $testcvs -Q up -A
+
+         ## file in CVS/Entries
+         dotest ls-filename-1 "$testcvs ls dead"
+
+         # ls'ing a file that already exists once caused an assertion failure.
+         dotest ls-filename-2 "$testcvs ls living" "living"
+
+         cd ..
+         dotest ls-filename-3 "$testcvs ls cemetery/dead"
+
+         # ls'ing a file that already exists once caused an assertion failure.
+         dotest ls-filename-4 "$testcvs ls cemetery/living" "cemetery/living"
+         cd cemetery
+
+         ## file not in CVS/Entries
+         echo D > CVS/Entries
+
+         dotest ls-filename-5 "$testcvs ls dead"
+
+         # ls'ing a file that already exists once caused an assertion failure.
+         dotest ls-filename-6 "$testcvs ls living" "living"
+
+         cd ..
+         dotest ls-filename-7 "$testcvs ls cemetery/dead"
+
+         # ls'ing a file that already exists once caused an assertion failure.
+         dotest ls-filename-8 "$testcvs ls cemetery/living" "cemetery/living"
+
+         cd cemetery
+
+         # Test the -D date option to cvs ls
+
+         # try and list a file before it's created, during an old revision, in
+         # a period when it was dead and in the future
+         time_prebirth=`date '+%Y-%m-%d %H:%M:%S'` ; sleep 1
+         touch dated
+         dotest ls-D-init-1 "$testcvs -Q add dated"
+         dotest ls-D-init-2 "$testcvs -Q ci -mm dated"
+         time_newborn=`date '+%Y-%m-%d %H:%M:%S'` ; sleep 1
+         echo mm >> dated
+         dotest ls-D-init-2 "$testcvs -Q ci -mm dated"
+         time_predeath=`date '+%Y-%m-%d %H:%M:%S'` ; sleep 1
+         rm dated
+         dotest ls-D-init-3 "$testcvs -Q rm dated"
+         dotest ls-D-init-4 "$testcvs -Q ci -mm dated"
+         time_postdeath=`date '+%Y-%m-%d %H:%M:%S'`
+
+         dotest ls-D-1 "$testcvs ls -D '$time_prebirth' -e dated"
+
+         # ls'ing a file that already exists once caused an assertion failure.
+         dotest ls-D-2 "$testcvs ls -D '$time_newborn' -e dated" \
+"/dated/1\.1/.*"
+
+         # ls'ing a file that already exists once caused an assertion failure.
+         dotest ls-D-3 "$testcvs ls -D '$time_predeath' -e dated" \
+"/dated/1.2/.*"
+
+         dotest ls-D-4 "$testcvs ls -D '$time_postdeath' -e dated"
+
+         dokeep
+         cd ../../..
+         rm -r ls
+         modify_repo rm -rf $CVSROOT_DIRNAME/notcheckedout \
+                            $CVSROOT_DIRNAME/cemetery
+         unset output_living output_dead
+         ;;
+
+
+
+       parseroot)
+         mkdir 1; cd 1
+         # Test odd cases involving CVSROOT.  At the moment, that means we
+         # are testing roots with '/'s on the end, which CVS should parse off.
+         CVSROOT_save=${CVSROOT}
+         CVSROOT="${CVSROOT}/////"
+         dotest parseroot-1 "${testcvs} -q co CVSROOT/modules" \
+"U CVSROOT/modules"
+         dotest parseroot-2 "${testcvs} -q ci -fmnull-change CVSROOT/modules" \
+"$CVSROOT_DIRNAME/CVSROOT/modules,v  <--  CVSROOT/modules
+new revision: 1\.2; previous revision: 1\.1
+$SPROG commit: Rebuilding administrative file database"
+
+         if $remote; then
+           # I only test these when testing remote in case CVS was compiled
+           # without client support.
+
+           # logout does not try to contact the server.
+           CVSROOT=":pserver;proxy=localhost;proxyport=8080:localhost/dev/null"
+           dotest parseroot-3r "$testcvs -d'$CVSROOT' logout" \
+"Logging out of :pserver:address@hidden:2401/dev/null
+$CPROG logout: warning: failed to open $HOME/\.cvspass for reading: No such 
file or directory
+$CPROG logout: Entry not found."
+           CVSROOT=":pserver;proxyport=8080:localhost/dev/null"
+           dotest_fail parseroot-4r "$testcvs -d'$CVSROOT' logout" \
+"$CPROG logout: Proxy port specified in CVSROOT without proxy host\.
+$CPROG \[logout aborted\]: Bad CVSROOT: 
\`:pserver;proxyport=8080:localhost/dev/null'\."
+           CVSROOT=":pserver;optionnoarg:localhost/dev/null"
+           dotest_fail parseroot-5r "$testcvs -d'$CVSROOT' logout" \
+"$CPROG logout: Option (\`optionnoarg') has no argument in CVSROOT\.
+$CPROG \[logout aborted\]: Bad CVSROOT: 
\`:pserver;optionnoarg:localhost/dev/null'\."
+           CVSROOT=":pserver;notanoption=anything:localhost/dev/null"
+           dotest_fail parseroot-6r "$testcvs -d'$CVSROOT' logout" \
+"$CPROG logout: Unknown option (\`notanoption') in CVSROOT\.
+$CPROG \[logout aborted\]: Bad CVSROOT: 
\`:pserver;notanoption=anything:localhost/dev/null'\."
+           CVSROOT=":local;proxy=localhost:/dev/null"
+           dotest_fail parseroot-7r "$testcvs -d'$CVSROOT' logout" \
+"$CPROG logout: CVSROOT proxy specification is only valid for gserver and
+$CPROG logout: pserver connection methods\.
+$CPROG \[logout aborted\]: Bad CVSROOT: \`:local;proxy=localhost:/dev/null'\."
+           CVSROOT="::address@hidden@test.org:/cvs"
+           dotest_fail parseroot-8r "$testcvs -d'$CVSROOT' co test" \
+"$CPROG checkout: Unknown method (\`') in CVSROOT\.
+$CPROG \[checkout aborted\]: Bad CVSROOT: \`$CVSROOT'\."
+         fi
+
+         dokeep
+
+         # Clean up
+         CVSROOT=$CVSROOT_save
+         cd ..
+         rm -r 1
+         ;;
+
+
+
+       files)
+         # Test of how we specify files on the command line
+         # (recurse.c and that sort of thing).  Vaguely similar to
+         # tests like basic* and deep.  See modules and such tests
+         # for what happens when we throw in modules and co -d, &c.
+
+         # This particular test is fairly carefully crafted, to spot
+         # one particular issue with remote.
+         mkdir 1; cd 1
+         dotest files-1 "${testcvs} -q co -l ." ""
+         mkdir first-dir
+         dotest files-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         touch tfile
+         dotest files-3 "${testcvs} add tfile" \
+"${SPROG} add: scheduling file .tfile. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest files-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/tfile,v  <--  tfile
+initial revision: 1\.1"
+         dotest files-5 "${testcvs} -q tag -b C" "T tfile"
+         dotest files-6 "${testcvs} -q update -r C" ""
+         mkdir dir
+         dotest files-7 "${testcvs} add dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir added to the repository
+--> Using per-directory sticky tag .C'"
+         cd dir
+         touch .file
+         dotest files-7b "${testcvs} add .file" \
+"${SPROG} add: scheduling file .\.file' for addition on branch .C.
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         mkdir sdir
+         dotest files-7c "${testcvs} add sdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir/sdir added to the repository
+--> Using per-directory sticky tag .C'"
+         cd sdir
+         mkdir ssdir
+         dotest files-8 "${testcvs} add ssdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir/sdir/ssdir added to the repository
+--> Using per-directory sticky tag .C'"
+         cd ssdir
+         touch .file
+         dotest files-9 "${testcvs} add .file" \
+"${SPROG} add: scheduling file .\.file' for addition on branch .C.
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         cd ../..
+         dotest files-10 "${testcvs} -q ci -m test" \
+"$CVSROOT_DIRNAME/first-dir/dir/Attic/\.file,v  <--  \.file
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dir/sdir/ssdir/Attic/\.file,v  <--  
sdir/ssdir/\.file
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+         dotest files-11 \
+"${testcvs} commit -m test -f ./.file ./sdir/ssdir/.file" \
+"${CVSROOT_DIRNAME}/first-dir/dir/Attic/\.file,v  <--  \.file
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1
+${CVSROOT_DIRNAME}/first-dir/dir/sdir/ssdir/Attic/\.file,v  <--  
\./sdir/ssdir/\.file
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+         if $remote; then
+           # FIXCVS:
+           # This is a bug, looks like that toplevel_repos cruft in
+           # client.c is coming back to haunt us.
+           # May want to think about the whole issue, toplevel_repos
+           # has always been crufty and trying to patch it up again
+           # might be a mistake.
+           dotest files-12r \
+"$testcvs commit -f -m test ./sdir/ssdir/.file ./.file" \
+"$CVSROOT_DIRNAME/first-dir/dir/sdir/ssdir/Attic/\.file,v  <--  
\./sdir/ssdir/\.file
+new revision: 1\.1\.2\.3; previous revision: 1\.1\.2\.2"
+
+           # Sync up the version numbers so that the rest of the
+           # tests don't need to expect different numbers based
+           # local or remote.
+           dotest files-12rworkaround \
+"$testcvs commit -f -m test .file" \
+"$CVSROOT_DIRNAME/first-dir/dir/Attic/\.file,v  <--  \.file
+new revision: 1\.1\.2\.3; previous revision: 1\.1\.2\.2"
+         else
+           dotest files-12 \
+"${testcvs} commit -f -m test ./sdir/ssdir/.file ./.file" \
+"${CVSROOT_DIRNAME}/first-dir/dir/sdir/ssdir/Attic/\.file,v  <--  
\./sdir/ssdir/\.file
+new revision: 1\.1\.2\.3; previous revision: 1\.1\.2\.2
+${CVSROOT_DIRNAME}/first-dir/dir/Attic/\.file,v  <--  \.file
+new revision: 1\.1\.2\.3; previous revision: 1\.1\.2\.2"
+         fi
+         dotest files-13 \
+"${testcvs} commit -fmtest ./sdir/../sdir/ssdir/..///ssdir/.file" \
+"${CVSROOT_DIRNAME}/first-dir/dir/sdir/ssdir/Attic/\.file,v  <--  
\./sdir/\.\./sdir/ssdir/\.\.///ssdir/\.file
+new revision: 1\.1\.2\.4; previous revision: 1\.1\.2\.3"
+         dotest files-14 \
+"${testcvs} commit -fmtest ../../first-dir/dir/.file" \
+"${CVSROOT_DIRNAME}/first-dir/dir/Attic/\.file,v  <--  
\.\./\.\./first-dir/dir/\.file
+new revision: 1\.1\.2\.4; previous revision: 1\.1\.2\.3"
+
+         dokeep
+         cd ../../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       spacefiles)
+         # More filename tests, in particular spaces in file names.
+         # (it might be better to just change a few of the names in
+         # basica or some other test instead, always good to keep the
+         # testsuite concise).
+
+         mkdir 1; cd 1
+         dotest spacefiles-1 "${testcvs} -q co -l ." ""
+         touch ./-c
+         dotest spacefiles-2 "${testcvs} add -- -c" \
+"${SPROG} add: scheduling file .-c. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest spacefiles-3 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/-c,v  <--  -c
+initial revision: 1\.1"
+         mkdir 'first dir'
+         dotest spacefiles-4 "${testcvs} add 'first dir'" \
+"Directory ${CVSROOT_DIRNAME}/first dir added to the repository"
+         mkdir ./-b
+         dotest spacefiles-5 "${testcvs} add -- -b" \
+"Directory ${CVSROOT_DIRNAME}/-b added to the repository"
+         cd 'first dir'
+         touch 'a file'
+         dotest spacefiles-6 "${testcvs} add 'a file'" \
+"${SPROG} add: scheduling file .a file. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest spacefiles-7 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first dir/a file,v  <--  a file
+initial revision: 1\.1"
+         dotest spacefiles-8 "${testcvs} -q tag new-tag" "T a file"
+         cd ../..
+
+         mkdir 2; cd 2
+         dotest spacefiles-10 "${testcvs} co -- -b" \
+"${SPROG} checkout: Updating -b"
+         dotest spacefiles-11 "${testcvs} -q co -- -c" "U \./-c"
+         rm ./-c
+         dotest spacefiles-13 "${testcvs} -q co 'first dir'" \
+"U first dir/a file"
+         cd ..
+
+         mkdir 3; cd 3
+         dotest spacefiles-14 "${testcvs} -q co 'first dir/a file'" \
+"U first dir/a file"
+         cd ..
+
+         rm -r 1 2 3
+         modify_repo rm -rf "'$CVSROOT_DIRNAME/first dir'" \
+                            $CVSROOT_DIRNAME/-b $CVSROOT_DIRNAME/-c,v
+         ;;
+
+
+
+       commit-readonly)
+         mkdir 1; cd 1
+         module=x
+
+         : > junk
+         dotest commit-readonly-1 "$testcvs -Q import -m . $module X Y" ''
+         dotest commit-readonly-2 "$testcvs -Q co $module" ''
+         cd $module
+
+         file=m
+
+         # Include an rcs keyword to be expanded.
+         echo '$Id''$' > $file
+
+         dotest commit-readonly-3 "$testcvs add $file" \
+"$SPROG add: scheduling file .$file. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         dotest commit-readonly-4 "$testcvs -Q ci -m . $file"
+
+         echo line2 >> $file
+         # Make the file read-only.
+         chmod a-w $file
+
+         dotest commit-readonly-5 "$testcvs -Q ci -m . $file"
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/"$module"
+         ;;
+
+
+
+       status)
+               # This tests for a bug in the status command which failed to
+               # notice resolved conflicts.
+               mkdir status; cd status
+               dotest status-init-1 "$testcvs -q co -l ."
+               mkdir first-dir
+               dotest status-init-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+               cd first-dir
+               echo a line >tfile
+               dotest status-init-3 "${testcvs} add tfile" \
+"${SPROG} add: scheduling file .tfile. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+               dotest status-init-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/tfile,v  <--  tfile
+initial revision: 1\.1"
+               cd ..
+               dotest status-init-5 "${testcvs} -q co -dsecond-dir first-dir" \
+"U second-dir/tfile"
+               cd second-dir
+               echo some junk >>tfile
+               dotest status-init-6 "${testcvs} -q ci -maline" \
+"$CVSROOT_DIRNAME/first-dir/tfile,v  <--  tfile
+new revision: 1\.2; previous revision: 1\.1"
+               cd ../first-dir
+               echo force a conflict >>tfile
+               dotest status-init-7 "${testcvs} -q up" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/tfile,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into tfile
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in tfile
+C tfile"
+
+               # Now note our status
+               dotest status-1 "${testcvs} status tfile" \
+"===================================================================
+File: tfile                    Status: Unresolved Conflict
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/tfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+               # touch the file, leaving conflict markers in place
+               # and note our status
+               touch tfile
+               dotest status-2 "${testcvs} status tfile" \
+"===================================================================
+File: tfile                    Status: File had conflicts on merge
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/tfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+               # resolve the conflict
+               echo resolution >tfile
+               dotest status-3 "${testcvs} status tfile" \
+"===================================================================
+File: tfile                    Status: Locally Modified
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/tfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+               # Check that there are no problems just using CVS/Root too.
+               save_CVSROOT=$CVSROOT
+               unset CVSROOT
+               dotest status-3a "${testcvs} status tfile" \
+"===================================================================
+File: tfile                    Status: Locally Modified
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/tfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+               CVSROOT=$save_CVSROOT
+               export CVSROOT
+
+               # FIXCVS:
+               # Update is supposed to re-Register() the file when it
+               # finds resolved conflicts:
+               dotest status-4 "grep 'Result of merge' CVS/Entries" \
+"/tfile/1\.2/Result of merge${PLUS}[a-zA-Z0-9 :]*//"
+
+                cd ..
+                mkdir fourth-dir
+                dotest status-init-8 "$testcvs add fourth-dir" \
+"Directory $CVSROOT_DIRNAME/fourth-dir added to the repository"
+                cd fourth-dir
+                echo yet another line >t3file
+                dotest status-init-9 "$testcvs add t3file" \
+"$SPROG add: scheduling file .t3file. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+                dotest status-init-10 "$testcvs -q ci -m add" \
+"$CVSROOT_DIRNAME/fourth-dir/t3file,v  <--  t3file
+initial revision: 1\.1"
+                cd ../first-dir
+                mkdir third-dir
+                dotest status-init-11 "$testcvs add third-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir/third-dir added to the repository"
+                cd third-dir
+                echo another line >t2file
+                dotest status-init-12 "$testcvs add t2file" \
+"$SPROG add: scheduling file .t2file. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+                dotest status-init-13 "$testcvs -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/third-dir/t2file,v  <--  t2file
+initial revision: 1\.1"
+                dotest status-5 "$testcvs status ../tfile" \
+"===================================================================
+File: tfile                    Status: Locally Modified
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    $CVSROOT_DIRNAME/first-dir/tfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+                dotest status-6 "$testcvs status ../../fourth-dir/t3file" \
+"===================================================================
+File: t3file                   Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    $CVSROOT_DIRNAME/fourth-dir/t3file,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+               dokeep
+               cd ../../..
+               rm -rf status
+               modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                                  $CVSROOT_DIRNAME/fourth-dir
+               ;;
+
+
+
+       commit-readonlyfs)
+         mkdir 1; cd 1
+         module=x
+         : > junk
+         dotest commit-readonlyfs-1 "${testcvs} -Q import -m . $module X Y" ''
+         if $remote; then
+           dotest_fail commit-readonlyfs-2r1 "${testcvs} -Q -R co $module" \
+"${CPROG} \[checkout aborted\]: Read-only repository feature unavailable with 
remote roots (cvsroot = ${CVSROOT_DIRNAME})"
+           dotest commit-readonlyfs-2r2 "${testcvs} -Q co $module" ''
+          else
+           dotest commit-readonlyfs-2 "${testcvs} -Q -R co $module" ''
+           rm -rf $module
+           dotest commit-readonlyfs-2r3 "${testcvs} -q -R co $module" \
+"U $module/junk"
+           rm -rf $module
+           dotest commit-readonlyfs-2r4 "${testcvs} -R co $module" \
+"${SPROG}: WARNING: Read-only repository access mode selected via \`cvs -R'\.
+Using this option to access a repository which some users write to may
+cause intermittent sandbox corruption\.
+${SPROG} checkout: Updating $module
+U $module/junk"
+          fi
+         cd $module
+         echo test > junk
+         if $remote; then
+           dotest_fail commit-readonlyfs-3r "${testcvs} -Q -R ci -m. junk" \
+"${SPROG} \[commit aborted\]: Read-only repository feature unavailable with 
remote roots (cvsroot = ${CVSROOT_DIRNAME})"
+         else
+           dotest_fail commit-readonlyfs-3 "${testcvs} -Q -R ci -m. junk" \
+"${SPROG} commit: write lock failed\.
+WARNING: Read-only repository access mode selected via \`cvs -R'\.
+Attempting to write to a read-only filesystem is not allowed\.
+${SPROG} \[commit aborted\]: lock failed - giving up"
+          fi
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/"$module"
+         ;;
+
+
+
+       rdiff)
+               # Test rdiff
+               # XXX for now this is just the most essential test...
+               cd ${TESTDIR}
+
+               mkdir testimport
+               cd testimport
+               echo '$''Id$' > foo
+               echo '$''Name$' >> foo
+               echo '$''Id$' > bar
+               echo '$''Name$' >> bar
+               dotest_sort rdiff-1 \
+                 "${testcvs} import -I ! -m test-import-with-keyword trdiff 
TRDIFF T1" \
+'
+
+N trdiff/bar
+N trdiff/foo
+No conflicts created by this import'
+               dotest rdiff-2 \
+                 "${testcvs} co -ko trdiff" \
+"${SPROG} checkout: Updating trdiff
+U trdiff/bar
+U trdiff/foo"
+               cd trdiff
+               echo something >> foo
+               dotest rdiff-3 \
+                 "${testcvs} ci -m added-something foo" \
+"${CVSROOT_DIRNAME}/trdiff/foo,v  <--  foo
+new revision: 1\.2; previous revision: 1\.1"
+               echo '#ident    "@(#)trdiff:$''Name$:$''Id$"' > new
+               echo "new file" >> new
+               dotest rdiff-4 \
+                 "${testcvs} add -m new-file-description new" \
+"${SPROG} add: scheduling file \`new' for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+               dotest rdiff-5 \
+                 "${testcvs} commit -m added-new-file new" \
+"${CVSROOT_DIRNAME}/trdiff/new,v  <--  new
+initial revision: 1\.1"
+               dotest rdiff-6 \
+                 "${testcvs} tag local-v0" \
+"${SPROG} tag: Tagging .
+T bar
+T foo
+T new"
+               dotest rdiff-7 \
+                 "${testcvs} status -v foo" \
+"===================================================================
+File: foo                      Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/trdiff/foo,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -ko
+
+   Existing Tags:
+       local-v0                        (revision: 1\.2)
+       T1                              (revision: 1\.1\.1\.1)
+       TRDIFF                          (branch: 1\.1\.1)"
+
+               cd ..
+               rm -r trdiff
+
+               dotest rdiff-8 \
+                 "${testcvs} rdiff -r T1 -r local-v0 trdiff" \
+"${SPROG}"' rdiff: Diffing trdiff
+Index: trdiff/foo
+diff -c trdiff/foo:1\.1\.1\.1 trdiff/foo:1\.2
+\*\*\* trdiff/foo:1\.1\.1\.1   '"${DATE}"'
+--- trdiff/foo '"${DATE}"'
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1,2 \*\*\*\*
+! \$''Id: foo,v 1\.1\.1\.1 [0-9/]* [0-9:]* '"${username}"' Exp \$
+! \$''Name: T1 \$
+--- 1,3 ----
+! \$''Id: foo,v 1\.2 [0-9/]* [0-9:]* '"${username}"' Exp \$
+! \$''Name: local-v0 \$
+! something
+Index: trdiff/new
+diff -c /dev/null trdiff/new:1\.1
+\*\*\* /dev/null       '"${DATE}"'
+--- trdiff/new '"${DATE}"'
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 0 \*\*\*\*
+--- 1,2 ----
+'"${PLUS}"' #ident     "@(#)trdiff:\$''Name: local-v0 \$:\$''Id: new,v 1\.1 
[0-9/]* [0-9:]* '"${username}"' Exp \$"
+'"${PLUS}"' new file'
+
+               dokeep
+               cd ..
+               rm -r testimport
+               modify_repo rm -rf $CVSROOT_DIRNAME/trdiff
+               ;;
+
+
+
+       rdiff-short)
+         # Test that the short patch behaves as expected
+         #   1) Added file.
+         #   2) Removed file.
+         #   3) Different revision number with no difference.
+         #   4) Different revision number with changes.
+         #   5) Against trunk.
+         #   6) Same revision number (no difference).
+         mkdir rdiff-short; cd rdiff-short
+         mkdir abc
+         dotest rdiff-short-init-1 \
+"${testcvs} -q import -I ! -m initial-import abc vendor initial" \
+'
+No conflicts created by this import'
+
+         dotest rdiff-short-init-2 "${testcvs} -q get abc" ''
+         cd abc
+         echo "abc" >file1.txt
+         dotest rdiff-short-init-3 "${testcvs} add file1.txt" \
+"${SPROG} add: scheduling file .file1\.txt' for addition
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+         dotest rdiff-short-init-4 \
+"${testcvs} commit -madd-file1 file1.txt" \
+"${CVSROOT_DIRNAME}/abc/file1\.txt,v  <--  file1\.txt
+initial revision: 1\.1"
+         echo def >>file1.txt
+         dotest rdiff-short-init-5 \
+"${testcvs} commit -mchange-file1 file1.txt" \
+"${CVSROOT_DIRNAME}/abc/file1\.txt,v  <--  file1\.txt
+new revision: 1\.2; previous revision: 1\.1"
+         echo "abc" >file1.txt
+         dotest rdiff-short-init-6 \
+"${testcvs} commit -mrestore-file1-rev1 file1.txt" \
+"${CVSROOT_DIRNAME}/abc/file1\.txt,v  <--  file1\.txt
+new revision: 1\.3; previous revision: 1\.2"
+         dotest rdiff-short-init-7 \
+"${testcvs} tag -r 1.1 tag1 file1.txt" \
+"T file1\.txt"
+         dotest rdiff-short-init-8 \
+"${testcvs} tag -r 1.2 tag2 file1.txt" \
+"T file1\.txt"
+         dotest rdiff-short-init-9 \
+"${testcvs} tag -r 1.3 tag3 file1.txt" \
+"T file1\.txt"
+         echo "abc" >file2.txt
+         dotest rdiff-short-init-10 \
+"${testcvs} add file2.txt" \
+"${SPROG} add: scheduling file .file2\.txt' for addition
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+         dotest rdiff-add-remove-nodiff-init-11 \
+"${testcvs} commit -madd-file2 file2.txt" \
+"${CVSROOT_DIRNAME}/abc/file2\.txt,v  <--  file2\.txt
+initial revision: 1\.1"
+         dotest rdiff-short-init-12 \
+"${testcvs} tag -r 1.1 tag4 file2.txt" \
+"T file2\.txt"
+         dotest rdiff-short-init-13 \
+"${testcvs} tag -r 1.1 tag5 file2.txt" \
+"T file2\.txt"
+         cd ../..
+         rm -fr rdiff-short
+
+         # 3) Different revision number with no difference.
+         dotest rdiff-short-no-real-change \
+"${testcvs} -q rdiff -s -r tag1 -r tag3 abc"
+
+         # 4) Different revision number with changes.
+         dotest rdiff-short-real-change \
+"${testcvs} -q rdiff -s -r tag1 -r tag2 abc" \
+'File abc/file1.txt changed from revision 1\.1 to 1\.2'
+
+         # 1) Added file.
+         # 2) Removed file.
+         dotest_sort rdiff-short-remove-add \
+"${testcvs} -q rdiff -s -r tag2 -r tag4 abc" \
+'File abc/file1\.txt is removed; tag2 revision 1\.2
+File abc/file2\.txt is new; tag4 revision 1\.1'
+
+         # 6) Same revision number (no difference).
+         dotest rdiff-short-no-change \
+"${testcvs} -q rdiff -s -r tag4 -r tag5 abc"
+
+         # 5) Against trunk.
+         # Check that the messages change when we diff against the trunk
+         # rather than a tag or date.
+         dotest rdiff-short-against-trunk-1 \
+"${testcvs} -q rdiff -s -rtag4 abc" \
+"File abc/file1\.txt is new; current revision 1\.3"
+
+         dotest rdiff-short-against-trunk-2 \
+"${testcvs} -q rdiff -s -rtag2 abc" \
+"File abc/file1\.txt changed from revision 1\.2 to 1\.3
+File abc/file2\.txt is new; current revision 1\.1"
+
+         modify_repo rm -rf $CVSROOT_DIRNAME/abc
+         ;;
+
+
+
+       rdiff2)
+         # Test for the segv problem reported by James Cribb
+         # Somewhere to work
+         mkdir rdiff2; cd rdiff2         
+         # Create a module "m" with files "foo" and "d/bar"
+         mkdir m; cd m
+         echo foo >foo
+         mkdir d
+         echo bar >d/bar
+         dotest_sort  rdiff2-1 \
+"${testcvs} -q import -I ! -m initial-import m vendor initial" \
+'
+
+N m/d/bar
+N m/foo
+No conflicts created by this import'
+
+         cd ..
+         rm -r m
+         
+         # Remove "foo"
+         dotest rdiff2-2 "${testcvs} get m" \
+"${SPROG} checkout: Updating m
+U m/foo
+${SPROG} checkout: Updating m/d
+U m/d/bar"
+         cd m
+         dotest rdiff2-3 "${testcvs} rm -f foo" \
+"${SPROG} remove: scheduling .foo. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+
+         dotest rdiff2-4 "${testcvs} commit -m Removed foo" \
+"${CVSROOT_DIRNAME}/m/foo,v  <--  foo
+new revision: delete; previous revision: 1\.1\.1\.1"
+
+         # Modify "d/bar"
+         echo foo >d/bar
+         dotest rdiff2-5 "${testcvs} commit -m Changed d/bar" \
+"${CVSROOT_DIRNAME}/m/d/bar,v  <--  d/bar
+new revision: 1\.2; previous revision: 1\.1"
+         
+         # Crash before showing d/bar diffs
+         dotest_fail rdiff2-6 "${testcvs} rdiff -t m" \
+"${SPROG} rdiff: Diffing m
+${SPROG} rdiff: Diffing m/d
+Index: m/d/bar
+diff -c m/d/bar:1\.1\.1\.1 m/d/bar:1\.2
+\*\*\* m/d/bar:1\.1\.1\.1      ${DATE}
+--- m/d/bar    ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+! bar
+--- 1 ----
+! foo"
+
+         dokeep
+         cd ../..
+         rm -rf rdiff2
+         modify_repo rm -rf $CVSROOT_DIRNAME/m
+         ;;
+
+
+
+       diff)
+         # Various tests specific to the "cvs diff" command.
+         # Related tests:
+         #   death2: -N
+         #   rcslib: cvs diff and $Name.
+         #   rdiff: cvs rdiff.
+         #   diffmerge*: nuts and bolts (stuff within diff library)
+         mkdir 1; cd 1
+         dotest diff-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest diff-2 "$testcvs add first-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir added to the repository"
+         cd first-dir
+
+         # diff is anomalous.  Most CVS commands print the "nothing
+         # known" message (or worse yet, no message in some cases) but
+         # diff says "I know nothing".  Shrug.
+         dotest_fail diff-3 "${testcvs} diff xyzpdq" \
+"${SPROG} diff: I know nothing about xyzpdq"
+         touch abc
+         dotest diff-4 "${testcvs} add abc" \
+"${SPROG} add: scheduling file .abc. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest diff-5 "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/abc,v  <--  abc
+initial revision: 1\.1"
+         echo "extern int gethostname ();" >abc
+         dotest diff-6 "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/abc,v  <--  abc
+new revision: 1\.2; previous revision: 1\.1"
+         echo "#include <winsock.h>" >abc
+         # check the behavior of the --ifdef=MACRO option
+         dotest_fail diff-7 "${testcvs} -q diff --ifdef=HAVE_WINSOCK_H" \
+"Index: abc
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/abc,v
+retrieving revision 1\.2
+diff --ifdef HAVE_WINSOCK_H -r1\.2 abc
+#ifndef HAVE_WINSOCK_H
+extern int gethostname ();
+#else /\* HAVE_WINSOCK_H \*/
+#include <winsock\.h>
+#endif /\* HAVE_WINSOCK_H \*/"
+
+         dokeep
+         cd ../..
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -r 1
+         ;;
+
+
+
+       diffnl)
+         # Test handling of 'cvs diff' of files without newlines
+         mkdir 1; cd 1
+         dotest diffnl-000 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest diffnl-001 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+
+         ${AWK} 'BEGIN {printf("one\ntwo\nthree\nfour\nfive\nsix")}' 
</dev/null >abc
+         dotest diffnl-002 "${testcvs} add abc" \
+"${SPROG} add: scheduling file .abc. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+          dotest diffnl-003 "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/abc,v  <--  abc
+initial revision: 1\.1"
+
+         # change to line near EOF
+         ${AWK} 'BEGIN {printf("one\ntwo\nthree\nfour\nsix")}' </dev/null >abc
+         dotest_fail diffnl-100 "${testcvs} diff abc" \
+"Index: abc
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/abc,v
+retrieving revision 1\.1
+diff -r1\.1 abc
+5d4
+< five"
+          dotest_fail diffnl-101 "${testcvs} diff -u abc" \
+"Index: abc
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/abc,v
+retrieving revision 1\.1
+diff -u -r1\.1 abc
+--- abc        ${RFCDATE}      1\.1
++++ abc        ${RFCDATE}
+@@ -2,5 +2,4 @@
+ two
+ three
+ four
+-five
+ six
+\\\\ No newline at end of file"
+          dotest diffnl-102 "${testcvs} -q ci -mtest abc" \
+"$CVSROOT_DIRNAME/first-dir/abc,v  <--  abc
+new revision: 1\.2; previous revision: 1\.1"
+
+          # Change to last line
+         ${AWK} 'BEGIN {printf("one\ntwo\nthree\nfour\nseven")}' </dev/null 
>abc
+          dotest_fail diffnl-200 "${testcvs} diff abc" \
+"Index: abc
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/abc,v
+retrieving revision 1\.2
+diff -r1\.2 abc
+5c5
+< six
+\\\\ No newline at end of file
+---
+> seven
+\\\\ No newline at end of file"
+         dotest_fail diffnl-201 "${testcvs} diff -u abc" \
+"Index: abc
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/abc,v
+retrieving revision 1\.2
+diff -u -r1\.2 abc
+--- abc        ${RFCDATE}      1\.2
++++ abc        ${RFCDATE}
+@@ -2,4 +2,4 @@
+ two
+ three
+ four
+-six
+\\\\ No newline at end of file
++seven
+\\\\ No newline at end of file"
+         dotest diffnl-202 "${testcvs} ci -mtest abc" \
+"${CVSROOT_DIRNAME}/first-dir/abc,v  <--  abc
+new revision: 1\.3; previous revision: 1\.2"
+
+         # Addition of newline
+         echo "one
+two
+three
+four
+seven" > abc
+         dotest_fail diffnl-300 "${testcvs} diff abc" \
+"Index: abc
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/abc,v
+retrieving revision 1\.3
+diff -r1\.3 abc
+5c5
+< seven
+\\\\ No newline at end of file
+---
+> seven"
+         dotest_fail diffnl-301 "${testcvs} diff -u abc" \
+"Index: abc
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/abc,v
+retrieving revision 1\.3
+diff -u -r1\.3 abc
+--- abc        ${RFCDATE}      1\.3
++++ abc        ${RFCDATE}
+@@ -2,4 +2,4 @@
+ two
+ three
+ four
+-seven
+\\\\ No newline at end of file
++seven"
+         dotest diffnl-302 "${testcvs} ci -mtest abc" \
+"${CVSROOT_DIRNAME}/first-dir/abc,v  <--  abc
+new revision: 1\.4; previous revision: 1\.3"
+
+         # Removal of newline
+         ${AWK} 'BEGIN {printf("one\ntwo\nthree\nfour\nseven")}' </dev/null 
>abc
+         dotest_fail diffnl-400 "${testcvs} diff abc" \
+"Index: abc
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/abc,v
+retrieving revision 1\.4
+diff -r1\.4 abc
+5c5
+< seven
+---
+> seven
+\\\\ No newline at end of file"
+         dotest_fail diffnl-401 "${testcvs} diff -u abc" \
+"Index: abc
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/abc,v
+retrieving revision 1\.4
+diff -u -r1\.4 abc
+--- abc        ${RFCDATE}      1\.4
++++ abc        ${RFCDATE}
+@@ -2,4 +2,4 @@
+ two
+ three
+ four
+-seven
++seven
+\\\\ No newline at end of file"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       death)
+               # next dive.  test death support.
+
+               # NOTE: this section has reached the size and
+               # complexity where it is getting to be a good idea to
+               # add new death support tests to a new section rather
+               # than continuing to piggyback them onto the tests here.
+
+               modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+               dotest death-init-1 "$testcvs -Q co first-dir"
+
+               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 ${CVSROOT_DIRNAME}/first-dir/subdir added to the repository"
+               cd subdir
+               echo file in subdir >sfile
+               dotest 65a1 "${testcvs} add sfile" \
+"${SPROG}"' add: scheduling file `sfile'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+               dotest 65a2 "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/subdir/sfile,v  <--  sfile
+initial revision: 1\.1"
+               rm sfile
+               dotest 65a3 "${testcvs} rm sfile" \
+"${SPROG}"' remove: scheduling `sfile'\'' for removal
+'"${SPROG}"' remove: use .'"${SPROG}"' commit. to remove this file permanently'
+               dotest 65a4 "${testcvs} -q ci -m remove-it" \
+"$CVSROOT_DIRNAME/first-dir/subdir/sfile,v  <--  sfile
+new revision: delete; previous revision: 1\.1"
+               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
+                   pass 66
+               else
+                   fail 66
+               fi
+
+               # commit
+               if ${CVS} ci -m test  >> ${LOGFILE} 2>&1; then
+                   pass 67
+               else
+                   fail 67
+               fi
+
+               # remove
+               rm file1
+               if ${CVS} rm file1  2>> ${LOGFILE}; then
+                   pass 68
+               else
+                   fail 68
+               fi
+
+               # commit
+               if ${CVS} ci -m test  >>${LOGFILE} ; then
+                   pass 69
+               else
+                   fail 69
+               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
+                   pass 70
+               else
+                   fail 70
+               fi
+
+               # commit
+               if ${CVS} ci -m test  >> ${LOGFILE} 2>&1; then
+                   pass 71
+               else
+                   fail 71
+               fi
+
+               # log
+               if ${CVS} log file1  >> ${LOGFILE}; then
+                   pass 72
+               else
+                   fail 72
+               fi
+
+               # file4 will be dead at the time of branching and stay dead.
+               echo file4 > file4
+               dotest death-file4-add "${testcvs} add file4" \
+"${SPROG}"' add: scheduling file `file4'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+               dotest death-file4-ciadd "${testcvs} -q ci -m add file4" \
+"$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+initial revision: 1\.1"
+               rm file4
+               dotest death-file4-rm "${testcvs} remove file4" \
+"${SPROG}"' remove: scheduling `file4'\'' for removal
+'"${SPROG}"' remove: use .'"${SPROG}"' commit. to remove this file permanently'
+               dotest death-file4-cirm "${testcvs} -q ci -m remove file4" \
+"$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: delete; previous revision: 1\.1"
+
+               # Tag the branchpoint.
+               dotest death-72a "${testcvs} -q tag bp_branch1" 'T file1
+T file2'
+
+               # branch1
+               if ${CVS} tag -b branch1  ; then
+                   pass 73
+               else
+                   fail 73
+               fi
+
+               # and move to the branch.
+               if ${CVS} update -r branch1  ; then
+                   pass 74
+               else
+                   fail 74
+               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
+                   pass 75
+               else
+                   fail 75
+               fi
+
+               # commit
+               if ${CVS} ci -m test  >> ${LOGFILE} 2>&1; then
+                   pass 76
+               else
+                   fail 76
+               fi
+
+               dotest death-76a0 \
+"${testcvs} -q rdiff -r bp_branch1 -r branch1 first-dir" \
+"Index: first-dir/file3
+diff -c /dev/null first-dir/file3:1\.1\.2\.1
+\*\*\* /dev/null       ${DATE}
+--- first-dir/file3    ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 0 \*\*\*\*
+--- 1 ----
+${PLUS} line1 from branch1"
+               dotest death-76a1 \
+"${testcvs} -q rdiff -r branch1 -r bp_branch1 first-dir" \
+"Index: first-dir/file3
+diff -c first-dir/file3:1\.1\.2\.1 first-dir/file3:removed
+\*\*\* first-dir/file3:1\.1\.2\.1      ${DATE}
+--- first-dir/file3    ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- line1 from branch1
+--- 0 ----"
+
+               # remove
+               rm file3
+               if ${CVS} rm file3  2>> ${LOGFILE}; then
+                   pass 77
+               else
+                   fail 77
+               fi
+
+               # commit
+               if ${CVS} ci -m test  >>${LOGFILE} ; then
+                   pass 78
+               else
+                   fail 78
+               fi
+
+               # add again
+               echo line1 from branch1 >> file3
+               if ${CVS} add file3  2>> ${LOGFILE}; then
+                   pass 79
+               else
+                   fail 79
+               fi
+
+               # commit
+               if ${CVS} ci -m test  >> ${LOGFILE} 2>&1; then
+                   pass 80
+               else
+                   fail 80
+               fi
+
+               # change the first file
+               echo line2 from branch1 >> file1
+
+               # commit
+               if ${CVS} ci -m test  >> ${LOGFILE} 2>&1; then
+                   pass 81
+               else
+                   fail 81
+               fi
+
+               # remove the second
+               rm file2
+               if ${CVS} rm file2  2>> ${LOGFILE}; then
+                   pass 82
+               else
+                   fail 82
+               fi
+
+               # commit
+               if ${CVS} ci -m test  >>${LOGFILE}; then
+                   pass 83
+               else
+                   fail 83
+               fi
+
+               # back to the trunk.
+               if ${CVS} update -A  2>> ${LOGFILE}; then
+                   pass 84
+               else
+                   fail 84
+               fi
+
+               dotest_fail death-file4-4 "test -f file4" ''
+
+               if test -f file3 ; then
+                   fail 85
+               else
+                   pass 85
+               fi
+
+               # join
+               dotest death-86 "$testcvs -q update -j branch1" \
+"RCS file: $CVSROOT_DIRNAME/first-dir/file1,v
+retrieving revision 1\.3
+retrieving revision 1\.3\.2\.1
+Merging differences between 1\.3 and 1\.3\.2\.1 into file1
+${SPROG} update: scheduling \`file2' for removal
+U file3"
+
+               dotest_fail death-file4-5 "test -f file4" ''
+
+               if test -f file3 ; then
+                   pass 87
+               else
+                   fail 87
+               fi
+
+               # Make sure that we joined the correct change to file1
+               dotest death-87a "echo line2 from branch1 |$diff_u - file1"
+
+               # update
+               if ${CVS} update  ; then
+                   pass 88
+               else
+                   fail 88
+               fi
+
+               # commit
+               dotest 89 "${testcvs} -q ci -m test" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.4; previous revision: 1\.3
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+new revision: 1\.2; previous revision: 1\.1"
+               cd ..
+               mkdir 2
+               cd 2
+               dotest 89a "${testcvs} -q co first-dir" 'U first-dir/file1
+U first-dir/file3'
+               cd ..
+               rm -r 2
+               cd first-dir
+
+               # remove first file.
+               rm file1
+               if ${CVS} rm file1  2>> ${LOGFILE}; then
+                   pass 90
+               else
+                   fail 90
+               fi
+
+               # commit
+               if ${CVS} ci -m test  >>${LOGFILE}; then
+                   pass 91
+               else
+                   fail 91
+               fi
+
+               if test -f file1 ; then
+                   fail 92
+               else
+                   pass 92
+               fi
+
+               # typo; try to get to the branch and fail
+               dotest_fail 92.1a "$testcvs update -r brnach1" \
+                 "$SPROG \[update aborted\]: no such tag \`brnach1'"
+               # Make sure we are still on the trunk
+               if test -f file1 ; then
+                   fail 92.1b
+               else
+                   pass 92.1b
+               fi
+               if test -f file3 ; then
+                   pass 92.1c
+               else
+                   fail 92.1c
+               fi
+
+               # back to branch1
+               if ${CVS} update -r branch1  2>> ${LOGFILE}; then
+                   pass 93
+               else
+                   fail 93
+               fi
+
+               dotest_fail death-file4-6 "test -f file4" ''
+
+               if test -f file1 ; then
+                   pass 94
+               else
+                   fail 94
+               fi
+
+               # and join
+               dotest 95 "${testcvs} -q update -j HEAD" \
+"${SPROG}"' update: file file1 has been modified, but has been removed in 
revision HEAD
+'"${SPROG}"' update: file file3 exists, but has been added in revision HEAD'
+
+               dotest_fail death-file4-7 "test -f file4" ''
+
+               # file2 should not have been recreated.  It was
+               # deleted on the branch, and has not been modified on
+               # the trunk.  That means that there have been no
+               # changes between the greatest common ancestor (the
+               # trunk version) and HEAD.
+               dotest_fail death-file2-1 "test -f file2" ''
+
+               dokeep
+               cd ..
+               rm -r first-dir
+               modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+               ;;
+
+
+
+       death2)
+         # More tests of death support.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest death2-1 "$testcvs -q co first-dir"
+
+         cd first-dir
+
+         # Add two files on the trunk.
+         echo "first revision" > file1
+         echo "file4 first revision" > file4
+         dotest death2-2 "${testcvs} add file1 file4" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition
+'"${SPROG}"' add: scheduling file `file4'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add these files permanently'
+
+         dotest death2-3 "${testcvs} -q commit -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+initial revision: 1\.1"
+
+         # Make a branch and a non-branch tag.
+         dotest death2-4 "${testcvs} -q tag -b branch" \
+'T file1
+T file4'
+         dotest death2-5 "${testcvs} -q tag tag" \
+'T file1
+T file4'
+
+         # Switch over to the branch.
+         dotest death2-6 "${testcvs} -q update -r branch" ''
+
+         # Delete the file on the branch.
+         rm file1
+         dotest death2-7 "${testcvs} rm file1" \
+"${SPROG} remove: scheduling .file1. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+
+         # Test diff of the removed file before it is committed.
+         dotest_fail death2-diff-1 "${testcvs} -q diff file1" \
+"${SPROG} diff: file1 was removed, no comparison available"
+
+         dotest_fail death2-diff-2 "${testcvs} -q diff -N -c file1" \
+"Index: file1
+===================================================================
+RCS file: file1
+diff -N file1
+\*\*\* file1   ${RFCDATE}      [0-9.]*
+--- /dev/null  ${RFCDATE_EPOCH}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- first revision
+--- 0 ----"
+
+         dotest death2-8 "${testcvs} -q ci -m removed" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: delete; previous revision: 1\.1"
+
+         # Test diff of a dead file.
+         dotest_fail death2-diff-3 \
+"${testcvs} -q diff -r1.1 -rbranch -c file1" \
+"${SPROG} diff: Tag branch refers to a dead (removed) revision in file 
.file1.\.
+${SPROG} diff: No comparison available\.  Pass .-N. to .${SPROG} 
diff.${QUESTION}"
+         # and in reverse
+         dotest_fail death2-diff-3a \
+"${testcvs} -q diff -rbranch -r1.1 -c file1" \
+"${SPROG} diff: Tag branch refers to a dead (removed) revision in file 
.file1.\.
+${SPROG} diff: No comparison available\.  Pass .-N. to .${SPROG} 
diff.${QUESTION}"
+
+         dotest_fail death2-diff-4 \
+"${testcvs} -q diff -r1.1 -rbranch -N -c file1" \
+"Index: file1
+===================================================================
+RCS file: file1
+diff -N file1
+\*\*\* file1   ${RFCDATE}      [0-9.]*
+--- /dev/null  ${RFCDATE_EPOCH}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- first revision
+--- 0 ----"
+         # and in reverse
+         dotest_fail death2-diff-4a \
+"${testcvs} -q diff -rbranch -r1.1 -N -c file1" \
+"Index: file1
+===================================================================
+RCS file: file1
+diff -N file1
+\*\*\* /dev/null       ${RFCDATE_EPOCH}
+--- file1      ${RFCDATE}      [0-9.]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 0 \*\*\*\*
+--- 1 ----
++ first revision"
+
+
+         dotest_fail death2-diff-5 "${testcvs} -q diff -rtag -c ." \
+"${SPROG} diff: file1 no longer exists, no comparison available"
+
+         dotest_fail death2-diff-6 "${testcvs} -q diff -rtag -N -c ." \
+"Index: file1
+===================================================================
+RCS file: file1
+diff -N file1
+\*\*\* file1   [-a-zA-Z0-9: ]* [0-9.]*
+--- /dev/null  ${RFCDATE_EPOCH}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- first revision
+--- 0 ----"
+
+         # Test rdiff of a dead file.
+         dotest death2-rdiff-1 \
+"${testcvs} -q rtag -rbranch rdiff-tag first-dir" ''
+
+         dotest death2-rdiff-2 "${testcvs} -q rdiff -rtag -rbranch first-dir" \
+"Index: first-dir/file1
+diff -c first-dir/file1:1\.1 first-dir/file1:removed
+\*\*\* first-dir/file1:1\.1    [a-zA-Z0-9: ]*
+--- first-dir/file1    [a-zA-Z0-9: ]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- first revision
+--- 0 ----"
+
+         # Readd the file to the branch.
+         echo "second revision" > file1
+         dotest death2-9 "${testcvs} add file1" \
+"${SPROG} add: Re-adding file \`file1' on branch \`branch' after dead revision 
1\.1\.2\.1\.
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+
+         # Test diff of the added file before it is committed.
+         dotest_fail death2-diff-7 "${testcvs} -q diff file1" \
+"${SPROG} diff: file1 is a new entry, no comparison available"
+
+         dotest_fail death2-diff-8 "${testcvs} -q diff -N -c file1" \
+"Index: file1
+===================================================================
+RCS file: file1
+diff -N file1
+\*\*\* /dev/null       ${RFCDATE_EPOCH}
+--- file1      ${RFCDATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 0 \*\*\*\*
+--- 1 ----
+${PLUS} second revision"
+
+         dotest death2-10 "${testcvs} -q commit -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+
+         # Delete file4 from the branch
+         dotest death2-10a "${testcvs} rm -f file4" \
+"${SPROG} remove: scheduling .file4. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest death2-10b "${testcvs} -q ci -m removed" \
+"$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: delete; previous revision: 1\.1"
+
+         # Back to the trunk.
+         dotest death2-11 "${testcvs} -q update -A" \
+"U file1
+U file4"
+
+         # Add another file on the trunk.
+         echo "first revision" > file2
+         dotest death2-12 "${testcvs} add file2" \
+"${SPROG}"' add: scheduling file `file2'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest death2-13 "${testcvs} -q commit -m add" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+
+         # Modify file4 on the trunk.
+         echo "new file4 revision" > file4
+         dotest death2-13a "${testcvs} -q commit -m mod" \
+"$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: 1\.2; previous revision: 1\.1"
+
+         # Back to the branch.
+         # The ``no longer in the repository'' message doesn't really
+         # look right to me, but that's what CVS currently prints for
+         # this case.
+         dotest death2-14 "${testcvs} -q update -r branch" \
+"U file1
+${SPROG} update: \`file2' is no longer in the repository
+${SPROG} update: \`file4' is no longer in the repository"
+
+         # Add a file on the branch with the same name.
+         echo "branch revision" > file2
+         dotest death2-15 "${testcvs} add file2" \
+"${SPROG}"' add: scheduling file `file2'\'' for addition on branch `branch'\''
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest death2-16 "${testcvs} -q commit -m add" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+
+         # Add a new file on the branch.
+         echo "first revision" > file3
+         dotest death2-17 "${testcvs} add file3" \
+"${SPROG}"' add: scheduling file `file3'\'' for addition on branch `branch'\''
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest death2-18 "${testcvs} -q commit -m add" \
+"$CVSROOT_DIRNAME/first-dir/Attic/file3,v  <--  file3
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         # Test diff of a nonexistent tag
+         dotest_fail death2-diff-9 "$testcvs -q diff -rtag -c file3" \
+"$SPROG diff: tag tag is not in file file3"
+
+         dotest_fail death2-diff-10 "${testcvs} -q diff -rtag -N -c file3" \
+"Index: file3
+===================================================================
+RCS file: file3
+diff -N file3
+\*\*\* /dev/null       ${RFCDATE_EPOCH}
+--- file3      ${RFCDATE}      [0-9.]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 0 \*\*\*\*
+--- 1 ----
+${PLUS} first revision"
+
+         dotest_fail death2-diff-11 "${testcvs} -q diff -rtag -c ." \
+"Index: file1
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.2
+diff -c -r1\.1 -r1\.1\.2\.2
+\*\*\* file1   ${RFCDATE}      [0-9.]*
+--- file1      ${RFCDATE}      [0-9.]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+! first revision
+--- 1 ----
+! second revision
+${SPROG} diff: tag tag is not in file file2
+${SPROG} diff: tag tag is not in file file3
+${SPROG} diff: file4 no longer exists, no comparison available"
+
+         dotest_fail death2-diff-12 "${testcvs} -q diff -rtag -c -N ." \
+"Index: file1
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.2
+diff -c -r1\.1 -r1\.1\.2\.2
+\*\*\* file1   ${RFCDATE}      [0-9.]*
+--- file1      ${RFCDATE}      [0-9.]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+! first revision
+--- 1 ----
+! second revision
+Index: file2
+===================================================================
+RCS file: file2
+diff -N file2
+\*\*\* /dev/null       ${RFCDATE_EPOCH}
+--- file2      ${RFCDATE}      [0-9.]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 0 \*\*\*\*
+--- 1 ----
+${PLUS} branch revision
+Index: file3
+===================================================================
+RCS file: file3
+diff -N file3
+\*\*\* /dev/null       ${RFCDATE_EPOCH}
+--- file3      ${RFCDATE}      [0-9.]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 0 \*\*\*\*
+--- 1 ----
+${PLUS} first revision
+Index: file4
+===================================================================
+RCS file: file4
+diff -N file4
+\*\*\* file4   ${RFCDATE}      [0-9.]*
+--- /dev/null  ${RFCDATE_EPOCH}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- file4 first revision
+--- 0 ----"
+
+         # Switch to the nonbranch tag.
+         dotest death2-19 "${testcvs} -q update -r tag" \
+"U file1
+${SPROG} update: \`file2' is no longer in the repository
+${SPROG} update: \`file3' is no longer in the repository
+U file4"
+
+         dotest_fail death2-20 "test -f file2"
+
+         # Make sure diff only reports appropriate files.
+         dotest_fail death2-diff-13 "${testcvs} -q diff -r rdiff-tag" \
+"${SPROG} diff: Tag rdiff-tag refers to a dead (removed) revision in file 
.file1.\.
+${SPROG} diff: No comparison available\.  Pass .-N. to .${SPROG} 
diff.${QUESTION}"
+
+         dotest_fail death2-diff-14 "${testcvs} -q diff -r rdiff-tag -c -N" \
+"Index: file1
+===================================================================
+RCS file: file1
+diff -N file1
+\*\*\* /dev/null       ${RFCDATE_EPOCH}
+--- file1      ${RFCDATE}      [0-9.]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 0 \*\*\*\*
+--- 1 ----
+${PLUS} first revision"
+
+         # now back to the trunk
+         dotest death2-21 "${testcvs} -q update -A" \
+"U file2
+U file4"
+
+         # test merging with a dead file
+         dotest death2-22 "${testcvs} -q co first-dir" \
+"U first-dir/file1
+U first-dir/file2
+U first-dir/file4"
+
+         cd first-dir
+         dotest death2-23 "${testcvs} rm -f file4" \
+"${SPROG} remove: scheduling .file4. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest death2-24 "${testcvs} -q ci -m removed file4" \
+"$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: delete; previous revision: 1\.2"
+         cd ..
+         echo "new stuff" >file4
+         dotest_fail death2-25 "${testcvs} up file4" \
+"${SPROG} update: conflict: \`file4' is modified but no longer in the 
repository
+C file4"
+
+         dokeep
+         cd ..
+         rm -r first-dir
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       rm-update-message)
+         # FIXME
+         # local CVS prints a warning message when update notices a missing
+         # file and client/server CVS doesn't.  These should be identical.
+         mkdir rm-update-message; cd rm-update-message
+         modify_repo mkdir $CVSROOT_DIRNAME/rm-update-message
+         dotest rm-update-message-setup-1 "$testcvs -q co rm-update-message" ''
+         cd rm-update-message
+         file=x
+         echo >$file
+         dotest rm-update-message-setup-2 "$testcvs -q add $file" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest rm-update-message-setup-3 "$testcvs -q ci -mcreate $file" \
+"$CVSROOT_DIRNAME/rm-update-message/$file,v  <--  $file
+initial revision: 1\.1"
+
+         rm $file
+         dotest rm-update-message-1 "$testcvs up $file" \
+"${SPROG} update: warning: \`$file' was lost
+U $file"
+
+         dokeep
+         cd ../..
+         rm -r rm-update-message
+         modify_repo rm -rf $CVSROOT_DIRNAME/rm-update-message
+         ;;
+
+
+
+       rmadd)
+         # More tests of adding and removing files.
+         # In particular ci -r.
+         # Other ci -r tests:
+         #   * editor-9: checking in a modified file,
+         #     where "ci -r" means a branch.
+         #   * basica-8a1: checking in a modified file with numeric revision.
+         #   * basica-8a2: likewise.
+         #   * keywordlog-4: adding a new file with numeric revision.
+         mkdir 1; cd 1
+         dotest rmadd-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest rmadd-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         echo first file1 >file1
+         dotest rmadd-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+
+         dotest_fail rmadd-4 "${testcvs} -q ci -r 1.2.2.4 -m add" \
+"${SPROG} commit: cannot add file .file1' with revision .1\.2\.2\.4'; must be 
on trunk
+${SPROG} \[commit aborted\]: correct above errors first!"
+         dotest_fail rmadd-5 "${testcvs} -q ci -r 1.2.2 -m add" \
+"${SPROG} commit: cannot add file .file1' with revision .1\.2\.2'; must be on 
trunk
+${SPROG} \[commit aborted\]: correct above errors first!"
+         dotest_fail rmadd-6 "$testcvs -q ci -r mybranch -m add" \
+"$SPROG \[commit aborted\]: no such tag \`mybranch'"
+
+         # The thing with the trailing periods strikes me as a very
+         # bizarre behavior, but it would seem to be intentional
+         # (see commit.c).  It probably could go away....
+         dotest rmadd-7 "${testcvs} -q ci -r 7.... -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 7\.1"
+         if $remote; then
+           # I guess remote doesn't set a sticky tag in this case.
+           # Kind of odd, in the sense that rmadd-24a does set one
+           # both local and remote.
+           dotest_fail rmadd-7a "test -f CVS/Tag"
+           echo T7 >CVS/Tag
+         else
+           dotest rmadd-7a "cat CVS/Tag" "T7"
+         fi
+
+         dotest rmadd-8 "${testcvs} -q tag -b mybranch" "T file1"
+         dotest rmadd-9 "${testcvs} -q tag mynonbranch" "T file1"
+
+         touch file2
+         # The previous "cvs ci -r" set a sticky tag of '7'.  Seems a
+         # bit odd, and I guess commit.c (findmaxrev) makes '7' sticky
+         # tags unnecessary (?).  I kind of suspect that it should be
+         # saying "sticky tag is not a branch" like keywordlog-4b.
+         # Or something.
+         dotest rmadd-10 "${testcvs} add file2" \
+"${SPROG} add: scheduling file .file2. for addition on branch .7'
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         # As in the previous example, CVS is confused....
+         dotest rmadd-11 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 7\.1"
+
+         dotest rmadd-12 "${testcvs} -q update -A" ""
+         touch file3
+         dotest rmadd-13 "${testcvs} add file3" \
+"${SPROG} add: scheduling file .file3. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         # Huh?  file2 is not up to date?  Seems buggy to me....
+         dotest_fail rmadd-14 "${testcvs} -q ci -r mybranch -m add" \
+"${SPROG} commit: Up-to-date check failed for .file2'
+${SPROG} \[commit aborted\]: correct above errors first!"
+         # Whatever, let's not let file2 distract us....
+         dotest rmadd-15 "${testcvs} -q ci -r mybranch -m add file3" \
+"$CVSROOT_DIRNAME/first-dir/Attic/file3,v  <--  file3
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         touch file4
+         dotest rmadd-16 "${testcvs} add file4" \
+"${SPROG} add: scheduling file .file4. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         # Prior to CVS 1.12.10, this would fail with a, "no such tag" message
+         # since val-tags used to be updated the first time the tag was used
+         # rather than when it was created.
+
+         # Try to make CVS write val-tags.
+         if $proxy; then :; else
+           # First remove the tag.
+           grep -v mynonbranch $CVSROOT_DIRNAME/CVSROOT/val-tags \
+                >$CVSROOT_DIRNAME/CVSROOT/val-tags-tmp
+           mv $CVSROOT_DIRNAME/CVSROOT/val-tags-tmp \
+              $CVSROOT_DIRNAME/CVSROOT/val-tags
+
+           dotest rmadd-18 "$testcvs -q update -p -r mynonbranch file1" \
+"first file1"
+           # Oops, -p suppresses writing val-tags (probably a questionable
+           # behavior).
+           dotest_fail rmadd-19 \
+"$testcvs -q ci -r mynonbranch -m add file4" \
+"$SPROG \[commit aborted\]: no such tag \`mynonbranch'"
+           # Now make CVS write val-tags for real.
+           dotest rmadd-20 "$testcvs -q update -r mynonbranch file1"
+         fi # !$proxy
+
+         # Oops - CVS isn't distinguishing between a branch tag and
+         # a non-branch tag.
+         dotest rmadd-21 \
+"${testcvs} -q ci -r mynonbranch -m add file4" \
+"$CVSROOT_DIRNAME/first-dir/Attic/file4,v  <--  file4
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         # OK, we add this one in a vanilla way, but then check in
+         # a modification with ci -r and sniff around for sticky tags.
+         echo file5 >file5
+         dotest rmadd-22 "${testcvs} add file5" \
+"${SPROG} add: scheduling file .file5. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         if $remote; then
+           # Interesting bug (or missing feature) here.  findmaxrev
+           # gets the major revision from the Entries.  Well, remote
+           # doesn't send the entries for files which are not involved.
+           dotest rmadd-23r "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file5,v  <--  file5
+initial revision: 1\.1"
+           dotest rmadd-23-workaroundr \
+"${testcvs} -q ci -r 7 -m bump-it file5" \
+"$CVSROOT_DIRNAME/first-dir/file5,v  <--  file5
+new revision: 7\.1; previous revision: 1\.1"
+         else
+           dotest rmadd-23 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file5,v  <--  file5
+initial revision: 7\.1"
+         fi
+         echo change it >file5
+         dotest_fail rmadd-24 "$testcvs -q ci -r 4.8 -m change file5" \
+"$CVSROOT_DIRNAME/first-dir/file5,v  <--  file5
+$SPROG commit: $CVSROOT_DIRNAME/first-dir/file5,v: revision 4\.8 too low; must 
be higher than 7\.1
+$SPROG commit: could not check in file5"
+         dotest rmadd-24a "${testcvs} -q ci -r 8.4 -m change file5" \
+"$CVSROOT_DIRNAME/first-dir/file5,v  <--  file5
+new revision: 8\.4; previous revision: 7\.1"
+         # I'm not really sure that a sticky tag make sense here.
+         # It seems to be longstanding behavior for what that is worth.
+         dotest rmadd-25 "${testcvs} status file5" \
+"===================================================================
+File: file5                    Status: Up-to-date
+
+   Working revision:   8\.4.*
+   Repository revision:        8\.4    ${CVSROOT_DIRNAME}/first-dir/file5,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         8\.4
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         # now try forced revision with recursion
+         mkdir sub
+         dotest rmadd-26 "${testcvs} -q add sub" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/sub added to the repository"
+         echo hello >sub/subfile
+         dotest rmadd-27 "${testcvs} -q add sub/subfile" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+
+         dotest rmadd-28 "${testcvs} -q ci -m. sub" \
+"$CVSROOT_DIRNAME/first-dir/sub/subfile,v  <--  sub/subfile
+initial revision: 1\.1"
+
+         # lose the branch
+         dotest rmadd-29 "${testcvs} -q up -A" \
+"${SPROG} update: \`file3' is no longer in the repository
+${SPROG} update: \`file4' is no longer in the repository"
+
+         # -f disables recursion
+         dotest rmadd-30 "${testcvs} -q ci -f -r9 -m." \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 9\.1; previous revision: 7\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 9\.1; previous revision: 7\.1
+$CVSROOT_DIRNAME/first-dir/file5,v  <--  file5
+new revision: 9\.1; previous revision: 8\.4"
+
+         # add -R to force recursion
+         dotest rmadd-31 "${testcvs} -q ci -f -r9 -R -m." \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 9\.2; previous revision: 9\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 9\.2; previous revision: 9\.1
+$CVSROOT_DIRNAME/first-dir/file5,v  <--  file5
+new revision: 9\.2; previous revision: 9\.1
+$CVSROOT_DIRNAME/first-dir/sub/subfile,v  <--  sub/subfile
+new revision: 9\.1; previous revision: 1\.1"
+
+         if $remote; then
+           # as noted above, remote doesn't set a sticky tag
+           :
+         else
+           dotest rmadd-32 "cat CVS/Tag" "T9"
+           dotest rmadd-33 "cat sub/CVS/Tag" "T9"
+         fi
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       rmadd2)
+         # Tests of undoing commits, including in the presence of
+         # adding and removing files.  See join for a list of -j tests.
+         mkdir 1; cd 1
+         dotest rmadd2-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest rmadd2-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         echo 'initial contents' >file1
+         dotest rmadd2-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest rmadd2-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         dotest rmadd2-4a "${testcvs} -Q tag tagone" ""
+         dotest rmadd2-5 "${testcvs} rm -f file1" \
+"${SPROG} remove: scheduling .file1. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest rmadd2-6 "${testcvs} -q ci -m remove" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: delete; previous revision: 1\.1"
+         dotest rmadd2-7 "$testcvs -q update -j 1.2 -j 1.1 file1" "U file1"
+         dotest rmadd2-8 "${testcvs} -q ci -m readd" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2"
+         echo 'new contents' >file1
+         dotest rmadd2-9 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.4; previous revision: 1\.3"
+         dotest rmadd2-10 "${testcvs} -q update -j 1.4 -j 1.3 file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.4
+retrieving revision 1\.3
+Merging differences between 1\.4 and 1\.3 into file1"
+         dotest rmadd2-11 "${testcvs} -q ci -m undo" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.5; previous revision: 1\.4"
+         dotest rmadd2-12 "cat file1" "initial contents"
+         dotest rmadd2-13 "${testcvs} -q update -p -r 1.3" "initial contents"
+
+         # Hmm, might be a bit odd that this works even if 1.3 is not
+         # the head.
+         dotest rmadd2-14 "${testcvs} -q update -j 1.3 -j 1.2 file1" \
+"${SPROG} update: scheduling \`file1' for removal"
+
+         # Check that -p can get arbitrary revisions of a removed file
+         dotest rmadd2-14a "${testcvs} -q update -p" "initial contents"
+         dotest rmadd2-14b "${testcvs} -q update -p -r 1.5" "initial contents"
+         dotest rmadd2-14c "${testcvs} -q update -p -r 1.3" "initial contents"
+
+         dotest rmadd2-15 "${testcvs} -q ci -m re-remove" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: delete; previous revision: 1\.5"
+         dotest rmadd2-16 "${testcvs} log -h file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+Working file: file1
+head: 1\.6
+branch:
+locks: strict
+access list:
+symbolic names:
+       tagone: 1\.1
+keyword substitution: kv
+total revisions: 6
+============================================================================="
+         dotest rmadd2-17 "${testcvs} status -v file1" \
+"===================================================================
+File: no file file1            Status: Up-to-date
+
+   Working revision:   No entry for file1
+   Repository revision:        1\.6    
${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+   Commit Identifier:  ${commitid}
+
+   Existing Tags:
+       tagone                          (revision: 1.1)"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       rmadd3)
+          # This test demonstrates that CVS notices that file1 exists rather
+         # that deleting or writing over it after:
+         #
+         #   cvs remove -f file1; touch file1; cvs add file1.
+         #
+          # According to the manual, this should work for:
+         #
+         #   rm file1; cvs remove file1; cvs add file1
+         #
+         # but in past version of CVS, new content in file1 would be
+         # erroneously deleted when file1 reappeared between the remove and
+         # the add.
+         #
+         # Later versions of CVS would refuse to perform the add, but still
+         # allow a subsequent local commit to erase the file from the
+         # workspace, possibly losing data.
+         mkdir 1; cd 1
+         dotest rmadd3-init1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest rmadd3-init2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+
+         echo initial content for file1 >file1
+         dotest rmadd3-init3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file \`file1' for addition
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+         dotest rmadd3-init4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+         # Here begins the guts of this test, as detailed above.
+         dotest rmadd3-1 "${testcvs} rm -f file1" \
+"${SPROG} remove: scheduling \`file1' for removal
+${SPROG} remove: use \`${SPROG} commit' to remove this file permanently"
+
+          # Now recreate the file:
+         echo desired future contents for file1 >file1
+
+         # And attempt to resurrect it at the same time:
+         dotest_fail rmadd3-2 "${testcvs} add file1" \
+"${SPROG} add: \`file1' should be removed and is still there (or is back 
again)"
+
+         # Now prove that commit knows that it shouldn't erase files.
+         dotest_fail rmadd3-3 "${testcvs} -q ci -m." \
+"$CPROG commit: \`file1' should be removed and is still there (or is back 
again)
+$CPROG \[commit aborted\]: correct above errors first!"
+
+         # Then these should pass too:
+         dotest rmadd3-4 "test -f file1"
+         dotest rmadd3-5 "cat file1" "desired future contents for file1"
+
+         if $keep; then
+           echo Keeping ${TESTDIR} and exiting due to --keep
+           exit 0
+         fi
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       resurrection)
+         # This test tests a few file resurrection scenarios.
+         mkdir 1; cd 1
+         dotest resurrection-init1 "$testcvs -q co -l ." ''
+         mkdir first-dir
+         dotest resurrection-init2 "$testcvs add first-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir added to the repository"
+         cd first-dir
+
+         echo initial content for file1 >file1
+         dotest resurrection-init3 "$testcvs add file1" \
+"$SPROG add: scheduling file \`file1' for addition
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+         dotest resurrection-init4 "$testcvs -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+         dotest resurrection-init5 "$testcvs -Q rm -f file1"
+
+         # The first test is that `cvs add' will resurrect a file before its
+         # removal has been committed.
+         dotest_sort resurrection-1 "$testcvs add file1" \
+"U file1
+$SPROG add: \`file1', version 1\.1, resurrected"
+         dotest resurrection-2 "$testcvs -Q diff file1" ""
+
+         dotest resurrection-init6 "$testcvs -Q tag -b resurrection"
+         dotest resurrection-init7 "$testcvs -Q rm -f file1"
+         dotest resurrection-init8 "$testcvs -Q ci -mrm"
+
+         # The next test is that CVS will resurrect a committed removal.
+         dotest_sort resurrection-3 "$testcvs add file1" \
+"U file1
+$SPROG add: Re-adding file \`file1' after dead revision 1\.2\.
+$SPROG add: Resurrecting file \`file1' from revision 1\.1\.
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+         dotest resurrection-4 "$testcvs -q diff -r1.1 file1" ""
+         dotest resurrection-5 "$testcvs -q ci -mreadd" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2"
+
+         dotest resurrection-init9 "$testcvs -Q up -rresurrection"
+         dotest resurrection-init10 "$testcvs -Q rm -f file1"
+         dotest resurrection-init11 "$testcvs -Q ci -mrm-on-resurrection"
+
+         # The next test is that CVS will resurrect a committed removal to a
+         # branch.
+         dotest_sort resurrection-6 "$testcvs -r add file1" \
+"U file1
+$SPROG add: Re-adding file \`file1' on branch \`resurrection' after dead 
revision 1\.1\.2\.1\.
+$SPROG add: Resurrecting file \`file1' from revision 1\.1\.
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+         # If the file is modified, it had better be read-write
+         # regardless of what the user has requested with the CVSREAD
+         # environment variable or the global -r switch
+          dotest resurrection-6b 'test -w file1' ''
+         dotest resurrection-7 "$testcvs -Q diff -r1.1 file1" ""
+         dotest resurrection-8 "$testcvs -q ci -mreadd" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+
+         # The next few tests verify that an attempted resurrection of a file
+         # with no previous revision on the trunk fails.
+         touch file2
+         dotest resurrection-9 "$testcvs -Q add file2"
+         dotest resurrection-10 "$testcvs -Q ci -mnew-file2"
+         dotest resurrection-11 "$testcvs -Q up -A"
+
+         # This command once caused an assertion failure.
+         dotest resurrection-12 "$testcvs add file2" \
+"$SPROG add: File \`file2' has no previous revision to resurrect\."
+
+         # Check what 'cvs -r add' does with resurrected files.
+         dotest resurrection-13 "$testcvs -Q rm -f file1"
+         dotest_sort resurrection-14 "$testcvs -r add file1" \
+"U file1
+$SPROG add: \`file1', version 1\.3, resurrected"
+         dotest_fail resurrection-15 'test -w file1' ''
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       dirs)
+         # Tests related to removing and adding directories.
+         # See also:
+         #   conflicts (especially dir1 in conflicts-130): What happens if
+         #     directory exists in repository and a non-CVS-controlled
+         #     directory in the working directory?
+         #   conflicts3-15.  More cases, especially where CVS directory
+         #     exists but without CVS/Repository and friends.
+         #   conflicts3-22.  Similar to conflicts-130 but there is a file
+         #     in the directory.
+         #   dirs2.  Sort of similar to conflicts3-22 but somewhat different.
+         mkdir imp-dir; cd imp-dir
+         echo file1 >file1
+         mkdir sdir
+         echo sfile >sdir/sfile
+         dotest_sort dirs-1 \
+"${testcvs} import -m import-it dir1 vend rel" "
+
+N dir1/file1
+N dir1/sdir/sfile
+No conflicts created by this import
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/dir1/sdir"
+         cd ..
+
+         mkdir 1; cd 1
+         dotest dirs-2 "$testcvs -Q co dir1" ""
+
+         # Various CVS administrators are in the habit of removing
+         # the repository directory for things they don't want any
+         # more.  I've even been known to do it myself (on rare
+         # occasions).  Not the usual recommended practice, but we want
+         # to try to come up with some kind of reasonable/documented/sensible
+         # behavior.
+         modify_repo rm -rf $CVSROOT_DIRNAME/dir1/sdir
+
+         dotest dirs-3 "${testcvs} update" \
+"${SPROG} update: Updating dir1
+${SPROG} update: Updating dir1/sdir
+${SPROG} update: cannot open directory ${CVSROOT_DIRNAME}/dir1/sdir: No such 
file or directory
+${SPROG} update: skipping directory dir1/sdir"
+         dotest dirs-3a "${testcvs} update -d" \
+"${SPROG} update: Updating dir1
+${SPROG} update: Updating dir1/sdir
+${SPROG} update: cannot open directory ${CVSROOT_DIRNAME}/dir1/sdir: No such 
file or directory
+${SPROG} update: skipping directory dir1/sdir"
+
+         # If we say "yes", then CVS gives errors about not being able to
+         # create lock files.
+         # The fact that it says "skipping directory " rather than
+         # "skipping directory dir1/sdir" is some kind of bug.
+         dotest dirs-4 "echo no | ${testcvs} release -d dir1/sdir" \
+"${SPROG} update: cannot open directory ${CVSROOT_DIRNAME}/dir1/sdir: No such 
file or directory
+${SPROG} update: skipping directory 
+You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .dir1/sdir': .. 
.release' aborted by user choice."
+
+         # OK, if "cvs release" won't help, we'll try it the other way...
+         rm -r dir1/sdir
+
+         dotest dirs-5 "cat dir1/CVS/Entries" \
+"/file1/1.1.1.1/[a-zA-Z0-9 :]*//
+D/sdir////"
+         dotest dirs-6 "${testcvs} update" "${SPROG} update: Updating dir1"
+         dotest dirs-7 "cat dir1/CVS/Entries" \
+"/file1/1.1.1.1/[a-zA-Z0-9 :]*//
+D/sdir////"
+         dotest dirs-8 "${testcvs} update -d dir1" \
+"${SPROG} update: Updating dir1"
+
+         dokeep
+         cd ..
+         rm -r imp-dir 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/dir1
+         ;;
+
+
+
+       dirs2)
+         # See "dirs" for a list of tests involving adding and
+         # removing directories.
+         mkdir 1; cd 1
+         dotest dirs2-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest dirs2-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         mkdir sdir
+         dotest dirs2-3 "${testcvs} add sdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/sdir added to the repository"
+         touch sdir/file1
+         dotest dirs2-4 "${testcvs} add sdir/file1" \
+"${SPROG} add: scheduling file .sdir/file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest dirs2-5 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/sdir/file1,v  <--  sdir/file1
+initial revision: 1\.1"
+         rm -r sdir/CVS
+         if $remote; then
+           # This is just like conflicts3-23
+           dotest_fail dirs2-6r "${testcvs} update -d" \
+"${QUESTION} sdir
+${SPROG} update: Updating \.
+${SPROG} update: Updating sdir
+${CPROG} update: move away \`sdir/file1'; it is in the way
+C sdir/file1"
+           rm sdir/file1
+           rm -r sdir/CVS
+
+           # This is where things are not just like conflicts3-23
+           dotest dirs2-7r "${testcvs} update -d" \
+"${QUESTION} sdir
+${SPROG} update: Updating \.
+${SPROG} update: Updating sdir
+U sdir/file1"
+         else
+           dotest dirs2-6 "${testcvs} update -d" \
+"${CPROG} update: Updating \.
+${QUESTION} sdir"
+           rm sdir/file1
+           dotest dirs2-7 "${testcvs} update -d" \
+"${CPROG} update: Updating \.
+${QUESTION} sdir"
+         fi
+         cd ../..
+
+         # Now, the same thing (more or less) on a branch.
+         mkdir 2; cd 2
+         dotest dirs2-8 "${testcvs} -q co first-dir" 'U first-dir/sdir/file1'
+         cd first-dir
+         dotest dirs2-9 "${testcvs} -q tag -b br" "T sdir/file1"
+         rm -r sdir/CVS
+
+         if $remote; then
+           # val-tags used to have a cute little quirk; if an update didn't
+           # recurse into the directories where the tag is defined, val-tags
+           # wouldn't get updated.  This is no longer a problem as of 1.12.10.
+           dotest_fail dirs2-10-againr "$testcvs update -d -r br" \
+"$QUESTION sdir
+$SPROG update: Updating \.
+$SPROG update: Updating sdir
+$CPROG update: move away \`sdir/file1'; it is in the way
+C sdir/file1"
+         else
+           dotest dirs2-10 "${testcvs} update -d -r br" \
+"$SPROG update: Updating \.
+$QUESTION sdir"
+# This is what used to happen.  I'm not sure why it changed with 1.12.10, but
+# as near as I can tell from the comments in update_direntproc, the new
+# behavior was the intended behavior.
+#"$CPROG update: in directory \`sdir':
+#$CPROG \[update aborted\]: there is no version here; do \`$CPROG checkout' 
first"
+         fi
+         cd ../..
+
+         # OK, the above tests make the situation somewhat harder
+         # than it might be, in the sense that they actually have a
+         # file which is alive on the branch we are updating.  Let's
+         # try it where it is just a directory where all the files
+         # have been removed.
+         mkdir 3; cd 3
+         dotest dirs2-11 "${testcvs} -q co -r br first-dir" \
+"U first-dir/sdir/file1"
+         cd first-dir
+         # Hmm, this doesn't mention the branch like add does.  That's
+         # an odd non-orthogonality.
+         dotest dirs2-12 "${testcvs} rm -f sdir/file1" \
+"${SPROG} remove: scheduling .sdir/file1. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest dirs2-13 "${testcvs} -q ci -m remove" \
+"$CVSROOT_DIRNAME/first-dir/sdir/file1,v  <--  sdir/file1
+new revision: delete; previous revision: 1\.1"
+         cd ../../2/first-dir
+         if $remote; then
+           dotest dirs2-14 "${testcvs} update -d -r br" \
+"${QUESTION} sdir/file1
+${SPROG} update: Updating \.
+${SPROG} update: Updating sdir"
+         else
+           dotest dirs2-14 "${testcvs} update -d -r br" \
+"${CPROG} update: Updating \.
+${QUESTION} sdir"
+         fi
+
+         dokeep
+         cd ../..
+         rm -r 1 2 3
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       branches)
+         # More branch tests, including branches off of branches
+         modify_repo 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" \
+"$SPROG add: scheduling file \`file1' for addition
+$SPROG add: scheduling file \`file2' for addition
+$SPROG add: scheduling file \`file3' for addition
+$SPROG add: scheduling file \`file4' for addition
+$SPROG add: use .$SPROG commit. to add these files permanently"
+         dotest branches-2a "$testcvs -n -q ci -m dont-commit"
+         dotest_lit branches-3 "$testcvs -q ci -m add-it" <<HERE
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1.1
+${CVSROOT_DIRNAME}/first-dir/file3,v  <--  file3
+initial revision: 1.1
+${CVSROOT_DIRNAME}/first-dir/file4,v  <--  file4
+initial revision: 1.1
+HERE
+         echo 4:trunk-2 >file4
+         dotest branches-3.2 "${testcvs} -q ci -m trunk-before-branch" \
+"$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: 1\.2; previous revision: 1\.1"
+         # The "cvs log file4" in test branches-14.3 will test that we
+         # didn't really add the tag.
+         dotest branches-3.3 "${testcvs} -qn tag dont-tag" \
+"T file1
+T file2
+T file3
+T file4"
+         # Modify this file before branching, to deal with the case where
+         # someone is hacking along, says "oops, I should be doing this on
+         # a branch", and only then creates the branch.
+         echo 1:br1 >file1
+         dotest branches-4 "${testcvs} tag -b br1" "${SPROG}"' tag: Tagging \.
+T file1
+T file2
+T file3
+T file4'
+         dotest branches-5 "${testcvs} update -r br1" \
+"${SPROG} update: Updating \.
+M file1"
+         echo 2:br1 >file2
+         echo 4:br1 >file4
+         dotest branches-6 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: 1\.2\.2\.1; previous revision: 1\.2"
+         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" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1\.2\.1; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: 1\.2\.2\.1\.2\.1; previous revision: 1\.2\.2\.1"
+         dotest branches-10 "cat file1 file2 file3 file4" '1:brbr
+2:br1
+3:ancest
+4:brbr'
+         dotest branches-11 "${testcvs} -q update -r br1" \
+'U file1
+U 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" \
+"$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: 1\.2\.2\.2; previous revision: 1\.2\.2\.1"
+         dotest branches-13 "${testcvs} -q update -A" 'U file1
+U file2
+U 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" \
+"$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: 1\.3; previous revision: 1\.2"
+         dotest branches-14.3 "${testcvs} log file4" \
+"
+RCS file: ${CVSROOT_DIRNAME}/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: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+trunk-change-after-branch
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+branches:  1\.2\.2;
+trunk-before-branch
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add-it
+----------------------------
+revision 1\.2\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+change-on-br1
+----------------------------
+revision 1\.2\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+branches:  1\.2\.2\.1\.2;
+modify
+----------------------------
+revision 1\.2\.2\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+modify
+============================================================================="
+         dotest_fail branches-14.4 \
+           "${testcvs} diff -c -r 1.1 -r 1.3 file4" \
+"Index: file4
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file4,v
+retrieving revision 1\.1
+retrieving revision 1\.3
+diff -c -r1\.1 -r1\.3
+\*\*\* file4   ${RFCDATE}      1\.1
+--- file4      ${RFCDATE}      1\.3
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+! 4:trunk-1
+--- 1 ----
+! 4:trunk-3"
+         dotest_fail branches-14.5 \
+           "${testcvs} diff -c -r 1.1 -r 1.2.2.1 file4" \
+"Index: file4
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file4,v
+retrieving revision 1\.1
+retrieving revision 1\.2\.2\.1
+diff -c -r1\.1 -r1\.2\.2\.1
+\*\*\* file4   ${RFCDATE}      1\.1
+--- file4      ${RFCDATE}      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: ${CVSROOT_DIRNAME}/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'
+
+         dotest branches-o1 "${testcvs} -q admin -o ::brbr" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file3,v
+done
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file4,v
+done"
+
+         dokeep
+         cd ..
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -r first-dir
+         ;;
+
+
+
+       branches2)
+         # More branch tests.
+         # Test that when updating a new subdirectory in a directory
+         # which was checked out on a branch, the new subdirectory is
+         # created on the appropriate branch.  Test this when joining
+         # as well.
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir trunk; cd trunk
+
+         # Create a file.
+         dotest branches2-1 "${testcvs} -q co first-dir"
+         cd first-dir
+         echo "file1 first revision" > file1
+         dotest branches2-2 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest branches2-3 "${testcvs} commit -m add file1" \
+"${CVSROOT_DIRNAME}/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+         # Tag the file.
+         dotest branches2-4 "${testcvs} -q tag tag1" 'T file1'
+
+         # Make two branches.
+         dotest branches2-5 "${testcvs} -q rtag -b -r tag1 b1 first-dir" ''
+         dotest branches2-6 "${testcvs} -q rtag -b -r tag1 b2 first-dir" ''
+
+         # Create some files and a subdirectory on branch b1.
+         cd ../..
+         mkdir b1; cd b1
+         dotest branches2-7 "${testcvs} -q co -r b1 first-dir" \
+"U first-dir/file1"
+         cd first-dir
+         echo "file2 first revision" > file2
+         dotest branches2-8 "${testcvs} add file2" \
+"${SPROG}"' add: scheduling file `file2'\'' for addition on branch `b1'\''
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         mkdir dir1
+         dotest branches2-9 "${testcvs} add dir1" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir1 added to the repository
+--> Using per-directory sticky tag "'`'"b1'"
+         echo "file3 first revision" > dir1/file3
+         dotest branches2-10 "${testcvs} add dir1/file3" \
+"${SPROG}"' add: scheduling file `dir1/file3'\'' for addition on branch `b1'\''
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest branches2-11 "${testcvs} -q ci -madd ." \
+"$CVSROOT_DIRNAME/first-dir/Attic/file2,v  <--  file2
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dir1/Attic/file3,v  <--  dir1/file3
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         # Check out the second branch, and update the working
+         # directory to the first branch, to make sure the right
+         # happens with dir1.
+         cd ../..
+         mkdir b2; cd b2
+         dotest branches2-12 "${testcvs} -q co -r b2 first-dir" \
+'U first-dir/file1'
+         cd first-dir
+         dotest branches2-13 "${testcvs} update -d -r b1 dir1" \
+"${SPROG} update: Updating dir1
+U dir1/file3"
+         dotest branches2-14 "${testcvs} -q status" \
+"===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         b2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Up-to-date
+
+   Working revision:   1\.1\.2\.1.*
+   Repository revision:        1\.1\.2\.1      
${CVSROOT_DIRNAME}/first-dir/dir1/Attic/file3,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         b1 (branch: 1\.1\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         # Test some calls to rls here because we can.  These should probably
+         # be somewhere else, but we already have some directories set up.
+          dotest branches2-14-rls-1 "$testcvs rls" \
+"$SPROG rls: Listing module: \`.'
+CVSROOT
+first-dir"
+          dotest branches2-14-rls-2 "$testcvs rls -R" \
+"$SPROG rls: Listing module: \`.'
+\.:
+CVSROOT
+first-dir
+
+CVSROOT:
+checkoutlist
+commitinfo
+config
+cvswrappers
+loginfo
+modules
+notify
+postadmin
+postproxy
+posttag
+postwatch
+preproxy
+rcsinfo
+taginfo
+verifymsg
+Emptydir
+
+CVSROOT/Emptydir:
+
+first-dir:
+file1
+dir1
+
+first-dir/dir1:"
+          dotest branches2-14-rls-3 "$testcvs rls -l -R" \
+"$SPROG rls: Listing module: \`.'
+\.:
+d--- $ISO8601DATE            CVSROOT
+d--- $ISO8601DATE            first-dir
+
+CVSROOT:
+---- $ISO8601DATE 1\.[0-9][0-9]*        checkoutlist
+---- $ISO8601DATE 1\.[0-9][0-9]*        commitinfo
+---- $ISO8601DATE 1\.[0-9][0-9]*        config
+---- $ISO8601DATE 1\.[0-9][0-9]*        cvswrappers
+---- $ISO8601DATE 1\.[0-9][0-9]*        loginfo
+---- $ISO8601DATE 1\.[0-9][0-9]*        modules
+---- $ISO8601DATE 1\.[0-9][0-9]*        notify
+---- $ISO8601DATE 1\.[0-9][0-9]*        postadmin
+---- $ISO8601DATE 1\.[0-9][0-9]*        postproxy
+---- $ISO8601DATE 1\.[0-9][0-9]*        posttag
+---- $ISO8601DATE 1\.[0-9][0-9]*        postwatch
+---- $ISO8601DATE 1\.[0-9][0-9]*        preproxy
+---- $ISO8601DATE 1\.[0-9][0-9]*        rcsinfo
+---- $ISO8601DATE 1\.[0-9][0-9]*        taginfo
+---- $ISO8601DATE 1\.[0-9][0-9]*        verifymsg
+d--- $ISO8601DATE            Emptydir
+
+CVSROOT/Emptydir:
+
+first-dir:
+---- $ISO8601DATE 1\.1        file1
+d--- $ISO8601DATE            dir1
+
+first-dir/dir1:"
+          dotest branches2-14-rls-4 "$testcvs rls -eR" \
+"$SPROG rls: Listing module: \`.'
+\.:
+D/CVSROOT////
+D/first-dir////
+
+CVSROOT:
+/checkoutlist/1\.[0-9][0-9]*/$DATE//
+/commitinfo/1\.[0-9][0-9]*/$DATE//
+/config/1\.[0-9][0-9]*/$DATE//
+/cvswrappers/1\.[0-9][0-9]*/$DATE//
+/loginfo/1\.[0-9][0-9]*/$DATE//
+/modules/1\.[0-9][0-9]*/$DATE//
+/notify/1\.[0-9][0-9]*/$DATE//
+/postadmin/1\.[0-9][0-9]*/$DATE//
+/postproxy/1\.[0-9][0-9]*/$DATE//
+/posttag/1\.[0-9][0-9]*/$DATE//
+/postwatch/1\.[0-9][0-9]*/$DATE//
+/preproxy/1\.[0-9][0-9]*/$DATE//
+/rcsinfo/1\.[0-9][0-9]*/$DATE//
+/taginfo/1\.[0-9][0-9]*/$DATE//
+/verifymsg/1\.[0-9][0-9]*/$DATE//
+D/Emptydir////
+
+CVSROOT/Emptydir:
+
+first-dir:
+/file1/1\.1/$DATE//
+D/dir1////
+
+first-dir/dir1:"
+          dotest branches2-14-rls-5 "$testcvs -q rls -R" \
+"\.:
+CVSROOT
+first-dir
+
+CVSROOT:
+checkoutlist
+commitinfo
+config
+cvswrappers
+loginfo
+modules
+notify
+postadmin
+postproxy
+posttag
+postwatch
+preproxy
+rcsinfo
+taginfo
+verifymsg
+Emptydir
+
+CVSROOT/Emptydir:
+
+first-dir:
+file1
+dir1
+
+first-dir/dir1:"
+          dotest branches2-14-rls-6 "$testcvs -q rls -lRrb1" \
+"\.:
+d--- $ISO8601DATE            CVSROOT
+d--- $ISO8601DATE            first-dir
+
+CVSROOT:
+d--- $ISO8601DATE            Emptydir
+
+CVSROOT/Emptydir:
+
+first-dir:
+---- $ISO8601DATE 1\.1        file1
+---- $ISO8601DATE 1\.1\.2\.1    file2
+d--- $ISO8601DATE            dir1
+
+first-dir/dir1:
+---- $ISO8601DATE 1\.1\.2\.1    file3"
+          dotest branches2-14-rls-7 "$testcvs -q rls -lRrb2" \
+"\.:
+d--- $ISO8601DATE            CVSROOT
+d--- $ISO8601DATE            first-dir
+
+CVSROOT:
+d--- $ISO8601DATE            Emptydir
+
+CVSROOT/Emptydir:
+
+first-dir:
+---- $ISO8601DATE 1\.1        file1
+d--- $ISO8601DATE            dir1
+
+first-dir/dir1:"
+
+         # Now some calls to ls.  These are more appropriate here.
+         dotest branches2-14-ls-1 "$testcvs ls" \
+"file1
+dir1"
+         dotest branches2-14-ls-2 "$testcvs ls -e" \
+"/file1/1\.1/$DATE//
+D/dir1////"
+         dotest branches2-14-ls-3 "$testcvs ls -R" \
+"\.:
+file1
+dir1
+
+dir1:
+file3"
+         dotest branches2-14-ls-4 "$testcvs ls -eRrHEAD" \
+"\.:
+/file1/1\.1/$DATE//THEAD
+D/dir1////
+
+dir1:"
+         dotest branches2-14-ls-5 "$testcvs ls -eRrb1" \
+"\.:
+/file1/1\.1/$DATE//Tb1
+/file2/1\.1\.2\.1/$DATE//Tb1
+D/dir1////
+
+dir1:
+/file3/1\.1\.2\.1/$DATE//Tb1"
+         dotest branches2-14-ls-6 "$testcvs ls -eRrb2" \
+"\.:
+/file1/1.1/$DATE//Tb2
+D/dir1////
+
+dir1:"
+         # Nonexistant tags used to cause assertion failures.
+         dotest_fail branches2-14-ls-7 "$testcvs ls -eRrnosuchtag" \
+"$SPROG \[ls aborted\]: no such tag \`nosuchtag'"
+
+         # FIXME: Just clobbering the directory like this is a bit
+         # tacky, although people generally expect it to work.  Maybe
+         # we should release it instead.  We do it a few other places
+         # below as well.
+         rm -r dir1
+         dotest branches2-15 "${testcvs} update -d -j b1 dir1" \
+"${SPROG} update: Updating dir1
+U dir1/file3"
+         # FIXCVS: The `No revision control file' stuff seems to be
+         # CVS's way of telling us that we're adding the file on a
+         # branch, and the file is not on that branch yet.  This
+         # should be nicer.
+         dotest branches2-16 "${testcvs} -q status" \
+"===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         b2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         b2 - MISSING from RCS file!
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         cd ../../trunk/first-dir
+         dotest branches2-17 "${testcvs} update -d -P dir1" \
+"${SPROG} update: Updating dir1"
+         dotest_fail branches2-18 "test -d dir1"
+         dotest branches2-19 "${testcvs} update -d -P -r b1 dir1" \
+"${SPROG} update: Updating dir1
+U dir1/file3"
+         dotest branches2-20 "${testcvs} -q status" \
+"===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Up-to-date
+
+   Working revision:   1\.1\.2\.1.*
+   Repository revision:        1\.1\.2\.1      
${CVSROOT_DIRNAME}/first-dir/dir1/Attic/file3,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         b1 (branch: 1\.1\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         rm -r dir1
+         dotest branches2-21 "${testcvs} update -d -P -j b1 dir1" \
+"${SPROG} update: Updating dir1
+U dir1/file3"
+         dotest branches2-22 "${testcvs} -q status" \
+"===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        1\.1    
${CVSROOT_DIRNAME}/first-dir/dir1/Attic/file3,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         cd ../..
+         rm -r b1 b2
+
+         # Check out branch b1 twice.  Crate a new directory in one
+         # working directory, then do a cvs update in the other
+         # working directory and see if the tags are right.
+         mkdir b1a
+         mkdir b1b
+         cd b1b
+         dotest branches2-23 "${testcvs} -q co -r b1 first-dir" \
+'U first-dir/file1
+U first-dir/file2
+U first-dir/dir1/file3'
+         cd ../b1a
+         dotest branches2-24 "${testcvs} -q co -r b1 first-dir" \
+'U first-dir/file1
+U first-dir/file2
+U first-dir/dir1/file3'
+         cd first-dir
+         mkdir dir2
+         dotest branches2-25 "${testcvs} add dir2" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir2 added to the repository
+--> Using per-directory sticky tag "'`'"b1'"
+         echo "file4 first revision" > dir2/file4
+         dotest branches2-26 "${testcvs} add dir2/file4" \
+"${SPROG}"' add: scheduling file `dir2/file4'\'' for addition on branch `b1'\''
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest branches2-27 "${testcvs} -q commit -madd" \
+"$CVSROOT_DIRNAME/first-dir/dir2/Attic/file4,v  <--  dir2/file4
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         cd ../../b1b/first-dir
+         dotest branches2-28 "${testcvs} update -d dir2" \
+"${SPROG} update: Updating dir2
+U dir2/file4"
+         cd dir2
+         dotest branches2-29 "${testcvs} -q status" \
+"===================================================================
+File: file4                    Status: Up-to-date
+
+   Working revision:   1\.1\.2\.1.*
+   Repository revision:        1\.1\.2\.1      
${CVSROOT_DIRNAME}/first-dir/dir2/Attic/file4,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         b1 (branch: 1\.1\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest branches2-30 "cat CVS/Tag" 'Tb1'
+
+         # Test update -A on a subdirectory
+         cd ..
+         rm -r dir2
+         dotest branches2-31 "${testcvs} update -A -d dir2" \
+"${SPROG} update: Updating dir2"
+         cd dir2
+         dotest branches2-32 "${testcvs} -q status" ''
+         dotest_fail branches2-33 "test -f CVS/Tag"
+
+         # Add a file on the trunk.
+         echo "file5 first revision" > file5
+         dotest branches2-34 "${testcvs} add file5" \
+"${SPROG}"' add: scheduling file `file5'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest branches2-35 "${testcvs} -q commit -madd" \
+"$CVSROOT_DIRNAME/first-dir/dir2/file5,v  <--  file5
+initial revision: 1\.1"
+
+         cd ../../../trunk/first-dir
+         dotest branches2-36 "${testcvs} -q update -d dir2" 'U dir2/file5'
+         cd dir2
+         dotest branches2-37 "${testcvs} -q status" \
+"===================================================================
+File: file5                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    
${CVSROOT_DIRNAME}/first-dir/dir2/file5,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest_fail branches2-38 "test -f CVS/status"
+
+          dotest branches2-39 "$testcvs rls -rb1 -l -R first-dir" \
+"$SPROG rls: Listing module: \`first-dir'
+first-dir:
+---- $ISO8601DATE 1\.1        file1
+---- $ISO8601DATE 1\.1\.2\.1    file2
+d--- $ISO8601DATE            dir1
+d--- $ISO8601DATE            dir2
+
+first-dir/dir1:
+---- $ISO8601DATE 1\.1\.2\.1    file3
+
+first-dir/dir2:
+---- $ISO8601DATE 1\.1\.2\.1    file4"
+
+         dokeep
+         cd ../../..
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -r trunk b1a b1b
+         ;;
+
+
+
+       branches3)
+         # test local branch number support
+
+         # This test is skipped in $remotehost mode since the
+         # CVS_LOCAL_BRANCH_NUM is not inherited by the server process as it
+         # is with :fork:, for hopefully obvious reasons.
+         #
+         # FIXCVS?  Is this correct?  Should CVS_LOCAL_BRANCH_NUM be sent as
+         # a protocol extension or is it reasonable to only want this set on
+         # the server?
+
+         if test -n "$remotehost"; then :;else
+           modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+           mkdir branches3; cd branches3
+
+           dotest branches3-1 "$testcvs -q co first-dir"
+           cd first-dir
+           echo "file1 first revision" > file1
+           dotest branches3-2 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+           dotest branches3-3 "${testcvs} commit -m add file1" \
+"${CVSROOT_DIRNAME}/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+           # Tag the file using a CVS_LOCAL_BRANCH_NUM of 1000
+           CVS_LOCAL_BRANCH_NUM=1000; export CVS_LOCAL_BRANCH_NUM
+           dotest branches3-4 "${testcvs} -q tag -b tag1" 'T file1'
+           unset CVS_LOCAL_BRANCH_NUM
+           dotest branches3-5 "${testcvs} -q log file1" \
+"
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       tag1: 1\.1\.0\.1000
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add
+============================================================================="
+
+           dokeep
+           cd ../..
+           modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+           rm -r branches3
+         fi # !$remotehost
+         ;;
+
+
+
+       branches4)
+         # test where a tag is a branch tag in some files and a revision
+         # tag in others
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir branches4; cd branches4
+
+         dotest branches4-1 "$testcvs -q co first-dir"
+         cd first-dir
+         mkdir branches mixed mixed2 versions
+         dotest branches4-2 "${testcvs} -q add branches mixed mixed2 versions" 
\
+"Directory ${CVSROOT_DIRNAME}/first-dir/branches added to the repository
+Directory ${CVSROOT_DIRNAME}/first-dir/mixed added to the repository
+Directory ${CVSROOT_DIRNAME}/first-dir/mixed2 added to the repository
+Directory ${CVSROOT_DIRNAME}/first-dir/versions added to the repository"
+
+         echo file1 >branches/file1
+         echo file2 >branches/file2
+         echo file3 >branches/file3
+         echo file4 >branches/file4
+         cp branches/file* mixed
+         cp branches/file* mixed2
+         cp branches/file* versions
+
+         dotest branches4-3 "${testcvs} -q add */file*" \
+"${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest branches4-3a "${testcvs} -Q ci -m."
+
+         dotest branches4-4 "${testcvs} -q tag xxx versions/file* mixed*/file1 
mixed*/file3" \
+"T versions/file1
+T versions/file2
+T versions/file3
+T versions/file4
+T mixed/file1
+T mixed/file3
+T mixed2/file1
+T mixed2/file3"
+
+         dotest branches4-5 "${testcvs} -q tag -b xxx branches/file* 
mixed*/file2 mixed*/file4" \
+"T branches/file1
+T branches/file2
+T branches/file3
+T branches/file4
+T mixed/file2
+T mixed/file4
+T mixed2/file2
+T mixed2/file4"
+
+         # make sure we get the appropriate warnings when updating       
+         dotest branches4-6 "${testcvs} update -r xxx" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating branches
+${SPROG} update: Updating mixed
+${SPROG} update: warning: xxx is a branch tag in some files and a revision tag 
in others\.
+${SPROG} update: Updating mixed2
+${SPROG} update: warning: xxx is a branch tag in some files and a revision tag 
in others\.
+${SPROG} update: Updating versions"
+
+         # make sure we don't get warned in quiet modes
+         dotest branches4-7 "${testcvs} -q update -A"
+         dotest branches4-8 "${testcvs} -q update -r xxx"
+         dotest branches4-9 "${testcvs} -q update -A"
+         dotest branches4-10 "${testcvs} -Q update -r xxx"
+
+         # make sure the Tag files are correct
+         dotest branches4-11 "cat branches/CVS/Tag" "Txxx"
+         dotest branches4-12 "cat mixed/CVS/Tag" "Nxxx"
+         dotest branches4-13 "cat mixed2/CVS/Tag" "Nxxx"
+         dotest branches4-14 "cat versions/CVS/Tag" "Nxxx"
+
+         # We only warn if there's mixed usage in a single directory.
+         # We may want to consider changing that in the future.
+         dotest branches4-15 "${testcvs} update -r xxx branches versions" \
+"${SPROG} update: Updating branches
+${SPROG} update: Updating versions"
+
+         dokeep
+         cd ../..
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -r branches4
+         ;;
+
+
+
+       tagc)
+         # Test the tag -c option.
+         mkdir 1; cd 1
+         dotest tagc-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest tagc-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         touch file1 file2
+         dotest tagc-3 "${testcvs} add file1 file2" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest tagc-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+         dotest tagc-5 "${testcvs} -q tag -c tag1" \
+"T file1
+T file2"
+         touch file1 file2
+         dotest tagc-6 "${testcvs} -q tag -c tag2" \
+"T file1
+T file2"
+         # Avoid timestamp granularity bugs (FIXME: CVS should be
+         # doing the sleep, right?).
+         sleep 1
+         echo myedit >>file1
+         dotest tagc-6a "${testcvs} rm -f file2" \
+"${SPROG} remove: scheduling .file2. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         touch file3
+         dotest tagc-6b "${testcvs} add file3" \
+"${SPROG} add: scheduling file .file3. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest_fail tagc-7 "${testcvs} -q tag -c tag3" \
+"${SPROG} tag: file1 is locally modified
+${SPROG} tag: file2 is locally modified
+${SPROG} tag: file3 is locally modified
+${SPROG} \[tag aborted\]: correct the above errors first!"
+         cd ../..
+         mkdir 2
+         cd 2
+         dotest tagc-8 "${testcvs} -q co first-dir" \
+"U first-dir/file1
+U first-dir/file2"
+         cd ../1/first-dir
+         dotest tagc-9 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+initial revision: 1\.1"
+         cd ../../2/first-dir
+         dotest tagc-10 "${testcvs} -q tag -c tag4" \
+"${SPROG} tag: \`file2' is no longer in the repository
+T file1
+T file2"
+
+         dokeep
+         cd ../..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       update-p)
+         # Make sure `cvs update -p -rT FILE' works from a branch when
+         # FILE is already on the trunk and is being added to that branch.
+
+         mkdir 1; cd 1
+         module=x
+
+         echo > unused-file
+
+         # Create the module.
+         dotest update-p-1 \
+           "$testcvs -Q import -m. $module X Y" ''
+
+         file=F
+         # Check it out and tag it.
+         dotest update-p-2 "$testcvs -Q co $module" ''
+         cd $module
+         dotest update-p-3 "$testcvs -Q tag -b B" ''
+         echo v1 > $file
+         dotest update-p-4 "$testcvs -Q add $file" ''
+         dotest update-p-5 "$testcvs -Q ci -m. $file"
+         dotest update-p-6 "$testcvs -Q tag T $file" ''
+         dotest update-p-7 "$testcvs -Q update -rB" ''
+
+         # This merge effectively adds file F on branch B.
+         dotest update-p-8 "$testcvs -Q update -jT" ''
+
+         # Before the fix that prompted the addition of this test,
+         # the following command would fail with this diagnostic:
+         # cvs update: conflict: F created independently by second party
+         dotest update-p-9 "$testcvs update -p -rT $file" \
+"===================================================================
+Checking out $file
+RCS:  ${CVSROOT_DIRNAME}/$module/$file,v
+VERS: 1\.1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+v1"
+
+         # Repeat the above, but with $file removed.
+         # This exercises a slightly different code path.
+         rm $file
+         # Before the fix that prompted the addition of this test,
+         # the following command would fail with this diagnostic:
+         # cvs update: warning: new-born \`F' has disappeared
+         dotest update-p-10 "$testcvs update -p -rT $file" \
+"===================================================================
+Checking out $file
+RCS:  ${CVSROOT_DIRNAME}/$module/$file,v
+VERS: 1\.1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+v1"
+
+         # Exercise yet another code path:
+         # the one that involves reviving a `dead' file.
+         # And a little more, for good measure...
+         touch new
+         dotest update-p-a1 "$testcvs -Q add new" ''
+         dotest update-p-a2 "$testcvs -Q update -p new" ''
+         dotest update-p-a3 "$testcvs -Q rm -f new" ''
+
+         # Both an update -A, *and* the following update are required
+         # to return to the state of being on the trunk with a $file
+         # that we can then remove.
+         dotest update-p-undead-0 "$testcvs update -A" \
+"${SPROG} update: Updating \.
+${SPROG} update: warning: new-born \`$file' has disappeared"
+         dotest update-p-undead-1 "$testcvs update" \
+"${SPROG} update: Updating \.
+U $file"
+         dotest update-p-undead-2 "$testcvs -Q update -p -rT $file" v1
+         dotest update-p-undead-3 "$testcvs -Q rm -f $file" ''
+         dotest update-p-undead-4 "$testcvs -Q update -p -rT $file" v1
+         dotest update-p-undead-5 "$testcvs -Q ci -m. $file"
+         dotest update-p-undead-6 "$testcvs -Q update -p -rT $file" v1
+         echo v2 > $file
+         dotest update-p-undead-7 "$testcvs -Q update -p -rT $file" v1
+         dotest update-p-undead-8 "$testcvs add $file" \
+"$SPROG add: Re-adding file .$file. after dead revision 1\.2\.
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+
+         dotest update-p-undead-9 "$testcvs -Q update -p -rT $file" v1
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         ;;
+
+
+
+       tagf)
+         # More tagging tests, including using tag -F -B to convert a
+         # branch tag to a regular tag and recovering thereof.
+
+         # Setup; check in first-dir/file1
+         mkdir 1; cd 1
+         dotest tagf-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest tagf-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         touch file1 file2
+         dotest tagf-3 "${testcvs} add file1 file2" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest tagf-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+
+         # Now create a branch and commit a revision there.
+         dotest tagf-5 "${testcvs} -q tag -b br" "T file1
+T file2"
+         dotest tagf-6 "${testcvs} -q update -r br" ""
+         echo brmod >> file1
+         echo brmod >> file2
+         dotest tagf-7 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+         # Here we try to make it a non-branch tag, but will
+         # succeed in getting only warnings, even with -F 
+         # because converting a branch tag to non-branch 
+         # is potentially catastrophic.
+         dotest tagf-8a "${testcvs} -q tag -F br" \
+"${SPROG} tag: file1: Not moving branch tag .br. from 1\.1\.2\.1 to 
1\.1\\.2\.1\.
+${SPROG} tag: file2: Not moving branch tag .br. from 1\.1\.2\.1 to 
1\.1\.2\.1\."
+         # however, if we *really* are sure we want to move a branch tag,
+         # "-F -B" will do the trick
+         dotest tagf-8 "${testcvs} -q tag -F -B br" "T file1
+T file2"
+         echo moremod >> file1
+         echo moremod >> file2
+         dotest tagf-9 "${testcvs} -q status -v file1" \
+"===================================================================
+File: file1                    Status: Locally Modified
+
+   Working revision:   1\.1\.2\.1.*
+   Repository revision:        1\.1\.2\.1      
${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br (revision: 1\.1\.2\.1)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       br                              (revision: 1\.1\.2\.1)"
+
+         # Now, how do we recover?
+         dotest tagf-10 "${testcvs} -q tag -d br" "D file1
+D file2"
+         # This creates a new branch, 1.1.4.  See the code in RCS_magicrev
+         # which will notice that there is a (non-magic) 1.1.2 and thus
+         # skip that number.
+         dotest tagf-11 "${testcvs} -q tag -r 1.1 -b br file1" "T file1"
+         # Fix it with admin -n (cf admin-18, admin-26-4).
+         dotest tagf-12 "${testcvs} -q admin -nbr:1.1.2 file2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+         # Another variation on the file2 test would be to use two working
+         # directories so that the update -r br would need to
+         # a merge to get from 1.1.2.1 to the head of the 1.1.2 branch.
+         dotest tagf-13 "${testcvs} -q update -r br" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1\.2\.1
+retrieving revision 1\.1
+Merging differences between 1\.1\.2\.1 and 1\.1 into file1
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in file1
+C file1
+M file2"
+         # CVS is giving a conflict because we are trying to get back to
+         # 1.1.4.  I'm not sure why it is a conflict rather than just
+         # "M file1".
+         dotest tagf-14 "cat file1" \
+"<<<<<<< file1
+brmod
+moremod
+[=]======
+[>]>>>>>> 1\.1"
+         echo resolve >file1
+         dotest tagf-15 "${testcvs} -q ci -m recovered" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.4\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+         # try accidentally deleting branch tag, "tag -d"
+         dotest_fail tagf-16 "${testcvs} tag -d br" \
+"${SPROG} tag: Untagging \.
+${SPROG} tag: Not removing branch tag .br. from 
.${CVSROOT_DIRNAME}/first-dir/file1,v.\.
+${SPROG} tag: Not removing branch tag .br. from 
.${CVSROOT_DIRNAME}/first-dir/file2,v.\."
+         # try accidentally deleting branch tag, "rtag -d"
+         dotest_fail tagf-17 "${testcvs} rtag -d br first-dir" \
+"${SPROG} rtag: Untagging first-dir
+${SPROG} rtag: Not removing branch tag .br. from 
.${CVSROOT_DIRNAME}/first-dir/file1,v.\.
+${SPROG} rtag: Not removing branch tag .br. from 
.${CVSROOT_DIRNAME}/first-dir/file2,v.\."
+         # try accidentally converting branch tag to non-branch tag "tag -F"
+         dotest tagf-18 "${testcvs} tag -r1.1 -F br file1" \
+"${SPROG} tag: file1: Not moving branch tag .br. from 1\.1\.4\.1 to 1\.1\."
+         # try accidentally converting branch tag to non-branch tag "rtag -F"
+         dotest tagf-19 "${testcvs} rtag -r1.1 -F br first-dir" \
+"${SPROG} rtag: Tagging first-dir
+${SPROG} rtag: first-dir/file1: Not moving branch tag .br. from 1\.1\.4\.1 to 
1\.1\.
+${SPROG} rtag: first-dir/file2: Not moving branch tag .br. from 1\.1\.2\.2 to 
1\.1\."
+         # create a non-branch tag
+         dotest tagf-20 "${testcvs} rtag regulartag first-dir" \
+"${SPROG} rtag: Tagging first-dir"
+         # try accidentally converting non-branch tag to branch tag (tag -F -B 
-b)
+         dotest tagf-21 "${testcvs} tag -F -B -b regulartag file1" \
+"${SPROG} tag: file1: Not moving non-branch tag .regulartag. from 1\.1 to 
1\.1\.4\.1\.0\.2 due to .-B. option\."
+         # try accidentally converting non-branch tag to branch rtag (rtag -F 
-B -b)
+         dotest tagf-22 "${testcvs} rtag -F -B -b regulartag first-dir" \
+"${SPROG} rtag: Tagging first-dir
+${SPROG} rtag: first-dir/file1: Not moving non-branch tag .regulartag. from 
1\.1 to 1\.1\.0\.6 due to .-B. option\.
+${SPROG} rtag: first-dir/file2: Not moving non-branch tag .regulartag. from 
1\.1 to 1\.1\.0\.4 due to .-B. option\."
+         # Try accidentally deleting non-branch: (tag -d -B)
+         dotest_fail tagf-23 "${testcvs} tag -d -B regulartag file1" \
+"${SPROG} tag: Not removing non-branch tag .regulartag. from 
.${CVSROOT_DIRNAME}/first-dir/file1,v. due to .-B. option\."
+         # Try accidentally deleting non-branch: (rtag -d -B)
+         dotest_fail tagf-24 \
+               "${testcvs} rtag -d -B regulartag first-dir" \
+"${SPROG} rtag: Untagging first-dir
+${SPROG} rtag: Not removing non-branch tag .regulartag. from 
.${CVSROOT_DIRNAME}/first-dir/file1,v. due to .-B. option\.
+${SPROG} rtag: Not removing non-branch tag .regulartag. from 
.${CVSROOT_DIRNAME}/first-dir/file2,v. due to .-B. option\."
+
+         # the following tests (throught the next commit) keep moving the same
+         # tag back and forth between 1.1.6 & 1.1.8  in file1 and between
+         # 1.1.4 and 1.1.6 in file2 since nothing was checked in on some of
+         # these branches and CVS only tracks branches via tags unless they 
contain data.
+
+         # try intentionally converting non-branch tag to branch tag (tag -F 
-b)
+         dotest tagf-25a "${testcvs} tag -F -b regulartag file1" "T file1"
+         # try intentionally moving a branch tag to a newly created branch 
(tag -F -b -B)
+         dotest tagf-25b "${testcvs} tag -F -B -b -r1.1 regulartag file1" \
+"T file1"
+         # try intentionally converting mixed tags to branch tags (rtag -F -b)
+         dotest tagf-26a "${testcvs} rtag -F -b regulartag first-dir" \
+"${SPROG} rtag: Tagging first-dir
+${SPROG} rtag: first-dir/file1: Not moving branch tag .regulartag. from 1\.1 
to 1\.1\.0\.8\."
+         # try intentionally converting a branch to a new branch tag (rtag -F 
-b -B)
+         dotest tagf-26b "${testcvs} rtag -F -B -b -r1.1 regulartag first-dir" 
\
+"${SPROG} rtag: Tagging first-dir"
+         # update to our new branch
+         dotest tagf-27 "${testcvs} update -r regulartag" \
+"${SPROG} update: Updating \.
+U file1
+U file2"
+         # commit some changes and see that all rev numbers look right
+         echo changes >> file1
+         echo changes >> file2
+         dotest tagf-28 "${testcvs} ci -m changes" \
+"${CPROG} commit: Examining \.
+${CVSROOT_DIRNAME}/first-dir/file1,v  <--  file1
+new revision: 1\.1\.8\.1; previous revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/file2,v  <--  file2
+new revision: 1\.1\.6\.1; previous revision: 1\.1"
+         # try intentional branch to non-branch (tag -F -B)
+         dotest tagf-29 "${testcvs} tag -F -B -r1.1 regulartag file1" \
+"T file1"
+         # try non-branch to non-branch (tag -F -B)
+         dotest tagf-29a "${testcvs} tag -F -B -r br regulartag file1" \
+"${SPROG} tag: file1: Not moving non-branch tag .regulartag. from 1\.1 to 
1\.1\.4\.1 due to .-B. option\."
+         # try mixed-branch to non-branch (rtag -F -B )
+         dotest tagf-29b "${testcvs} rtag -F -B -r br regulartag first-dir" \
+"${SPROG} rtag: Tagging first-dir
+${SPROG} rtag: first-dir/file1: Not moving non-branch tag .regulartag. from 
1\.1 to 1\.1\.4\.1 due to .-B. option\."
+         # at this point, regulartag is a regular tag within
+         # file1 and file2
+
+         # try intentional branch to non-branch (rtag -F -B)
+         dotest tagf-30 "${testcvs} rtag -F -B -r1.1 br first-dir"  \
+"${SPROG} rtag: Tagging first-dir"
+         # create a branch tag so we can try to delete it.
+         dotest tagf-31 "${testcvs} rtag -b brtag first-dir"  \
+"${SPROG} rtag: Tagging first-dir"
+       
+         # try intentinal deletion of branch tag (tag -d -B)
+         dotest tagf-32 "${testcvs} tag -d -B brtag file1" "D file1"
+         # try intentinal deletion of branch tag (rtag -d -B)
+         dotest tagf-33 "${testcvs} rtag -d -B brtag first-dir" \
+"${SPROG} rtag: Untagging first-dir"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       tag-space)
+         # Test tags with spaces in the names.
+         #
+         # Prior to releases 1.11.18 & 1.12.10, some commands used with
+         # tags with spaces in the names could hang CVS.
+
+         # Setup; check in first-dir/file1
+         mkdir 1; cd 1
+         dotest tag-space-init-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest tag-space-init-2 "$testcvs add first-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir added to the repository"
+         cd first-dir
+         touch file1
+         dotest tag-space-init-3 "$testcvs add file1" \
+"$SPROG add: scheduling file \`file1' for addition
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+         dotest tag-space-init-4 "$testcvs -Q ci -m add"
+
+         # Reportedly, the following two tags make it past WinCVS.
+         dotest_fail tag-space-1 "$testcvs tag ' spacetag '" \
+"$SPROG \[tag aborted\]: tag \` spacetag ' must start with a letter"
+         dotest_fail tag-space-2 "$testcvs tag 'spacetag '" \
+"$SPROG \[tag aborted\]: tag \`spacetag ' has non-visible graphic characters"
+
+         if $remote; then
+           # Verify that this isn't a client check.
+           dotest tag-space-3 "$testcvs server" \
+"E $SPROG \[tag aborted\]: tag \` spacetag ' must start with a letter
+error  " <<EOF
+Root $CVSROOT_DIRNAME
+UseUnchanged
+Argument --
+Argument  spacetag 
+Directory .
+$CVSROOT_DIRNAME/first-dir
+Entry /file1/1.1///
+Unchanged file1
+tag
+EOF
+
+           dotest tag-space-4 "$testcvs server" \
+"E $SPROG \[tag aborted\]: tag \`spacetag ' has non-visible graphic characters
+error  " <<EOF
+Root $CVSROOT_DIRNAME
+UseUnchanged
+Argument --
+Argument spacetag 
+Directory .
+$CVSROOT_DIRNAME/first-dir
+Entry /file1/1.1///
+Unchanged file1
+tag
+EOF
+         fi # $remote
+
+         # Any number of normal tags and branches were handled correctly.
+         dotest tag-space-5 "$testcvs -Q tag t1"
+         dotest tag-space-5b "$testcvs -Q tag t2"
+         dotest tag-space-5c "$testcvs -Q tag -b b1"
+
+         cd ../..
+         mkdir 2; cd 2
+
+         # But once a vendor branch exists, it's all over.
+         mkdir project; cd project
+         touch file1
+         dotest tag-space-init-4 \
+"$testcvs -Q import -mimport second-dir VENDOR RELEASE"
+
+         cd ..
+
+         dotest_fail tag-space-6 "$testcvs -Q co -r ' spacetag ' first-dir" \
+"$SPROG \[checkout aborted\]: tag \` spacetag ' must start with a letter"
+
+         # But when any files were imported, this test hung prior to CVS
+         # versions 1.11.18 & 1.12.10.
+         dotest_fail tag-space-7 "$testcvs -Q co -r ' spacetag ' second-dir" \
+"$SPROG \[checkout aborted\]: tag \` spacetag ' must start with a letter"
+
+         if $remote; then
+           # I based the client input in the next two tests on actual input
+           # from WinCVS 1.2.
+           dotest tag-space-8 "$testcvs server" \
+"E $SPROG \[checkout aborted\]: tag \` spacetag ' must start with a letter
+error  " <<EOF
+Root $CVSROOT_DIRNAME
+Argument -P
+Argument -r
+Argument  spacetag 
+Argument first-dir
+Directory .
+$CVSROOT_DIRNAME
+co
+EOF
+
+           # Verify the test is not on the client side.
+           dotest tag-space-9 "$testcvs server" \
+"E $SPROG \[checkout aborted\]: tag \` spacetag ' must start with a letter
+error  " <<EOF
+Root $CVSROOT_DIRNAME
+Argument -P
+Argument -r
+Argument  spacetag 
+Argument second-dir
+Directory .
+$CVSROOT_DIRNAME
+co
+EOF
+         fi # $remote
+
+         dotest tag-space-10 "$testcvs -Q co second-dir"
+         cd second-dir
+
+         # This test would also hang.
+         dotest_fail tag-space-11 "$testcvs -Q up -r ' spacetag '" \
+"$SPROG \[update aborted\]: tag \` spacetag ' must start with a letter"
+
+         if $remote; then
+           dotest tag-space-12 "$testcvs server" \
+"E $SPROG \[update aborted\]: tag \` spacetag ' must start with a letter
+error  " <<EOF
+Root $CVSROOT_DIRNAME
+Argument -r
+Argument  spacetag 
+Argument -u
+Argument --
+Directory .
+$CVSROOT_DIRNAME
+Unchanged file1
+update
+EOF
+         fi # $remote
+
+         # I'm skipping tests for other commands that may have had the same
+         # problem.  Hopefully, if a new issue arises, one of the above tests
+         # will catch the problem.
+
+         if $keep; then
+           echo Keeping $TESTDIR and exiting due to --keep
+           exit 0
+         fi
+
+         cd ../..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/second-dir
+         ;;
+
+
+
+       rcslib)
+         # Test librarification of RCS.
+         # First: test whether `cvs diff' handles $Name expansion
+         # correctly.  We diff two revisions with their symbolic tags;
+         # neither tag should be expanded in the output.  Also diff
+         # one revision with the working copy.
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest rcslib-diff1 "${testcvs} -q co first-dir" ''
+         cd first-dir
+         echo "I am the first foo, and my name is $""Name$." > foo.c
+         dotest rcslib-diff2 "${testcvs} add -m new-file foo.c" \
+"${SPROG} add: scheduling file .foo\.c. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest rcslib-diff3 "${testcvs} commit -m rev1 foo.c" \
+"${CVSROOT_DIRNAME}/first-dir/foo.c,v  <--  foo\.c
+initial revision: 1\.1"
+         dotest rcslib-diff4 "${testcvs} tag first foo.c" "T foo\.c"
+         dotest rcslib-diff5 "${testcvs} update -p -r first foo.c" \
+"===================================================================
+Checking out foo\.c
+RCS:  ${CVSROOT_DIRNAME}/first-dir/foo\.c,v
+VERS: 1\.1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+I am the first foo, and my name is \$""Name: first \$\."
+
+         echo "I am the second foo, and my name is $""Name$." > foo.c
+         dotest rcslib-diff6 "${testcvs} commit -m rev2 foo.c" \
+"${CVSROOT_DIRNAME}/first-dir/foo\.c,v  <--  foo\.c
+new revision: 1\.2; previous revision: 1\.1"
+         dotest rcslib-diff7 "${testcvs} tag second foo.c" "T foo\.c"
+         dotest rcslib-diff8 "${testcvs} update -p -r second foo.c" \
+"===================================================================
+Checking out foo\.c
+RCS:  ${CVSROOT_DIRNAME}/first-dir/foo\.c,v
+VERS: 1\.2
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+I am the second foo, and my name is \$""Name: second \$\."
+
+       dotest_fail rcslib-diff9 "${testcvs} diff -r first -r second" \
+"${SPROG} diff: Diffing \.
+Index: foo\.c
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/foo\.c,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+diff -r1\.1 -r1\.2
+1c1
+< I am the first foo, and my name is \$""Name:  \$\.
+---
+> I am the second foo, and my name is \$""Name:  \$\."
+
+         echo "I am the once and future foo, and my name is $""Name$." > foo.c
+         dotest_fail rcslib-diff10 "${testcvs} diff -r first" \
+"${SPROG} diff: Diffing \.
+Index: foo\.c
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/foo\.c,v
+retrieving revision 1\.1
+diff -r1\.1 foo\.c
+1c1
+< I am the first foo, and my name is \$""Name:  \$\.
+---
+> I am the once and future foo, and my name is \$""Name\$\."
+
+         # Test handling of libdiff options.  diff gets quite enough
+         # of a workout elsewhere in sanity.sh, so we assume that it's
+         # mostly working properly if it passes all the other tests.
+         # The main one we want to try is regex handling, since we are
+         # using CVS's regex matcher and not diff's.
+
+         cat >rgx.c <<EOF
+test_regex (whiz, bang)
+{
+foo;
+bar;
+baz;
+grumble;
+}
+EOF
+
+         dotest rcslib-diffrgx-1 "${testcvs} -q add -m '' rgx.c" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest rcslib-diffrgx-2 "${testcvs} -q ci -m '' rgx.c" \
+"$CVSROOT_DIRNAME/first-dir/rgx\.c,v  <--  rgx\.c
+initial revision: 1\.1"
+         cat >rgx.c <<EOF
+test_regex (whiz, bang)
+{
+foo;
+bar;
+baz;
+mumble;
+}
+EOF
+         # Use dotest_fail because exit status from `cvs diff' must be 1.
+         #
+         # Incidentally test that CVS no longer splits diff arguments on
+         # spaces.
+         dotest_fail rcslib-diffrgx-3 "$testcvs diff -c -F'.* (' rgx.c" \
+"Index: rgx\.c
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/rgx\.c,v
+retrieving revision 1\.1
+diff -c -F '\.\* (' -r1\.1 rgx\.c
+\*\*\* rgx\.c  ${RFCDATE}      1\.1
+--- rgx\.c     ${RFCDATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* test_regex (whiz, bang)
+\*\*\* 3,7 \*\*\*\*
+  foo;
+  bar;
+  baz;
+! grumble;
+  }
+--- 3,7 ----
+  foo;
+  bar;
+  baz;
+! mumble;
+  }"
+
+         # Tests of rcsmerge/diff3.  Merge operations get a good general
+         # workout elsewhere; we want to make sure that options are still
+         # handled properly.  Try merging two branches with -kv, to test
+         # both -j and -k switches.
+
+         cd ..
+
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -r first-dir
+
+         mkdir 1; cd 1
+         dotest rcslib-merge-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest rcslib-merge-2 "$testcvs -q add first-dir" \
+"Directory $CVSROOT_DIRNAME.*/first-dir added to the repository"
+         cd ..; rm -r 1
+
+         dotest rcslib-merge-3 "$testcvs -q co first-dir" ""
+         cd first-dir
+
+         echo '$''Revision$' > file1
+         echo '2' >> file1
+         echo '3' >> file1
+         dotest rcslib-merge-4 "${testcvs} -q add file1" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest rcslib-merge-5 "${testcvs} -q commit -m '' file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         sed -e 's/2/two/' file1 > f; mv f file1
+         dotest rcslib-merge-6 "${testcvs} -q commit -m '' file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         dotest rcslib-merge-7 "${testcvs} -q tag -b -r 1.1 patch1" "T file1"
+         dotest rcslib-merge-8 "${testcvs} -q update -r patch1" "U file1"
+         dotest rcslib-merge-9 "${testcvs} -q status" \
+"===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         patch1 (branch: 1\.1\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest rcslib-merge-10 "cat file1" \
+'$''Revision: 1\.1 $
+2
+3'
+         sed -e 's/3/three/' file1 > f; mv f file1
+         dotest rcslib-merge-11 "${testcvs} -q commit -m '' file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+         dotest rcslib-merge-12 "${testcvs} -q update -kv -j1.2" \
+"U file1
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into file1
+rcsmerge: warning: conflicts during merge"
+         dotest rcslib-merge-13 "cat file1" \
+"<<<<<<< file1
+1\.1\.2\.1
+2
+three
+[=]======
+1\.2
+two
+3
+[>]>>>>>> 1\.2"
+
+         # Test behavior of symlinks in the repository.
+         if test -n "$remotehost"; then
+           # Create the link on the remote system.  This is because Cygwin's
+           # Windows support creates *.lnk files for Windows.  When creating
+           # these in an SMB share from UNIX, these links won't work from the
+           # UNIX side.
+           modify_repo $CVS_RSH $remotehost "'ln -s file1,v 
$CVSROOT_DIRNAME/first-dir/file2,v'"
+         else
+           modify_repo ln -s file1,v $CVSROOT_DIRNAME/first-dir/file2,v
+         fi
+         dotest rcslib-symlink-2 "$testcvs update file2" "U file2"
+         echo "This is a change" >> file2
+         dotest rcslib-symlink-3 "$testcvs ci -m because file2" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file2
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+
+         # Switch as for rcslib-symlink-1
+         if test -n "$remotehost"; then
+           dotest rcslib-symlink-4 "$CVS_RSH $remotehost 'ls -l 
$CVSROOT_DIRNAME/first-dir/file2,v'" \
+".*$CVSROOT_DIRNAME/first-dir/file2,v -> file1,v"
+         else
+           dotest rcslib-symlink-4 "ls -l $CVSROOT_DIRNAME/first-dir/file2,v" \
+".*$CVSROOT_DIRNAME/first-dir/file2,v -> file1,v"
+         fi
+
+         # CVS was failing to check both the symlink and the file
+         # for timestamp changes for a while.  Test that.
+         rm file1
+         dotest rcslib-symlink-3a "${testcvs} -q up file1" \
+"${SPROG} update: warning: \`file1' was lost
+U file1"
+         echo "This is a change" >> file1
+         dotest rcslib-symlink-3b "${testcvs} ci -m because file1" \
+"${CVSROOT_DIRNAME}/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.[0-9]*; previous revision: 1\.1\.2\.[0-9]*"
+         dotest rcslib-symlink-3c "${testcvs} update file2" "U file2"
+
+         echo some new text >file3
+         dotest rcslib-symlink-3d "${testcvs} -Q add file3" ''
+         dotest rcslib-symlink-3e "$testcvs -Q ci -mtest file3"
+
+         rm -f ${CVSROOT_DIRNAME}/first-dir/file2,v
+         # As for rcslib-symlink-1
+         if test -n "$remotehost"; then
+           modify_repo "$CVS_RSH $remotehost 'ln -s Attic/file3,v 
$CVSROOT_DIRNAME/first-dir/file2,v'"
+         else
+           modify_repo ln -s Attic/file3,v $CVSROOT_DIRNAME/first-dir/file2,v
+         fi
+
+         dotest rcslib-symlink-3g "$testcvs update file2" "U file2"
+
+         # restore the link to file1 for the following tests
+         dotest rcslib-symlink-3i "$testcvs -Q rm -f file3" ''
+         dotest rcslib-symlink-3j "$testcvs -Q ci -mwhatever file3"
+         rm -f $CVSROOT_DIRNAME/first-dir/file2,v
+         rm -f $CVSROOT_DIRNAME/first-dir/Attic/file3,v
+         # As for rcslib-symlink-1
+         if test -n "$remotehost"; then
+           modify_repo "$CVS_RSH $remotehost 'ln -s file1,v 
$CVSROOT_DIRNAME/first-dir/file2,v'"
+         else
+           modify_repo ln -s file1,v $CVSROOT_DIRNAME/first-dir/file2,v
+         fi
+
+         # Test 5 reveals a problem with having symlinks in the
+         # repository.  CVS will try to tag both of the files
+         # separately.  After processing one, it will do the same
+         # operation to the other, which is actually the same file,
+         # so the tag will already be there.  FIXME: do we bother
+         # changing operations to notice cases like this?  This
+         # strikes me as a difficult problem.  -Noel
+         dotest rcslib-symlink-5 "$testcvs tag the_tag" \
+"$SPROG tag: Tagging .
+T file1
+W file2 : the_tag already exists on version 1.1.2.3 : NOT MOVING tag to 
version 1.1.2.1"
+         # As for rcslib-symlink-1
+         if test -n "$remotehost"; then
+           dotest rcslib-symlink-6 "$CVS_RSH $remotehost 'ls -l 
$CVSROOT_DIRNAME/first-dir/file2,v'" \
+".*$CVSROOT_DIRNAME/first-dir/file2,v -> file1,v"
+         else
+           dotest rcslib-symlink-6 "ls -l $CVSROOT_DIRNAME/first-dir/file2,v" \
+".*$CVSROOT_DIRNAME/first-dir/file2,v -> file1,v"
+         fi
+
+         # Symlinks tend to interact poorly with the Attic.
+         cd ..
+         mkdir 2; cd 2
+         dotest rcslib-symlink-7 "$testcvs -q co first-dir" \
+"U first-dir/file1
+U first-dir/file2"
+         cd first-dir
+         dotest rcslib-symlink-8 "$testcvs rm -f file2" \
+"$SPROG remove: scheduling .file2. for removal
+$SPROG remove: use .$SPROG commit. to remove this file permanently"
+         dotest rcslib-symlink-9 "$testcvs -q ci -m rm-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file2
+new revision: delete; previous revision: 1\.2"
+         # OK, why this message happens twice is relatively clear
+         # (the check_* and rtag_* calls to start_recursion).
+         # Why it happens a third time I didn't try to find out.
+         #
+         # DRP: One of the error messages disappeared while I was making
+         # proxy modifications.  Until I detect a deeper issue, I'm not
+         # going to stress over it.
+         #
+         # DRP: Both messages disappear starting with glibc 2.3.3 due to a bug
+         # in the glob function which causes it to fail to return broken
+         # symlinks.  I'm submitting a bug fix to glibc which will hopefully
+         # be released with glibc 2.3.6.  Once it is released and versions
+         # 2.3.3-2.3.5 of glibc become uncommon, the first, empty case below
+         # should be removed again.
+         dotest rcslib-symlink-10 \
+"$testcvs -q rtag -b -r the_tag brtag first-dir" "" \
+"$SPROG rtag: could not read RCS file for first-dir/file2
+$SPROG rtag: could not read RCS file for first-dir/file2"
+
+         # Restore file1 for the next test.
+         dotest rcslib-long-symlink-init-1 "$testcvs -Q up -A"
+         dotest rcslib-long-symlink-init-2 "$testcvs -Q add file1"
+         dotest rcslib-long-symlink-init-3 "$testcvs -Q ci -mback"
+
+         cd ../..  # $TESTDIR
+
+         # CVS has a hard-coded default link path size of 127 characters.
+         # Make sure it knows how to exceed that.
+         longpath=$CVSROOT_DIRNAME
+         count=0
+         while test $count -lt 10; do
+           # 10 * 30 characters + len $CVSROOT_DIRNAME
+           count=`expr $count + 1`
+           longpath=$longpath/123456789012345678901234567890
+           modify_repo mkdir $longpath
+         done
+         modify_repo cp $CVSROOT_DIRNAME/first-dir/file1,v $longpath
+         modify_repo mkdir $CVSROOT_DIRNAME/second-dir
+
+         # Switch as for rcslib-symlink-1
+         if test -n "$remotehost"; then
+           modify_repo $CVS_RSH $remotehost \
+           'ln -s $longpath/file1,v $CVSROOT_DIRNAME/second-dir/fileX,v'
+         else
+           modify_repo ln -s $longpath/file1,v \
+                             $CVSROOT_DIRNAME/second-dir/fileX,v
+         fi
+
+         dotest rcslib-long-symlink-2 "$testcvs co second-dir" \
+"$SPROG checkout: Updating second-dir
+U second-dir/fileX"
+
+         cd second-dir
+         echo change-it >>fileX
+
+         # Writes actually cause symlinks to be resolved.
+         dotest rcslib-long-symlink-3 "$testcvs -q ci -mwrite-it" \
+"$CVSROOT_DIRNAME/123456789012345678901234567890/123456789012345678901234567890/123456789012345678901234567890/123456789012345678901234567890/123456789012345678901234567890/123456789012345678901234567890/123456789012345678901234567890/123456789012345678901234567890/123456789012345678901234567890/123456789012345678901234567890/file1,v
  <--  fileX
+new revision: 1\.5; previous revision: 1\.4"
+
+         dokeep
+         cd ..
+
+         # Must remove the symlink first.  Samba doesn't appear to show
+         # broken symlink across the SMB share, and rm -rf by itself
+         # will remove file1,v first and leave file2,v a broken link and the
+         # rm -rf will fail since it doesn't find file2,v and it still gets
+         # directory not empty errors removing cvsroot/first-dir.
+         #
+         # I'm not sure why I need to do this on $remotehost.  The rm above
+         # rcslib-symlink-3j works fine, but the next one doesn't unless run
+         # remotely under Cygwin and using a TESTDIR on a Samba share.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost \
+"rm -f $CVSROOT_DIRNAME/first-dir/file2,v $CVSROOT_DIRNAME/second-dir/fileX,v"
+         fi
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/second-dir \
+                            $CVSROOT_DIRNAME/123456789012345678901234567890
+         rm -r first-dir second-dir 2
+         ;;
+
+
+
+       multibranch)
+         # Test the ability to have several branchpoints coming off the
+         # same revision.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest multibranch-1 "${testcvs} -q co first-dir" ''
+         cd first-dir
+         echo 1:trunk-1 >file1
+         dotest multibranch-2 "${testcvs} add file1" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest_lit multibranch-3 "${testcvs} -q ci -m add-it" <<HERE
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1.1
+HERE
+         dotest multibranch-4 "${testcvs} tag -b br1" \
+"${SPROG} tag: Tagging \.
+T file1"
+         dotest multibranch-5 "${testcvs} tag -b br2" \
+"${SPROG} tag: Tagging \.
+T file1"
+         dotest multibranch-6 "${testcvs} -q update -r br1" ''
+         echo on-br1 >file1
+         dotest multibranch-7 "${testcvs} -q ci -m modify-on-br1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+         dotest multibranch-8 "${testcvs} -q update -r br2" 'U file1'
+         echo br2 adds a line >>file1
+         dotest multibranch-9 "${testcvs} -q ci -m modify-on-br2" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.4\.1; previous revision: 1\.1"
+         dotest multibranch-10 "${testcvs} -q update -r br1" 'U file1'
+         dotest multibranch-11 "cat file1" 'on-br1'
+         dotest multibranch-12 "${testcvs} -q update -r br2" 'U file1'
+         dotest multibranch-13 "cat file1" '1:trunk-1
+br2 adds a line'
+
+         dotest multibranch-14 "${testcvs} log file1" \
+"
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       br2: 1\.1\.0\.4
+       br1: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.2;  1\.1\.4;
+add-it
+----------------------------
+revision 1\.1\.4\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+modify-on-br2
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+modify-on-br1
+============================================================================="
+
+         dokeep
+         cd ..
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -r first-dir
+         ;;
+
+
+
+       import) # test death after import
+               # Tests of "cvs import":
+               # basic2
+               # rdiff  -- imports with keywords
+               # import  -- more tests of imports with keywords
+               # importb  -- -b option.
+               # importc -- bunch o' files in bunch o' directories
+               # importX  -- -X option.
+               # importX2 -- CVSROOT/config ImportNewFilesToVendorBranchOnly
+               #             flag
+               # modules3
+               # mflag -- various -m messages
+               # ignore  -- import and cvsignore
+               # binwrap -- import and -k wrappers
+               # info -- imports which are rejected by verifymsg
+               # head -- intended to test vendor branches and HEAD,
+               #   although it doesn't really do it yet.
+               # import-CVS -- refuse to import directories named "CVS".
+               # import-quirks -- short tests of import quirks.
+
+               # import
+               mkdir import-dir ; cd import-dir
+
+               for i in 1 2 3 4 ; do
+                 echo imported file"$i" > imported-f"$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-f2
+               cp imported-f2 ../imported-f2-orig.tmp
+
+               dotest_sort import-96 \
+"${testcvs} import -m first-import first-dir vendor-branch junk-1_0" \
+"
+
+I first-dir/RCS
+N first-dir/imported-f1
+N first-dir/imported-f2
+N first-dir/imported-f3
+N first-dir/imported-f4
+No conflicts created by this import"
+
+               dotest import-96.5 "$diff_u ../imported-f2-orig.tmp imported-f2"
+
+               cd ..
+
+               # co
+               dotest import-97 "${testcvs} -q co first-dir" \
+"U first-dir/imported-f1
+U first-dir/imported-f2
+U first-dir/imported-f3
+U first-dir/imported-f4"
+
+               cd first-dir
+
+               for i in 1 2 3 4 ; do
+                 dotest import-98-$i "test -f imported-f$i" ''
+               done
+               dotest_fail import-98.5 "test -d RCS" ''
+
+               # remove
+               rm imported-f1
+               dotest import-99 "${testcvs} rm imported-f1" \
+"${SPROG}"' remove: scheduling `imported-f1'\'' for removal
+'"${SPROG}"' remove: use .'"${SPROG}"' commit. to remove this file permanently'
+
+               # change
+               echo local-change >> imported-f2
+
+               # commit
+               dotest import-100 "${testcvs} ci -m local-changes" \
+"${CPROG} commit: Examining .
+${CVSROOT_DIRNAME}/first-dir/imported-f1,v  <--  imported-f1
+new revision: delete; previous revision: 1\.1\.1\.1
+${CVSROOT_DIRNAME}/first-dir/imported-f2,v  <--  imported-f2
+new revision: 1\.2; previous revision: 1\.1"
+
+               # log
+               dotest import-101 "${testcvs} log imported-f1" \
+"
+RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/imported-f1,v
+Working file: imported-f1
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       junk-1_0: 1\.1\.1\.1
+       vendor-branch: 1\.1\.1
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: dead;  lines: ${PLUS}0 -0; 
 commitid: ${commitid};
+local-changes
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.1;
+Initial revision
+----------------------------
+revision 1\.1\.1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+first-import
+============================================================================="
+
+               # update into the vendor branch.
+               dotest import-102 "${testcvs} update -rvendor-branch" \
+"${SPROG} update: Updating .
+U imported-f1
+U imported-f2"
+
+               # remove file4 on the vendor branch
+               rm imported-f4
+               dotest import-103 "${testcvs} rm imported-f4" \
+"${SPROG}"' remove: scheduling `imported-f4'\'' for removal
+'"${SPROG}"' remove: use .'"${SPROG}"' commit. to remove this file permanently'
+
+               # commit
+               dotest import-104 \
+"${testcvs} ci -m vendor-removed imported-f4" \
+"${CVSROOT_DIRNAME}/first-dir/imported-f4,v  <--  imported-f4
+new revision: delete; previous revision: 1\.1\.1\.1"
+
+               # update to main line
+               dotest import-105 "${testcvs} -q update -A" \
+"${SPROG} update: \`imported-f1' is no longer in the repository
+U imported-f2"
+
+               # second import - file4 deliberately unchanged
+               cd ../import-dir
+               for i in 1 2 3 ; do
+                 echo rev 2 of file $i >> imported-f"$i"
+               done
+               cp imported-f2 ../imported-f2-orig.tmp
+
+               dotest_sort import-106 \
+"${testcvs} import -m second-import first-dir vendor-branch junk-2_0" \
+"
+
+
+ ${CPROG} checkout -j<prev_rel_tag> -jjunk-2_0 first-dir
+2 conflicts created by this import.
+C first-dir/imported-f1
+C first-dir/imported-f2
+I first-dir/RCS
+U first-dir/imported-f3
+U first-dir/imported-f4
+Use the following command to help the merge:"
+
+               dotest import-106.5 "$diff_u ../imported-f2-orig.tmp 
imported-f2"
+
+               cd ..
+
+               rm imported-f2-orig.tmp
+
+               # co
+               dotest import-107 "${testcvs} co first-dir" \
+"${SPROG} checkout: Updating first-dir
+U first-dir/imported-f3
+U first-dir/imported-f4"
+
+               cd first-dir
+
+               dotest_fail import-108 "test -f imported-f1" ''
+
+               for i in 2 3 ; do
+                 dotest import-109-$i "test -f imported-f$i" ''
+               done
+
+               # check vendor branch for file4
+               dotest import-110 "${testcvs} -q update -rvendor-branch" \
+"U imported-f1
+U imported-f2"
+
+               dotest import-111 "test -f imported-f4" ''
+
+               # update to main line
+               dotest import-112 "${testcvs} -q update -A" \
+"${SPROG} update: \`imported-f1' is no longer in the repository
+U imported-f2"
+
+               cd ..
+
+               dotest import-113 \
+"${testcvs} -q co -jjunk-1_0 -jjunk-2_0 first-dir" \
+"${SPROG} checkout: file first-dir/imported-f1 does not exist, but is present 
in revision junk-2_0
+RCS file: ${CVSROOT_DIRNAME}/first-dir/imported-f2,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 imported-f2
+rcsmerge: warning: conflicts during merge
+first-dir/imported-f3 already contains the differences between 1\.1\.1\.1 and 
1\.1\.1\.2
+first-dir/imported-f4 already contains the differences between 1\.1\.1\.1 and 
1\.1\.1\.3"
+
+               cd first-dir
+
+               dotest_fail import-114 "test -f imported-f1" ''
+
+               for i in 2 3 ; do
+                 dotest import-115-$i "test -f imported-f$i" ''
+               done
+
+               dotest import-116 'cat imported-f2' \
+'imported file2
+[<]<<<<<< imported-f2
+import should not expand \$''Id: imported-f2,v 1\.2 [0-9/]* [0-9:]* 
'"${username}"' Exp \$
+local-change
+[=]======
+import should not expand \$''Id: imported-f2,v 1\.1\.1\.2 [0-9/]* [0-9:]* 
'"${username}"' Exp \$
+rev 2 of file 2
+[>]>>>>>> 1\.1\.1\.2'
+
+               dokeep
+               cd ..
+               rm -r first-dir
+               modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+               rm -r import-dir
+               ;;
+
+
+
+       importb)
+         # More cvs import tests, especially -b option.
+
+         # OK, first we get some sources from the NetMunger project, and
+         # import them into the 1.1.1 vendor branch.
+         mkdir imp-dir
+         cd imp-dir
+         echo 'OpenMunger sources' >file1
+         echo 'OpenMunger sources' >file2
+         dotest_sort importb-1 \
+"${testcvs} import -m add first-dir openmunger openmunger-1_0" \
+"
+
+N first-dir/file1
+N first-dir/file2
+No conflicts created by this import"
+         cd ..
+         rm -r imp-dir
+
+         # Now we put the sources we get from FreeMunger into 1.1.3
+         mkdir imp-dir
+         cd imp-dir
+         echo 'FreeMunger sources' >file1
+         echo 'FreeMunger sources' >file2
+         # Not completely sure how the conflict detection is supposed to
+         # be working here (haven't really thought about it).
+         # We use an explicit -d option to test that it is reflected
+         # in the suggested checkout.
+         dotest_sort importb-2 \
+"$testcvs -d '$CVSROOT' import -m add -b 1.1.3 \
+          first-dir freemunger freemunger-1_0" \
+"
+
+
+ ${CPROG} -d ${CVSROOT} checkout -j<prev_rel_tag> -jfreemunger-1_0 first-dir
+2 conflicts created by this import.
+C first-dir/file1
+C first-dir/file2
+Use the following command to help the merge:"
+         cd ..
+         rm -r imp-dir
+
+         # Now a test of main branch import (into second-dir, not first-dir).
+         mkdir imp-dir
+         cd imp-dir
+         echo 'my own stuff' >mine1.c
+         echo 'my own stuff' >mine2.c
+         dotest_fail importb-3 \
+"${testcvs} import -m add -b 1 second-dir dummy really_dumb_y" \
+"$CPROG \[import aborted\]: Only numeric branch specifications with two dots 
are
+supported by import, not \`1'\.  For example: \`1\.1\.1'\."
+         : when we implement main-branch import, should be \
+"N second-dir/mine1\.c
+N second-dir/mine2\.c
+
+No conflicts created by this import"
+         cd ..
+         rm -r imp-dir
+
+         mkdir 1
+         cd 1
+         # when we implement main branch import, will want to 
+         # add "second-dir" here.
+         dotest importb-4 "${testcvs} -q co first-dir" \
+"U first-dir/file1
+U first-dir/file2"
+         cd first-dir
+         dotest importb-5 "${testcvs} -q log file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch: 1\.1\.1
+locks: strict
+access list:
+symbolic names:
+       freemunger-1_0: 1\.1\.3\.1
+       freemunger: 1\.1\.3
+       openmunger-1_0: 1\.1\.1\.1
+       openmunger: 1\.1\.1
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.1;  1\.1\.3;
+Initial revision
+----------------------------
+revision 1\.1\.3\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+add
+----------------------------
+revision 1\.1\.1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+add
+============================================================================="
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/second-dir
+         ;;
+
+
+
+       importc)
+         # Test importing a bunch o' files in a bunch o' directories.
+         # Also the -d option.
+
+         # Set a predictable time zone for these tests.
+         save_TZ=$TZ
+         TZ=UTC0; export TZ
+
+         mkdir 1; cd 1
+         mkdir adir bdir cdir
+         mkdir adir/sub1 adir/sub2
+         mkdir adir/sub1/ssdir
+         mkdir bdir/subdir
+         touch adir/sub1/file1 adir/sub2/file2 adir/sub1/ssdir/ssfile
+         touch -t ${TOUCH1971} bdir/subdir/file1
+         touch -t ${TOUCH2034} cdir/cfile
+         dotest_sort importc-1 \
+"${testcvs} import -d -m import-it first-dir vendor release" \
+"
+
+N first-dir/adir/sub1/file1
+N first-dir/adir/sub1/ssdir/ssfile
+N first-dir/adir/sub2/file2
+N first-dir/bdir/subdir/file1
+N first-dir/cdir/cfile
+No conflicts created by this import
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/first-dir/adir
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/first-dir/adir/sub1
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/first-dir/adir/sub1/ssdir
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/first-dir/adir/sub2
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/first-dir/bdir
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/first-dir/bdir/subdir
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/first-dir/cdir"
+         cd ..
+         mkdir 2; cd 2
+         dotest importc-2 "${testcvs} -q co first-dir" \
+"U first-dir/adir/sub1/file1
+U first-dir/adir/sub1/ssdir/ssfile
+U first-dir/adir/sub2/file2
+U first-dir/bdir/subdir/file1
+U first-dir/cdir/cfile"
+         cd first-dir
+         dotest importc-3 "${testcvs} update adir/sub1" \
+"${SPROG} update: Updating adir/sub1
+${SPROG} update: Updating adir/sub1/ssdir"
+         dotest importc-4 "${testcvs} update adir/sub1 bdir/subdir" \
+"${SPROG} update: Updating adir/sub1
+${SPROG} update: Updating adir/sub1/ssdir
+${SPROG} update: Updating bdir/subdir"
+
+         echo modify >>cdir/cfile
+         dotest importc-5 \
+"${testcvs} -q rtag -b -r release wip_test first-dir" ""
+         dotest importc-6 "${testcvs} -q update -r wip_test" "M cdir/cfile"
+
+         # This used to fail in local mode
+         dotest importc-7 "${testcvs} -q ci -m modify -r wip_test" \
+"$CVSROOT_DIRNAME/first-dir/cdir/cfile,v  <--  cdir/cfile
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1"
+
+         # TODO: should also be testing "import -d" when we update
+         # an existing file.
+         dotest importc-8 "${testcvs} -q log cdir/cfile" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/cdir/cfile,v
+Working file: cdir/cfile
+head: 1\.1
+branch: 1\.1\.1
+locks: strict
+access list:
+symbolic names:
+       wip_test: 1\.1\.1\.1\.0\.2
+       release: 1\.1\.1\.1
+       vendor: 1\.1\.1
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE2034};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.1;
+Initial revision
+----------------------------
+revision 1\.1\.1\.1
+date: ${ISO8601DATE2034};  author: ${username};  state: Exp;  lines: ${PLUS}0 
-0;  commitid: ${commitid};
+branches:  1\.1\.1\.1\.2;
+import-it
+----------------------------
+revision 1\.1\.1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+modify
+============================================================================="
+
+         dotest importc-9 "${testcvs} -q log bdir/subdir/file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/bdir/subdir/file1,v
+Working file: bdir/subdir/file1
+head: 1\.1
+branch: 1\.1\.1
+locks: strict
+access list:
+symbolic names:
+       wip_test: 1\.1\.1\.1\.0\.2
+       release: 1\.1\.1\.1
+       vendor: 1\.1\.1
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE1971};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.1;
+Initial revision
+----------------------------
+revision 1\.1\.1\.1
+date: ${ISO8601DATE1971};  author: ${username};  state: Exp;  lines: ${PLUS}0 
-0;  commitid: ${commitid};
+import-it
+============================================================================="
+         cd ..
+
+         # Now tests of absolute pathnames and .. as repository directory.
+         cd ../1
+         dotest_fail importc-10 \
+"${testcvs} import -m imp ../other vendor release2" \
+"${CPROG} \[import aborted\]: directory \.\./other not relative within the 
repository"
+         dotest_fail importc-11 \
+"${testcvs} import -m imp ${TESTDIR}/other vendor release3" \
+"${CPROG} \[import aborted\]: directory ${TESTDIR}/other not relative within 
the repository"
+         dotest_fail importc-12 "test -d ${TESTDIR}/other" ""
+
+         dokeep
+         TZ=$save_TZ
+         cd ..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       importX)
+         # More cvs import tests, especially -X option.
+
+         # OK, first we get some sources from the Munger version 0.9,
+         # and import them into the 1.1.1 vendor branch (w/o -X).  (This
+         # will be used to test subsequent imports of the same file
+         # with -X.)
+         mkdir imp-dir
+         cd imp-dir
+         echo 'Munger sources 0.9' >file0
+         dotest_sort importX-1 \
+"${testcvs} import -m add first-dir munger munger-0_9" \
+"
+
+N first-dir/file0
+No conflicts created by this import"
+         cd ..
+         rm -r imp-dir
+
+         # Now we put the sources we get from Munger version 1.0 on
+         # to the 1.1.1 vendor branch using -X.  (This imports a new
+         # version of file0, and imports all-new files file1 and file2.)
+         mkdir imp-dir
+         cd imp-dir
+         echo 'Munger sources' >file0
+         echo 'Munger sources' >file1
+         echo 'Munger sources' >file2
+         dotest_sort importX-2 \
+"${testcvs} import -X -m add first-dir munger munger-1_0" \
+"
+
+
+ ${CPROG} checkout -j<prev_rel_tag> -jmunger-1_0 first-dir
+N first-dir/file1
+N first-dir/file2
+No conflicts created by this import.
+U first-dir/file0
+Use the following command to help the merge:"
+         cd ..
+         rm -r imp-dir
+
+         # Now we put the sources we get from Munger version 1.1 on
+         # to the 1.1.1 vendor branch using -X.  (This imports unchanged
+         # versions of file0 and file2, a changed version of file1, and
+         # an all-new file3.)
+         mkdir imp-dir
+         cd imp-dir
+         echo 'Munger sources' >file0
+         echo 'Munger sources 1.1' >file1
+         echo 'Munger sources' >file2
+         echo 'Munger sources 1.1' >file3
+         dotest_sort importX-3 \
+"$testcvs -d '$CVSROOT' import -X -m add first-dir munger munger-1_1" \
+"
+
+
+ ${CPROG} -d ${CVSROOT} checkout -j<prev_rel_tag> -jmunger-1_1 first-dir
+1 conflicts created by this import.
+C first-dir/file1
+N first-dir/file3
+U first-dir/file0
+U first-dir/file2
+Use the following command to help the merge:"
+         cd ..
+         rm -r imp-dir
+
+         mkdir 1
+         cd 1
+         # only file0 should be checked out
+         dotest importX-4 "${testcvs} -q co first-dir" \
+"U first-dir/file0"
+         cd first-dir
+
+         dotest importX-5 "${testcvs} -q log file0" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file0,v
+Working file: file0
+head: 1\.1
+branch: 1\.1\.1
+locks: strict
+access list:
+symbolic names:
+       munger-1_1: 1\.1\.1\.2
+       munger-1_0: 1\.1\.1\.2
+       munger-0_9: 1\.1\.1\.1
+       munger: 1\.1\.1
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.1;
+Initial revision
+----------------------------
+revision 1\.1\.1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+add
+----------------------------
+revision 1\.1\.1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+add
+============================================================================="
+
+         dotest importX-6 "${testcvs} -q log file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+Working file: file1
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       munger-1_1: 1\.1\.1\.2
+       munger-1_0: 1\.1\.1\.1
+       munger: 1\.1\.1
+keyword substitution: kv
+total revisions: 4;    selected revisions: 4
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: dead;  lines: ${PLUS}0 -0; 
 commitid: ${commitid};
+Revision 1\.1 was added on the vendor branch\.
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.1;
+Initial revision
+----------------------------
+revision 1\.1\.1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+add
+----------------------------
+revision 1\.1\.1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+add
+============================================================================="
+
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf ${CVSROOT_DIRNAME}/first-dir
+         ;;
+
+
+
+       importX2)
+         # Test ImportNewFilesToVendorBranchOnly config file option.
+
+         # On Windows, we can't check out CVSROOT, because the case
+         # insensitivity means that this conflicts with cvsroot.
+         mkdir wnt
+         cd wnt
+
+         dotest importX2-1 "${testcvs} -q co CVSROOT" "U CVSROOT${DOTSTAR}"
+         cd CVSROOT
+          echo "ImportNewFilesToVendorBranchOnly=yes" >> config
+
+         dotest importX2-2 "$testcvs -q ci -m force-cvs-import-X" \
+"$TESTDIR/cvsroot/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         cd ../..
+
+         # Import a sources file, but do NOT specify -X.  The new file
+         # should be killed, anyway (because of the config option).
+         mkdir imp-dir
+         cd imp-dir
+         echo 'source' >file1
+         dotest_sort importX2-3 \
+"${testcvs} import -m add first-dir source source-1_0" \
+"
+
+
+ ${CPROG} checkout -j<prev_rel_tag> -jsource-1_0 first-dir
+N first-dir/file1
+No conflicts created by this import.
+Use the following command to help the merge:"
+         cd ..
+         rm -r imp-dir
+
+         mkdir 1
+         cd 1
+         # **nothing** should be checked out**
+         dotest importX2-4 "${testcvs} -q co first-dir" ""
+
+         cd first-dir
+         dotest importX2-5 "${testcvs} -q log file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+Working file: file1
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       source-1_0: 1\.1\.1\.1
+       source: 1\.1\.1
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: dead;  lines: ${PLUS}0 -0; 
 commitid: ${commitid};
+Revision 1\.1 was added on the vendor branch\.
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.1;
+Initial revision
+----------------------------
+revision 1\.1\.1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+add
+============================================================================="
+
+         dokeep
+         cd ../..
+         restore_adm
+         rm -r 1
+         rm -r wnt
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       import-CVS)
+         mkdir import-CVS
+         cd import-CVS
+         touch file1 file2 file3
+         dotest_fail import-CVS-1 "$testcvs import CVS vtag rtag" \
+"$CPROG import: The word \`CVS' is reserved by CVS and may not be used
+$CPROG \[import aborted\]: as a directory in a path or as a file name\."
+         mkdir sdir
+         mkdir sdir/CVS
+         touch sdir/CVS/file4 sdir/CVS/file5 sdir/file6 sdir/file7
+         # Calling the imported directory import-CVS is dual purpose in the
+         # following test.  It makes sure the path test which matched above
+         # wasn't too strict.
+         dotest_sort import-CVS-2 \
+"$testcvs import -I! -mimport import-CVS vtag rtag" \
+"
+
+I import-CVS/sdir/CVS
+N import-CVS/file1
+N import-CVS/file2
+N import-CVS/file3
+N import-CVS/sdir/file6
+N import-CVS/sdir/file7
+No conflicts created by this import
+$SPROG import: Importing $CVSROOT_DIRNAME/import-CVS/sdir"
+
+         dokeep
+         cd ..
+         rm -r import-CVS
+         modify_repo rm -rf $CVSROOT_DIRNAME/import-CVS
+         ;;
+
+
+
+       import-quirks)
+         # Short tests of quirky import behavior.
+         #
+         # For a list of other import tests with short descriptions, see the
+         # comment header of the "import" test.
+         mkdir import-quirks
+         cd import-quirks
+         touch file1 file2 file3
+
+         # CVS prior to 1.11.18 and 1.12.10 used to happily import to
+         # "branch 1.1", creating RCS archives with revisions like,
+         # "1.1..1".  That double-dot is *not* a typo.
+         dotest_fail import-quirks-1 \
+"$testcvs import -b1.1. -mbad-bad-bad import-quirks VB RT" \
+"$CPROG \[import aborted\]: Only numeric branch specifications with two dots 
are
+supported by import, not \`1\.1\.'\.  For example: \`1\.1\.1'\."
+
+         dotest_fail import-quirks-2 \
+"$testcvs import -b1.1.1.. -mbad-bad-bad import-quirks VB RT" \
+"$CPROG \[import aborted\]: Only numeric branch specifications with two dots 
are
+supported by import, not \`1\.1\.1\.\.'\.  For example: \`1\.1\.1'\."
+
+         # Try a few odd numbers.  This is hardly comprehensive.
+         dotest_sort import-quirks-2 \
+"$testcvs import -b10.10.101 -mthis-ones-ok import-quirks-2 VB RT" \
+"
+
+N import-quirks-2/file1
+N import-quirks-2/file2
+N import-quirks-2/file3
+No conflicts created by this import"
+
+         dotest_sort import-quirks-3 \
+"$testcvs import -b2345678901.2345678901.2345678901 -mthis-ones-ok 
import-quirks-3 VB RT" \
+"
+
+N import-quirks-3/file1
+N import-quirks-3/file2
+N import-quirks-3/file3
+No conflicts created by this import"
+
+         dotest_sort import-quirks-4 \
+"$testcvs import -b1.1.2 -mthis-ones-ok import-quirks-4 VB RT" \
+"
+
+N import-quirks-4/file1
+N import-quirks-4/file2
+N import-quirks-4/file3
+No conflicts created by this import"
+
+         dokeep
+         cd ..
+         rm -r import-quirks
+         rm -rf $CVSROOT_DIRNAME/import-quirks-2 \
+                $CVSROOT_DIRNAME/import-quirks-3 \
+                $CVSROOT_DIRNAME/import-quirks-4
+         ;;
+
+
+
+       import-after-initial)
+         # Properly handle the case in which the first version of a
+         # file is created by a regular cvs add and commit, and there
+         # is a subsequent cvs import of the same file.  cvs update with
+         # a date tag must resort to searching the vendor branch only if
+         # the initial version of the file was created at the same time
+         # as the initial version on the vendor branch.
+
+         mkdir 1; cd 1
+         module=x
+
+         echo > unused-file
+
+         # Create the module.
+         dotest import-after-initial-1 \
+           "$testcvs -Q import -m. $module X Y" ''
+
+         file=m
+         # Check it out and add a file.
+         dotest import-after-initial-2 "$testcvs -Q co $module" ''
+         cd $module
+         echo original > $file
+         dotest import-after-initial-3 "${testcvs} -Q add $file" ""
+         dotest import-after-initial-4 "$testcvs -Q ci -m. $file"
+
+         # Delay a little so the following import isn't done in the same
+         # second as the preceding commit.
+         sleep 2
+
+         # Do the first import of $file *after* $file already has an
+         # initial version.
+         mkdir sub
+         cd sub
+         echo newer-via-import > $file
+         dotest import-after-initial-5 \
+           "$testcvs -Q import -m. $module X Y2" ''
+         cd ..
+
+         # Sleep a second so we're sure to be after the second of the import.
+         sleep 1
+
+         dotest import-after-initial-6 \
+           "$testcvs -Q update -p -D now $file" 'original'
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         ;;
+
+
+
+        branch-after-import)
+         # Test branching after an import via both cvs tag -b and
+         # cvs add to verify that the HEAD remains at 1.1.1.1
+         # This was a FreeBSD bug documented at the URL:
+         # http://www.freebsd.org/cgi/query-pr.cgi?pr=4033
+
+         mkdir branch-after-import
+         cd branch-after-import
+
+         # OK, first we get some sources from the NetMunger project,
+         # and import them into the 1.1.1 vendor branch.
+         mkdir imp-dir
+         cd imp-dir
+         echo 'OpenMunger sources' >file1
+         echo 'OpenMunger sources' >file2
+         dotest_sort branch-after-import-1 \
+"${testcvs} import -m add first-dir openmunger openmunger-1_0" \
+'
+
+N first-dir/file1
+N first-dir/file2
+No conflicts created by this import'
+         cd ..
+
+         # Next checkout the new module
+         dotest branch-after-import-2 \
+"${testcvs} -q co first-dir" \
+'U first-dir/file1
+U first-dir/file2'
+         cd first-dir
+         # Branch tag the file1 and cvs add file2,
+         # the branch should remain the same in both cases
+         # such that a new import will not require a conflict
+         # resolution.
+         dotest branch-after-import-3 \
+"${testcvs} tag -b TESTTOTRON file1" \
+'T file1'
+         dotest branch-after-import-4 \
+"${testcvs} -q update -r TESTTOTRON" \
+"${SPROG} update: \`file2' is no longer in the repository"
+
+         cp ../imp-dir/file2 .
+         dotest branch-after-import-5 \
+"${testcvs} add file2" \
+"${SPROG} add: scheduling file .file2. for addition on branch .TESTTOTRON.
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+
+         dotest branch-after-import-6 \
+"$testcvs commit -m cvs-add file2" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.1\.1\.1\.2\.2; previous revision: 1\.1\.1\.1\.2\.1"
+
+         dokeep
+         cd ../..
+         rm -r branch-after-import
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       join)
+         # Test doing joins which involve adding and removing files.
+         #   Variety of scenarios (see list below), in the context of:
+         #     * merge changes from T1 to T2 into the main line
+         #     * merge changes from branch 'branch' into the main line
+         #     * merge changes from branch 'branch' into branch 'br2'.
+         # See also binfile2, which does similar things with binary files.
+         # See also join2, which tests joining (and update -A) on only
+         # a single file, rather than a directory.
+         # See also rmadd2, which tests -j cases not involving branches
+         #   (e.g. undoing a commit)
+         # See also join3, which tests some cases involving the greatest
+         # common ancestor.  Here is a list of tests according to branch
+         # topology:
+         #
+         # --->bp---->trunk          too many to mention
+         #     \----->branch
+         #
+         #     /----->branch1
+         # --->bp---->trunk          multibranch, multibranch2
+         #     \----->branch2
+         #
+         # --->bp1----->bp2---->trunk   join3
+         #     \->br1   \->br2
+         #
+         # --->bp1----->trunk
+         #     \----bp2---->branch                branches
+         #          \------>branch-of-branch
+
+         # We check merging changes from T1 to T2 into the main line.
+         # Here are the interesting cases I can think of:
+         #   1) File added between T1 and T2, not on main line.
+         #      File should be marked for addition.
+         #   2) File added between T1 and T2, also added on main line.
+         #      Conflict.
+         #   3) File removed between T1 and T2, unchanged on main line.
+         #      File should be marked for removal.
+         #   4) File removed between T1 and T2, modified on main line.
+         #      If mod checked in, file should be marked for removal.
+         #      If mod still in working directory, conflict.
+         #   5) File removed between T1 and T2, was never on main line.
+         #      Nothing should happen.
+         #   6) File removed between T1 and T2, also removed on main line.
+         #      Nothing should happen.
+         #   7) File not added between T1 and T2, added on main line.
+         #      Nothing should happen.
+         #   8) File not modified between T1 and T2, removed on main line.
+         #      Nothing should happen.
+         #   9) File modified between T1 and T2, removed on main line.
+         #      Conflict.
+         #  10) File was never on branch, removed on main line.
+         #      Nothing should happen.
+
+         # We also check merging changes from a branch into the main
+         # line.  Here are the interesting cases:
+         #   1) File added on branch, not on main line.
+         #      File should be marked for addition.
+         #   2) File added on branch, also added on main line.
+         #      Conflict.
+         #   3) File removed on branch, unchanged on main line.
+         #      File should be marked for removal.
+         #   4) File removed on branch, modified on main line.
+         #      Conflict.
+         #   5) File removed on branch, was never on main line.
+         #      Nothing should happen.
+         #   6) File removed on branch, also removed on main line.
+         #      Nothing should happen.
+         #   7) File added on main line, not added on branch.
+         #      Nothing should happen.
+         #   8) File removed on main line, not modified on branch.
+         #      Nothing should happen.
+         #   9) File modified on branch, removed on main line.
+         #      Conflict.
+         #  10) File was never on branch, removed on main line.
+         #      Nothing should happen.
+
+         # In the tests below, fileN represents case N in the above
+         # lists.
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir 1
+         cd 1
+         dotest join-1 "$testcvs -q co first-dir"
+
+         cd first-dir
+
+         # Add two files.
+         echo 'first revision of file3' > file3
+         echo 'first revision of file4' > file4
+         echo 'first revision of file6' > file6
+         echo 'first revision of file8' > file8
+         echo 'first revision of file9' > file9
+         dotest join-2 "${testcvs} add file3 file4 file6 file8 file9" \
+"${SPROG}"' add: scheduling file `file3'\'' for addition
+'"${SPROG}"' add: scheduling file `file4'\'' for addition
+'"${SPROG}"' add: scheduling file `file6'\'' for addition
+'"${SPROG}"' add: scheduling file `file8'\'' for addition
+'"${SPROG}"' add: scheduling file `file9'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add these files permanently'
+
+         dotest join-3 "${testcvs} -q commit -m add" \
+"$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file6,v  <--  file6
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file8,v  <--  file8
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file9,v  <--  file9
+initial revision: 1\.1"
+
+         # Make a branch.
+         dotest join-4 "${testcvs} -q tag -b branch ." \
+'T file3
+T file4
+T file6
+T file8
+T file9'
+
+         # Add file2, file7, and file10, modify file4, and remove
+         # file6, file8, and file9.
+         echo 'first revision of file2' > file2
+         echo 'second revision of file4' > file4
+         echo 'first revision of file7' > file7
+         rm file6 file8 file9
+         echo 'first revision of file10' > file10
+         dotest join-5 "${testcvs} add file2 file7 file10" \
+"${SPROG}"' add: scheduling file `file2'\'' for addition
+'"${SPROG}"' add: scheduling file `file7'\'' for addition
+'"${SPROG}"' add: scheduling file `file10'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add these files permanently'
+         dotest join-6 "${testcvs} rm file6 file8 file9" \
+"${SPROG}"' remove: scheduling `file6'\'' for removal
+'"${SPROG}"' remove: scheduling `file8'\'' for removal
+'"${SPROG}"' remove: scheduling `file9'\'' for removal
+'"${SPROG}"' remove: use .'"${SPROG}"' commit. to remove these files 
permanently'
+         dotest join-7 "${testcvs} -q ci -mx ." \
+"$CVSROOT_DIRNAME/first-dir/file10,v  <--  file10
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: 1\.2; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file6,v  <--  file6
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file7,v  <--  file7
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file8,v  <--  file8
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file9,v  <--  file9
+new revision: delete; previous revision: 1\.1"
+
+         # Remove file10
+         dotest join-7a "${testcvs} rm -f file10" \
+"${SPROG}"' remove: scheduling `file10'\'' for removal
+'"${SPROG}"' remove: use .'"${SPROG}"' commit. to remove this file permanently'
+         dotest join-7b "${testcvs} -q ci -mx ." \
+"$CVSROOT_DIRNAME/first-dir/file10,v  <--  file10
+new revision: delete; previous revision: 1\.1"
+
+         # Check out the branch.
+         cd ../..
+         mkdir 2
+         cd 2
+         dotest join-8 "${testcvs} -q co -r branch first-dir" \
+'U first-dir/file3
+U first-dir/file4
+U first-dir/file6
+U first-dir/file8
+U first-dir/file9'
+
+         cd first-dir
+
+         # Modify the files on the branch, so that T1 is not an
+         # ancestor of the main line, and add file5
+         echo 'first branch revision of file3' > file3
+         echo 'first branch revision of file4' > file4
+         echo 'first branch revision of file5' > file5
+         echo 'first branch revision of file6' > file6
+         echo 'first branch revision of file9' > file9
+         dotest join-9 "${testcvs} add file5" \
+"${SPROG}"' add: scheduling file `file5'\'' for addition on branch `branch'\''
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest join-10 "${testcvs} -q ci -mx ." \
+"$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file5,v  <--  file5
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file6,v  <--  file6
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file9,v  <--  file9
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         # Tag the current revisions on the branch.
+         dotest join-11 "${testcvs} -q tag T1 ." \
+'T file3
+T file4
+T file5
+T file6
+T file8
+T file9'
+
+         # Add file1 and file2, modify file9, and remove the other files.
+         echo 'first branch revision of file1' > file1
+         echo 'first branch revision of file2' > file2
+         echo 'second branch revision of file9' > file9
+         rm file3 file4 file5 file6
+         dotest join-12 "${testcvs} add file1 file2" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition on branch `branch'\''
+'"${SPROG}"' add: scheduling file `file2'\'' for addition on branch `branch'\''
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add these files permanently'
+         dotest join-13 "${testcvs} rm file3 file4 file5 file6" \
+"${SPROG}"' remove: scheduling `file3'\'' for removal
+'"${SPROG}"' remove: scheduling `file4'\'' for removal
+'"${SPROG}"' remove: scheduling `file5'\'' for removal
+'"${SPROG}"' remove: scheduling `file6'\'' for removal
+'"${SPROG}"' remove: use .'"${SPROG}"' commit. to remove these files 
permanently'
+         dotest join-14 "${testcvs} -q ci -mx ." \
+"$CVSROOT_DIRNAME/first-dir/Attic/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+new revision: delete; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: delete; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file5,v  <--  file5
+new revision: delete; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file6,v  <--  file6
+new revision: delete; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file9,v  <--  file9
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+
+         # Tag the current revisions on the branch.
+         dotest join-15 "${testcvs} -q tag T2 ." \
+'T file1
+T file2
+T file8
+T file9'
+
+         # Do a checkout with a merge.
+         cd ../..
+         mkdir 3
+         cd 3
+         dotest_fail join-16a \
+"$testcvs -q co -jno-such-tag -jT2 first-dir" \
+"$SPROG \[checkout aborted\]: no such tag \`no-such-tag'"
+
+         dotest join-16b "$testcvs -q co -jT1 -jT2 first-dir" \
+"U first-dir/file1
+U first-dir/file2
+${SPROG} checkout: file first-dir/file2 exists, but has been added in revision 
T2
+U first-dir/file3
+${SPROG} checkout: scheduling \`first-dir/file3' for removal
+U first-dir/file4
+${SPROG} checkout: scheduling \`first-dir/file4' for removal
+U first-dir/file7
+${SPROG} checkout: file first-dir/file9 does not exist, but is present in 
revision T2"
+
+         # Verify that the right changes have been scheduled.
+         cd first-dir
+         dotest join-17 "${testcvs} -q update" \
+'A file1
+R file3
+R file4'
+
+         # Modify file4 locally, and do an update with a merge.
+         cd ../../1/first-dir
+         echo 'third revision of file4' > file4
+         dotest join-18 "${testcvs} -q update -jT1 -jT2 ." \
+"U file1
+$SPROG update: file file2 exists, but has been added in revision T2
+$SPROG update: scheduling \`file3' for removal
+M file4
+$SPROG update: file file4 is locally modified, but has been removed in 
revision T2
+$SPROG update: file file9 does not exist, but is present in revision T2"
+
+         # Verify that the right changes have been scheduled.
+         dotest join-19 "${testcvs} -q update" \
+'A file1
+R file3
+M file4'
+
+         # Do a checkout with a merge from a single revision.
+
+         # FIXME: CVS currently gets this wrong.  file2 has been
+         # added on both the branch and the main line, and so should
+         # be regarded as a conflict.  However, given the way that
+         # CVS sets up the RCS file, there is no way to distinguish
+         # this case from the case of file2 having existed before the
+         # branch was made.  This could be fixed by reserving
+         # a revision somewhere, perhaps 1.1, as an always dead
+         # revision which can be used as the source for files added
+         # on branches.
+         cd ../../3
+         rm -r first-dir
+         dotest join-20 "${testcvs} -q co -jbranch first-dir" \
+"U first-dir/file1
+U first-dir/file2
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.2
+Merging differences between 1\.1 and 1\.1\.2\.2 into file2
+U first-dir/file3
+${SPROG} checkout: scheduling \`first-dir/file3' for removal
+U first-dir/file4
+${SPROG} checkout: file first-dir/file4 has been modified, but has been 
removed in revision branch
+U first-dir/file7
+${SPROG} checkout: file first-dir/file9 does not exist, but is present in 
revision branch"
+
+         # Verify that the right changes have been scheduled.
+         # The M file2 line is a bug; see above join-20.
+         cd first-dir
+         dotest join-21 "${testcvs} -q update" \
+'A file1
+M file2
+R file3'
+
+         # Checkout the main line again.
+         cd ../../1
+         rm -r first-dir
+         dotest join-22 "${testcvs} -q co first-dir" \
+'U first-dir/file2
+U first-dir/file3
+U first-dir/file4
+U first-dir/file7'
+
+         # Modify file4 locally, and do an update with a merge from a
+         # single revision.
+         # The file2 handling is a bug; see above join-20.
+         cd first-dir
+         echo 'third revision of file4' > file4
+         dotest join-23 "${testcvs} -q update -jbranch ." \
+"U file1
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.2
+Merging differences between 1\.1 and 1\.1\.2\.2 into file2
+${SPROG} update: scheduling \`file3' for removal
+M file4
+${SPROG} update: file file4 is locally modified, but has been removed in 
revision branch
+${SPROG} update: file file9 does not exist, but is present in revision branch"
+
+         # Verify that the right changes have been scheduled.
+         # The M file2 line is a bug; see above join-20
+         dotest join-24 "${testcvs} -q update" \
+'A file1
+M file2
+R file3
+M file4'
+
+         cd ..
+
+         # Checkout the main line again and make a new branch which we
+         # merge to.
+         rm -r first-dir
+         dotest join-25 "${testcvs} -q co first-dir" \
+'U first-dir/file2
+U first-dir/file3
+U first-dir/file4
+U first-dir/file7'
+         cd first-dir
+         dotest join-26 "${testcvs} -q tag -b br2" \
+"T file2
+T file3
+T file4
+T file7"
+         dotest join-27 "${testcvs} -q update -r br2" ""
+         # The handling of file8 and file9 here look fishy to me.  I don't
+         # see why it should be different from the case where we merge to
+         # the trunk (e.g. join-23).
+         dotest join-28 "${testcvs} -q update -j branch" \
+"U file1
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+retrieving revision 1.1
+retrieving revision 1.1.2.2
+Merging differences between 1.1 and 1.1.2.2 into file2
+${SPROG} update: scheduling \`file3' for removal
+${SPROG} update: file file4 has been modified, but has been removed in 
revision branch
+U file8
+U file9"
+         # Verify that the right changes have been scheduled.
+         dotest join-29 "${testcvs} -q update" \
+"A file1
+M file2
+R file3
+A file8
+A file9"
+
+         # Checkout the mainline again to try updating and merging between two
+         # branches in the same step
+         # this seems a likely scenario - the user finishes up on branch and
+         # updates to br2 and merges in the same step - and there was a bug
+         # once that if the file was removed in the update then it wouldn't be
+         # readded in the merge
+         cd ..
+         rm -r first-dir
+         dotest join-twobranch-1 "${testcvs} -q co -rbranch first-dir" \
+'U first-dir/file1
+U first-dir/file2
+U first-dir/file8
+U first-dir/file9'
+         cd first-dir
+         dotest join-twobranch-2 "${testcvs} -q update -rbr2 -jbranch" \
+"${SPROG} update: \`file1' is no longer in the repository
+U file1
+U file2
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.2
+Merging differences between 1\.1 and 1\.1\.2\.2 into file2
+U file3
+${SPROG} update: scheduling \`file3' for removal
+U file4
+${SPROG} update: file file4 has been modified, but has been removed in 
revision branch
+U file7
+${SPROG} update: \`file8' is no longer in the repository
+U file8
+${SPROG} update: \`file9' is no longer in the repository
+U file9"
+         # Verify that the right changes have been scheduled.
+         dotest join-twobranch-3 "${testcvs} -q update" \
+"A file1
+M file2
+R file3
+A file8
+A file9"
+
+         # Checkout the mainline again to try merging from the trunk
+         # to a branch.
+         cd ..
+         rm -r first-dir
+         dotest join-30 "${testcvs} -q co first-dir" \
+'U first-dir/file2
+U first-dir/file3
+U first-dir/file4
+U first-dir/file7'
+         cd first-dir
+
+         # Tag the current revisions on the trunk.
+         dotest join-31 "${testcvs} -q tag T3 ." \
+'T file2
+T file3
+T file4
+T file7'
+
+         # Modify file7.
+         echo 'second revision of file7' > file7
+         dotest join-32 "${testcvs} -q ci -mx ." \
+"$CVSROOT_DIRNAME/first-dir/file7,v  <--  file7
+new revision: 1\.2; previous revision: 1\.1"
+
+         # And Tag again.
+         dotest join-33 "${testcvs} -q tag T4 ." \
+'T file2
+T file3
+T file4
+T file7'
+
+         # Now update branch to T3.
+         cd ../../2/first-dir
+         dotest join-34 "${testcvs} -q up -jT3" \
+"${SPROG} update: file file4 does not exist, but is present in revision T3
+U file7"
+
+         # Verify that the right changes have been scheduled.
+         dotest join-35 "${testcvs} -q update" \
+'A file7'
+
+         # Now update to T4.
+         # This is probably a bug, although in this particular case it just
+         # happens to do the right thing; see above join-20.
+         dotest join-36 "${testcvs} -q up -j T3 -j T4" \
+"A file7
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file7,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into file7"
+
+         # Verify that the right changes have been scheduled.
+         dotest join-37 "${testcvs} -q update" \
+'A file7'
+
+         dokeep
+         cd ../..
+         rm -r 1 2 3
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       join2)
+         # More joining tests.
+
+         # First the usual setup; create a directory first-dir, a file
+         # first-dir/file1, and a branch br1.
+         mkdir 1; cd 1
+         dotest join2-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest join2-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+         echo 'initial contents of file1' >file1
+         dotest join2-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest join2-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         dotest join2-5 "${testcvs} -q tag -b br1" "T file1"
+         dotest join2-6 "${testcvs} -q update -r br1" ""
+         echo 'modify on branch' >>file1
+         touch bradd
+         dotest join2-6a "${testcvs} add bradd" \
+"${SPROG} add: scheduling file .bradd. for addition on branch .br1.
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest join2-7 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/Attic/bradd,v  <--  bradd
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         # Here is the unusual/pathological part.  We switch back to
+         # the trunk *for file1 only*, not for the whole directory.
+         dotest join2-8 "${testcvs} -q update -A file1" 'U file1'
+         dotest join2-9 "${testcvs} -q status file1" \
+"===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest join2-10 "cat CVS/Tag" "Tbr1"
+
+         dotest join2-11 "${testcvs} -q update -j br1 file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.1
+Merging differences between 1\.1 and 1\.1\.2\.1 into file1"
+         dotest join2-12 "cat file1" "initial contents of file1
+modify on branch"
+         # We should have no sticky tag on file1
+         dotest join2-13 "${testcvs} -q status file1" \
+"===================================================================
+File: file1                    Status: Locally Modified
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest join2-14 "cat CVS/Tag" "Tbr1"
+         # And the checkin should go to the trunk
+         dotest join2-15 "${testcvs} -q ci -m modify file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+
+         # OK, the above is all well and good and has worked for some
+         # time.  Now try the case where the file had been added on
+         # the branch.
+         dotest join2-16 "${testcvs} -q update -r br1" "U file1"
+         # The workaround is to update the whole directory.
+         # The non-circumvented version won't work.  The reason is that
+         # update removes the entry from CVS/Entries, so of course we get
+         # the tag from CVS/Tag and not Entries.  I suppose maybe
+         # we could invent some new format in Entries which would handle
+         # this, but doing so, and handling it properly throughout
+         # CVS, would be a lot of work and I'm not sure this case justifies
+         # it.
+         dotest join2-17-circumvent "${testcvs} -q update -A" \
+"${SPROG} update: \`bradd' is no longer in the repository
+U file1"
+:        dotest join2-17 "${testcvs} -q update -A bradd" \
+"${SPROG} update: warning: \`bradd' is not (any longer) pertinent"
+         dotest join2-18 "${testcvs} -q update -j br1 bradd" "U bradd"
+         dotest join2-19 "${testcvs} -q status bradd" \
+"===================================================================
+File: bradd                    Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        1\.1    
${CVSROOT_DIRNAME}/first-dir/Attic/bradd,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest join2-20 "${testcvs} -q ci -m modify bradd" \
+"$CVSROOT_DIRNAME/first-dir/bradd,v  <--  bradd
+new revision: 1\.2; previous revision: 1\.1"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       join3)
+         # See "join" for a list of other joining/branching tests.
+         # First the usual setup; create a directory first-dir, a file
+         # first-dir/file1, and a branch br1.
+         mkdir 1; cd 1
+         dotest join3-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest join3-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         echo 'initial contents of file1' >file1
+         dotest join3-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest join3-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         dotest join3-5 "${testcvs} -q tag -b br1" "T file1"
+         dotest join3-6 "${testcvs} -q update -r br1" ""
+         echo 'br1:line1' >>file1
+         dotest join3-7 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         # Now back to the trunk for:
+         # another revision and another branch for file1.
+         # add file2, which will exist on trunk and br2 but not br1.
+         dotest join3-8 "${testcvs} -q update -A" "U file1"
+         echo 'trunk:line1' > file2
+         dotest join3-8a "${testcvs} add file2" \
+"${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         echo 'trunk:line1' >>file1
+         dotest join3-9 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+         dotest join3-10 "${testcvs} -q tag -b br2" "T file1
+T file2"
+
+         # Before we actually have any revision on br2, let's try a join
+         dotest join3-11 "${testcvs} -q update -r br1" "U file1
+${SPROG} update: \`file2' is no longer in the repository"
+         dotest join3-12 "${testcvs} -q update -j br2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into file1
+rcsmerge: warning: conflicts during merge
+U file2"
+         dotest join3-13 "cat file1" \
+"initial contents of file1
+[<]<<<<<< file1
+br1:line1
+[=]======
+trunk:line1
+[>]>>>>>> 1\.2"
+         rm file1
+
+         # OK, we'll try the same thing with a revision on br2.
+         dotest join3-14 "${testcvs} -q update -r br2 file1" \
+"${SPROG} update: warning: \`file1' was lost
+U file1" "U file1"
+         echo 'br2:line1' >>file1
+         dotest join3-15 "${testcvs} -q ci -m modify file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2\.2\.1; previous revision: 1\.2"
+
+         # OK, now we can join br2 to br1
+         dotest join3-16 "${testcvs} -q update -r br1 file1" "U file1"
+         # It may seem odd, to merge a higher branch into a lower
+         # branch, but in fact CVS defines the ancestor as 1.1
+         # and so it merges both the 1.1->1.2 and 1.2->1.2.2.1 changes.
+         # This seems like a reasonably plausible behavior.
+         dotest join3-17 "${testcvs} -q update -j br2 file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.2\.2\.1
+Merging differences between 1\.1 and 1\.2\.2\.1 into file1
+rcsmerge: warning: conflicts during merge"
+         dotest join3-18 "cat file1" \
+"initial contents of file1
+[<]<<<<<< file1
+br1:line1
+[=]======
+trunk:line1
+br2:line1
+[>]>>>>>> 1\.2\.2\.1"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       join4)
+         # Like join, but with local (uncommitted) modifications.
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir 1
+         cd 1
+         dotest join4-1 "${testcvs} -q co first-dir" ''
+
+         cd first-dir
+
+         # Add two files.
+         echo 'first revision of file3' > file3
+         echo 'first revision of file4' > file4
+         echo 'first revision of file6' > file6
+         echo 'first revision of file8' > file8
+         echo 'first revision of file9' > file9
+         dotest join4-2 "${testcvs} add file3 file4 file6 file8 file9" \
+"${SPROG}"' add: scheduling file `file3'\'' for addition
+'"${SPROG}"' add: scheduling file `file4'\'' for addition
+'"${SPROG}"' add: scheduling file `file6'\'' for addition
+'"${SPROG}"' add: scheduling file `file8'\'' for addition
+'"${SPROG}"' add: scheduling file `file9'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add these files permanently'
+
+         dotest join4-3 "${testcvs} -q commit -m add" \
+"$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file6,v  <--  file6
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file8,v  <--  file8
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file9,v  <--  file9
+initial revision: 1\.1"
+
+         # Make a branch.
+         dotest join4-4 "${testcvs} -q tag -b branch ." \
+'T file3
+T file4
+T file6
+T file8
+T file9'
+
+         # Add file10
+         echo 'first revision of file10' > file10
+         dotest join4-7a "${testcvs} add file10" \
+"${SPROG}"' add: scheduling file `file10'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest join4-7b "${testcvs} -q ci -mx ." \
+"$CVSROOT_DIRNAME/first-dir/file10,v  <--  file10
+initial revision: 1\.1"
+
+         # Add file2 and file7, modify file4, and remove
+         # file6, file8, file9, and file10.
+         echo 'first revision of file2' > file2
+         echo 'second revision of file4' > file4
+         echo 'first revision of file7' > file7
+         rm file6 file8 file9 file10
+         dotest join4-5 "${testcvs} add file2 file7" \
+"${SPROG}"' add: scheduling file `file2'\'' for addition
+'"${SPROG}"' add: scheduling file `file7'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add these files permanently'
+         dotest join4-6 "${testcvs} rm file6 file8 file9 file10" \
+"${SPROG}"' remove: scheduling `file6'\'' for removal
+'"${SPROG}"' remove: scheduling `file8'\'' for removal
+'"${SPROG}"' remove: scheduling `file9'\'' for removal
+'"${SPROG}"' remove: scheduling `file10'\'' for removal
+'"${SPROG}"' remove: use .'"${SPROG}"' commit. to remove these files 
permanently'
+
+         # Check out the branch.
+         cd ../..
+         mkdir 2
+         cd 2
+         dotest join4-8 "${testcvs} -q co -r branch first-dir" \
+'U first-dir/file3
+U first-dir/file4
+U first-dir/file6
+U first-dir/file8
+U first-dir/file9'
+
+         cd first-dir
+
+         # Modify the files on the branch, so that T1 is not an
+         # ancestor of the main line, and add file5
+         echo 'first branch revision of file3' > file3
+         echo 'first branch revision of file4' > file4
+         echo 'first branch revision of file5' > file5
+         echo 'first branch revision of file6' > file6
+         echo 'first branch revision of file9' > file9
+         dotest join4-9 "${testcvs} add file5" \
+"${SPROG}"' add: scheduling file `file5'\'' for addition on branch `branch'\''
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest join4-10 "${testcvs} -q ci -mx ." \
+"$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file5,v  <--  file5
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file6,v  <--  file6
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file9,v  <--  file9
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         # Tag the current revisions on the branch.
+         dotest join4-11 "${testcvs} -q tag T1 ." \
+'T file3
+T file4
+T file5
+T file6
+T file8
+T file9'
+
+         # Add file1 and file2, modify file9, and remove the other files.
+         echo 'first branch revision of file1' > file1
+         echo 'first branch revision of file2' > file2
+         echo 'second branch revision of file9' > file9
+         rm file3 file4 file5 file6
+         dotest join4-12 "${testcvs} add file1 file2" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition on branch `branch'\''
+'"${SPROG}"' add: scheduling file `file2'\'' for addition on branch `branch'\''
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add these files permanently'
+         dotest join4-13 "${testcvs} rm file3 file4 file5 file6" \
+"${SPROG}"' remove: scheduling `file3'\'' for removal
+'"${SPROG}"' remove: scheduling `file4'\'' for removal
+'"${SPROG}"' remove: scheduling `file5'\'' for removal
+'"${SPROG}"' remove: scheduling `file6'\'' for removal
+'"${SPROG}"' remove: use .'"${SPROG}"' commit. to remove these files 
permanently'
+         dotest join4-14 "${testcvs} -q ci -mx ." \
+"$CVSROOT_DIRNAME/first-dir/Attic/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file2,v  <--  file2
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file3,v  <--  file3
+new revision: delete; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/file4,v  <--  file4
+new revision: delete; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file5,v  <--  file5
+new revision: delete; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/file6,v  <--  file6
+new revision: delete; previous revision: 1\.1\.2\.1
+$CVSROOT_DIRNAME/first-dir/file9,v  <--  file9
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+
+         # Tag the current revisions on the branch.
+         dotest join4-15 "${testcvs} -q tag T2 ." \
+'T file1
+T file2
+T file8
+T file9'
+
+         # Modify file4 locally, and do an update with a merge.
+         cd ../../1/first-dir
+         echo 'third revision of file4' > file4
+         dotest join4-18 "${testcvs} -q update -jT1 -jT2 ." \
+"U file1
+R file10
+A file2
+${SPROG} update: file file2 exists, but has been added in revision T2
+${SPROG} update: scheduling \`file3' for removal
+M file4
+${SPROG} update: file file4 is locally modified, but has been removed in 
revision T2
+R file6
+A file7
+R file8
+R file9
+${SPROG} update: file file9 does not exist, but is present in revision T2"
+
+         # Verify that the right changes have been scheduled.
+         dotest join4-19 "${testcvs} -q update" \
+'A file1
+R file10
+A file2
+R file3
+M file4
+R file6
+A file7
+R file8
+R file9'
+
+         dokeep
+         cd ../..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       join5)
+         # This test verifies that CVS can handle filenames starting with a
+         # dash (`-') properly.  What used to happen was that CVS handled it
+         # just fine, until it went to pass them as arguments to the diff
+         # library, at which point it neglected to pass `--' before the file
+         # list, causing the diff library to attempt to interpret the file
+         # name as an argument.
+         mkdir join5; cd join5
+         mkdir 1; cd 1
+         dotest join5-init-1 "${testcvs} -Q co -l ."
+         mkdir join5
+         dotest join5-init-2 "${testcvs} -Q add join5"
+         cd join5
+         echo "there once was a file from harrisburg" >-file
+         echo "who's existance it seems was quiteabsurd" >>-file
+         dotest join5-init-3 "${testcvs} -Q add -- -file"
+         dotest join5-init-4 "${testcvs} -q ci -minitial" \
+"$CVSROOT_DIRNAME/join5/-file,v  <--  -file
+initial revision: 1\.1"
+         cd ../..
+
+         mkdir 2; cd 2
+         dotest join5-init-5 "${testcvs} -Q co join5"
+         cd join5
+         echo "it tested for free" >>-file
+         echo "when paid it should be" >>-file
+         dotest join5-init-4 "${testcvs} -q ci -msecond" \
+"$CVSROOT_DIRNAME/join5/-file,v  <--  -file
+new revision: 1\.2; previous revision: 1\.1"
+         cd ../..
+
+         cd 1/join5
+         echo "but maybe it could charge bytheword" >>-file
+         # This is the test that used to spew complaints from diff3:
+         dotest join5 "${testcvs} up" \
+"${SPROG} update: Updating \.
+RCS file: ${CVSROOT_DIRNAME}/join5/-file,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into -file
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in -file
+C -file"
+
+         dokeep
+         cd ../../..
+         rm -r join5
+         modify_repo rm -rf $CVSROOT_DIRNAME/join5
+         ;;
+
+
+
+        join6)
+         mkdir join6; cd join6
+          mkdir 1; cd 1
+         dotest join6-init-1 "${testcvs} -Q co -l ."
+         mkdir join6
+         dotest join6-init-2 "${testcvs} -Q add join6"
+         cd join6
+          echo aaa >temp.txt
+         echo bbb >>temp.txt
+         echo ccc >>temp.txt
+         dotest join6-1 "${testcvs} -Q add temp.txt"
+         dotest join6-2 "${testcvs} -q commit -minitial temp.txt" \
+"$CVSROOT_DIRNAME/join6/temp\.txt,v  <--  temp\.txt
+initial revision: 1\.1"
+         cp temp.txt temp2.txt
+         echo ddd >>temp.txt
+         dotest join6-3 "${testcvs} -q commit -madd temp.txt" \
+"$CVSROOT_DIRNAME/join6/temp.txt,v  <--  temp\.txt
+new revision: 1\.2; previous revision: 1\.1"
+
+         # The case where the merge target is up-to-date and its base revision
+         # matches the second argument to -j: CVS doesn't bother attempting
+         # the merge since it already knows that the target contains the
+         # change.
+         dotest join6-3.3 "${testcvs} update -j1.1 -j1.2 temp.txt" \
+"temp\.txt already contains the differences between 1\.1 and 1\.2"
+         dotest join6-3.4 "${testcvs} diff temp.txt" ""
+
+         # The case where the merge target is modified but already contains
+         # the change.
+         echo bbb >temp.txt
+         echo ccc >>temp.txt
+         echo ddd >>temp.txt
+         dotest join6-3.5 "${testcvs} update -j1.1 -j1.2 temp.txt" \
+"M temp\.txt
+RCS file: ${CVSROOT_DIRNAME}/join6/temp\.txt,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into temp\.txt
+temp\.txt already contains the differences between 1\.1 and 1\.2"
+         dotest_fail join6-3.6 "${testcvs} diff temp.txt" \
+"Index: temp\.txt
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/join6/temp\.txt,v
+retrieving revision 1\.2
+diff -r1\.2 temp.txt
+1d0
+< aaa"
+
+         cp temp2.txt temp.txt
+         dotest_fail join6-4 "${testcvs} diff temp.txt" \
+"Index: temp.txt
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/join6/temp\.txt,v
+retrieving revision 1\.2
+diff -r1\.2 temp\.txt
+4d3
+< ddd"
+
+         dotest join6-5 "${testcvs} update -j1.1 -j1.2 temp.txt" \
+"M temp\.txt
+RCS file: ${CVSROOT_DIRNAME}/join6/temp\.txt,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into temp\.txt"
+         dotest join6-6 "${testcvs} diff temp.txt" ""
+         mv temp.txt temp3.txt
+         dotest join6-7 "sed 's/ddd/dddd/' < temp3.txt > temp.txt" ""
+         dotest join6-8 "${testcvs} update -j1.1 -j1.2 temp.txt" \
+"M temp\.txt
+RCS file: ${CVSROOT_DIRNAME}/join6/temp\.txt,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into temp\.txt
+rcsmerge: warning: conflicts during merge"
+         dotest_fail join6-9 "${testcvs} diff temp.txt" \
+"Index: temp\.txt
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/join6/temp.txt,v
+retrieving revision 1\.2
+diff -r1\.2 temp\.txt
+3a4,6
+> <<<<<<< temp\.txt
+> dddd
+> =======
+4a8
+> >>>>>>> 1\.2"
+         cp temp2.txt temp.txt
+         dotest join6-10 "${testcvs} -q ci -m del temp.txt" \
+"$CVSROOT_DIRNAME/join6/temp.txt,v  <--  temp\.txt
+new revision: 1\.3; previous revision: 1\.2"
+          cp temp3.txt temp.txt
+         dotest_fail join6-11 "${testcvs} diff temp.txt" \
+"Index: temp\.txt
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/join6/temp.txt,v
+retrieving revision 1\.3
+diff -r1\.3 temp\.txt
+3a4
+> ddd"
+         dotest join6-12 "${testcvs} update -j1.2 -j1.3 temp.txt" \
+"M temp\.txt
+RCS file: ${CVSROOT_DIRNAME}/join6/temp\.txt,v
+retrieving revision 1\.2
+retrieving revision 1\.3
+Merging differences between 1\.2 and 1\.3 into temp\.txt"
+         dotest join6-13 "${testcvs} diff temp.txt" ""
+
+         # The case where the merge target wasn't created until after the
+         # first tag was applied
+         rm temp2.txt temp3.txt
+         dotest join6-20 "${testcvs} -q tag -r1.1 t1" \
+"T temp.txt"
+         echo xxx >temp2.txt
+         dotest join6-21 "${testcvs} -Q add temp2.txt"
+         dotest join6-22 "${testcvs} -q ci -m." \
+"$CVSROOT_DIRNAME/join6/temp2\.txt,v  <--  temp2\.txt
+initial revision: 1\.1"
+         dotest join6-23 "${testcvs} -q tag t2" \
+"T temp.txt
+T temp2.txt"
+         echo xxx >>temp.txt
+         dotest join6-24 "${testcvs} -q ci -m." \
+"$CVSROOT_DIRNAME/join6/temp.txt,v  <--  temp\.txt
+new revision: 1\.4; previous revision: 1\.3"
+         dotest join6-25 "${testcvs} -q up -jt1 -jt2" \
+"RCS file: ${CVSROOT_DIRNAME}/join6/temp.txt,v
+retrieving revision 1\.1
+retrieving revision 1\.3
+Merging differences between 1\.1 and 1\.3 into temp.txt
+temp.txt already contains the differences between 1\.1 and 1\.3
+temp2.txt already contains the differences between creation and 1\.1"
+
+         # Now for my next trick: delete the file, recreate it, and
+         # try to merge
+         dotest join6-30 "${testcvs} -q rm -f temp2.txt" \
+"${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest join6-31 "${testcvs} -q ci -m. temp2.txt" \
+"$CVSROOT_DIRNAME/join6/temp2\.txt,v  <--  temp2\.txt
+new revision: delete; previous revision: 1\.1"
+         echo new >temp2.txt
+         # FIXCVS: Local and remote really shouldn't be different and there
+         # really shouldn't be two different status lines for temp2.txt
+         if $remote; then
+           dotest_fail join6-32 "${testcvs} -q up -jt1 -jt2" \
+"? temp2\.txt
+RCS file: ${CVSROOT_DIRNAME}/join6/temp.txt,v
+retrieving revision 1\.1
+retrieving revision 1\.3
+Merging differences between 1\.1 and 1\.3 into temp.txt
+temp.txt already contains the differences between 1\.1 and 1\.3
+$CPROG update: move away .\./temp2\.txt.; it is in the way
+C temp2\.txt"
+         else
+           dotest join6-32 "${testcvs} -q up -jt1 -jt2" \
+"RCS file: ${CVSROOT_DIRNAME}/join6/temp.txt,v
+retrieving revision 1\.1
+retrieving revision 1\.3
+Merging differences between 1\.1 and 1\.3 into temp.txt
+temp.txt already contains the differences between 1\.1 and 1\.3
+${SPROG} update: use .${SPROG} add. to create an entry for .temp2\.txt.
+U temp2\.txt
+? temp2\.txt"
+         fi
+
+         dokeep
+         cd ../../..
+         rm -r join6
+         modify_repo rm -rf $CVSROOT_DIRNAME/join6
+         ;;
+
+
+
+       join7)
+         # This test deals with joins that happen with the -n switch
+         mkdir join7; cd join7
+         mkdir impdir; cd impdir
+          echo aaa >temp.txt
+         echo bbb >>temp.txt
+         echo ccc >>temp.txt
+         dotest join7-1 \
+"${testcvs} -Q import -minitial join7 vendor vers-1" \
+""
+         cd ..
+         dotest join7-2 "${testcvs} -Q co join7" ""
+         cd join7
+         echo ddd >> temp.txt
+         dotest join7-3 "${testcvs} -Q ci -madded-line temp.txt" ""
+         cd ../impdir
+         echo aaaa >temp.txt
+         echo bbbb >>temp.txt
+         echo ccc >>temp.txt
+         echo eee >>temp.txt
+         dotest join7-4 \
+"${testcvs} -Q import -minitial join7 vendor vers-2" \
+""
+         cd ../join7
+         dotest join7-5 \
+"${testcvs} -n update -jvers-1 -jvers-2 temp.txt" \
+"RCS file: $CVSROOT_DIRNAME/join7/temp.txt,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 temp.txt
+rcsmerge: warning: conflicts during merge"
+         touch temp.txt
+         dotest join7-6 "${testcvs} -n update -jvers-1 -jvers-2 temp.txt" \
+"RCS file: $CVSROOT_DIRNAME/join7/temp.txt,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 temp.txt
+rcsmerge: warning: conflicts during merge" \
+"RCS file: $CVSROOT_DIRNAME/join7/temp.txt,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 temp.txt
+rcsmerge: warning: conflicts during merge"
+
+         dokeep
+         cd ../..
+         rm -r join7
+         modify_repo rm -rf $CVSROOT_DIRNAME/join7
+         ;;
+
+
+
+       join-readonly-conflict)
+         # Previously, only tests 1 & 11 were being tested.  I added the
+         # intermediate dotest's to try and diagnose a different failure
+         #
+         # Demonstrate that cvs-1.9.29 can fail on 2nd and subsequent
+         # conflict-evoking join attempts.
+         # Even with that version of CVS, This test failed only in
+         # client-server mode, and would have been noticed in normal
+         # operation only for files that were read-only (either due to
+         # use of cvs' global -r option, setting the CVSREAD envvar,
+         # or use of watch lists).
+         mkdir join-readonly-conflict; cd join-readonly-conflict
+         dotest join-readonly-conflict-1 "$testcvs -q co -l ." ''
+         module=join-readonly-conflict
+         mkdir $module
+         $testcvs -q add $module >>$LOGFILE 2>&1
+         cd $module
+
+         file=m
+         echo trunk > $file
+         dotest join-readonly-conflict-2 "$testcvs -Q add $file" ''
+
+         dotest join-readonly-conflict-3 "$testcvs -q ci -m . $file" \
+"$CVSROOT_DIRNAME/$module/$file,v  <--  $file
+initial revision: 1\.1"
+
+         dotest join-readonly-conflict-4 "$testcvs tag -b B $file" "T $file"
+         dotest join-readonly-conflict-5 "$testcvs -q update -rB $file" ''
+         echo branch B > $file
+         dotest join-readonly-conflict-6 "$testcvs -q ci -m . $file" \
+"$CVSROOT_DIRNAME/$module/$file,v  <--  $file
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         rm $file
+         dotest join-readonly-conflict-7 "$testcvs -Q update -A $file" ''
+         # Make sure $file is read-only.  This can happen more realistically
+         # via patch -- which could be used to apply a delta, yet would
+         # preserve a file's read-only permissions.
+         echo conflict > $file; chmod u-w $file
+         dotest join-readonly-conflict-8 "$testcvs update -r B $file" \
+"RCS file: $CVSROOT_DIRNAME/$module/$file,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.1
+Merging differences between 1\.1 and 1\.1\.2\.1 into $file
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in $file
+C $file"
+
+         # restore to the trunk
+         rm -f $file
+         dotest join-readonly-conflict-9 "$testcvs -Q update -A $file" ''
+
+         # This one would fail because cvs couldn't open the existing
+         # (and read-only) .# file for writing.
+         echo conflict > $file
+
+         # verify that the backup file is not writable
+         if test -w ".#$file.1.1"; then
+           fail "join-readonly-conflict-10 : .#$file.1.1 is writable"
+         else
+           pass "join-readonly-conflict-10"
+         fi
+         dotest join-readonly-conflict-11 "$testcvs update -r B $file" \
+"RCS file: $CVSROOT_DIRNAME/$module/$file,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.1
+Merging differences between 1\.1 and 1\.1\.2\.1 into $file
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in $file
+C m"
+
+         dokeep
+         cd ../..
+         rm -r join-readonly-conflict
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         ;;
+
+
+
+       join-admin)
+         mkdir 1; cd 1
+         dotest join-admin-0-1 "$testcvs -q co -l ."
+         module=x
+         mkdir $module
+         dotest join-admin-0-2 "$testcvs -q add $module" \
+"Directory $CVSROOT_DIRNAME/$module added to the repository"
+         cd $module
+
+         # Create a file so applying the first tag works.
+         echo foo > a
+         dotest join-admin-0-3 "$testcvs -Q add a" ''
+         dotest join-admin-0-4 "$testcvs -Q ci -m. a" ''
+
+         dotest join-admin-0-5 "$testcvs -Q tag -b B" ''
+         dotest join-admin-0-6 "$testcvs -Q tag -b M1" ''
+         echo '$''Id$' > b
+         dotest join-admin-0-7 "$testcvs -Q add b" ''
+         dotest join-admin-0-8 "$testcvs -Q ci -m. b" ''
+         dotest join-admin-0-9 "$testcvs -Q tag -b M2" ''
+
+         dotest join-admin-0-10 "$testcvs -Q update -r B" ''
+         dotest join-admin-0-11 "$testcvs -Q update -kk -jM1 -jM2" ''
+         dotest join-admin-0-12 "$testcvs -Q ci -m. b" ''
+
+         dotest join-admin-0-13 "$testcvs -Q update -A" ''
+
+         # Verify that the -kk flag from the update did not
+         # propagate to the repository.
+         dotest join-admin-1 "$testcvs status b" \
+"===================================================================
+File: b                        Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/x/b,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         ;;
+
+
+
+       join-admin-2)
+         # Show that when a merge (via update -kk -jtag1 -jtag2) first
+         # removes a file, then modifies another containing an $Id...$ line,
+         # the resulting file contains the unexpanded `$Id.$' string, as
+         # -kk requires.
+         mkdir 1; cd 1
+         dotest join-admin-2-1 "$testcvs -q co -l ." ''
+         module=x
+         mkdir $module
+         dotest join-admin-2-2 "$testcvs -q add $module" \
+"Directory ${CVSROOT_DIRNAME}/x added to the repository"
+         cd $module
+
+         # Create a file so applying the first tag works.
+         echo '$''Id$' > e0
+         cp e0 e
+         dotest join-admin-2-3 "$testcvs -Q add e"
+         dotest join-admin-2-4 "$testcvs -Q ci -m. e"
+
+         dotest join-admin-2-5 "$testcvs -Q tag -b T" '' "${QUESTION} e0"
+         dotest join-admin-2-6 "$testcvs -Q update -r T" '' "${QUESTION} e0"
+         cp e0 e
+         dotest join-admin-2-7 "$testcvs -Q ci -m. e"
+
+         dotest join-admin-2-8 "$testcvs -Q update -A" '' "${QUESTION} e0"
+         dotest join-admin-2-9 "$testcvs -Q tag -b M1" '' "${QUESTION} e0"
+
+         echo '$''Id$' > b
+         dotest join-admin-2-10 "$testcvs -Q add b" ''
+         cp e0 e
+         dotest join-admin-2-11 "$testcvs -Q ci -m. b e"
+
+         dotest join-admin-2-12 "$testcvs -Q tag -b M2" '' "${QUESTION} e0"
+
+         dotest join-admin-2-13 "$testcvs -Q update -r T" '' "${QUESTION} e0"
+         dotest join-admin-2-14 "$testcvs update -kk -jM1 -jM2" \
+"${SPROG} update: Updating .
+U b
+U e
+RCS file: ${CVSROOT_DIRNAME}/x/e,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into e
+e already contains the differences between 1\.1 and 1\.2
+${QUESTION} e0" \
+"${QUESTION} e0
+${SPROG} update: Updating .
+U b
+U e
+RCS file: ${CVSROOT_DIRNAME}/x/e,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into e
+e already contains the differences between 1\.1 and 1\.2"
+
+         # Verify that the $Id.$ string is not expanded.
+         dotest join-admin-2-15 "cat e" '$''Id$'
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         ;;
+
+
+
+       join-rm)
+         # This first half of this test checks that a single-argument merge
+         # from a branch is capable of removing files.
+         #
+         # The second half verifies that an update to another location with an
+         # uncommitted removal will transfer the destination branch of the
+         # removal.
+
+         module=join-rm
+         mkdir $module; cd $module
+
+         dotest join-rm-init-1 "$testcvs -q co -l ." ''
+         mkdir $module
+         dotest join-rm-init-2 "$testcvs -q add $module" \
+"Directory $CVSROOT_DIRNAME/$module added to the repository"
+         cd $module
+
+         # add some files.
+         touch a b c d e f g
+         dotest join-rm-init-3 "$testcvs -Q add a b c d e f g"
+         dotest join-rm-init-4 "$testcvs -Q ci -m add-em"
+         
+         # create the branch and update to it
+         dotest join-rm-init-5 "$testcvs -Q tag -b br"
+         dotest join-rm-init-6 "$testcvs -Q up -rbr"
+
+         # remove a few files from the branch
+         dotest join-rm-init-7 "$testcvs -Q rm -f b d g"
+         dotest join-rm-init-8 "$testcvs -Q ci -mrm"
+
+         # update to the trunk
+         dotest join-rm-init-9 "$testcvs -Q up -A"
+
+         # now for the test - try and merge the removals.
+         dotest join-rm-1 "$testcvs -q up -jbr" \
+"$SPROG update: scheduling \`b' for removal
+$SPROG update: scheduling \`d' for removal
+$SPROG update: scheduling \`g' for removal"
+
+         # And make sure the merge took
+         dotest join-rm-2 "$testcvs -qn up" \
+"R b
+R d
+R g"
+
+         dotest join-rm-3 "$testcvs -q ci -m 'save the merge'" \
+"$CVSROOT_DIRNAME/join-rm/b,v  <--  b
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/join-rm/d,v  <--  d
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/join-rm/g,v  <--  g
+new revision: delete; previous revision: 1\.1"
+
+         # and verify that it was the head revision which was removed.
+         dotest join-rm-4 "$testcvs -q log b"  "
+RCS file: $CVSROOT_DIRNAME/join-rm/Attic/b,v
+Working file: b
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       br: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: $username;  state: dead;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+save the merge
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+branches:  1.1.2;
+add-em
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: $username;  state: dead;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+rm
+============================================================================="
+
+         # go back to the branch to set up for the second set of tests
+         dotest join-rm-init-10 "$testcvs -Q up -rbr"
+         dotest join-rm-init-11 "$testcvs -Q rm -f a"
+         dotest join-rm-init-12 "$testcvs -Q ci -m rma"
+
+         # now the test: update to the trunk
+         #
+         # FIXCVS: This update should merge the removal to the trunk.  It does
+         # not.
+         dotest join-rm-5 "$testcvs -q up -A" "U a"
+
+         # and verify that there is no sticky tag
+         dotest join-rm-6 "$testcvs status a" \
+"===================================================================
+File: a                        Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    $CVSROOT_DIRNAME/join-rm/a,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         dokeep
+         cd ../..
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         rm -r $module
+         ;;
+
+
+
+       new) # look for stray "no longer pertinent" messages.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest new-init-1 "$testcvs -Q co first-dir"
+
+         cd first-dir
+         touch a
+
+         dotest new-1 "$testcvs -Q add a"
+
+         dotest new-2 "$testcvs -Q ci -m added"
+         rm a
+
+         dotest new-3 "$testcvs -Q rm a"
+         dotest new-4 "$testcvs -Q ci -m removed"
+         dotest new-5 "$testcvs -Q update -A"
+         dotest new-6 "$testcvs -Q update -rHEAD"
+
+         dokeep
+         cd ..
+         rm -r first-dir
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       newb)
+         # Test removing a file on a branch and then checking it out.
+
+         # We call this "newb" only because it, like the "new" tests,
+         # has something to do with "no longer pertinent" messages.
+         # Not necessarily the most brilliant nomenclature.
+
+         # Create file 'a'.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest newb-123a "${testcvs} -q co first-dir" ''
+         cd first-dir
+         touch a
+         dotest newb-123b "${testcvs} add a" \
+"${SPROG} add: scheduling file .a. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest newb-123c "${testcvs} -q ci -m added" \
+"$CVSROOT_DIRNAME/first-dir/a,v  <--  a
+initial revision: 1\.1"
+
+         # Make a branch.
+         dotest newb-123d "${testcvs} -q tag -b branch" "T a"
+
+         # Check out the branch.
+         cd ..
+         rm -r first-dir
+         mkdir 1
+         cd 1
+         dotest newb-123e "${testcvs} -q co -r branch first-dir" \
+"U first-dir/a"
+
+         # Remove 'a' on another copy of the branch.
+         cd ..
+         mkdir 2
+         cd 2
+         dotest newb-123f "${testcvs} -q co -r branch first-dir" \
+"U first-dir/a"
+         cd first-dir
+         rm a
+         dotest newb-123g "${testcvs} rm a" \
+"${SPROG} remove: scheduling .a. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest newb-123h "${testcvs} -q ci -m removed" \
+"$CVSROOT_DIRNAME/first-dir/a,v  <--  a
+new revision: delete; previous revision: 1\.1"
+
+         # Check out the file on the branch.  This used to report
+         # that the file is not pertinent, but this only makes sense on
+         # update.
+         cd ..
+         rm -r first-dir
+         dotest newb-123i "$testcvs -q co -r branch first-dir/a"
+
+         # Update the other copy, and make sure that a is removed.
+         cd ../1/first-dir
+         # "Entry Invalid" is a rather strange output here.  Something like
+         # "Removed in Repository" would make more sense.
+         dotest newb-123j0 "${testcvs} status a" \
+"${SPROG} status: \`a' is no longer in the repository
+===================================================================
+File: a                        Status: Entry Invalid
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1\.2\.1      ${CVSROOT_DIRNAME}/first-dir/a,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         branch (branch: 1\.1\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)${DOTSTAR}"
+         dotest newb-123j "${testcvs} -q update" \
+"${SPROG} update: \`a' is no longer in the repository"
+
+         if test -f a; then
+           fail newb-123k
+         else
+           pass newb-123k
+         fi
+
+         dokeep
+         cd ../..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       conflicts)
+               modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+
+               mkdir 1
+               cd 1
+
+               dotest conflicts-124 "${testcvs} -q co first-dir" ''
+
+               cd first-dir
+               touch a
+
+               dotest conflicts-125 "${testcvs} add a" \
+"${SPROG} add: scheduling file .a. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+               dotest conflicts-126 "${testcvs} -q ci -m added" \
+"$CVSROOT_DIRNAME/first-dir/a,v  <--  a
+initial revision: 1\.1"
+
+               cd ../..
+               mkdir 2
+               cd 2
+
+               dotest conflicts-126.5 "${testcvs} co -p first-dir" \
+"${SPROG} checkout: Updating first-dir
+===================================================================
+Checking out first-dir/a
+RCS:  ${CVSROOT_DIRNAME}/first-dir/a,v
+VERS: 1\.1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*"
+               dotest conflicts-127 "${testcvs} -Q co first-dir" ''
+               cd first-dir
+               dotest conflicts-127a "test -f a" ''
+
+               cd ../../1/first-dir
+               echo add a line >>a
+               mkdir dir1
+               dotest conflicts-127b "${testcvs} add dir1" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir1 added to the repository"
+               dotest conflicts-128 "${testcvs} -q ci -m changed" \
+"$CVSROOT_DIRNAME/first-dir/a,v  <--  a
+new revision: 1\.2; previous revision: 1\.1"
+               cd ../..
+
+               # Similar to conflicts-126.5, but now the file has nonempty
+               # contents.
+               mkdir 3
+               cd 3
+               dotest conflicts-128.5 "${testcvs} co -p -l first-dir" \
+"${SPROG} checkout: Updating first-dir
+===================================================================
+Checking out first-dir/a
+RCS:  ${CVSROOT_DIRNAME}/first-dir/a,v
+VERS: 1\.2
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+add a line"
+               cd ..
+               rmdir 3
+
+               # Now go over the to the other working directory and
+               # start testing conflicts
+               cd 2/first-dir
+               echo add a conflicting line >>a
+               dotest_fail conflicts-129 "${testcvs} -q ci -m changed" \
+"${SPROG}"' commit: Up-to-date check failed for `a'\''
+'"${SPROG}"' \[commit aborted\]: correct above errors first!'
+               mkdir dir1
+               mkdir sdir
+               dotest conflicts-status-0 "${testcvs} status a" \
+"===================================================================
+File: a                        Status: Needs Merge
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/a,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+               dotest conflicts-129a "${testcvs} -nq update a" \
+"RCS file: ${CVSROOT_DIRNAME}/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
+${SPROG} update: conflicts found in a
+C a"
+               dotest conflicts-130 "${testcvs} -q update" \
+"RCS file: ${CVSROOT_DIRNAME}/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
+${SPROG} update: conflicts found in a
+C a
+${QUESTION} dir1
+${QUESTION} sdir" \
+"${QUESTION} dir1
+${QUESTION} sdir
+RCS file: ${CVSROOT_DIRNAME}/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
+${SPROG} update: conflicts found in a
+C a"
+               rmdir dir1 sdir
+
+               dotest conflicts-status-1 "${testcvs} status a" \
+"===================================================================
+File: a                        Status: Unresolved Conflict
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/a,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+               dotest_fail conflicts-131 "${testcvs} -q ci -m try" \
+"${SPROG} commit: file .a. had a conflict and has not been modified
+${SPROG} \[commit aborted\]: correct above errors first!"
+
+               # Try to check in the file with the conflict markers in it.
+               # Make sure we detect any one of the three conflict markers
+               mv a aa
+               grep '^<<<<<<<' aa >a
+               dotest conflicts-status-2 "${testcvs} -nq ci -m try a" \
+"${SPROG} commit: warning: file .a. seems to still contain conflict indicators"
+
+               grep '^=======' aa >a
+               dotest conflicts-status-3 "${testcvs} -nq ci -m try a" \
+"${SPROG} commit: warning: file .a. seems to still contain conflict indicators"
+
+               grep '^>>>>>>>' aa >a
+               dotest conflicts-status-4 "${testcvs} -qn ci -m try a" \
+"${SPROG} commit: warning: file .a. seems to still contain conflict indicators"
+
+               mv aa a
+               echo lame attempt at resolving it >>a
+               dotest conflicts-status-5 "${testcvs} status a" \
+"===================================================================
+File: a                        Status: File had conflicts on merge
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/a,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+               dotest conflicts-132 "$testcvs -q ci -m try" \
+"$SPROG commit: warning: file .a. seems to still contain conflict indicators
+$CVSROOT_DIRNAME/first-dir/a,v  <--  a
+new revision: 1\.3; previous revision: 1\.2"
+
+               # OK, the user saw the warning (good user), and now
+               # resolves it for real.
+               echo resolve conflict >a
+               dotest conflicts-status-6 "${testcvs} status a" \
+"===================================================================
+File: a                        Status: Locally Modified
+
+   Working revision:   1\.3.*
+   Repository revision:        1\.3    ${CVSROOT_DIRNAME}/first-dir/a,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+               dotest conflicts-133 "${testcvs} -q ci -m resolved" \
+"$CVSROOT_DIRNAME/first-dir/a,v  <--  a
+new revision: 1\.4; previous revision: 1\.3"
+               dotest conflicts-status-7 "${testcvs} status a" \
+"===================================================================
+File: a                        Status: Up-to-date
+
+   Working revision:   1\.4.*
+   Repository revision:        1\.4    ${CVSROOT_DIRNAME}/first-dir/a,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+               # 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
+                   pass 134
+               else
+                   fail 134
+               fi
+               if ${testcvs} ci -m 'add abc' abc >>${LOGFILE} 2>&1; then
+                   pass 135
+               else
+                   fail 135
+               fi
+               cd ../../2
+               mkdir first-dir/dir1 first-dir/sdir
+               dotest conflicts-136 "${testcvs} -q update first-dir" \
+'U first-dir/abc
+'"${QUESTION}"' first-dir/dir1
+'"${QUESTION}"' first-dir/sdir' \
+''"${QUESTION}"' first-dir/dir1
+'"${QUESTION}"' first-dir/sdir
+U 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
+               dotest conflicts-138 "${testcvs} add subdir" "${DOTSTAR}"
+               cd ../..
+               mkdir 3
+               cd 3
+               dotest conflicts-139 \
+"${testcvs} -q co first-dir/abc first-dir/subdir" "${DOTSTAR}"
+               cd ../1/first-dir/subdir
+               echo sss >sss
+               dotest conflicts-140 "${testcvs} add sss" "${DOTSTAR}"
+               dotest conflicts-140a "${testcvs} ci -m adding sss" \
+"${DOTSTAR}"
+               cd ../../../3/first-dir
+               dotest conflicts-141 "${testcvs} -q update" "${DOTSTAR}"
+               dotest conflicts-142 "test -f subdir/sss"
+
+               dokeep
+               cd ../..
+               rm -r 1 2 3
+               modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+               restore_adm
+               ;;
+
+
+
+       conflicts2)
+         # More conflicts tests; separate from conflicts to keep each
+         # test a manageable size.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+
+         mkdir 1
+         cd 1
+
+         dotest conflicts2-142a1 "${testcvs} -q co first-dir" ''
+
+         cd first-dir
+         touch a abc
+
+         dotest conflicts2-142a2 "${testcvs} add a abc" \
+"${SPROG} add: scheduling file .a. for addition
+${SPROG} add: scheduling file .abc. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest conflicts2-142a3 "${testcvs} -q ci -m added" \
+"$CVSROOT_DIRNAME/first-dir/a,v  <--  a
+initial revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/abc,v  <--  abc
+initial revision: 1\.1"
+
+         cd ../..
+         mkdir 2
+         cd 2
+
+         dotest conflicts2-142a4 "${testcvs} -q co first-dir" 'U first-dir/a
+U first-dir/abc'
+         cd ..
+
+         # BEGIN TESTS USING THE FILE A
+         # FIXME: would be cleaner to separate them out into their own
+         # tests; conflicts2 is getting long.
+         # Now test that if one person modifies and commits a
+         # file and a second person removes it, it is a
+         # conflict
+         cd 1/first-dir
+         echo modify a >>a
+         dotest conflicts2-142b2 "${testcvs} -q ci -m modify-a" \
+"$CVSROOT_DIRNAME/first-dir/a,v  <--  a
+new revision: 1\.2; previous revision: 1\.1"
+         cd ../../2/first-dir
+         rm a
+         dotest conflicts2-142b3 "${testcvs} rm a" \
+"${SPROG} remove: scheduling .a. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest_fail conflicts2-142b4 "${testcvs} -q update" \
+"${SPROG} update: conflict: removed \`a' was modified by second party
+C a"
+         # Resolve the conflict by deciding not to remove the file
+         # after all.
+         dotest_sort conflicts2-142b5 "$testcvs add a" "U a
+${SPROG} add: \`a', version 1\.1, resurrected"
+         dotest conflicts2-142b5b1 "$testcvs status a" \
+"===================================================================
+File: a                        Status: Needs Patch
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.2    $CVSROOT_DIRNAME/first-dir/a,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest conflicts2-142b6 "$testcvs -q update" 'U a'
+
+         # Now one level up.
+         cd ..
+         dotest conflicts2-142b7 "${testcvs} rm -f first-dir/a" \
+"${SPROG} remove: scheduling \`first-dir/a' for removal
+${SPROG} remove: use \`${SPROG} commit' to remove this file permanently"
+
+         if $remote; then
+           # Haven't investigated this one.
+           dotest_fail conflicts2-142b8r "$testcvs add first-dir/a" \
+"${CPROG} add: in directory \`\.':
+${CPROG} \[add aborted\]: there is no version here; do \`${CPROG} checkout' 
first"
+           cd first-dir
+         else
+           dotest conflicts2-142b8 "${testcvs} add first-dir/a" \
+"U first-dir/a
+$SPROG add: \`first-dir/a', version 1\.2, resurrected"
+           cd first-dir
+           # Now recover from the damage that the 142b8 test did.
+           dotest conflicts2-142b9 "${testcvs} rm -f a" \
+"${SPROG} remove: scheduling \`a' for removal
+${SPROG} remove: use \`${SPROG} commit' to remove this file permanently"
+         fi
+
+         # As before, 1.2 instead of 1.1 is a bug.
+         dotest_sort conflicts2-142b10 "$testcvs add a" "U a
+${SPROG} add: \`a', version 1\.2, resurrected"
+         # As with conflicts2-142b6, check that things are normal again.
+         dotest conflicts2-142b11 "${testcvs} -q update" ''
+         cd ../..
+         # END TESTS USING THE FILE A
+
+         # Now test that if one person removes a file and
+         # commits it, and a second person removes it, is it
+         # not a conflict.
+         cd 1/first-dir
+         rm abc
+         dotest conflicts2-142c0 "${testcvs} rm abc" \
+"${SPROG} remove: scheduling \`abc' for removal
+${SPROG} remove: use \`${SPROG} commit' to remove this file permanently"
+         dotest conflicts2-142c1 "${testcvs} -q ci -m remove-abc" \
+"$CVSROOT_DIRNAME/first-dir/abc,v  <--  abc
+new revision: delete; previous revision: 1\.1"
+         cd ../../2/first-dir
+         rm abc
+         dotest conflicts2-142c2 "${testcvs} rm abc" \
+"${SPROG} remove: scheduling \`abc' for removal
+${SPROG} remove: use \`${SPROG} commit' to remove this file permanently"
+         dotest conflicts2-142c3 "${testcvs} update" \
+"${SPROG} update: Updating \."
+         cd ../..
+
+         # conflicts2-142d*: test that if one party adds a file, and another
+         # party has a file of the same name, cvs notices
+         cd 1/first-dir
+         touch aa.c
+         echo 'contents unchanged' >same.c
+         dotest conflicts2-142d0 "${testcvs} add aa.c same.c" \
+"${SPROG} add: scheduling file .aa\.c. for addition
+${SPROG} add: scheduling file .same\.c. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest conflicts2-142d1 "${testcvs} -q ci -m added" \
+"$CVSROOT_DIRNAME/first-dir/aa\.c,v  <--  aa\.c
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/same\.c,v  <--  same\.c
+initial revision: 1\.1"
+
+         # Test the case where the second user manages the add before the
+         # first commits
+         touch bb.c
+         dotest conflicts2-142d1a "$testcvs add bb.c" \
+"$SPROG add: scheduling file .bb\.c. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         cd ../../2/first-dir
+         echo "don't you dare obliterate this text" >bb.c
+         dotest conflicts2-142d1b "$testcvs add bb.c" \
+"$SPROG add: scheduling file .bb\.c. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         cd ../../1/first-dir
+         dotest conflicts2-142d1c "$testcvs -q ci -m added" \
+"$CVSROOT_DIRNAME/first-dir/bb\.c,v  <--  bb\.c
+initial revision: 1\.1"
+
+         cd ../../2/first-dir
+         echo "don't you dare obliterate this text either" >aa.c
+         echo 'contents unchanged' >same.c
+         # Note the discrepancy between local and remote in the handling
+         # of same.c.  I kind
+         # of suspect that the local CVS behavior is the more useful one
+         # although I do sort of wonder whether we should make people run
+         # cvs add just to get them in that habit (also, trying to implement
+         # the local CVS behavior for remote without the cvs add seems 
+         # pretty difficult).
+         if $remote; then
+           dotest_fail conflicts2-142d2r "${testcvs} -q update" \
+"${QUESTION} aa\.c
+${QUESTION} same\.c
+${CPROG} update: move away \`\./aa\.c'; it is in the way
+C aa\.c
+${SPROG} update: conflict: \`bb\.c' created independently by second party
+C bb\.c
+${CPROG} update: move away \`\./same\.c'; it is in the way
+C same\.c"
+         else
+           dotest_fail conflicts2-142d2 "${testcvs} -q update" \
+"${CPROG} update: move away \`aa\.c'; it is in the way
+C aa\.c
+${CPROG} update: conflict: \`bb\.c' created independently by second party
+C bb\.c
+U same\.c"
+         fi
+         dotest conflicts2-142d3 "${testcvs} -q status aa.c" \
+"${SPROG} status: move away \`aa\.c'; it is in the way
+===================================================================
+File: aa\.c                    Status: Unresolved Conflict
+
+   Working revision:   No entry for aa\.c
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/aa\.c,v
+   Commit Identifier:  ${commitid}"
+         dotest conflicts2-142d3a "${testcvs} -q status bb.c" \
+"${SPROG} status: conflict: \`bb\.c' created independently by second party
+===================================================================
+File: bb\.c                    Status: Unresolved Conflict
+
+   Working revision:   New file!
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/bb\.c,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         # FIXCVS
+         # This message seems somewhat bogus.  I mean, parallel development
+         # means that we get to work in parallel if we choose, right?  And
+         # then at commit time it would be a conflict.
+         #
+         # Well, the status is "Unresolved conflict" before _and_ after
+         # the update/merge (when conflicts happen, not at commit time).
+         # It is possible that this message could be changed to something
+         # more infomrative to novice users, like "File of same name exists
+         # in repository", or "File of same name committed independantly by
+         # second party", but these two messages look too long for the Status
+         # field and the move away & added independantly error messages _are_
+         # displayed.  Still, we get a lot of questions about this on the
+         # email lists.  Somehow we need to get more information to users
+         # via these messages and the ones generated by update. -DRP
+         dotest_fail conflicts2-142d4 "${testcvs} -q add aa.c" \
+"${SPROG} add: \`aa.c' added independently by second party"
+
+         # The user might want to see just what the conflict is.
+         # Don't bother, diff seems to kind of lose its mind, with or
+         # without -N.  This is a CVS bug(s).
+         #dotest conflicts2-142d5 \
+         #"${testcvs} -q diff -r HEAD -N aa.c" FIXCVS THEN FIXME
+
+         # Now: "how can the user resolve this conflict", I hear you cry.
+         # Well, one way is to forget about the file in the working
+         # directory.
+         # Since it didn't let us do the add in conflicts2-142d4, there
+         # is no need to run cvs rm here.
+         #dotest conflicts2-142d6 "${testcvs} -q rm -f aa.c" fixme
+         dotest conflicts2-142d6 "rm aa.c" ''
+         dotest conflicts2-142d7 "${testcvs} -q update aa.c" "U aa\.c"
+         dotest conflicts2-142d8 "cat aa.c" ''
+
+         # The other way is to use the version from the working directory
+         # instead of the version from the repository.  Unfortunately,
+         # there doesn't seem to be any particularly clear way to do
+         # this (?).
+
+         dokeep
+         cd ../..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       conflicts3)
+         # More tests of conflicts and/or multiple working directories
+         # in general.
+
+         mkdir 1; cd 1
+         dotest conflicts3-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest conflicts3-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd ..
+         mkdir 2; cd 2
+         dotest conflicts3-3 "${testcvs} -q co -l first-dir" ''
+         cd ../1/first-dir
+         touch file1 file2
+         dotest conflicts3-4 "${testcvs} add file1 file2" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest conflicts3-5 "${testcvs} -q ci -m add-them" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+         cd ../../2/first-dir
+         # Check that -n doesn't make CVS lose its mind as it creates
+         # (or rather, doesn't) a new file.
+         dotest conflicts3-6 "${testcvs} -nq update" \
+"U file1
+U file2"
+         dotest_fail conflicts3-7 "test -f file1" ''
+         dotest conflicts3-8 "${testcvs} -q update" \
+"U file1
+U file2"
+         dotest conflicts3-9 "test -f file2" ''
+
+         # OK, now remove two files at once
+         dotest conflicts3-10 "${testcvs} rm -f file1 file2" \
+"${SPROG} remove: scheduling .file1. for removal
+${SPROG} remove: scheduling .file2. for removal
+${SPROG} remove: use .${SPROG} commit. to remove these files permanently"
+         dotest conflicts3-11 "${testcvs} -q ci -m remove-them" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: delete; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: delete; previous revision: 1\.1"
+         cd ../../1/first-dir
+         dotest conflicts3-12 "${testcvs} -n -q update" \
+"${SPROG} update: \`file1' is no longer in the repository
+${SPROG} update: \`file2' is no longer in the repository"
+         dotest conflicts3-13 "${testcvs} -q update" \
+"${SPROG} update: \`file1' is no longer in the repository
+${SPROG} update: \`file2' is no longer in the repository"
+
+         # OK, now add a directory to both working directories
+         # and see that CVS doesn't lose its mind.
+         mkdir sdir
+         dotest conflicts3-14 "${testcvs} add sdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/sdir added to the repository"
+         touch sdir/sfile
+         dotest conflicts3-14a "${testcvs} add sdir/sfile" \
+"${SPROG} add: scheduling file .sdir/sfile. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest conflicts3-14b "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/sdir/sfile,v  <--  sdir/sfile
+initial revision: 1\.1"
+
+         cd ../../2/first-dir
+
+         # Create a CVS directory without the proper administrative
+         # files in it.  This can happen for example if you hit ^C
+         # in the middle of a checkout.
+         mkdir sdir
+         mkdir sdir/CVS
+         # OK, in the local case CVS sees that the directory exists
+         # in the repository and recurses into it.  In the remote case
+         # CVS can't see the repository and has no way of knowing
+         # that sdir is even a directory (stat'ing everything would be
+         # too slow).  The remote behavior makes more sense to me (but
+         # would this affect other cases?).
+         if $remote; then
+           dotest conflicts3-15 "${testcvs} -q update" \
+"${QUESTION} sdir"
+         else
+           dotest conflicts3-15 "${testcvs} -q update" \
+"${QUESTION} sdir
+${SPROG} update: ignoring sdir (CVS/Repository missing)"
+           touch sdir/CVS/Repository
+           dotest conflicts3-16 "${testcvs} -q update" \
+"${QUESTION} sdir
+${SPROG} update: ignoring sdir (CVS/Entries missing)"
+           cd ..
+           dotest conflicts3-16a "${testcvs} -q update first-dir" \
+"${QUESTION} first-dir/sdir
+${SPROG} update: ignoring first-dir/sdir (CVS/Entries missing)"
+           cd first-dir
+         fi
+         rm -r sdir
+
+         # OK, now the same thing, but the directory doesn't exist
+         # in the repository.
+         mkdir newdir
+         mkdir newdir/CVS
+         dotest conflicts3-17 "${testcvs} -q update" "${QUESTION} newdir"
+         echo "D/newdir////" >> CVS/Entries
+         dotest conflicts3-18 "${testcvs} -q update" \
+"${CPROG} update: ignoring newdir (CVS/Repository missing)"
+         touch newdir/CVS/Repository
+         dotest conflicts3-19 "${testcvs} -q update" \
+"${CPROG} update: ignoring newdir (CVS/Entries missing)"
+         cd ..
+         dotest conflicts3-20 "${testcvs} -q update first-dir" \
+"${CPROG} update: ignoring first-dir/newdir (CVS/Entries missing)"
+         cd first-dir
+         rm -r newdir
+
+         # The previous tests have left CVS/Entries in something of a mess.
+         # While we "should" be able to deal with that (maybe), for now
+         # we just start over.
+         cd ..
+         rm -r first-dir
+         dotest conflicts3-20a "${testcvs} -q co -l first-dir" ''
+         cd first-dir
+
+         dotest conflicts3-21 "${testcvs} -q update -d sdir" "U sdir/sfile"
+         rm -r sdir/CVS
+         dotest conflicts3-22 "${testcvs} -q update" "${QUESTION} sdir"
+         if $remote; then
+           dotest_fail conflicts3-23 "${testcvs} -q update -PdA" \
+"${QUESTION} sdir
+${CPROG} update: move away \`sdir/sfile'; it is in the way
+C sdir/sfile"
+         else
+           dotest conflicts3-23 "${testcvs} -q update -PdA" \
+"${QUESTION} sdir"
+         fi
+
+         # Not that it should really affect much, but let's do the case
+         # where sfile has been removed.  For example, suppose that sdir
+         # had been a CVS-controlled directory which was then removed
+         # by removing each file (and using update -P or some such).  Then
+         # suppose that the build process creates an sdir directory which
+         # is not supposed to be under CVS.
+         rm -r sdir
+         dotest conflicts3-24 "${testcvs} -q update -d sdir" "U sdir/sfile"
+         rm sdir/sfile
+         dotest conflicts3-25 "${testcvs} rm sdir/sfile" \
+"${SPROG} remove: scheduling .sdir/sfile. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest conflicts3-26 "${testcvs} ci -m remove sdir/sfile" \
+"${CVSROOT_DIRNAME}/first-dir/sdir/sfile,v  <--  sdir/sfile
+new revision: delete; previous revision: 1\.1"
+         rm -r sdir/CVS
+         dotest conflicts3-27 "${testcvs} -q update" "${QUESTION} sdir"
+         dotest conflicts3-28 "${testcvs} -q update -PdA" \
+"${QUESTION} sdir"
+
+         dokeep
+         cd ../..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       clean)
+         # Test update -C (overwrite local mods w/ repository copies)
+         mkdir 1; cd 1
+         dotest clean-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest clean-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         echo "The usual boring test text." > cleanme.txt
+          dotest clean-3 "${testcvs} add cleanme.txt" \
+"${SPROG} add: scheduling file .cleanme\.txt. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest clean-4 "${testcvs} -q ci -m clean-3" \
+"$CVSROOT_DIRNAME/first-dir/cleanme\.txt,v  <--  cleanme\.txt
+initial revision: 1\.1"
+          # Okay, preparation is done, now test.
+          # Check that updating an unmodified copy works.
+         dotest clean-5 "${testcvs} -q update" ''
+          # Check that updating -C an unmodified copy works.
+         dotest clean-6 "${testcvs} -q update -C" ''
+          # Check that updating a modified copy works.
+         echo "fish" >> cleanme.txt
+         dotest clean-7 "${testcvs} -q update" 'M cleanme\.txt'
+          # Check that updating -C a modified copy works.
+         dotest clean-8 "${testcvs} -q update -C" \
+"(Locally modified cleanme\.txt moved to \.#cleanme\.txt\.1\.1)
+U cleanme\.txt"
+         # And check that the backup copy really was made.
+         dotest clean-9 "cat .#cleanme.txt.1.1" \
+"The usual boring test text\.
+fish"
+
+          # Do it all again, this time naming the file explicitly.
+         rm .#cleanme.txt.1.1
+         dotest clean-10 "${testcvs} -q update cleanme.txt" ''
+         dotest clean-11 "${testcvs} -q update -C cleanme.txt" ''
+         echo "bluegill" >> cleanme.txt
+         dotest clean-12 "${testcvs} -q update cleanme.txt" 'M cleanme\.txt'
+         dotest clean-13 "${testcvs} -q update -C cleanme.txt" \
+"(Locally modified cleanme\.txt moved to \.#cleanme\.txt\.1\.1)
+U cleanme\.txt"
+         # And check that the backup copy really was made.
+         dotest clean-14 "cat .#cleanme.txt.1.1" \
+"The usual boring test text\.
+bluegill"
+
+         # Now try with conflicts
+         cd ..
+         dotest clean-15 "${testcvs} -q co -d second-dir first-dir" \
+'U second-dir/cleanme\.txt'
+         cd second-dir
+         echo "conflict test" >> cleanme.txt
+         dotest clean-16 "${testcvs} -q ci -m." \
+"$CVSROOT_DIRNAME/first-dir/cleanme\.txt,v  <--  cleanme\.txt
+new revision: 1\.2; previous revision: 1\.1"
+         cd ../first-dir
+         echo "fish" >> cleanme.txt
+         dotest clean-17 "${testcvs} -nq update" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/cleanme\.txt,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into cleanme\.txt
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in cleanme\.txt
+C cleanme\.txt"
+         dotest clean-18 "${testcvs} -q update -C" \
+"(Locally modified cleanme\.txt moved to \.#cleanme\.txt\.1\.1)
+U cleanme\.txt"
+         dotest clean-19 "cat .#cleanme.txt.1.1" \
+"The usual boring test text\.
+fish"
+         
+          # Done.  Clean up.
+         dokeep
+         cd ../..
+          rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       keywordexpand)
+         # Tests of the original *BSD tag= and keywordexpand= features
+         # are done via the LocalKeyword= and KeywordExpand features.
+
+         # Skip this in noredirect mode because it is too easy for the primary
+         # and secondary error messages to get out of sync when the
+         # CVSROOT/config files are broken.  This is intentional, since it is
+         # possible and even likely that an administrator might want to set up
+         # different configurations on the two servers and the paths to the
+         # config files on the secondary and primary were intentionally left
+         # intact even though they might be different.
+         if $noredirect; then
+            notnoredirect keywordexpand
+           continue
+         fi
+
+         mkdir keywordexpand; cd keywordexpand
+
+         dotest keywordexpand-1 "${testcvs} -q co CVSROOT" \
+'U CVSROOT/checkoutlist
+U CVSROOT/commitinfo
+U CVSROOT/config
+U CVSROOT/cvswrappers
+U CVSROOT/loginfo
+U CVSROOT/modules
+U CVSROOT/notify
+U CVSROOT/postadmin
+U CVSROOT/postproxy
+U CVSROOT/posttag
+U CVSROOT/postwatch
+U CVSROOT/preproxy
+U CVSROOT/rcsinfo
+U CVSROOT/taginfo
+U CVSROOT/verifymsg'
+         cd CVSROOT
+         echo LocalKeyword=MyBSD=CVSHeader >> config
+         # First do not expand any keywords
+         echo KeywordExpand=i >> config
+         dotest keywordexpand-2 "${testcvs} -Q ci -mkeywordexpand config"
+
+         cd ..
+
+         mkdir testimport; cd testimport
+         echo '$''Author$' > file1
+         echo '$''Date$' >> file1
+         echo '$''CVSHeader$' >> file1
+         echo '$''Header$' >> file1
+         echo '$''Id$' >> file1
+         echo '$''Locker$' >> file1
+         echo '$''Log$' >> file1
+         echo '$''Name$' >> file1
+         echo '$''RCSfile$' >> file1
+         echo '$''Revision$' >> file1
+         echo '$''Source$' >> file1
+         echo '$''State$' >> file1
+         echo '$''MyBSD$' >> file1
+         dotest keywordexpand-3 \
+"${testcvs} -Q import -I ! -m test-import-with-bsd-keyword keywordexpand 
vendor v1" \
+''
+         cd ..
+
+         dotest keywordexpand-4 "${testcvs} -Q checkout keywordexpand" ''
+         cd keywordexpand
+         dotest keywordexpand-5 "cat file1" \
+"\$""Author\$
+\$""Date\$
+\$""CVSHeader\$
+\$""Header\$
+\$""Id\$
+\$""Locker\$
+\$""Log\$
+\$""Name\$
+\$""RCSfile\$
+\$""Revision\$
+\$""Source\$
+\$""State\$
+\$MyBSD\$"
+         cd ../CVSROOT
+         # Now expand just the MyBSD and Id keywords
+         mv config config.old
+         sed -e 's/KeywordExpand=i/KeywordExpand=iMyBSD,Id/' < config.old > 
config
+         rm -f config.old
+         dotest keywordexpand-6 "${testcvs} -Q ci -mkeywordexpand config"
+         cd ../keywordexpand
+         echo 'a change' >> file1
+         dotest keywordexpand-7 "${testcvs} -Q ci -madd"
+         dotest keywordexpand-8 "cat file1" \
+"\$""Author\$
+\$""Date\$
+\$""CVSHeader\$
+\$""Header\$
+\$""Id: file1,v 1\.2 [0-9/]* [0-9:]* ${username} Exp \$
+\$""Locker\$
+\$""Log\$
+\$""Name\$
+\$""RCSfile\$
+\$""Revision\$
+\$""Source\$
+\$""State\$
+\$MyBSD: keywordexpand/file1,v 1\.2 [0-9/]* [0-9:]* ${username} Exp \$
+a change"
+
+         cd ../CVSROOT
+         mv config config.old
+         sed -e 's/LocalKeyword=MyBSD/LocalKeyword=My_BSD/' \
+             <config.old >config
+         dotest keywordexpand-9 "$testcvs -Q ci -minvalidlocalkeyword config"
+         dotest keywordexpand-10 "$testcvs -Q update config" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: 
LocalKeyword ignored: Bad character \`_' in key \`My_BSD'"
+         cp config.old config
+         dotest keywordexpand-11 "$testcvs -Q ci -mfixit config" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: 
LocalKeyword ignored: Bad character \`_' in key \`My_BSD'" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: 
LocalKeyword ignored: Bad character \`_' in key \`My_BSD'
+$SPROG [a-z]*: $CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: LocalKeyword 
ignored: Bad character \`_' in key \`My_BSD'"
+         dotest keywordexpand-12 "$testcvs -Q update config"
+         sed -e 's/LocalKeyword=MyBSD=CVSHeader/LocalKeyword=MyBSD=Name/' \
+             <config.old >config
+         dotest keywordexpand-13 \
+"$testcvs -Q ci -minvalidlocalkeyword2 config"
+         dotest keywordexpand-14 "$testcvs -Q update config" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: 
LocalKeyword ignored: Unknown LocalId mode: \`Name'"
+         cp config.old config
+         dotest keywordexpand-15 "$testcvs -Q ci -mfixit2 config" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: 
LocalKeyword ignored: Unknown LocalId mode: \`Name'" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: 
LocalKeyword ignored: Unknown LocalId mode: \`Name'
+$SPROG [a-z]*: $CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: LocalKeyword 
ignored: Unknown LocalId mode: \`Name'"
+         dotest keywordexpand-16 "$testcvs -Q update config"
+
+         dokeep
+         # Done. Clean up.
+         cd ../..
+         rm -rf $TESTDIR/keywordexpand
+          modify_repo rm -rf $CVSROOT_DIRNAME/keywordexpand
+         restore_adm
+         ;;
+
+
+
+       tag-ext)
+         # Test the new tag extensions:
+         #   .trunk
+         #   .base
+         #   .next
+         #   .prev
+         #   .root
+         #   .origin
+         save_TZ=$TZ
+         TZ=UTC0; export TZ
+         module=tag-ext
+         mkdir $module; cd $module
+         mkdir top; cd top
+         dotest tag-ext-init-1 "$testcvs -Q co -l ."
+         mkdir tag-ext
+         dotest tag-ext-init-1b "$testcvs -Q add tag-ext"
+         cd ..
+         dotest tag-ext-init-1c "$testcvs -Q co $module"
+         cd $module
+         echo content >file1
+         echo different content >file2
+         dotest tag-ext-init-2 "$testcvs -Q add file1 file2"
+         dotest tag-ext-init-3 "$testcvs -Q ci -madd-em"
+         echo content2 >file1
+         echo different content2 >file2
+         dotest tag-ext-init-4 "$testcvs -Q ci -mvers2"
+         dotest tag-ext-init-5 "$testcvs -Q tag -b BRANCH1"
+         echo content3 >file1
+         echo different content3 >file2
+         dotest tag-ext-init-6 "$testcvs -Q ci -mvers3"
+         dotest tag-ext-init-7 "$testcvs -Q tag -b BRANCH2"
+         dotest tag-ext-init-8 "$testcvs -Q update -r BRANCH1"
+         echo b1content >file3
+         dotest tag-ext-init-9 "$testcvs -Q add file3"
+         dotest tag-ext-init-10 "$testcvs -Q ci -maddbranch1"
+         dotest tag-ext-init-11 "$testcvs -Q update -r BRANCH2"
+         echo b2content >file3
+         dotest tag-ext-init-12 "$testcvs -Q add file3"
+         dotest tag-ext-init-13 "$testcvs -Q ci -maddbranch2"
+         dotest tag-ext-init-14 "$testcvs -Q update -A"
+         echo content >file3
+         dotest tag-ext-init-15 "$testcvs -Q add file3"
+         dotest tag-ext-init-16 "$testcvs -Q ci -maddtrunk"
+         cd ..
+         mkdir import
+         cd import
+         echo content vicontent >file3
+         dotest tag-ext-init-17 \
+"$testcvs -Q import -mimportvendor tag-ext VENDOR RELEASE"
+         cd ..
+         rm -r import
+         cd $module
+
+
+
+         dotest tag-ext-1 "$testcvs -q log" "
+RCS file: $CVSROOT_DIRNAME/$module/file1,v
+Working file: file1
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+       BRANCH2: 1\.3\.0\.2
+       BRANCH1: 1\.2\.0\.2
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+vers3
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+vers2
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add-em
+=============================================================================
+
+RCS file: $CVSROOT_DIRNAME/$module/file2,v
+Working file: file2
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+       BRANCH2: 1\.3\.0\.2
+       BRANCH1: 1\.2\.0\.2
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+vers3
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+vers2
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add-em
+=============================================================================
+
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+Working file: file3
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       RELEASE: 1\.1\.1\.1
+       VENDOR: 1\.1\.1
+       BRANCH2: 1\.1\.0\.4
+       BRANCH1: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 6;    selected revisions: 6
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -0;  
commitid: ${commitid};
+addtrunk
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: dead;  commitid: 
${commitid};
+branches:  1\.1\.1;  1\.1\.2;  1\.1\.4;
+file file3 was initially added on branch BRANCH1\.
+----------------------------
+revision 1\.1\.4\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -0;  
commitid: ${commitid};
+addbranch2
+----------------------------
+revision 1\.1\.4\.1
+date: ${ISO8601DATE};  author: ${username};  state: dead;  lines: +0 -0;  
commitid: ${commitid};
+file file3 was added on branch BRANCH2 on ${ISO8601DATE}
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -0;  
commitid: ${commitid};
+addbranch1
+----------------------------
+revision 1\.1\.1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -0;  
commitid: ${commitid};
+importvendor
+============================================================================="
+
+         dotest tag-ext-2 "$testcvs -q update -r.base"
+         dotest tag-ext-3 "cat CVS/Entries && cat CVS/Tag" \
+"/file1/1\.3/[a-zA-Z0-9 :]*//T\.base
+/file2/1\.3/[a-zA-Z0-9 :]*//T\.base
+/file3/1\.2/[a-zA-Z0-9 :]*//T\.base
+D
+T\.base"
+
+         dotest tag-ext-4 "$testcvs -q update -r.trunk"
+         dotest tag-ext-5 "cat CVS/Entries && cat CVS/Tag" \
+"/file1/1\.3/[a-zA-Z0-9 :]*//T\.trunk
+/file2/1\.3/[a-zA-Z0-9 :]*//T\.trunk
+/file3/1\.2/[a-zA-Z0-9 :]*//T\.trunk
+D
+T\.trunk"
+
+         dotest tag-ext-6 "$testcvs -q update -r.trunk.prev" \
+"U file1
+U file2
+cvs update: \`file3' is no longer in the repository"
+         dotest tag-ext-7 "cat CVS/Entries && cat CVS/Tag" \
+"/file1/1\.2/[a-zA-Z0-9 :]*//T1\.2
+/file2/1\.2/[a-zA-Z0-9 :]*//T1\.2
+D
+N\.trunk\.prev"
+
+         dotest tag-ext-8 "$testcvs -q update -rBRANCH1" \
+"U file3"
+         dotest tag-ext-9 "cat CVS/Entries && cat CVS/Tag" \
+"/file1/1\.2/[a-zA-Z0-9 :]*//TBRANCH1
+/file2/1\.2/[a-zA-Z0-9 :]*//TBRANCH1
+/file3/1\.1\.2\.1/[a-zA-Z0-9 :]*//TBRANCH1
+D
+TBRANCH1"
+
+         echo b1content >file1
+         echo b1content >file2
+         echo b1content >file3
+         dotest tag-ext-10 "$testcvs -Q ci -maddbranch1"
+         dotest tag-ext-12 "$testcvs -Q tag -b BRANCH1-1"
+         echo b1content2 >file1
+         echo b1content2 >file2
+         echo b1content2 >file3
+         dotest tag-ext-13 "$testcvs -Q ci -maddbranch1"
+         dotest tag-ext-14 "$testcvs -q update -rBRANCH1-1" \
+"U file1
+U file2
+U file3"
+         echo b1-1content >file1
+         echo b1-1content >file2
+         echo b1-1content >file3
+         dotest tag-ext-15 "$testcvs -Q ci -maddbranch1-1"
+         echo b1-1content2 >file1
+         echo b1-1content2 >file2
+         echo b1-1content2 >file3
+         dotest tag-ext-16 "$testcvs -Q ci -maddbranch1-1"
+         dotest tag-ext-17 "cat CVS/Entries && cat CVS/Tag" \
+"/file1/1\.2\.2\.1\.2\.2/[a-zA-Z0-9 :]*//TBRANCH1-1
+/file2/1\.2\.2\.1\.2\.2/[a-zA-Z0-9 :]*//TBRANCH1-1
+/file3/1\.1\.2\.1\.2\.2/[a-zA-Z0-9 :]*//TBRANCH1-1
+D
+TBRANCH1-1"
+
+         dotest_fail tag-ext-18 "$testcvs -Q -n diff -r \.prev" \
+"${SPROG} \[diff aborted\]: Tag .\.prev. invalid\. Tag must not be relative: 
.\.prev."
+
+         dotest_fail tag-ext-19 "$testcvs diff -r \.prev file2 file3" \
+"Index: file2
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file2,v
+retrieving revision 1\.2\.2\.1\.2\.1
+retrieving revision 1\.2\.2\.1\.2\.2
+diff -r1\.2\.2\.1\.2\.1 -r1\.2\.2\.1\.2\.2
+1c1
+< b1-1content
+---
+> b1-1content2
+Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.1\.2\.1
+retrieving revision 1\.1\.2\.1\.2\.2
+diff -r1\.1\.2\.1\.2\.1 -r1\.1\.2\.1\.2\.2
+1c1
+< b1-1content
+---
+> b1-1content2"
+
+         dotest_fail tag-ext-20 "$testcvs diff -r \.root file2 file3" \
+"Index: file2
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file2,v
+retrieving revision 1\.2\.2\.1
+retrieving revision 1\.2\.2\.1\.2\.2
+diff -r1\.2\.2\.1 -r1\.2\.2\.1\.2\.2
+1c1
+< b1content
+---
+> b1-1content2
+Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.1
+retrieving revision 1\.1\.2\.1\.2\.2
+diff -r1\.1\.2\.1 -r1\.1\.2\.1\.2\.2
+1c1
+< b1content
+---
+> b1-1content2"
+
+         dotest_fail tag-ext-21 "$testcvs diff -r \.root.root file2 file3" \
+"Index: file2
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file2,v
+retrieving revision 1\.2
+retrieving revision 1\.2\.2\.1\.2\.2
+diff -r1\.2 -r1\.2\.2\.1\.2\.2
+1c1
+< different content2
+---
+> b1-1content2
+${SPROG} diff: Tag \.root\.root refers to a dead (removed) revision in file 
.file3.\.
+cvs diff: No comparison available\.  Pass .-N. to .${SPROG} diff.${QUESTION}"
+
+         dotest_fail tag-ext-22 "$testcvs diff -r \.origin file2 file3" \
+"Index: file2
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file2,v
+retrieving revision 1\.1
+retrieving revision 1\.2\.2\.1\.2\.2
+diff -r1\.1 -r1\.2\.2\.1\.2\.2
+1c1
+< different content
+---
+> b1-1content2
+Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.1
+retrieving revision 1\.1\.2\.1\.2\.2
+diff -r1\.1\.2\.1 -r1\.1\.2\.1\.2\.2
+1c1
+< b1content
+---
+> b1-1content2"
+
+         dotest_fail tag-ext-23 "$testcvs diff -r \.origin.head file2 file3" \
+"Index: file2
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file2,v
+retrieving revision 1\.3
+retrieving revision 1\.2\.2\.1\.2\.2
+diff -r1\.3 -r1\.2\.2\.1\.2\.2
+1c1
+< different content3
+---
+> b1-1content2
+Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.2
+retrieving revision 1\.1\.2\.1\.2\.2
+diff -r1\.1\.2\.2 -r1\.1\.2\.1\.2\.2
+1c1
+< b1content2
+---
+> b1-1content2"
+
+         dotest tag-ext-24 "$testcvs -q update -r\.trunk" \
+"U file1
+U file2
+U file3"
+
+         echo tcontent >file3
+         dotest tag-ext-25 "$testcvs -Q ci -maddtrunk"
+
+         dotest_fail tag-ext-26 "$testcvs -Q -n diff -r \.prev" \
+"${SPROG} \[diff aborted\]: Tag .\.prev. invalid\. Tag must not be relative: 
.\.prev."
+
+         echo tcontent >file4
+         dotest tag-ext-27 "$testcvs -Q add file4"
+
+         dotest tag-ext-28 "$testcvs -Q ci -maddtrunk"
+
+         rm file4
+         dotest tag-ext-29 "$testcvs remove file4" \
+"cvs remove: scheduling \`file4' for removal
+cvs remove: use \`cvs commit' to remove this file permanently"
+
+         dotest tag-ext-30 "$testcvs -Q ci -mremtrunk"
+
+         dotest_fail tag-ext-31 "$testcvs diff -r \.prev file2 file3" \
+"Index: file2
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file2,v
+retrieving revision 1\.2
+retrieving revision 1\.3
+diff -r1\.2 -r1\.3
+1c1
+< different content2
+---
+> different content3
+Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.2
+retrieving revision 1\.3
+diff -r1\.2 -r1\.3
+1c1
+< content
+---
+> tcontent"
+
+         dotest_fail tag-ext-32 "$testcvs diff -r \.root file2 file3" \
+"cvs diff: tag \.root is not in file file2
+cvs diff: tag \.root is not in file file3"
+
+         dotest tag-ext-33 "$testcvs diff -r \.origin.head file2 file3"
+
+         dotest_fail tag-ext-34 "$testcvs diff -r BRANCH1-1\.root file2 file3" 
\
+"Index: file2
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file2,v
+retrieving revision 1\.2\.2\.1
+retrieving revision 1\.3
+diff -r1\.2\.2\.1 -r1\.3
+1c1
+< b1content
+---
+> different content3
+Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.1
+retrieving revision 1\.3
+diff -r1\.1\.2\.1 -r1\.3
+1c1
+< b1content
+---
+> tcontent"
+
+         dotest_fail tag-ext-35 "$testcvs diff -r BRANCH1-1\.root\.prev\.head 
file2 file3" \
+"cvs diff: tag BRANCH1-1\.root\.prev\.head is not in file file3"
+
+         dotest tag-ext-36 "$testcvs -q update -r BRANCH1 file3" \
+"U file3"
+
+         dotest tag-ext-37 "$testcvs tag -b BRANCH1-2 file3" \
+"T file3"
+
+         echo b1content3 >file3
+         dotest tag-ext-38 "$testcvs -Q ci -maddbranch1 file3"
+
+         dotest tag-ext-39 "$testcvs -q update -r BRANCH1-2 file3" \
+"U file3"
+
+         echo b1-2content1 >file3
+         dotest tag-ext-40 "$testcvs -Q ci -maddbranch1-2.1 file3"
+
+         echo b1-2content2 >file3
+         dotest tag-ext-41 "$testcvs -Q ci -maddbranch1-2.2 file3"
+
+         dotest tag-ext-42 "$testcvs tag -b BRANCH1-2-1 file3" \
+"T file3"
+
+         echo b1-2content3 >file3
+         dotest tag-ext-43 "$testcvs -Q ci -maddbranch1-2.3 file3"
+
+         dotest tag-ext-44 "$testcvs -q update -r BRANCH1-2-1 file3" \
+"U file3"
+
+         echo b1-2-1content1 >file3
+         dotest tag-ext-45 "$testcvs -Q ci -maddbranch1-2-1.1 file3"
+
+         echo b1-2-1content2 >file3
+         dotest tag-ext-46 "$testcvs -Q ci -maddbranch1-2-1.2 file3"
+
+
+         echo b1-2-1content3 >file3
+         dotest tag-ext-47 "$testcvs -Q ci -maddbranch1-2-1.3 file3"
+
+         echo b1-2-1content4 >file3
+         dotest tag-ext-48 "$testcvs -Q ci -maddbranch1-2-1.4 file3"
+
+         echo b1-2-1content5 >file3
+         dotest tag-ext-49 "$testcvs -Q ci -maddbranch1-2-1.5 file3"
+
+         echo b1-2-1content6 >file3
+         dotest tag-ext-50 "$testcvs -Q ci -maddbranch1-2-1.6 file3"
+
+         echo b1-2-1content7 >file3
+         dotest tag-ext-51 "$testcvs -Q ci -maddbranch1-2-1.7 file3"
+
+         echo b1-2-1content8 >file3
+         dotest tag-ext-52 "$testcvs -Q ci -maddbranch1-2-1.8 file3"
+
+         echo b1-2-1content9 >file3
+         dotest tag-ext-53 "$testcvs -Q ci -maddbranch1-2-1.9 file3"
+
+         dotest tag-ext-54 "$testcvs tag -b BRANCH1-2-1-1 file3" \
+"T file3"
+
+         echo b1-2-1content10 >file3
+         dotest tag-ext-55 "$testcvs -Q ci -maddbranch1-2-1.10 file3"
+
+         rm file3
+         dotest tag-ext-56 "$testcvs remove file3" \
+"cvs remove: scheduling \`file3' for removal
+cvs remove: use \`cvs commit' to remove this file permanently"
+
+         dotest tag-ext-57 "$testcvs -Q ci -mremBRANCH1-2-1.11 file3"
+
+         dotest tag-ext-58 "$testcvs -q update -r BRANCH1-2-1-1 file3" \
+"U file3"
+
+         echo b1-2-1-1content1 >file3
+         dotest tag-ext-59 "$testcvs -Q ci -maddbranch1-2-1-1.1 file3"
+
+         echo b1-2-1-1content2 >file3
+         dotest tag-ext-60 "$testcvs -Q ci -maddbranch1-2-1-1.2 file3"
+
+         dotest tag-ext-61 "$testcvs tag -b BRANCH1-2-1-1-1-1 file3" \
+"T file3"
+
+         echo b1-2-1-1content3 >file3
+         dotest tag-ext-62 "$testcvs -Q ci -maddbranch1-2-1-1.3 file3"
+
+         dotest tag-ext-63 "$testcvs admin -o 1.1.2.2.2.2.2.4::1.1.2.2.2.2.2.8 
file3" \
+"RCS file: $CVSROOT_DIRNAME/$module/file3,v
+deleting revision 1\.1\.2\.2\.2\.2\.2\.5
+deleting revision 1\.1\.2\.2\.2\.2\.2\.6
+deleting revision 1\.1\.2\.2\.2\.2\.2\.7
+done"
+
+         dotest tag-ext-64 "$testcvs admin -o 1.1.2.2.2.2.2.2::1.1.2.2.2.2.2.4 
file3" \
+"RCS file: $CVSROOT_DIRNAME/$module/file3,v
+deleting revision 1\.1\.2\.2\.2\.2\.2\.3
+done"
+
+         dotest_fail tag-ext-65 "$testcvs diff -r \.root\.root\.root\.head 
file3" \
+"Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.3
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+diff -r1\.1\.2\.3 -r1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+1c1
+< b1content3
+---
+> b1-2-1-1content3"
+
+         dotest_fail tag-ext-66 "$testcvs diff -r \.origin -r \.origin\.head 
file3" \
+"Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.1
+retrieving revision 1\.1\.2\.3
+diff -r1\.1\.2\.1 -r1\.1\.2\.3
+1c1
+< b1content
+---
+> b1content3"
+
+         dotest tag-ext-67 "$testcvs diff -r \.origin\.prev -r \.root\.head 
file3"
+
+         dotest_fail tag-ext-68 "$testcvs diff -r \.origin\.prev -r \.root 
file3" \
+"cvs diff: tag \.origin\.prev is not in file file3"
+
+         dotest_fail tag-ext-69 "$testcvs diff -r \.origin -r \.root\.head 
file3" \
+"cvs diff: Tag \.root\.head refers to a dead (removed) revision in file 
\`file3'\.
+cvs diff: No comparison available\.  Pass \`-N' to \`cvs diff'?"
+
+         dotest_fail tag-ext-70 "$testcvs diff -r 
\.prev\.prev\.prev\.prev\.prev\.prev\.prev\.prev\.prev\.prev\.prev file3" \
+"Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.1
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+diff -r1\.1\.2\.1 -r1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+1c1
+< b1content
+---
+> b1-2-1-1content3"
+
+         date_T1=`getrlogdate -r1\.2 tag-ext/file3`
+         date_T2=`getrlogdate -r1\.1\.2\.2\.2\.2\.2\.8 tag-ext/file3`
+         
+         dotest_fail tag-ext-71 "$testcvs diff -r \.trunk:'$date_T1' file3" \
+"Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.2
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+diff -r1\.2 -r1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+1c1
+< content
+---
+> b1-2-1-1content3"
+
+         dotest_fail tag-ext-72 "$testcvs diff -r BRANCH1-2-1:'$date_T2' 
file3" \
+"Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.8
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+diff -r1\.1\.2\.2\.2\.2\.2\.8 -r1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+1c1
+< b1-2-1content8
+---
+> b1-2-1-1content3"
+
+          dotest_fail tag-ext-73 "$testcvs diff -rBRANCH1.prev file1" \
+"Index: file1
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file1,v
+retrieving revision 1\.2\.2\.1
+retrieving revision 1\.3
+diff -r1\.2\.2\.1 -r1\.3
+1c1
+< b1content
+---
+> content3"
+
+          COMMITID=$(cat $CVSROOT_DIRNAME/tag-ext/file3,v | grep -m 21 
commitid | tail -n 1);
+          COMMITID=`expr match ${COMMITID:9} '\([a-zA-Z0-9]*\)'`
+         dotest_fail tag-ext-74 "$testcvs diff -r .commitid.$COMMITID file3" \
+"Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.2
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+diff -r1\.1\.2\.2\.2\.2\.2\.9\.2\.2 -r1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+1c1
+< b1-2-1-1content2
+---
+> b1-2-1-1content3"
+
+         dotest_fail tag-ext-75 "$testcvs diff -r @$COMMITID file3" \
+"Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.2
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+diff -r1\.1\.2\.2\.2\.2\.2\.9\.2\.2 -r1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+1c1
+< b1-2-1-1content2
+---
+> b1-2-1-1content3"
+
+         dotest_fail tag-ext-76 "$testcvs diff -r '@<$COMMITID' file3" \
+"Index: file3
+===================================================================
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.1
+retrieving revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+diff -r1\.1\.2\.2\.2\.2\.2\.9\.2\.1 -r1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+1c1
+< b1-2-1-1content1
+---
+> b1-2-1-1content3"
+
+          COMMITID=$(cat $CVSROOT_DIRNAME/tag-ext/file1,v | grep -m 3 commitid 
| tail -n 1);
+          COMMITID=`expr match ${COMMITID:9} '\([a-zA-Z0-9]*\)'`
+          dotest tag-ext-77 "$testcvs update -r .commitid.$COMMITID" \
+"cvs update: Updating .
+U file1
+U file2
+cvs update: \`file3' is no longer in the repository"
+
+         dotest tag-ext-78 "cat CVS/Entries && cat CVS/Tag" \
+"/file1/1\.1/[a-zA-Z0-9 :]*//T\.commitid\.$COMMITID
+/file2/1\.1/[a-zA-Z0-9 :]*//T\.commitid\.$COMMITID
+D
+N\.commitid\.$COMMITID"
+
+          dotest tag-ext-79 "$testcvs update -r @$COMMITID" \
+"cvs update: Updating ."
+
+         dotest tag-ext-80 "cat CVS/Entries && cat CVS/Tag" \
+"/file1/1\.1/[a-zA-Z0-9 :]*//address@hidden
+/file2/1\.1/[a-zA-Z0-9 :]*//address@hidden
+D
address@hidden"
+
+         dotest tag-ext-81 "$testcvs -Q update -r BRANCH1-1"
+
+         dotest tag-ext-82 "$testcvs -Q update -r BRANCH1 file1"
+
+          COMMITID=$(cat $CVSROOT_DIRNAME/tag-ext/file3,v | grep -m 3 commitid 
| tail -n 1);
+          COMMITID=`expr match ${COMMITID:9} '\([a-zA-Z0-9]*\)'`
+          dotest_fail tag-ext-83 "$testcvs diff -r .commitid.$COMMITID" \
+"cvs diff: Diffing .
+cvs diff: tag .commitid.$COMMITID is not in file file1
+cvs diff: tag .commitid.$COMMITID is not in file file2
+Index: file3
+===================================================================
+RCS file: /tmp/cvs-sanity/cvsroot/tag-ext/file3,v
+retrieving revision 1\.1\.2\.1
+retrieving revision 1\.1\.2\.1\.2\.2
+diff -r1\.1\.2\.1 -r1\.1\.2\.1\.2\.2
+1c1
+< b1content
+---
+> b1-1content2"
+
+          dotest_fail tag-ext-84 "$testcvs diff -r '@<$COMMITID'" \
+"cvs diff: Diffing .
+cvs diff: tag @<$COMMITID is not in file file1
+cvs diff: tag @<$COMMITID is not in file file2
+cvs diff: tag @<$COMMITID is not in file file3"
+
+          COMMITID=$(cat $CVSROOT_DIRNAME/tag-ext/file3,v | grep -m 2 commitid 
| tail -n 1);
+          COMMITID=`expr match ${COMMITID:9} '\([a-zA-Z0-9]*\)'`
+          dotest_fail tag-ext-85 "$testcvs diff -r '@<$COMMITID'" \
+"cvs diff: Diffing .
+cvs diff: tag @<$COMMITID is not in file file1
+cvs diff: tag @<$COMMITID is not in file file2
+Index: file3
+===================================================================
+RCS file: /tmp/cvs-sanity/cvsroot/tag-ext/file3,v
+retrieving revision 1\.1\.2\.1
+retrieving revision 1\.1\.2\.1\.2\.2
+diff -r1\.1\.2\.1 -r1\.1\.2\.1\.2\.2
+1c1
+< b1content
+---
+> b1-1content2"
+
+          dotest tag-ext-86 "$testcvs update -j .origin -j .prev file1 file2 
file3" \
+"RCS file: /tmp/cvs-sanity/cvsroot/tag-ext/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.2\.2\.1
+Merging differences between 1\.1 and 1\.2\.2\.1 into file1
+rcsmerge: warning: conflicts during merge
+RCS file: /tmp/cvs-sanity/cvsroot/tag-ext/file2,v
+retrieving revision 1\.1
+retrieving revision 1\.2\.2\.1\.2\.1
+Merging differences between 1\.1 and 1\.2\.2\.1\.2\.1 into file2
+rcsmerge: warning: conflicts during merge
+RCS file: /tmp/cvs-sanity/cvsroot/tag-ext/file3,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 file3
+rcsmerge: warning: conflicts during merge"
+
+          dotest tag-ext-87 "cat CVS/Entries && cat CVS/Tag" \
+"/file1/1\.2\.2\.2/Result of merge+[a-zA-Z0-9 :]*//TBRANCH1
+/file2/1\.2\.2\.1\.2\.2/Result of merge+[a-zA-Z0-9 :]*//TBRANCH1-1
+/file3/1\.1\.2\.1\.2\.2/Result of merge+[a-zA-Z0-9 :]*//TBRANCH1-1
+D
+TBRANCH1-1"
+
+          dotest tag-ext-88 "$testcvs tag -r .prev atag file1 file2 file3" \
+"T file1
+T file2
+T file3"
+
+          dotest tag-ext-89 "$testcvs rtag -r BRANCH1-1.root.next btag 
tag-ext" \
+"cvs rtag: Tagging tag-ext"
+
+          dotest tag-ext-90 "$testcvs rlog tag-ext" \
+"cvs rlog: Logging tag-ext
+
+RCS file: $CVSROOT_DIRNAME/$module/file1,v
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+       btag: 1\.2\.2\.2
+       atag: 1\.2\.2\.1
+       BRANCH1-1: 1\.2\.2\.1\.0\.2
+       BRANCH2: 1\.3\.0\.2
+       BRANCH1: 1\.2\.0\.2
+keyword substitution: kv
+total revisions: 7;    selected revisions: 7
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+vers3
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+branches:  1\.2\.2;
+vers2
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add-em
+----------------------------
+revision 1\.2\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1
+----------------------------
+revision 1\.2\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+branches:  1\.2\.2\.1\.2;
+addbranch1
+----------------------------
+revision 1\.2\.2\.1\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-1
+----------------------------
+revision 1\.2\.2\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-1
+=============================================================================
+
+RCS file: $CVSROOT_DIRNAME/$module/file2,v
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+       btag: 1\.2\.2\.2
+       atag: 1\.2\.2\.1\.2\.1
+       BRANCH1-1: 1\.2\.2\.1\.0\.2
+       BRANCH2: 1\.3\.0\.2
+       BRANCH1: 1\.2\.0\.2
+keyword substitution: kv
+total revisions: 7;    selected revisions: 7
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+vers3
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+branches:  1\.2\.2;
+vers2
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add-em
+----------------------------
+revision 1\.2\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1
+----------------------------
+revision 1\.2\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+branches:  1\.2\.2\.1\.2;
+addbranch1
+----------------------------
+revision 1\.2\.2\.1\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-1
+----------------------------
+revision 1\.2\.2\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-1
+=============================================================================
+
+RCS file: $CVSROOT_DIRNAME/$module/file3,v
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+       btag: 1\.1\.2\.2
+       atag: 1\.1\.2\.1\.2\.1
+       BRANCH1-2-1-1-1-1: 1\.1\.2\.2\.2\.2\.2\.9\.2\.2\.0\.2
+       BRANCH1-2-1-1: 1\.1\.2\.2\.2\.2\.2\.9\.0\.2
+       BRANCH1-2-1: 1\.1\.2\.2\.2\.2\.0\.2
+       BRANCH1-2: 1\.1\.2\.2\.0\.2
+       BRANCH1-1: 1\.1\.2\.1\.0\.2
+       RELEASE: 1\.1\.1\.1
+       VENDOR: 1\.1\.1
+       BRANCH2: 1\.1\.0\.4
+       BRANCH1: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 24;   selected revisions: 24
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addtrunk
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -0;  
commitid: ${commitid};
+addtrunk
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: dead;  commitid: 
${commitid};
+branches:  1\.1\.1;  1\.1\.2;  1\.1\.4;
+file file3 was initially added on branch BRANCH1\.
+----------------------------
+revision 1\.1\.4\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -0;  
commitid: ${commitid};
+addbranch2
+----------------------------
+revision 1\.1\.4\.1
+date: ${ISO8601DATE};  author: ${username};  state: dead;  lines: +0 -0;  
commitid: ${commitid};
+file file3 was added on branch BRANCH2 on ${ISO8601DATE}
+----------------------------
+revision 1\.1\.2\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1
+----------------------------
+revision 1\.1\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+branches:  1\.1\.2\.2\.2;
+addbranch1
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -0;  
commitid: ${commitid};
+branches:  1\.1\.2\.1\.2;
+addbranch1
+----------------------------
+revision 1\.1\.2\.2\.2\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2\.3
+----------------------------
+revision 1\.1\.2\.2\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+branches:  1\.1\.2\.2\.2\.2\.2;
+addbranch1-2\.2
+----------------------------
+revision 1\.1\.2\.2\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2\.1
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.11
+date: ${ISO8601DATE};  author: ${username};  state: dead;  lines: +0 -0;  
commitid: ${commitid};
+remBRANCH1-2-1\.11
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.10
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2-1\.10
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.9
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+branches:  1\.1\.2\.2\.2\.2\.2\.9\.2;
+addbranch1-2-1\.9
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.8
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2-1\.8
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.4
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2-1\.4
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2-1\.2
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2-1\.1
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2-1-1\.3
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2-1-1\.2
+----------------------------
+revision 1\.1\.2\.2\.2\.2\.2\.9\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-2-1-1\.1
+----------------------------
+revision 1\.1\.2\.1\.2\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-1
+----------------------------
+revision 1\.1\.2\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+addbranch1-1
+----------------------------
+revision 1\.1\.1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -0;  
commitid: ${commitid};
+importvendor
+=============================================================================
+
+RCS file: $CVSROOT_DIRNAME/$module/Attic/file4,v
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: dead;  lines: +0 -0;  
commitid: ${commitid};
+remtrunk
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +1 -0;  
commitid: ${commitid};
+addtrunk
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: dead;  commitid: 
${commitid};
+file file4 was initially added on branch \.trunk\.
+============================================================================="
+
+         # clean up
+         dokeep
+         restore_adm
+         cd ../..
+         rm -r $module
+         modify_repo rm -rf $CVSROOT_DIRNAME/tag-ext
+         TZ=$save_TZ
+         ;;
+
+
+
+       modules)
+         # Tests of various ways to define and use modules.
+         # Roadmap to various modules tests:
+         # -a:
+         #   error on incorrect placement: modules
+         #   error combining with other options: modules2-a*
+         #   infinite loops: modules148a1.1 - modules148a1.2
+         #   use to specify a file more than once: modules3
+         #   use with ! feature: modules4
+         # regular modules: modules, modules2, cvsadm
+         # ampersand modules: modules2
+         # -s: modules.
+         # -d: modules, modules3, cvsadm
+         # -i, -o, -u, -e, -t: modules5
+         # slashes in module names: modules3
+         # invalid module definitions: modules6
+
+         ############################################################
+         # These tests are to make sure that administrative files get
+         # rebuilt, regardless of how and where files are checked
+         # out.
+         ############################################################
+         # Check out the whole repository
+         mkdir 1; cd 1
+         dotest modules-1 "${testcvs} -q co ." 'U CVSROOT/checkoutlist
+U CVSROOT/commitinfo
+U CVSROOT/config
+U CVSROOT/cvswrappers
+U CVSROOT/loginfo
+U CVSROOT/modules
+U CVSROOT/notify
+U CVSROOT/postadmin
+U CVSROOT/postproxy
+U CVSROOT/posttag
+U CVSROOT/postwatch
+U CVSROOT/preproxy
+U CVSROOT/rcsinfo
+U CVSROOT/taginfo
+U CVSROOT/verifymsg'
+         echo "# made a change" >>CVSROOT/modules
+         dotest modules-1d "${testcvs} -q ci -m add-modules" \
+"$CVSROOT_DIRNAME/CVSROOT/modules,v  <--  CVSROOT/modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ..
+         rm -rf 1
+
+         ############################################################
+         # Check out CVSROOT
+         mkdir 1; cd 1
+         dotest modules-2 "${testcvs} -q co CVSROOT" 'U CVSROOT/checkoutlist
+U CVSROOT/commitinfo
+U CVSROOT/config
+U CVSROOT/cvswrappers
+U CVSROOT/loginfo
+U CVSROOT/modules
+U CVSROOT/notify
+U CVSROOT/postadmin
+U CVSROOT/postproxy
+U CVSROOT/posttag
+U CVSROOT/postwatch
+U CVSROOT/preproxy
+U CVSROOT/rcsinfo
+U CVSROOT/taginfo
+U CVSROOT/verifymsg'
+         echo "# made a change" >>CVSROOT/modules
+         dotest modules-2d "${testcvs} -q ci -m add-modules" \
+"$CVSROOT_DIRNAME/CVSROOT/modules,v  <--  CVSROOT/modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ..
+         rm -rf 1
+
+         ############################################################
+         # Check out CVSROOT in some other directory
+         modify_repo mkdir $CVSROOT_DIRNAME/somedir
+         mkdir 1; cd 1
+         dotest modules-3 "${testcvs} -q co somedir" ''
+         cd somedir
+         dotest modules-3d "${testcvs} -q co CVSROOT" 'U CVSROOT/checkoutlist
+U CVSROOT/commitinfo
+U CVSROOT/config
+U CVSROOT/cvswrappers
+U CVSROOT/loginfo
+U CVSROOT/modules
+U CVSROOT/notify
+U CVSROOT/postadmin
+U CVSROOT/postproxy
+U CVSROOT/posttag
+U CVSROOT/postwatch
+U CVSROOT/preproxy
+U CVSROOT/rcsinfo
+U CVSROOT/taginfo
+U CVSROOT/verifymsg'
+         echo "# made a change" >>CVSROOT/modules
+         dotest modules-3g "${testcvs} -q ci -m add-modules" \
+"$CVSROOT_DIRNAME/CVSROOT/modules,v  <--  CVSROOT/modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/somedir
+         ############################################################
+         # end rebuild tests
+         ############################################################
+
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+
+         mkdir 1
+         cd 1
+
+         dotest modules-143 "${testcvs} -q co first-dir" ""
+
+         cd first-dir
+         mkdir subdir
+         dotest modules-143a "${testcvs} add subdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/subdir added to the repository"
+
+         cd subdir
+         mkdir ssdir
+         dotest modules-143b "${testcvs} add ssdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/subdir/ssdir added to the repository"
+
+         touch a b
+
+         dotest modules-144 "${testcvs} add a b" \
+"${SPROG} add: scheduling file .a. for addition
+${SPROG} add: scheduling file .b. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+
+         dotest modules-145 "$testcvs ci -m added" \
+"$CPROG commit: Examining .
+$CPROG commit: Examining ssdir
+$CVSROOT_DIRNAME/first-dir/subdir/a,v  <--  a
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/subdir/b,v  <--  b
+initial revision: 1\.1"
+
+         cd ..
+         dotest modules-146 "$testcvs -q co CVSROOT" \
+"U CVSROOT/checkoutlist
+U CVSROOT/commitinfo
+U CVSROOT/config
+U CVSROOT/cvswrappers
+U CVSROOT/loginfo
+U CVSROOT/modules
+U CVSROOT/notify
+U CVSROOT/postadmin
+U CVSROOT/postproxy
+U CVSROOT/posttag
+U CVSROOT/postwatch
+U CVSROOT/preproxy
+U CVSROOT/rcsinfo
+U CVSROOT/taginfo
+U CVSROOT/verifymsg"
+
+         # 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.
+         dotest modules-147 "$testcvs -q update"
+
+         cat >CVSROOT/modules <<EOF
+realmodule first-dir/subdir a
+dirmodule first-dir/subdir
+namedmodule -d nameddir first-dir/subdir
+aliasmodule -a first-dir/subdir/a
+aliasnested -a first-dir/subdir/ssdir
+topfiles -a first-dir/file1 first-dir/file2
+world -a .
+statusmod -s Mungeable
+# Check for ability to block infinite loops.
+infinitealias -a infinitealias
+# Prior to 1.11.12 & 1.12.6, the infinite alias loop check didn't strip
+# slashes or work if a module called a module which then called itself
+# (A -> A was blocked, but not A -> B -> A or deeper).
+infinitealias2 -a infinitealias2/
+infinitealias3 -a infinitealias4/
+infinitealias4 -a aliasmodule infinitealias5
+infinitealias5 -a infinitealias3/
+# 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.
+bogusalias first-dir/subdir/a -a
+EOF
+         dotest modules-148 "$testcvs ci -m 'add modules' CVSROOT/modules" \
+"$CVSROOT_DIRNAME/CVSROOT/modules,v  <--  CVSROOT/modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         cd ..
+         # The "statusmod" module contains an error; trying to use it
+         # will produce "modules file missing directory" I think.
+         # However, that shouldn't affect the ability of "cvs co -c" or
+         # "cvs co -s" to do something reasonable with it.
+         dotest modules-148a0 "$testcvs co -c" \
+'aliasmodule  -a first-dir/subdir/a
+aliasnested  -a first-dir/subdir/ssdir
+bogusalias   first-dir/subdir/a -a
+dirmodule    first-dir/subdir
+infinitealias -a infinitealias
+infinitealias2 -a infinitealias2/
+infinitealias3 -a infinitealias4/
+infinitealias4 -a aliasmodule infinitealias5
+infinitealias5 -a infinitealias3/
+namedmodule  -d nameddir first-dir/subdir
+realmodule   first-dir/subdir a
+statusmod    -s Mungeable
+topfiles     -a first-dir/file1 first-dir/file2
+world        -a \.'
+         # There is code in modules.c:save_d which explicitly skips
+         # modules defined with -a, which is why aliasmodule is not
+         # listed.
+         dotest modules-148a1 "${testcvs} co -s" \
+'statusmod    Mungeable  
+bogusalias   NONE        first-dir/subdir/a -a
+dirmodule    NONE        first-dir/subdir
+namedmodule  NONE        first-dir/subdir
+realmodule   NONE        first-dir/subdir a'
+
+         # Check that infinite loops are avoided
+         dotest modules-148a1.1 "${testcvs} co infinitealias" \
+"$CPROG checkout: module \`infinitealias' in modules file contains infinite 
loop" \
+"$SPROG server: module \`infinitealias' in modules file contains infinite loop
+$SPROG checkout: module \`infinitealias' in modules file contains infinite 
loop"
+         # Prior to 1.11.12 & 1.12.6, the inifinte alias loop check did not
+         # strip slashes.
+         dotest modules-148a1.2 "${testcvs} co infinitealias2" \
+"$CPROG checkout: module \`infinitealias2' in modules file contains infinite 
loop" \
+"$SPROG server: module \`infinitealias2' in modules file contains infinite loop
+$SPROG checkout: module \`infinitealias2' in modules file contains infinite 
loop"
+         # Prior to 1.11.12 & 1.12.6, the inifinte alias loop check did not
+         # notice when A -> B -> A, it only noticed A -> A.
+         dotest modules-148a1.3 "${testcvs} co infinitealias3/" \
+"$CPROG checkout: module \`infinitealias3' in modules file contains infinite 
loop" \
+"$SPROG server: module \`infinitealias3' in modules file contains infinite loop
+$SPROG checkout: module \`infinitealias3' in modules file contains infinite 
loop"
+
+         # Test that real modules check out to realmodule/a, not subdir/a.
+         dotest modules-149a1 "${testcvs} co realmodule" "U realmodule/a"
+         dotest modules-149a2 "test -d realmodule && test -f realmodule/a" ""
+         dotest_fail modules-149a3 "test -f realmodule/b" ""
+         dotest modules-149a4 "${testcvs} -q co realmodule" ""
+         dotest modules-149a5 "echo yes | ${testcvs} release -d realmodule" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .realmodule.: "
+
+         dotest_fail modules-149b1 "${testcvs} co realmodule/a" \
+"${SPROG}"' checkout: module `realmodule/a'\'' is a request for a file in a 
module which is not a directory' \
+"${SPROG}"' server: module `realmodule/a'\'' is a request for a file in a 
module which is not a directory
+'"${CPROG}"' \[checkout aborted\]: cannot expand modules'
+
+         # Now test the ability to check out a single file from a directory
+         dotest modules-150c "${testcvs} co dirmodule/a" "U dirmodule/a"
+         dotest modules-150d "test -d dirmodule && test -f dirmodule/a" ""
+         dotest_fail modules-150e "test -f dirmodule/b" ""
+         dotest modules-150f "echo yes | ${testcvs} release -d dirmodule" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .dirmodule.: "
+         # Now test the ability to correctly reject a non-existent filename.
+         # For maximum studliness we would check that an error message is
+         # being output.
+         # We accept a zero exit status because it is what CVS does
+         # (Dec 95).  Probably the exit status should be nonzero,
+         # however.
+         dotest modules-150g1 "$testcvs co dirmodule/nonexist" \
+"$SPROG checkout: nothing known about \`dirmodule/nonexist'"
+         # 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.
+         dotest_fail modules-150g2 "test -f dirmodule/a || test -f 
dirmodule/b" ""
+         rm -r dirmodule
+
+         # Now test that a module using -d checks out to the specified
+         # directory.
+         dotest modules-150h1 "${testcvs} -q co namedmodule" \
+'U nameddir/a
+U nameddir/b'
+         dotest modules-150h2 "test -f nameddir/a && test -f nameddir/b" ""
+         echo add line >>nameddir/a
+         dotest modules-150h3 "${testcvs} -q co namedmodule" 'M nameddir/a'
+         rm nameddir/a
+         dotest modules-150h4 "${testcvs} -q co namedmodule" 'U nameddir/a'
+         dotest modules-150h99 "echo yes | ${testcvs} release -d nameddir" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .nameddir.: "
+
+         # Now test that alias modules check out to subdir/a, not
+         # aliasmodule/a.
+         dotest modules-151 "${testcvs} co aliasmodule" ""
+         dotest_fail modules-152 "test -d aliasmodule" ""
+         echo abc >>first-dir/subdir/a
+         dotest modules-153 "${testcvs} -q co aliasmodule" "M 
first-dir/subdir/a"
+
+         cd ..
+         rm -r 1
+
+         mkdir 2
+         cd 2
+         dotest modules-155a0 "${testcvs} co aliasnested" \
+"${SPROG} checkout: 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" "first-dir" \
+"CVS
+first-dir"
+         cd ..
+         rm -r 2
+
+         # Test checking out everything.
+         mkdir 1
+         cd 1
+         dotest modules-155b "${testcvs} -q co world" \
+"U CVSROOT/${DOTSTAR}
+U first-dir/subdir/a
+U first-dir/subdir/b"
+         cd ..
+         rm -r 1
+
+         # Test checking out a module which lists at least two
+         # specific files twice.  At one time, this failed over
+         # remote CVS.
+         mkdir 1
+         cd 1
+         dotest modules-155c1 "${testcvs} -q co first-dir" \
+"U first-dir/subdir/a
+U first-dir/subdir/b"
+
+         cd first-dir
+         echo 'first revision' > file1
+         echo 'first revision' > file2
+         dotest modules-155c2 "${testcvs} add file1 file2" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition
+'"${SPROG}"' add: scheduling file `file2'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add these files permanently'
+         dotest modules-155c3 "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+
+         cd ..
+         rm -r first-dir
+         dotest modules-155c4 "${testcvs} -q co topfiles" \
+"U first-dir/file1
+U first-dir/file2"
+         dotest modules-155c5 "${testcvs} -q co topfiles" ""
+
+         # Make sure the right thing happens if we remove a file.
+         cd first-dir
+         dotest modules-155c6 "${testcvs} -q rm -f file1" \
+"${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest modules-155c7 "${testcvs} -q ci -m remove-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: delete; previous revision: 1\.1"
+         cd ..
+         rm -r first-dir
+         dotest modules-155c8 "$testcvs -q co topfiles" \
+"U first-dir/file2"
+
+         dokeep
+         cd ..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       modules2)
+         # More tests of modules, in particular the & feature.
+         mkdir 1; cd 1
+         dotest modules2-setup-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir second-dir third-dir
+         dotest modules2-setup-2 \
+"${testcvs} add first-dir second-dir third-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository
+Directory ${CVSROOT_DIRNAME}/second-dir added to the repository
+Directory ${CVSROOT_DIRNAME}/third-dir added to the repository"
+         cd third-dir
+         touch file3
+         dotest modules2-setup-3 "${testcvs} add file3" \
+"${SPROG} add: scheduling file .file3. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest modules2-setup-4 "${testcvs} -q ci -m add file3" \
+"$CVSROOT_DIRNAME/third-dir/file3,v  <--  file3
+initial revision: 1\.1"
+         cd ../..
+         rm -r 1
+
+         mkdir 1
+         cd 1
+
+         dotest modules2-1 "${testcvs} -q co CVSROOT/modules" \
+'U CVSROOT/modules'
+         cd CVSROOT
+         cat >> modules << EOF
+ampermodule &first-dir &second-dir
+combmodule third-dir file3 &first-dir
+ampdirmod -d newdir &first-dir &second-dir
+badmod -d newdir
+messymod first-dir &messymodchild
+messymodchild -d sdir/child second-dir
+EOF
+         # Depending on whether the user also ran the modules test
+         # we will be checking in revision 1.2 or 1.3.
+         dotest modules2-2 "${testcvs} -q ci -m add-modules" \
+"$CVSROOT_DIRNAME/CVSROOT/modules,v  <--  modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         cd ..
+
+         dotest modules2-3 "${testcvs} -q co ampermodule" ''
+         dotest modules2-4 "test -d ampermodule/first-dir" ''
+         dotest modules2-5 "test -d ampermodule/second-dir" ''
+
+         # Test ability of cvs release to handle multiple arguments
+         # See comment at "release" for list of other cvs release tests.
+         cd ampermodule
+         if ${testcvs} release -d first-dir second-dir <<EOF >>${LOGFILE}
+yes
+yes
+EOF
+         then
+           pass modules2-6
+         else
+           fail modules2-6
+         fi
+         dotest_fail modules2-7 "test -d first-dir" ''
+         dotest_fail modules2-8 "test -d second-dir" ''
+
+         cd ..
+
+         # There used to be a nasty-hack that made CVS skip creation of the
+         # module dir (in this case ampermodule) when -n was specified
+         dotest modules2-ampermod-1 "${testcvs} -q co -n ampermodule" ''
+         dotest modules2-ampermod-2 "test -d ampermodule/first-dir" ''
+         dotest modules2-ampermod-3 "test -d ampermodule/second-dir" ''
+
+         # Test release of a module
+         if echo yes |${testcvs} release -d ampermodule >>${LOGFILE}; then
+           pass modules2-ampermod-release-1
+         else
+           fail modules2-ampermod-release-1
+         fi
+         dotest_fail modules2-ampermod-release-2 "test -d ampermodule" ''
+
+         # and the '-n' test again, but in conjunction with '-d'
+         dotest modules2-ampermod-4 "${testcvs} -q co -n -d newname 
ampermodule" ''
+         dotest modules2-ampermod-5 "test -d newname/first-dir" ''
+         dotest modules2-ampermod-6 "test -d newname/second-dir" ''
+         rm -rf newname
+
+         # Now we create another directory named first-dir and make
+         # sure that CVS doesn't get them mixed up.
+         mkdir first-dir
+         # Note that this message should say "Updating ampermodule/first-dir"
+         # I suspect.  This is a long-standing behavior/bug....
+         dotest modules2-9 "${testcvs} co ampermodule" \
+"${SPROG} checkout: Updating first-dir
+${SPROG} checkout: Updating second-dir"
+         touch ampermodule/first-dir/amper1
+         cd ampermodule
+         dotest modules2-10 "${testcvs} add first-dir/amper1" \
+"${SPROG} add: scheduling file .first-dir/amper1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         cd ..
+
+         # As with the "Updating xxx" message, the "U first-dir/amper1"
+         # message (instead of "U ampermodule/first-dir/amper1") is
+         # rather fishy.
+         dotest modules2-12 "${testcvs} co ampermodule" \
+"${SPROG} checkout: Updating first-dir
+A first-dir/amper1
+${SPROG} checkout: Updating second-dir"
+
+         if $remote; then
+           dotest modules2-13r "$testcvs -q ci -m add-it ampermodule" \
+"$CVSROOT_DIRNAME/first-dir/amper1,v  <--  ampermodule/first-dir/amper1
+initial revision: 1\.1"
+         else
+           # Trying this as above led to a "protocol error" message.
+           # Work around this bug.
+           cd ampermodule
+           dotest modules2-13 "$testcvs -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/amper1,v  <--  first-dir/amper1
+initial revision: 1\.1"
+           cd ..
+         fi
+         cd ..
+         rm -r 1
+
+         # Now test the "combmodule" module (combining regular modules
+         # and ampersand modules in the same module definition).
+         mkdir 1; cd 1
+         dotest modules2-14 "${testcvs} co combmodule" \
+"U combmodule/file3
+${SPROG} checkout: Updating first-dir
+U first-dir/amper1"
+         dotest modules2-15 "test -f combmodule/file3" ""
+         dotest modules2-16 "test -f combmodule/first-dir/amper1" ""
+         cd combmodule
+         rm -r first-dir
+         # At least for now there is no way to tell CVS that
+         # some files/subdirectories come from one repository directory,
+         # and others from another.
+         # This seems like a pretty sensible behavior to me, in the
+         # sense that first-dir doesn't "really" exist within
+         # third-dir, so CVS just acts as if there is nothing there
+         # to do.
+         dotest modules2-17 "${testcvs} update -d" \
+"${SPROG} update: Updating \."
+
+         cd ..
+         dotest modules2-18 "${testcvs} -q co combmodule" \
+"U first-dir/amper1"
+         dotest modules2-19 "test -f combmodule/first-dir/amper1" ""
+         cd ..
+         rm -r 1
+
+         # Now test the "ampdirmod" and "badmod" modules to be sure that
+         # options work with ampersand modules but don't prevent the
+         # "missing directory" error message.
+         mkdir 1; cd 1
+         dotest modules2-20 "${testcvs} co ampdirmod" \
+"${SPROG} checkout: Updating first-dir
+U first-dir/amper1
+${SPROG} checkout: Updating second-dir"
+         dotest modules2-21 "test -f newdir/first-dir/amper1" ""
+         dotest modules2-22 "test -d newdir/second-dir" ""
+         dotest_fail modules2-23 "${testcvs} co badmod" \
+"${SPROG} checkout: modules file missing directory for module badmod" \
+"${SPROG} server: modules file missing directory for module badmod
+${CPROG} \[checkout aborted\]: cannot expand modules"
+         cd ..
+         rm -r 1
+
+         # Confirm that a rename with added depth nested in an ampersand
+         # module works.
+         mkdir 1; cd 1
+         dotest modules2-nestedrename-1 "${testcvs} -q co messymod" \
+"U messymod/amper1"
+         dotest modules2-nestedrename-2 "test -d messymod/sdir" ''
+         dotest modules2-nestedrename-3 "test -d messymod/sdir/CVS" ''
+         dotest modules2-nestedrename-4 "test -d messymod/sdir/child" ''
+         dotest modules2-nestedrename-5 "test -d messymod/sdir/child/CVS" ''
+         cd ..; rm -r 1
+
+         # FIXME:  client/server has a bug.  It should be working like a local
+         # repository in this case, but fails to check out the second module
+         # in the list when a branch is specified.
+         mkdir 1; cd 1
+         dotest modules2-ampertag-setup-1 \
+"${testcvs} -Q rtag tag first-dir second-dir third-dir" \
+''
+         dotest modules2-ampertag-1 "${testcvs} -q co -rtag ampermodule" \
+"U first-dir/amper1"
+         if $remote; then
+           dotest_fail modules2-ampertag-2 "test -d ampermodule/second-dir" ''
+           dotest_fail modules2-ampertag-3 "test -d 
ampermodule/second-dir/CVS" ''
+         else
+           dotest modules2-ampertag-2 "test -d ampermodule/second-dir" ''
+           dotest modules2-ampertag-3 "test -d ampermodule/second-dir/CVS" ''
+         fi
+         cd ..; rm -r 1
+
+         # Test for tag files when an ampermod is renamed with more path
+         # elements than it started with.
+         #
+         # FIXME: This is currently broken in the remote case, possibly only
+         # because the messymodchild isn't being checked out at all.
+         mkdir 1; cd 1
+#        dotest modules2-tagfiles-setup-1 \
+#"${testcvs} -Q rtag -b branch first-dir second-dir" \
+#''
+         dotest modules2-tagfiles-1 "${testcvs} -q co -rtag messymod" \
+"U messymod/amper1"
+         if $remote; then
+           dotest_fail modules2-tagfiles-2r "test -d messymod/sdir" ''
+         else
+           dotest modules2-tagfiles-2 "cat messymod/sdir/CVS/Tag" 'Ttag'
+         fi
+         cd ..; rm -r 1
+
+         # Test that CVS gives an error if one combines -a with
+         # other options.
+         # Probably would be better to break this out into a separate
+         # test.  Although it is short, it shares no files/state with
+         # the rest of the modules2 tests.
+         mkdir 1; cd 1
+         dotest modules2-a0.5 "${testcvs} -q co CVSROOT/modules" \
+'U CVSROOT/modules'
+         cd CVSROOT
+         echo 'aliasopt -a -d onedir first-dir' >modules
+         dotest modules2-a0 "${testcvs} -q ci -m add-modules" \
+"$CVSROOT_DIRNAME/CVSROOT/modules,v  <--  modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ..
+         dotest_fail modules2-a1 "${testcvs} -q co aliasopt" \
+"${SPROG} checkout: -a cannot be specified in the modules file along with 
other options" \
+"${SPROG} server: -a cannot be specified in the modules file along with other 
options
+${CPROG} \[checkout aborted\]: cannot expand modules"
+         cd ..;  rm -r 1
+
+         # Clean up.
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/second-dir \
+                            $CVSROOT_DIRNAME/third-dir
+         ;;
+
+
+
+       modules3)
+         # More tests of modules, in particular what happens if several
+         # modules point to the same file.
+
+         # First just set up a directory first-dir and a file file1 in it.
+         mkdir 1; cd 1
+
+         dotest modules3-0 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest modules3-1 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+
+         cd first-dir
+         echo file1 >file1
+         dotest modules3-2 "${testcvs} add file1" \
+"${SPROG} add: scheduling file \`file1' for addition
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+         dotest modules3-3 "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         cd ..
+
+         dotest modules3-4 "${testcvs} -q update -d CVSROOT" \
+"U CVSROOT${DOTSTAR}"
+         cd CVSROOT
+         cat >modules <<EOF
+mod1 -a first-dir/file1
+bigmod -a mod1 first-dir/file1
+namednest -d src/sub/dir first-dir
+nestdeeper -d src/sub1/sub2/sub3/dir first-dir
+nestshallow -d src/dir second-dir/suba/subb
+path/in/modules &mod1
+another/path/test -d another/path/test first-dir
+EOF
+         dotest modules3-5 "${testcvs} -q ci -m add-modules" \
+"$CVSROOT_DIRNAME/CVSROOT/modules,v  <--  modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ..
+
+         dotest modules3-6 "${testcvs} -q co bigmod" ''
+         rm -r first-dir
+         dotest modules3-7 "${testcvs} -q co bigmod" 'U first-dir/file1'
+         cd ..
+         rm -r 1
+
+         mkdir 1; cd 1
+         mkdir suba
+         mkdir suba/subb
+         # This fails to work remote (it doesn't notice the directories,
+         # I suppose because they contain no files).  Bummer, especially
+         # considering this is a documented technique and everything.
+         dotest modules3-7a \
+"${testcvs} import -m add-dirs second-dir tag1 tag2" \
+"${SPROG} import: Importing ${CVSROOT_DIRNAME}/second-dir/suba
+${SPROG} import: Importing ${CVSROOT_DIRNAME}/second-dir/suba/subb
+
+No conflicts created by this import" "
+No conflicts created by this import"
+         cd ..; rm -r 1
+         mkdir 1; cd 1
+         dotest modules3-7b "${testcvs} co second-dir" \
+"${SPROG} checkout: Updating second-dir
+${SPROG} checkout: Updating second-dir/suba
+${SPROG} checkout: Updating second-dir/suba/subb" \
+"${SPROG} checkout: Updating second-dir"
+
+         if $remote; then
+           cd second-dir
+           mkdir suba
+           dotest modules3-7-workaround1 "${testcvs} add suba" \
+"Directory ${CVSROOT_DIRNAME}/second-dir/suba added to the repository"
+           cd suba
+           mkdir subb
+           dotest modules3-7-workaround2 "${testcvs} add subb" \
+"Directory ${CVSROOT_DIRNAME}/second-dir/suba/subb added to the repository"
+           cd ../..
+         fi
+
+         cd second-dir/suba/subb
+         touch fileb
+         dotest modules3-7c "${testcvs} add fileb" \
+"${SPROG} add: scheduling file .fileb. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest modules3-7d "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/second-dir/suba/subb/fileb,v  <--  fileb
+initial revision: 1\.1"
+         cd ../../..
+         cd ..; rm -r 1
+
+         mkdir 1
+         cd 1
+         dotest modules3-8 "${testcvs} -q co namednest" \
+'U src/sub/dir/file1'
+         dotest modules3-9 "test -f src/sub/dir/file1" ''
+         cd ..
+         rm -r 1
+
+         # Try the same thing, but with the directories nested even
+         # deeper (deeply enough so they are nested more deeply than
+         # the number of directories from / to ${TESTDIR}).
+         mkdir 1
+         cd 1
+         dotest modules3-10 "${testcvs} -q co nestdeeper" \
+'U src/sub1/sub2/sub3/dir/file1'
+         dotest modules3-11 "test -f src/sub1/sub2/sub3/dir/file1" ''
+
+         # While we are doing things like twisted uses of '/' (e.g.
+         # modules3-12), try this one.
+         if $remote; then
+           dotest_fail modules3-11b \
+"${testcvs} -q update ${TESTDIR}/1/src/sub1/sub2/sub3/dir/file1" \
+"absolute pathnames invalid for server (specified 
.${TESTDIR}/1/src/sub1/sub2/sub3/dir.)"
+         fi # end of remote-only tests
+
+         cd ..
+         rm -r 1
+
+         # This one is almost too twisted for words.  The pathname output
+         # in the message from "co" doesn't include the "path/in/modules",
+         # but those directories do get created (with no CVSADM except
+         # in "modules" which has a CVSNULLREPOS).
+         # I'm not sure anyone is relying on this nonsense or whether we
+         # need to keep doing it, but it is what CVS currently does...
+         # Skip it for remote; the remote code has the good sense to
+         # not deal with it (on the minus side it gives
+         # "internal error: repository string too short." (CVS 1.9) or
+         # "warning: server is not creating directories one at a time" (now)
+         # instead of a real error).
+         # I'm tempted to just make it a fatal error to have '/' in a
+         # module name.  But see comments at modules3-16.
+         if $remote; then :; else
+           mkdir 1; cd 1
+           dotest modules3-12 "${testcvs} -q co path/in/modules" \
+"U first-dir/file1"
+           dotest modules3-13 "test -f path/in/modules/first-dir/file1" ''
+           cd ..; rm -r 1
+         fi # end of tests skipped for remote
+
+         # Now here is where it used to get seriously bogus.
+         mkdir 1; cd 1
+         dotest modules3-14 \
+"${testcvs} -q rtag tag1 path/in/modules" ''
+         # CVS used to create this even though rtag should *never* affect
+         # the directory current when it is called!
+         dotest_fail modules3-15 "test -d path/in/modules" ''
+         # Just for trivia's sake, rdiff was not similarly vulnerable
+         # because it passed 0 for run_module_prog to do_module.
+         cd ..; rm -r 1
+
+         # Some people seem to want this to work.  I still suspect there
+         # are dark corners in slashes in module names.  This probably wants
+         # more thought before we start hacking on CVS (one way or the other)
+         # or documenting this.
+         mkdir 2; cd 2
+         dotest modules3-16 "${testcvs} -q co another/path/test" \
+"U another/path/test/file1"
+         dotest modules3-17 "cat another/path/test/file1" 'file1'
+
+         dokeep
+         cd ..; rm -r 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/second-dir
+         ;;
+
+
+
+       modules4)
+         # Some tests using the modules file with aliases that
+         # exclude particular directories.
+
+         mkdir 1; cd 1
+
+         dotest modules4-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest modules4-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+
+         cd first-dir
+          mkdir subdir subdir_long
+          dotest modules4-3 "${testcvs} add subdir subdir_long" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/subdir added to the repository
+Directory ${CVSROOT_DIRNAME}/first-dir/subdir_long added to the repository"
+
+         echo file1 > file1
+         dotest modules4-4 "${testcvs} add file1" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+
+         echo file2 > subdir/file2
+         dotest modules4-5 "${testcvs} add subdir/file2" \
+"${SPROG}"' add: scheduling file `subdir/file2'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+
+         echo file3 > subdir_long/file3
+         dotest modules4-6 "${testcvs} add subdir_long/file3" \
+"${SPROG}"' add: scheduling file `subdir_long/file3'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+
+         dotest modules4-7 "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/subdir/file2,v  <--  subdir/file2
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/subdir_long/file3,v  <--  subdir_long/file3
+initial revision: 1\.1"
+
+         cd ..
+
+         dotest modules4-8 "${testcvs} -q update -d CVSROOT" \
+"U CVSROOT${DOTSTAR}"
+         cd CVSROOT
+         cat >modules <<EOF
+all -a first-dir
+some -a !first-dir/subdir first-dir
+other -a !first-dir/subdir !first-dir/subdir_long first-dir
+somewhat -a first-dir !first-dir/subdir
+EOF
+         dotest modules4-9 "${testcvs} -q ci -m add-modules" \
+"$CVSROOT_DIRNAME/CVSROOT/modules,v  <--  modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ..
+
+         cd ..
+         mkdir 2; cd 2
+
+         dotest modules4-10 "${testcvs} -q co all" \
+"U first-dir/file1
+U first-dir/subdir/file2
+U first-dir/subdir_long/file3"
+         rm -r first-dir
+
+         dotest modules4-11 "${testcvs} -q co some" \
+"U first-dir/file1
+U first-dir/subdir_long/file3"
+         dotest_fail modules4-12 "test -d first-dir/subdir" ''
+         dotest modules4-13 "test -d first-dir/subdir_long" ''
+         rm -r first-dir
+
+         if $remote; then
+           # But remote seems to do it the other way.
+           dotest modules4-14r-1 "${testcvs} -q co somewhat" \
+"U first-dir/file1
+U first-dir/subdir_long/file3"
+           dotest_fail modules4-14r-2 "test -d first-dir/subdir" ''
+           dotest modules4-14r-3 "test -d first-dir/subdir_long" ''
+         else
+           # This is strange behavior, in that the order of the
+           # "!first-dir/subdir" and "first-dir" matter, and it isn't
+           # clear that they should.  I suspect it is long-standing
+           # strange behavior but I haven't verified that.
+           dotest modules4-14-1 "${testcvs} -q co somewhat" \
+"U first-dir/file1
+U first-dir/subdir/file2
+U first-dir/subdir_long/file3"
+           dotest modules4-14-2 "test -d first-dir/subdir" ''
+           dotest modules4-14-3 "test -d first-dir/subdir_long" ''
+         fi
+         rm -r first-dir
+
+         dotest modules4-15 "${testcvs} -q co other" \
+"U first-dir/file1"
+         dotest_fail modules4-16 "test -d first-dir/subdir" ''
+         dotest_fail modules4-17 "test -d first-dir/subdir_long" ''
+         rm -r first-dir
+
+         cd ..
+         rm -r 2
+
+         dotest modules4-18 "${testcvs} rtag tag some" \
+"${SPROG} rtag: Tagging first-dir
+${SPROG} rtag: Ignoring first-dir/subdir
+${SPROG} rtag: Tagging first-dir/subdir_long"
+
+         cd 1/first-dir/subdir
+         dotest modules4-19 "${testcvs} log file2" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/subdir/file2,v
+Working file: file2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add-it
+============================================================================="
+
+         dokeep
+         cd ../../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       modules5)
+         # Test module programs
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir 1
+         cd 1
+         dotest modules5-1 "$testcvs -q co first-dir"
+         cd first-dir
+         mkdir subdir
+         dotest modules5-2 "${testcvs} add subdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/subdir added to the repository"
+         cd subdir
+         mkdir ssdir
+         dotest modules5-3 "${testcvs} add ssdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/subdir/ssdir added to the repository"
+         touch a b
+         dotest modules5-4 "${testcvs} add a b" \
+"${SPROG} add: scheduling file .a. for addition
+${SPROG} add: scheduling file .b. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+
+         dotest modules5-5 "${testcvs} ci -m added" \
+"${CPROG} commit: Examining .
+${CPROG} commit: Examining ssdir
+${CVSROOT_DIRNAME}/first-dir/subdir/a,v  <--  a
+initial revision: 1\.1
+${CVSROOT_DIRNAME}/first-dir/subdir/b,v  <--  b
+initial revision: 1\.1"
+
+         cd ..
+         dotest modules5-6 "${testcvs} -q co CVSROOT" \
+"U CVSROOT/checkoutlist
+U CVSROOT/commitinfo
+U CVSROOT/config
+U CVSROOT/cvswrappers
+U CVSROOT/loginfo
+U CVSROOT/modules
+U CVSROOT/notify
+U CVSROOT/postadmin
+U CVSROOT/postproxy
+U CVSROOT/posttag
+U CVSROOT/postwatch
+U CVSROOT/preproxy
+U CVSROOT/rcsinfo
+U CVSROOT/taginfo
+U CVSROOT/verifymsg"
+
+         # FIXCVS: The sleep in the following script helps avoid out of
+         # order messages, but we really need to figure out how to fix
+         # cvs to prevent them in the first place.
+         for i in checkout export tag; do
+           cat >> ${CVSROOT_DIRNAME}/$i.sh <<EOF
+#! $TESTSHELL
+sleep 1
+echo "$i script invoked in \`pwd\`"
+echo "args: \$@"
+EOF
+           # Cygwin doesn't set premissions correctly over the Samba share.
+           if test -n "$remotehost"; then
+             $CVS_RSH $remotehost "chmod +x ${CVSROOT_DIRNAME}/$i.sh"
+           else
+             chmod +x ${CVSROOT_DIRNAME}/$i.sh
+           fi
+         done
+
+         OPTS="-o${CVSROOT_DIRNAME}/checkout.sh -e 
${CVSROOT_DIRNAME}/export.sh -t${CVSROOT_DIRNAME}/tag.sh"
+         cat >CVSROOT/modules <<EOF
+realmodule ${OPTS} first-dir/subdir a
+dirmodule ${OPTS} first-dir/subdir
+namedmodule -d nameddir ${OPTS} first-dir/subdir
+EOF
+
+         dotest modules5-7 "$testcvs -Q ci -m 'add modules' CVSROOT/modules"
+
+         cd ..
+         rm -rf first-dir
+
+         # Test that real modules check out to realmodule/a, not subdir/a.
+         if $remote; then
+           # FIXCVS?
+           # Mac OSX 10.3 (Darwin ppc-osx1 5.5) fails here when $TMPDIR
+           # contains a symlink (it does not fail the local modules5-8).
+           # Since no other platforms are exhibiting the same problem, I
+           # suspect an issue with OSX and fork() or the like dereferencing
+           # the symlink, but it is possible it is something that could be
+           # fixed or worked around in CVS.
+           dotest modules5-8r "$testcvs co realmodule" \
+"U realmodule/a
+${SPROG} checkout: Executing ..${CVSROOT_DIRNAME}/checkout\.sh. .realmodule..
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: realmodule"
+         else
+           dotest modules5-8 "${testcvs} co realmodule" \
+"U realmodule/a
+${SPROG} checkout: Executing ..${CVSROOT_DIRNAME}/checkout\.sh. .realmodule..
+checkout script invoked in ${TESTDIR}/1
+args: realmodule"
+         fi
+         dotest modules5-9 "test -d realmodule && test -f realmodule/a" ""
+         dotest_fail modules5-10 "test -f realmodule/b" ""
+         if $remote; then
+           dotest modules5-11 "${testcvs} -q co realmodule" \
+"checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: realmodule"
+           dotest modules5-12 "${testcvs} -q update" ''
+           echo "change" >>realmodule/a
+           dotest modules5-13 "${testcvs} -q ci -m." \
+"$CVSROOT_DIRNAME/first-dir/subdir/a,v  <--  realmodule/a
+new revision: 1\.2; previous revision: 1\.1"
+         else
+           dotest modules5-11 "${testcvs} -q co realmodule" \
+"checkout script invoked in ${TESTDIR}/1
+args: realmodule"
+           dotest modules5-12 "${testcvs} -q update" ''
+           echo "change" >>realmodule/a
+           dotest modules5-13 "${testcvs} -q ci -m." \
+"$CVSROOT_DIRNAME/first-dir/subdir/a,v  <--  realmodule/a
+new revision: 1\.2; previous revision: 1\.1"
+         fi
+         dotest modules5-14 "echo yes | ${testcvs} release -d realmodule" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .realmodule.: "
+         dotest modules5-15 "${testcvs} -q rtag -Dnow MYTAG realmodule" \
+"tag script invoked in ${TESTDIR}/1
+args: realmodule MYTAG" \
+"tag script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: realmodule MYTAG"
+         if $remote; then
+           dotest modules5-16 "${testcvs} -q export -r MYTAG realmodule" \
+"U realmodule/a
+export script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: realmodule"
+         else
+           dotest modules5-16 "${testcvs} -q export -r MYTAG realmodule" \
+"U realmodule/a
+export script invoked in ${TESTDIR}/1
+args: realmodule"
+         fi
+         rm -r realmodule
+
+         dotest_fail modules5-17 "${testcvs} co realmodule/a" \
+"${SPROG}"' checkout: module `realmodule/a'\'' is a request for a file in a 
module which is not a directory' \
+"${SPROG}"' server: module `realmodule/a'\'' is a request for a file in a 
module which is not a directory
+'"${CPROG}"' \[checkout aborted\]: cannot expand modules'
+
+         # Now test the ability to check out a single file from a directory
+         if $remote; then
+           dotest modules5-18 "${testcvs} co dirmodule/a" \
+"U dirmodule/a
+${SPROG} checkout: Executing ..${CVSROOT_DIRNAME}/checkout\.sh. .dirmodule..
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: dirmodule"
+         else
+           dotest modules5-18 "${testcvs} co dirmodule/a" \
+"U dirmodule/a
+${SPROG} checkout: Executing ..${CVSROOT_DIRNAME}/checkout\.sh. .dirmodule..
+checkout script invoked in ${TESTDIR}/1
+args: dirmodule"
+         fi
+         dotest modules5-19 "test -d dirmodule && test -f dirmodule/a" ""
+         dotest_fail modules5-20 "test -f dirmodule/b" ""
+         dotest modules5-21 "echo yes | ${testcvs} release -d dirmodule" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .dirmodule.: "
+
+         # Now test the ability to correctly reject a non-existent filename.
+         # For maximum studliness we would check that an error message is
+         # being output.
+         # We accept a zero exit status because it is what CVS does
+         # (Dec 95).  Probably the exit status should be nonzero,
+         # however.
+         if $remote; then
+           dotest modules5-22r "$testcvs co dirmodule/nonexist" \
+"$SPROG checkout: nothing known about \`dirmodule/nonexist'
+$SPROG checkout: Executing ..$CVSROOT_DIRNAME/checkout\.sh. .dirmodule..
+checkout script invoked in $TMPDIR/cvs-serv[0-9a-z]*
+args: dirmodule"
+         else
+           dotest modules5-22 "$testcvs co dirmodule/nonexist" \
+"$SPROG checkout: nothing known about \`dirmodule/nonexist'
+$SPROG checkout: Executing ..$CVSROOT_DIRNAME/checkout\.sh. .dirmodule..
+checkout script invoked in $TESTDIR/1
+args: dirmodule"
+         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.
+         dotest_fail modules5-23 "test -f dirmodule/a || test -f dirmodule/b" 
""
+         rm -r dirmodule
+
+         # Now test that a module using -d checks out to the specified
+         # directory.
+         if $remote; then
+           dotest modules5-24 "${testcvs} -q co namedmodule" \
+"U nameddir/a
+U nameddir/b
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: nameddir"
+         else
+           dotest modules5-24 "${testcvs} -q co namedmodule" \
+"U nameddir/a
+U nameddir/b
+checkout script invoked in ${TESTDIR}/1
+args: nameddir"
+         fi
+         dotest modules5-25 "test -f nameddir/a && test -f nameddir/b" ""
+         echo add line >>nameddir/a
+         # This seems suspicious: when we checkout an existing directory,
+         # the checkout script gets executed in addition to the update
+         # script.  Is that by design or accident?
+         if $remote; then
+           dotest modules5-26 "${testcvs} -q co namedmodule" \
+"M nameddir/a
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: nameddir"
+         else
+           dotest modules5-26 "${testcvs} -q co namedmodule" \
+"M nameddir/a
+checkout script invoked in ${TESTDIR}/1
+args: nameddir"
+         fi
+         rm nameddir/a
+
+         if $remote; then
+           dotest modules5-27 "${testcvs} -q co namedmodule" \
+"U nameddir/a
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: nameddir"
+         else
+           dotest modules5-27 "${testcvs} -q co namedmodule" \
+"U nameddir/a
+checkout script invoked in ${TESTDIR}/1
+args: nameddir"
+         fi
+         dotest modules5-28 "echo yes | ${testcvs} release -d nameddir" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .nameddir.: "
+
+         # Now try the same tests with -d on command line
+         # FIXCVS?  The manual says the modules programs get the module name,
+         # but they really get the directory name.
+         if $remote; then
+           dotest modules5-29 "${testcvs} co -d mydir realmodule" \
+"U mydir/a
+${SPROG} checkout: Executing ..${CVSROOT_DIRNAME}/checkout\.sh. .mydir..
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: mydir"
+         else
+           dotest modules5-29 "${testcvs} co -d mydir realmodule" \
+"U mydir/a
+${SPROG} checkout: Executing ..${CVSROOT_DIRNAME}/checkout\.sh. .mydir..
+checkout script invoked in ${TESTDIR}/1
+args: mydir"
+         fi
+         dotest modules5-30 "test -d mydir && test -f mydir/a" ""
+         dotest_fail modules5-31 "test -d realmodule || test -f mydir/b" ""
+         if $remote; then
+           dotest modules5-32 "${testcvs} -q co -d mydir realmodule" \
+"checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: mydir"
+           dotest modules5-33 "${testcvs} -q update" ''
+           echo "change" >>mydir/a
+           dotest modules5-34 "${testcvs} -q ci -m." \
+"$CVSROOT_DIRNAME/first-dir/subdir/a,v  <--  mydir/a
+new revision: 1\.3; previous revision: 1\.2"
+         else
+           dotest modules5-32 "${testcvs} -q co -d mydir realmodule" \
+"checkout script invoked in ${TESTDIR}/1
+args: mydir"
+           dotest modules5-33 "${testcvs} -q update" ''
+           echo "change" >>mydir/a
+           dotest modules5-34 "${testcvs} -q ci -m." \
+"$CVSROOT_DIRNAME/first-dir/subdir/a,v  <--  mydir/a
+new revision: 1\.3; previous revision: 1\.2"
+         fi
+         dotest modules5-35 "echo yes | ${testcvs} release -d mydir" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .mydir.: "
+         if $remote; then
+           dotest modules5-36 "${testcvs} -q rtag -Dnow MYTAG2 realmodule" \
+"tag script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: realmodule MYTAG2"
+           dotest modules5-37 "${testcvs} -q export -r MYTAG2 -d mydir 
realmodule" \
+"U mydir/a
+export script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: mydir"
+         else
+           dotest modules5-36 "${testcvs} -q rtag -Dnow MYTAG2 realmodule" \
+"tag script invoked in ${TESTDIR}/1
+args: realmodule MYTAG2"
+           dotest modules5-37 "${testcvs} -q export -r MYTAG2 -d mydir 
realmodule" \
+"U mydir/a
+export script invoked in ${TESTDIR}/1
+args: mydir"
+         fi
+         rm -r mydir
+
+         # Now test the ability to check out a single file from a directory
+         if $remote; then
+           dotest modules5-38 "${testcvs} co -d mydir dirmodule/a" \
+"U mydir/a
+${SPROG} checkout: Executing ..${CVSROOT_DIRNAME}/checkout\.sh. .mydir..
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: mydir"
+         else
+           dotest modules5-38 "${testcvs} co -d mydir dirmodule/a" \
+"U mydir/a
+${SPROG} checkout: Executing ..${CVSROOT_DIRNAME}/checkout\.sh. .mydir..
+checkout script invoked in ${TESTDIR}/1
+args: mydir"
+         fi
+         dotest modules5-39 "test -d mydir && test -f mydir/a" ""
+         dotest_fail modules5-40 "test -d dirmodule || test -f mydir/b" ""
+         dotest modules5-41 "echo yes | ${testcvs} release -d mydir" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .mydir.: "
+
+         # Now test the ability to correctly reject a non-existent filename.
+         # For maximum studliness we would check that an error message is
+         # being output.
+         # We accept a zero exit status because it is what CVS does
+         # (Dec 95).  Probably the exit status should be nonzero,
+         # however.
+         if $remote; then
+           dotest modules5-42r "$testcvs co -d mydir dirmodule/nonexist" \
+"$SPROG checkout: nothing known about \`mydir/nonexist'
+$SPROG checkout: Executing ..$CVSROOT_DIRNAME/checkout\.sh. .mydir..
+checkout script invoked in $TMPDIR/cvs-serv[0-9a-z]*
+args: mydir"
+         else
+           dotest modules5-42 "$testcvs co -d mydir dirmodule/nonexist" \
+"$SPROG checkout: nothing known about \`mydir/nonexist'
+$SPROG checkout: Executing ..$CVSROOT_DIRNAME/checkout\.sh. .mydir..
+checkout script invoked in $TESTDIR/1
+args: mydir"
+         fi
+         # We tolerate the creation of the mydir directory, since that
+         # is what CVS does, not because we view that as preferable to not
+         # creating it.
+         dotest_fail modules5-43 "test -f mydir/a || test -f mydir/b" ""
+         rm -r mydir
+
+         if $remote; then
+           dotest modules5-44 "${testcvs} -q co -d mydir namedmodule" \
+"U mydir/a
+U mydir/b
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: mydir"
+         else
+           dotest modules5-44 "${testcvs} -q co -d mydir namedmodule" \
+"U mydir/a
+U mydir/b
+checkout script invoked in ${TESTDIR}/1
+args: mydir"
+         fi
+         dotest modules5-45 "test -f mydir/a && test -f mydir/b" ""
+         dotest_fail modules5-46 "test -d namedir"
+         echo add line >>mydir/a
+         # This seems suspicious: when we checkout an existing directory,
+         # the checkout script gets executed in addition to the update
+         # script.  Is that by design or accident?
+         if $remote; then
+           dotest modules5-47 "${testcvs} -q co -d mydir namedmodule" \
+"M mydir/a
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: mydir"
+         else
+           dotest modules5-47 "${testcvs} -q co -d mydir namedmodule" \
+"M mydir/a
+checkout script invoked in ${TESTDIR}/1
+args: mydir"
+         fi
+         rm mydir/a
+
+         if $remote; then
+           dotest modules5-48 "${testcvs} -q co -d mydir namedmodule" \
+"U mydir/a
+checkout script invoked in ${TMPDIR}/cvs-serv[0-9a-z]*
+args: mydir"
+         else
+           dotest modules5-48 "${testcvs} -q co -d mydir namedmodule" \
+"U mydir/a
+checkout script invoked in ${TESTDIR}/1
+args: mydir"
+         fi
+         dotest modules5-49 "echo yes | ${testcvs} release -d mydir" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory .mydir.: "
+
+         dokeep
+         cd ..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/*.sh
+         ;;
+
+
+
+       modules6)
+         #
+         # Test invalid module definitions
+         #
+         # See the header comment for the `modules' test for an index of
+         # the complete suite of modules tests.
+         #
+
+         #
+         # There was a bug in CVS through 1.11.1p1 where a bad module name
+         # would cause the previous line to be parsed as the module
+         # definition.  This test proves this doesn't happen anymore.
+         #
+         mkdir modules6
+         cd modules6
+         dotest module6-setup-1 "${testcvs} -Q co CVSROOT" ""
+         cd CVSROOT
+         echo "longmodulename who cares" >modules
+         echo "badname" >>modules
+         # This test almost isn't setup since it generates the error message
+         # we are looking for if `-Q' isn't specified, but I want to test the
+         # filename in the message later.
+         dotest modules6-setup-2 "$testcvs -Q ci -mbad-modules"
+
+         # Here's where CVS would report not being able to find `lename'
+         cd ..
+         dotest_fail modules6-1 "${testcvs} -q co badname" \
+"${SPROG} checkout: warning: NULL value for key .badname. at line 2 of 
.${CVSROOT_DIRNAME}/CVSROOT/modules.
+${SPROG} checkout: cannot find module .badname. - ignored" \
+"${SPROG} server: warning: NULL value for key .badname. at line 2 of 
.${CVSROOT_DIRNAME}/CVSROOT/modules.
+${SPROG} server: cannot find module .badname. - ignored
+${CPROG} \[checkout aborted\]: cannot expand modules"
+
+         dokeep
+         restore_adm
+         cd ..
+         rm -r modules6
+         ;;
+
+
+
+       modules7)
+         #
+         # Test tag problems vs an empty CVSROOT/val-tags file
+         #
+         # See the header comment for the `modules' test for an index of
+         # the complete suite of modules tests.
+         #
+         mkdir modules7
+         cd modules7
+         dotest modules7-1 "$testcvs -Q co -d top ."
+         cd top
+         mkdir zero one
+         dotest modules7-2 "$testcvs -Q add zero one"
+         cd one
+         echo 'file1 contents' > file1
+         dotest modules7-2 "$testcvs -Q add file1"
+         dotest modules7-3 "$testcvs -Q ci -mnew file1"
+         dotest modules7-4 "$testcvs -Q tag mytag file1"
+         cd ../CVSROOT
+         echo 'all -a zero one' > modules
+         dotest modules7-5 "$testcvs -Q ci -mall-module"
+         cd ../..
+         mkdir myexport
+         cd myexport
+
+         # This failed prior to CVS version 1.12.10.
+         dotest modules7-7 "$testcvs export -rmytag all" \
+"$SPROG export: Updating zero
+$SPROG export: Updating one
+U one/file1"
+         dotest modules7-8 'cat one/file1' 'file1 contents'
+
+         dokeep
+
+         # cleanup
+         restore_adm
+         cd ../..
+         rm -fr modules7
+         rm -rf $CVSROOT_DIRNAME/zero $CVSROOT_DIRNAME/one
+         ;;
+
+
+
+       mkmodules)
+         # When a file listed in checkoutlist doesn't exist, cvs-1.10.4
+         # would fail to remove the CVSROOT/.#[0-9]* temporary file it
+         # creates while mkmodules is in the process of trying to check
+         # out the missing file.
+
+         mkdir 1; cd 1
+         dotest mkmodules-temp-file-removal-1 "${testcvs} -Q co CVSROOT" ''
+         cd CVSROOT
+         echo no-such-file >> checkoutlist
+         dotest mkmodules-temp-file-removal-2 "$testcvs -Q ci -m. checkoutlist"
+
+         dotest mkmodules-temp-file-removal-3 \
+"echo $CVSROOT_DIRNAME/CVSROOT/.#[0-9]*" \
+"$CVSROOT_DIRNAME/CVSROOT/\.#\[0-9\]\*"
+
+         # Versions 1.11.6 & 1.12.1 and earlier of CVS printed most of the
+         # white space included before error messages in checkoutlist.
+         echo "no-such-file     Failed to update no-such-file." >checkoutlist
+         dotest mkmodules-error-message-1 "$testcvs -Q ci -m. checkoutlist" \
+"$SPROG commit: Failed to update no-such-file\."
+
+         # Versions 1.11.6 & 1.12.1 and earlier of CVS used the error string
+         # from the checkoutlist file as the format string passed to error()'s
+         # printf.  Check that this is no longer the case by verifying that
+         # printf format patterns remain unchanged.
+         echo "no-such-file     Failed to update %s %lx times because %s 
happened %d times." >checkoutlist
+         dotest mkmodules-error-message-2 "$testcvs -Q ci -m. checkoutlist" \
+"$SPROG commit: Failed to update %s %lx times because %s happened %d times\."
+
+         dotest mkmodules-cleanup-1 \
+"$testcvs -Q up -pr1.1 checkoutlist >checkoutlist"
+         dotest mkmodules-cleanup-2 "$testcvs -Q ci -m. checkoutlist"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         ;;
+
+
+
+       co-d)
+         # Some tests of various permutations of co-d when directories exist
+         # and checkouts lengthen.
+         #
+         # Interestingly enough, these same tests pass when the directory
+         # lengthening happens via the modules file.  Go figure.
+         module=co-d
+         mkdir $module; cd $module
+         mkdir top; cd top
+         dotest co-d-init-1 "$testcvs -Q co -l ."
+         mkdir $module
+         dotest co-d-init-2 "$testcvs -Q add $module"
+         cd $module
+         echo content >file1
+         echo different content >file2
+         dotest co-d-init-3 "$testcvs -Q add file1 file2"
+         dotest co-d-init-4 "$testcvs -Q ci -madd-em"
+         cd ../..
+
+         mkdir 2; cd 2
+         dotest co-d-1 "$testcvs -q co -d dir $module" \
+"U dir/file1
+U dir/file2"
+         dotest co-d-1.2 "cat dir/CVS/Repository" "$module"
+
+         dotest co-d-2 "$testcvs -q co -d dir2/sdir $module" \
+"U dir2/sdir/file1
+U dir2/sdir/file2"
+         dotest co-d-2.2 "cat dir2/CVS/Repository" "."
+         dotest co-d-2.3 "cat dir2/sdir/CVS/Repository" "$module"
+
+         dotest co-d-2.4 "$testcvs -q co -d dir2.4/sdir/sdir2 $module" \
+"U dir2.4/sdir/sdir2/file1
+U dir2.4/sdir/sdir2/file2"
+         dotest co-d-2.4.2 "cat dir2.4/CVS/Repository" "CVSROOT/Emptydir"
+         dotest co-d-2.4.3 "cat dir2.4/sdir/CVS/Repository" "."
+         dotest co-d-2.4.3 "cat dir2.4/sdir/sdir2/CVS/Repository" "$module"
+
+         mkdir dir3
+         dotest co-d-3 "$testcvs -q co -d dir3 $module" \
+"U dir3/file1
+U dir3/file2"
+         dotest co-d-3.2 "cat dir3/CVS/Repository" "$module"
+
+         mkdir dir4
+         dotest co-d-4 "$testcvs -q co -d dir4/sdir $module" \
+"U dir4/sdir/file1
+U dir4/sdir/file2"
+
+         # CVS is only supposed to create administration directories in
+         # directories it also creates, and in the directory specified by
+         # the last portion of the path passed to -d regardless.  This is
+         #
+         # FIXCVS:
+         # This is broken in client/server mode because the server does not
+         # know the client's directory structure and has to create
+         # everything.
+         if $remote; then
+           dotest co-d-4.2r "cat dir4/CVS/Repository" "."
+         else
+           dotest_fail co-d-4.2 "test -d dir4/CVS"
+         fi
+
+         dotest co-d-4.3 "cat dir4/sdir/CVS/Repository" "$module"
+
+         mkdir dir5
+         mkdir dir5/sdir
+         dotest co-d-5 "$testcvs -q co -d dir5/sdir $module" \
+"U dir5/sdir/file1
+U dir5/sdir/file2"
+           # FIXCVS as for co-d-4.2r.
+         if $remote; then
+           dotest co-d-5.2 "cat dir5/CVS/Repository" "."
+         else
+           dotest_fail co-d-5.2 "test -d dir5/CVS"
+         fi
+
+         dotest co-d-5.3 "cat dir5/sdir/CVS/Repository" "$module"
+
+         # clean up
+         dokeep
+         cd ../..
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         rm -r $module
+         ;;
+
+
+
+       cvsadm)
+         # These test check the content of CVS' administrative
+         # files as they are checked out in various configurations.
+         # (As a side note, I'm not using the "-q" flag in any of
+         # this code, which should provide some extra checking for
+          # those messages which don't seem to be checked thoroughly
+         # anywhere else.)  To do a thorough test, we need to make
+         # a bunch of modules in various configurations.
+         #
+         # <1mod> is a directory at the top level of cvsroot
+         #    ``foo bar''
+         # <2mod> is a directory at the second level of cvsroot
+         #    ``foo bar/baz''
+         # <1d1mod> is a directory at the top level which is
+         #   checked out into another directory
+         #     ``foo -d bar baz''
+         # <1d2mod> is a directory at the second level which is
+         #   checked out into another directory
+         #     ``foo -d bar baz/quux''
+         # <2d1mod> is a directory at the top level which is
+         #   checked out into a directory that is two deep
+         #     ``foo -d bar/baz quux''
+         # <2d2mod> is a directory at the second level which is
+         #   checked out into a directory that is two deep
+         #     ``foo -d bar/baz quux''
+         #
+         # The tests do each of these types separately and in twos.
+         # We also repeat each test -d flag for 1-deep and 2-deep
+         # directories.
+         #
+         # Each test should check the output for the Repository
+         # file, since that is the one which varies depending on 
+         # the directory and how it was checked out.
+         #
+         # Yes, this is verbose, but at least it's very thorough.
+
+         # convenience variables
+         REP=${CVSROOT}
+
+         # First, set TopLevelAdmin=yes so we're sure to get
+         # top-level CVS directories.
+         mkdir 1; cd 1
+         dotest cvsadm-setup-1 "${testcvs} -q co CVSROOT/config" \
+"U CVSROOT/config"
+         cd CVSROOT
+         echo "TopLevelAdmin=yes" >>config
+         dotest cvsadm-setup-2 "${testcvs} -q ci -m yes-top-level" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ../..
+         rm -r 1
+
+         # Second, check out the modules file and edit it.
+         mkdir 1; cd 1
+         dotest cvsadm-1 "${testcvs} co CVSROOT/modules" \
+"U CVSROOT/modules"
+
+         # Test CVS/Root once.  Since there is only one part of
+         # the code which writes CVS/Root files (Create_Admin),
+         # there is no point in testing this every time.
+         dotest cvsadm-1a "cat CVS/Root" ${REP}
+         dotest cvsadm-1b "cat CVS/Repository" "\."
+         dotest cvsadm-1c "cat CVSROOT/CVS/Root" ${REP}
+         dotest cvsadm-1d "cat CVSROOT/CVS/Repository" "CVSROOT"
+          # All of the defined module names begin with a number.
+         # All of the top-level directory names begin with "dir".
+         # All of the subdirectory names begin with "sub".
+         # All of the top-level modules begin with "mod".
+         echo "# Module defs for cvsadm tests" > CVSROOT/modules
+         echo "1mod mod1" >> CVSROOT/modules
+         echo "1mod-2 mod1-2" >> CVSROOT/modules
+         echo "2mod mod2/sub2" >> CVSROOT/modules
+         echo "2mod-2 mod2-2/sub2-2" >> CVSROOT/modules
+         echo "1d1mod -d dir1d1 mod1" >> CVSROOT/modules
+         echo "1d1mod-2 -d dir1d1-2 mod1-2" >> CVSROOT/modules
+         echo "1d2mod -d dir1d2 mod2/sub2" >> CVSROOT/modules
+         echo "1d2mod-2 -d dir1d2-2 mod2-2/sub2-2" >> CVSROOT/modules
+         echo "2d1mod -d dir2d1/sub2d1 mod1" >> CVSROOT/modules
+         echo "2d1mod-2 -d dir2d1-2/sub2d1-2 mod1-2" >> CVSROOT/modules
+         echo "2d2mod -d dir2d2/sub2d2 mod2/sub2" >> CVSROOT/modules
+         echo "2d2mod-2 -d dir2d2-2/sub2d2-2 mod2-2/sub2-2" >> CVSROOT/modules
+         dotest cvsadm-1e "${testcvs} ci -m add-modules" \
+"${CPROG} commit: Examining .
+${CPROG} commit: Examining CVSROOT
+${CVSROOT_DIRNAME}/CVSROOT/modules,v  <--  CVSROOT/modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+${SPROG} commit: Rebuilding administrative file database" \
+"${CPROG} commit: Examining .
+${CPROG} commit: Examining CVSROOT"
+         rm -rf CVS CVSROOT;
+
+         # Create the various modules
+         dotest cvsadm-2 "${testcvs} -q co -l ." ''
+         mkdir mod1
+         mkdir mod1-2
+         mkdir mod2
+         mkdir mod2/sub2
+         mkdir mod2-2
+         mkdir mod2-2/sub2-2
+         dotest cvsadm-2a "${testcvs} add mod1 mod1-2 mod2 mod2/sub2 mod2-2 
mod2-2/sub2-2" \
+"Directory ${CVSROOT_DIRNAME}/mod1 added to the repository
+Directory ${CVSROOT_DIRNAME}/mod1-2 added to the repository
+Directory ${CVSROOT_DIRNAME}/mod2 added to the repository
+Directory ${CVSROOT_DIRNAME}/mod2/sub2 added to the repository
+Directory ${CVSROOT_DIRNAME}/mod2-2 added to the repository
+Directory ${CVSROOT_DIRNAME}/mod2-2/sub2-2 added to the repository"
+
+         # Populate the directories for the halibut
+         echo "file1" > mod1/file1
+         echo "file1-2" > mod1-2/file1-2
+         echo "file2" > mod2/sub2/file2
+         echo "file2-2" > mod2-2/sub2-2/file2-2
+         dotest cvsadm-2aa "${testcvs} add mod1/file1 mod1-2/file1-2 
mod2/sub2/file2 mod2-2/sub2-2/file2-2" \
+"${SPROG} add: scheduling file .mod1/file1. for addition
+${SPROG} add: scheduling file .mod1-2/file1-2. for addition
+${SPROG} add: scheduling file .mod2/sub2/file2. for addition
+${SPROG} add: scheduling file .mod2-2/sub2-2/file2-2. for addition
+${SPROG} add: use \`${SPROG} commit' to add these files permanently"
+
+         dotest cvsadm-2b "${testcvs} ci -m yup mod1 mod1-2 mod2 mod2-2" \
+"${CPROG} commit: Examining mod1
+${CPROG} commit: Examining mod1-2
+${CPROG} commit: Examining mod2
+${CPROG} commit: Examining mod2/sub2
+${CPROG} commit: Examining mod2-2
+${CPROG} commit: Examining mod2-2/sub2-2
+${CVSROOT_DIRNAME}/mod1/file1,v  <--  mod1/file1
+initial revision: 1.1
+${CVSROOT_DIRNAME}/mod1-2/file1-2,v  <--  mod1-2/file1-2
+initial revision: 1.1
+${CVSROOT_DIRNAME}/mod2/sub2/file2,v  <--  mod2/sub2/file2
+initial revision: 1.1
+${CVSROOT_DIRNAME}/mod2-2/sub2-2/file2-2,v  <--  mod2-2/sub2-2/file2-2
+initial revision: 1.1"
+         # Finished creating the modules -- clean up.
+         rm -rf CVS mod1 mod1-2 mod2 mod2-2
+         # Done.
+
+         ##################################################
+         ## Start the dizzying array of possibilities.
+         ## Begin with each module type separately.
+         ##################################################
+         
+         # Pattern -- after each checkout, first check the top-level
+         # CVS directory.  Then, check the directories in numerical
+         # order.
+
+         dotest cvsadm-3 "${testcvs} co 1mod" \
+"${SPROG} checkout: Updating 1mod
+U 1mod/file1"
+         dotest cvsadm-3b "cat CVS/Repository" "\."
+         dotest cvsadm-3d "cat 1mod/CVS/Repository" "mod1"
+         rm -rf CVS 1mod
+
+         dotest cvsadm-4 "${testcvs} co 2mod" \
+"${SPROG} checkout: Updating 2mod
+U 2mod/file2"
+         dotest cvsadm-4b "cat CVS/Repository" "\."
+         dotest cvsadm-4d "cat 2mod/CVS/Repository" "mod2/sub2"
+         rm -rf CVS 2mod
+
+         dotest cvsadm-5 "${testcvs} co 1d1mod" \
+"${SPROG} checkout: Updating dir1d1
+U dir1d1/file1"
+         dotest cvsadm-5b "cat CVS/Repository" "\."
+         dotest cvsadm-5d "cat dir1d1/CVS/Repository" "mod1"
+         rm -rf CVS dir1d1
+
+         dotest cvsadm-6 "${testcvs} co 1d2mod" \
+"${SPROG} checkout: Updating dir1d2
+U dir1d2/file2"
+         dotest cvsadm-6b "cat CVS/Repository" "\."
+         dotest cvsadm-6d "cat dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir1d2
+
+         dotest cvsadm-7 "${testcvs} co 2d1mod" \
+"${SPROG} checkout: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+         dotest cvsadm-7b "cat CVS/Repository" "\."
+         dotest cvsadm-7d "cat dir2d1/CVS/Repository" "\."
+         dotest cvsadm-7f "cat dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS dir2d1
+
+         dotest cvsadm-8 "${testcvs} co 2d2mod" \
+"${SPROG} checkout: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+         dotest cvsadm-8b "cat CVS/Repository" "\."
+         dotest cvsadm-8d "cat dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-8f "cat dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir2d2
+
+         ##################################################
+         ## You are in a shell script of twisted little
+         ## module combination statements, all alike.
+         ##################################################
+
+         ### 1mod
+         
+         dotest cvsadm-9 "${testcvs} co 1mod 1mod-2" \
+"${SPROG} checkout: Updating 1mod
+U 1mod/file1
+${SPROG} checkout: Updating 1mod-2
+U 1mod-2/file1-2"
+         # the usual for the top level
+         dotest cvsadm-9b "cat CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-9d "cat 1mod/CVS/Repository" "mod1"
+         # the usual for 1mod copy
+         dotest cvsadm-9f "cat 1mod-2/CVS/Repository" "mod1-2"
+         rm -rf CVS 1mod 1mod-2
+
+         # 1mod 2mod redmod bluemod
+         dotest cvsadm-10 "${testcvs} co 1mod 2mod" \
+"${SPROG} checkout: Updating 1mod
+U 1mod/file1
+${SPROG} checkout: Updating 2mod
+U 2mod/file2"
+         # the usual for the top level
+         dotest cvsadm-10b "cat CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-10d "cat 1mod/CVS/Repository" "mod1"
+         # the usual for 2dmod
+         dotest cvsadm-10f "cat 2mod/CVS/Repository" "mod2/sub2"
+         rm -rf CVS 1mod 2mod
+
+         dotest cvsadm-11 "${testcvs} co 1mod 1d1mod" \
+"${SPROG} checkout: Updating 1mod
+U 1mod/file1
+${SPROG} checkout: Updating dir1d1
+U dir1d1/file1"
+         # the usual for the top level
+         dotest cvsadm-11b "cat CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-11d "cat 1mod/CVS/Repository" "mod1"
+         # the usual for 1d1mod
+         dotest cvsadm-11f "cat dir1d1/CVS/Repository" "mod1"
+         rm -rf CVS 1mod dir1d1
+
+         dotest cvsadm-12 "${testcvs} co 1mod 1d2mod" \
+"${SPROG} checkout: Updating 1mod
+U 1mod/file1
+${SPROG} checkout: Updating dir1d2
+U dir1d2/file2"
+         # the usual for the top level
+         dotest cvsadm-12b "cat CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-12d "cat 1mod/CVS/Repository" "mod1"
+         # the usual for 1d2mod
+         dotest cvsadm-12f "cat dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS 1mod dir1d2
+
+         dotest cvsadm-13 "${testcvs} co 1mod 2d1mod" \
+"${SPROG} checkout: Updating 1mod
+U 1mod/file1
+${SPROG} checkout: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+         # the usual for the top level
+         dotest cvsadm-13b "cat CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-13d "cat 1mod/CVS/Repository" "mod1"
+         # the usual for 2d1mod
+         dotest cvsadm-13f "cat dir2d1/CVS/Repository" "\."
+         dotest cvsadm-13h "cat dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS 1mod dir2d1
+
+         dotest cvsadm-14 "${testcvs} co 1mod 2d2mod" \
+"${SPROG} checkout: Updating 1mod
+U 1mod/file1
+${SPROG} checkout: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+         # the usual for the top level
+         dotest cvsadm-14b "cat CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-14d "cat 1mod/CVS/Repository" "mod1"
+         # the usual for 2d2mod
+         dotest cvsadm-14f "cat dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-14h "cat dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS 1mod dir2d2
+
+
+         ### 2mod
+         
+         dotest cvsadm-15 "${testcvs} co 2mod 2mod-2" \
+"${SPROG} checkout: Updating 2mod
+U 2mod/file2
+${SPROG} checkout: Updating 2mod-2
+U 2mod-2/file2-2"
+         # the usual for the top level
+         dotest cvsadm-15b "cat CVS/Repository" "\."
+         # the usual for 2mod
+         dotest cvsadm-15d "cat 2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 2mod copy
+         dotest cvsadm-15f "cat 2mod-2/CVS/Repository" "mod2-2/sub2-2"
+         rm -rf CVS 2mod 2mod-2
+
+
+         dotest cvsadm-16 "${testcvs} co 2mod 1d1mod" \
+"${SPROG} checkout: Updating 2mod
+U 2mod/file2
+${SPROG} checkout: Updating dir1d1
+U dir1d1/file1"
+         # the usual for the top level
+         dotest cvsadm-16b "cat CVS/Repository" "\."
+         # the usual for 2mod
+         dotest cvsadm-16d "cat 2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 1d1mod
+         dotest cvsadm-16f "cat dir1d1/CVS/Repository" "mod1"
+         rm -rf CVS 2mod dir1d1
+
+         dotest cvsadm-17 "${testcvs} co 2mod 1d2mod" \
+"${SPROG} checkout: Updating 2mod
+U 2mod/file2
+${SPROG} checkout: Updating dir1d2
+U dir1d2/file2"
+         # the usual for the top level
+         dotest cvsadm-17b "cat CVS/Repository" "\."
+         # the usual for 2mod
+         dotest cvsadm-17d "cat 2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 1d2mod
+         dotest cvsadm-17f "cat dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS 2mod dir1d2
+
+         dotest cvsadm-18 "${testcvs} co 2mod 2d1mod" \
+"${SPROG} checkout: Updating 2mod
+U 2mod/file2
+${SPROG} checkout: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+         # the usual for the top level
+         dotest cvsadm-18b "cat CVS/Repository" "\."
+         # the usual for 2mod
+         dotest cvsadm-18d "cat 2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 2d1mod
+         dotest cvsadm-18f "cat dir2d1/CVS/Repository" "\."
+         dotest cvsadm-18h "cat dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS 2mod dir2d1
+
+         dotest cvsadm-19 "${testcvs} co 2mod 2d2mod" \
+"${SPROG} checkout: Updating 2mod
+U 2mod/file2
+${SPROG} checkout: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+         # the usual for the top level
+         dotest cvsadm-19b "cat CVS/Repository" "\."
+         # the usual for 2mod
+         dotest cvsadm-19d "cat 2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 2d2mod
+         dotest cvsadm-19f "cat dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-19h "cat dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS 2mod dir2d2
+
+
+         ### 1d1mod
+
+         dotest cvsadm-20 "${testcvs} co 1d1mod 1d1mod-2" \
+"${SPROG} checkout: Updating dir1d1
+U dir1d1/file1
+${SPROG} checkout: Updating dir1d1-2
+U dir1d1-2/file1-2"
+         # the usual for the top level
+         dotest cvsadm-20b "cat CVS/Repository" "\."
+         # the usual for 1d1mod
+         dotest cvsadm-20d "cat dir1d1/CVS/Repository" "mod1"
+         # the usual for 1d1mod copy
+         dotest cvsadm-20f "cat dir1d1-2/CVS/Repository" "mod1-2"
+         rm -rf CVS dir1d1 dir1d1-2
+
+         dotest cvsadm-21 "${testcvs} co 1d1mod 1d2mod" \
+"${SPROG} checkout: Updating dir1d1
+U dir1d1/file1
+${SPROG} checkout: Updating dir1d2
+U dir1d2/file2"
+         # the usual for the top level
+         dotest cvsadm-21b "cat CVS/Repository" "\."
+         # the usual for 1d1mod
+         dotest cvsadm-21d "cat dir1d1/CVS/Repository" "mod1"
+         # the usual for 1d2mod
+         dotest cvsadm-21f "cat dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir1d1 dir1d2
+
+         dotest cvsadm-22 "${testcvs} co 1d1mod 2d1mod" \
+"${SPROG} checkout: Updating dir1d1
+U dir1d1/file1
+${SPROG} checkout: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+         # the usual for the top level
+         dotest cvsadm-22b "cat CVS/Repository" "\."
+         # the usual for 1d1mod
+         dotest cvsadm-22d "cat dir1d1/CVS/Repository" "mod1"
+         # the usual for 2d1mod
+         dotest cvsadm-22f "cat dir2d1/CVS/Repository" "\."
+         dotest cvsadm-22h "cat dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS dir1d1 dir2d1
+
+         dotest cvsadm-23 "${testcvs} co 1d1mod 2d2mod" \
+"${SPROG} checkout: Updating dir1d1
+U dir1d1/file1
+${SPROG} checkout: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+         # the usual for the top level
+         dotest cvsadm-23b "cat CVS/Repository" "\."
+         # the usual for 1d1mod
+         dotest cvsadm-23d "cat dir1d1/CVS/Repository" "mod1"
+         # the usual for 2d2mod
+         dotest cvsadm-23f "cat dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-23h "cat dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir1d1 dir2d2
+
+
+         ### 1d2mod
+
+         dotest cvsadm-24 "${testcvs} co 1d2mod 1d2mod-2" \
+"${SPROG} checkout: Updating dir1d2
+U dir1d2/file2
+${SPROG} checkout: Updating dir1d2-2
+U dir1d2-2/file2-2"
+         # the usual for the top level
+         dotest cvsadm-24b "cat CVS/Repository" "\."
+         # the usual for 1d2mod
+         dotest cvsadm-24d "cat dir1d2/CVS/Repository" "mod2/sub2"
+         # the usual for 1d2mod copy
+         dotest cvsadm-24f "cat dir1d2-2/CVS/Repository" "mod2-2/sub2-2"
+         rm -rf CVS dir1d2 dir1d2-2
+
+         dotest cvsadm-25 "${testcvs} co 1d2mod 2d1mod" \
+"${SPROG} checkout: Updating dir1d2
+U dir1d2/file2
+${SPROG} checkout: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+         # the usual for the top level
+         dotest cvsadm-25b "cat CVS/Repository" "\."
+         # the usual for 1d2mod
+         dotest cvsadm-25d "cat dir1d2/CVS/Repository" "mod2/sub2"
+         # the usual for 2d1mod
+         dotest cvsadm-25f "cat dir2d1/CVS/Repository" "\."
+         dotest cvsadm-25h "cat dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS dir1d2 dir2d1
+
+         dotest cvsadm-26 "${testcvs} co 1d2mod 2d2mod" \
+"${SPROG} checkout: Updating dir1d2
+U dir1d2/file2
+${SPROG} checkout: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+         # the usual for the top level
+         dotest cvsadm-26b "cat CVS/Repository" "\."
+         # the usual for 1d2mod
+         dotest cvsadm-26d "cat dir1d2/CVS/Repository" "mod2/sub2"
+         # the usual for 2d2mod
+         dotest cvsadm-26f "cat dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-26h "cat dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir1d2 dir2d2
+
+
+         # 2d1mod
+
+         dotest cvsadm-27 "${testcvs} co 2d1mod 2d1mod-2" \
+"${SPROG} checkout: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1
+${SPROG} checkout: Updating dir2d1-2/sub2d1-2
+U dir2d1-2/sub2d1-2/file1-2"
+         # the usual for the top level
+         dotest cvsadm-27b "cat CVS/Repository" "\."
+         # the usual for 2d1mod
+         dotest cvsadm-27d "cat dir2d1/CVS/Repository" "\."
+         dotest cvsadm-27f "cat dir2d1/sub2d1/CVS/Repository" "mod1"
+         # the usual for 2d1mod
+         dotest cvsadm-27h "cat dir2d1-2/CVS/Repository" "\."
+         dotest cvsadm-27j "cat dir2d1-2/sub2d1-2/CVS/Repository" "mod1-2"
+         rm -rf CVS dir2d1 dir2d1-2
+
+         dotest cvsadm-28 "${testcvs} co 2d1mod 2d2mod" \
+"${SPROG} checkout: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1
+${SPROG} checkout: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+         # the usual for the top level
+         dotest cvsadm-28b "cat CVS/Repository" "\."
+         # the usual for 2d1mod
+         dotest cvsadm-28d "cat dir2d1/CVS/Repository" "\."
+         dotest cvsadm-28f "cat dir2d1/sub2d1/CVS/Repository" "mod1"
+         # the usual for 2d2mod
+         dotest cvsadm-28h "cat dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-28j "cat dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir2d1 dir2d2
+
+         
+         # 2d2mod
+
+         dotest cvsadm-29 "${testcvs} co 2d2mod 2d2mod-2" \
+"${SPROG} checkout: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2
+${SPROG} checkout: Updating dir2d2-2/sub2d2-2
+U dir2d2-2/sub2d2-2/file2-2"
+         # the usual for the top level
+         dotest cvsadm-29b "cat CVS/Repository" "\."
+         # the usual for 2d2mod
+         dotest cvsadm-29d "cat dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-29f "cat dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         # the usual for 2d2mod
+         dotest cvsadm-29h "cat dir2d2-2/CVS/Repository" "mod2-2"
+         dotest cvsadm-29j "cat dir2d2-2/sub2d2-2/CVS/Repository" \
+"mod2-2/sub2-2"
+         rm -rf CVS dir2d2 dir2d2-2
+
+         ##################################################
+         ## And now, all of that again using the "-d" flag
+         ## on the command line.
+         ##################################################
+
+         dotest cvsadm-1d3 "${testcvs} co -d dir 1mod" \
+"${SPROG} checkout: Updating dir
+U dir/file1"
+         dotest cvsadm-1d3b "cat CVS/Repository" "\."
+         dotest cvsadm-1d3d "cat dir/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d4 "${testcvs} co -d dir 2mod" \
+"${SPROG} checkout: Updating dir
+U dir/file2"
+         dotest cvsadm-1d4b "cat CVS/Repository" "\."
+         dotest cvsadm-1d4d "cat dir/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d5 "${testcvs} co -d dir 1d1mod" \
+"${SPROG} checkout: Updating dir
+U dir/file1"
+         dotest cvsadm-1d5b "cat CVS/Repository" "\."
+         dotest cvsadm-1d5d "cat dir/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d6 "${testcvs} co -d dir 1d2mod" \
+"${SPROG} checkout: Updating dir
+U dir/file2"
+         dotest cvsadm-1d6b "cat CVS/Repository" "\."
+         dotest cvsadm-1d6d "cat dir/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d7 "${testcvs} co -d dir 2d1mod" \
+"${SPROG} checkout: Updating dir
+U dir/file1"
+         dotest cvsadm-1d7b "cat CVS/Repository" "\."
+         dotest cvsadm-1d7d "cat dir/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d8 "${testcvs} co -d dir 2d2mod" \
+"${SPROG} checkout: Updating dir
+U dir/file2"
+         dotest cvsadm-1d8b "cat CVS/Repository" "\."
+         dotest cvsadm-1d8d "cat dir/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         ##################################################
+         ## Los Combonaciones
+         ##################################################
+
+         ### 1mod
+
+         dotest cvsadm-1d9 "${testcvs} co -d dir 1mod 1mod-2" \
+"${SPROG} checkout: Updating dir/1mod
+U dir/1mod/file1
+${SPROG} checkout: Updating dir/1mod-2
+U dir/1mod-2/file1-2"
+         # the usual for the top level
+         dotest cvsadm-1d9b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d9d "cat dir/CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-1d9f "cat dir/1mod/CVS/Repository" "mod1"
+         # the usual for 1mod copy
+         dotest cvsadm-1d9h "cat dir/1mod-2/CVS/Repository" "mod1-2"
+         rm -rf CVS dir
+
+         # 1mod 2mod redmod bluemod
+         dotest cvsadm-1d10 "${testcvs} co -d dir 1mod 2mod" \
+"${SPROG} checkout: Updating dir/1mod
+U dir/1mod/file1
+${SPROG} checkout: Updating dir/2mod
+U dir/2mod/file2"
+         dotest cvsadm-1d10b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d10d "cat dir/CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-1d10f "cat dir/1mod/CVS/Repository" "mod1"
+         # the usual for 2dmod
+         dotest cvsadm-1d10h "cat dir/2mod/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d11 "${testcvs} co -d dir 1mod 1d1mod" \
+"${SPROG} checkout: Updating dir/1mod
+U dir/1mod/file1
+${SPROG} checkout: Updating dir/dir1d1
+U dir/dir1d1/file1"
+         dotest cvsadm-1d11b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d11d "cat dir/CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-1d11f "cat dir/1mod/CVS/Repository" "mod1"
+         # the usual for 1d1mod
+         dotest cvsadm-1d11h "cat dir/dir1d1/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d12 "${testcvs} co -d dir 1mod 1d2mod" \
+"${SPROG} checkout: Updating dir/1mod
+U dir/1mod/file1
+${SPROG} checkout: Updating dir/dir1d2
+U dir/dir1d2/file2"
+         dotest cvsadm-1d12b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d12d "cat dir/CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-1d12f "cat dir/1mod/CVS/Repository" "mod1"
+         # the usual for 1d2mod
+         dotest cvsadm-1d12h "cat dir/dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d13 "${testcvs} co -d dir 1mod 2d1mod" \
+"${SPROG} checkout: Updating dir/1mod
+U dir/1mod/file1
+${SPROG} checkout: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+         dotest cvsadm-1d13b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d13d "cat dir/CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-1d13f "cat dir/1mod/CVS/Repository" "mod1"
+         # the usual for 2d1mod
+         dotest cvsadm-1d13h "cat dir/dir2d1/CVS/Repository" "\."
+         dotest cvsadm-1d13j "cat dir/dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d14 "${testcvs} co -d dir 1mod 2d2mod" \
+"${SPROG} checkout: Updating dir/1mod
+U dir/1mod/file1
+${SPROG} checkout: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+         dotest cvsadm-1d14b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d14d "cat dir/CVS/Repository" "\."
+         # the usual for 1mod
+         dotest cvsadm-1d14f "cat dir/1mod/CVS/Repository" "mod1"
+         # the usual for 2d2mod
+         dotest cvsadm-1d14h "cat dir/dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-1d14j "cat dir/dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+
+         ### 2mod
+
+         dotest cvsadm-1d15 "${testcvs} co -d dir 2mod 2mod-2" \
+"${SPROG} checkout: Updating dir/2mod
+U dir/2mod/file2
+${SPROG} checkout: Updating dir/2mod-2
+U dir/2mod-2/file2-2"
+         dotest cvsadm-1d15b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d15d "cat dir/CVS/Repository" "mod2"
+         # the usual for 2mod
+         dotest cvsadm-1d15f "cat dir/2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 2mod copy
+         dotest cvsadm-1d15h "cat dir/2mod-2/CVS/Repository" "mod2-2/sub2-2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d16 "${testcvs} co -d dir 2mod 1d1mod" \
+"${SPROG} checkout: Updating dir/2mod
+U dir/2mod/file2
+${SPROG} checkout: Updating dir/dir1d1
+U dir/dir1d1/file1"
+         dotest cvsadm-1d16b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d16d "cat dir/CVS/Repository" "mod2"
+         # the usual for 2mod
+         dotest cvsadm-1d16f "cat dir/2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 1d1mod
+         dotest cvsadm-1d16h "cat dir/dir1d1/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d17 "${testcvs} co -d dir 2mod 1d2mod" \
+"${SPROG} checkout: Updating dir/2mod
+U dir/2mod/file2
+${SPROG} checkout: Updating dir/dir1d2
+U dir/dir1d2/file2"
+         dotest cvsadm-1d17b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d17d "cat dir/CVS/Repository" "mod2"
+         # the usual for 2mod
+         dotest cvsadm-1d17f "cat dir/2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 1d2mod
+         dotest cvsadm-1d17h "cat dir/dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d18 "${testcvs} co -d dir 2mod 2d1mod" \
+"${SPROG} checkout: Updating dir/2mod
+U dir/2mod/file2
+${SPROG} checkout: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+         dotest cvsadm-1d18b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d18d "cat dir/CVS/Repository" "mod2"
+         # the usual for 2mod
+         dotest cvsadm-1d18f "cat dir/2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 2d1mod
+         dotest cvsadm-1d18h "cat dir/dir2d1/CVS/Repository" "\."
+         dotest cvsadm-1d18j "cat dir/dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d19 "${testcvs} co -d dir 2mod 2d2mod" \
+"${SPROG} checkout: Updating dir/2mod
+U dir/2mod/file2
+${SPROG} checkout: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+         dotest cvsadm-1d19b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d19d "cat dir/CVS/Repository" "mod2"
+         # the usual for 2mod
+         dotest cvsadm-1d19f "cat dir/2mod/CVS/Repository" "mod2/sub2"
+         # the usual for 2d2mod
+         dotest cvsadm-1d19h "cat dir/dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-1d19j "cat dir/dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+
+         ### 1d1mod
+
+         dotest cvsadm-1d20 "${testcvs} co -d dir 1d1mod 1d1mod-2" \
+"${SPROG} checkout: Updating dir/dir1d1
+U dir/dir1d1/file1
+${SPROG} checkout: Updating dir/dir1d1-2
+U dir/dir1d1-2/file1-2"
+         dotest cvsadm-1d20b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d20d "cat dir/CVS/Repository" "\."
+         # the usual for 1d1mod
+         dotest cvsadm-1d20f "cat dir/dir1d1/CVS/Repository" "mod1"
+         # the usual for 1d1mod copy
+         dotest cvsadm-1d20h "cat dir/dir1d1-2/CVS/Repository" "mod1-2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d21 "${testcvs} co -d dir 1d1mod 1d2mod" \
+"${SPROG} checkout: Updating dir/dir1d1
+U dir/dir1d1/file1
+${SPROG} checkout: Updating dir/dir1d2
+U dir/dir1d2/file2"
+         dotest cvsadm-1d21b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d21d "cat dir/CVS/Repository" "\."
+         # the usual for 1d1mod
+         dotest cvsadm-1d21f "cat dir/dir1d1/CVS/Repository" "mod1"
+         # the usual for 1d2mod
+         dotest cvsadm-1d21h "cat dir/dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d22 "${testcvs} co -d dir 1d1mod 2d1mod" \
+"${SPROG} checkout: Updating dir/dir1d1
+U dir/dir1d1/file1
+${SPROG} checkout: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+         dotest cvsadm-1d22b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d22d "cat dir/CVS/Repository" "\."
+         # the usual for 1d1mod
+         dotest cvsadm-1d22f "cat dir/dir1d1/CVS/Repository" "mod1"
+         # the usual for 2d1mod
+         dotest cvsadm-1d22h "cat dir/dir2d1/CVS/Repository" "\."
+         dotest cvsadm-1d22j "cat dir/dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d23 "${testcvs} co -d dir 1d1mod 2d2mod" \
+"${SPROG} checkout: Updating dir/dir1d1
+U dir/dir1d1/file1
+${SPROG} checkout: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+         dotest cvsadm-1d23b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d23d "cat dir/CVS/Repository" "\."
+         # the usual for 1d1mod
+         dotest cvsadm-1d23f "cat dir/dir1d1/CVS/Repository" "mod1"
+         # the usual for 2d2mod
+         dotest cvsadm-1d23h "cat dir/dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-1d23j "cat dir/dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+
+         ### 1d2mod
+
+         dotest cvsadm-1d24 "${testcvs} co -d dir 1d2mod 1d2mod-2" \
+"${SPROG} checkout: Updating dir/dir1d2
+U dir/dir1d2/file2
+${SPROG} checkout: Updating dir/dir1d2-2
+U dir/dir1d2-2/file2-2"
+         dotest cvsadm-1d24b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d24d "cat dir/CVS/Repository" "mod2"
+         # the usual for 1d2mod
+         dotest cvsadm-1d24f "cat dir/dir1d2/CVS/Repository" "mod2/sub2"
+         # the usual for 1d2mod copy
+         dotest cvsadm-1d24h "cat dir/dir1d2-2/CVS/Repository" "mod2-2/sub2-2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d25 "${testcvs} co -d dir 1d2mod 2d1mod" \
+"${SPROG} checkout: Updating dir/dir1d2
+U dir/dir1d2/file2
+${SPROG} checkout: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+         dotest cvsadm-1d25b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d25d "cat dir/CVS/Repository" "mod2"
+         # the usual for 1d2mod
+         dotest cvsadm-1d25f "cat dir/dir1d2/CVS/Repository" "mod2/sub2"
+         # the usual for 2d1mod
+         dotest cvsadm-1d25h "cat dir/dir2d1/CVS/Repository" "\."
+         dotest cvsadm-1d25j "cat dir/dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d26 "${testcvs} co -d dir 1d2mod 2d2mod" \
+"${SPROG} checkout: Updating dir/dir1d2
+U dir/dir1d2/file2
+${SPROG} checkout: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+         dotest cvsadm-1d26b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d26d "cat dir/CVS/Repository" "mod2"
+         # the usual for 1d2mod
+         dotest cvsadm-1d26f "cat dir/dir1d2/CVS/Repository" "mod2/sub2"
+         # the usual for 2d2mod
+         dotest cvsadm-1d26h "cat dir/dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-1d26j "cat dir/dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+
+         # 2d1mod
+
+         dotest cvsadm-1d27 "${testcvs} co -d dir 2d1mod 2d1mod-2" \
+"${SPROG} checkout: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1
+${SPROG} checkout: Updating dir/dir2d1-2/sub2d1-2
+U dir/dir2d1-2/sub2d1-2/file1-2"
+         dotest cvsadm-1d27b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d27d "cat dir/CVS/Repository" "CVSROOT/Emptydir"
+         # the usual for 2d1mod
+         dotest cvsadm-1d27f "cat dir/dir2d1/CVS/Repository" "\."
+         dotest cvsadm-1d27h "cat dir/dir2d1/sub2d1/CVS/Repository" "mod1"
+         # the usual for 2d1mod
+         dotest cvsadm-1d27j "cat dir/dir2d1-2/CVS/Repository" "\."
+         dotest cvsadm-1d27l "cat dir/dir2d1-2/sub2d1-2/CVS/Repository" \
+"mod1-2"
+         rm -rf CVS dir
+
+         dotest cvsadm-1d28 "${testcvs} co -d dir 2d1mod 2d2mod" \
+"${SPROG} checkout: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1
+${SPROG} checkout: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+         dotest cvsadm-1d28b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d28d "cat dir/CVS/Repository" "CVSROOT/Emptydir"
+         # the usual for 2d1mod
+         dotest cvsadm-1d28f "cat dir/dir2d1/CVS/Repository" "\."
+         dotest cvsadm-1d28h "cat dir/dir2d1/sub2d1/CVS/Repository" "mod1"
+         # the usual for 2d2mod
+         dotest cvsadm-1d28j "cat dir/dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-1d28l "cat dir/dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         
+         # 2d2mod
+
+         dotest cvsadm-1d29 "${testcvs} co -d dir 2d2mod 2d2mod-2" \
+"${SPROG} checkout: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2
+${SPROG} checkout: Updating dir/dir2d2-2/sub2d2-2
+U dir/dir2d2-2/sub2d2-2/file2-2"
+         dotest cvsadm-1d29b "cat CVS/Repository" "\."
+         # the usual for the dir level
+         dotest cvsadm-1d29d "cat dir/CVS/Repository" "\."
+         # the usual for 2d2mod
+         dotest cvsadm-1d29f "cat dir/dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-1d29h "cat dir/dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         # the usual for 2d2mod
+         dotest cvsadm-1d29j "cat dir/dir2d2-2/CVS/Repository" "mod2-2"
+         dotest cvsadm-1d29l "cat dir/dir2d2-2/sub2d2-2/CVS/Repository" \
+"mod2-2/sub2-2"
+         rm -rf CVS dir
+
+         ##################################################
+         ## And now, some of that again using the "-d" flag
+         ## on the command line, but use a longer path.
+         ##################################################
+
+         dotest cvsadm-2d3-1 "$testcvs co -d dir/dir2 1mod" \
+"$SPROG checkout: Updating dir/dir2
+U dir/dir2/file1"
+
+         # Remote couldn't handle this, even with the "mkdir dir", before
+         # CVS 1.11.14.
+         dotest cvsadm-2d3b "cat CVS/Repository" "\."
+         dotest cvsadm-2d3d "cat dir/CVS/Repository" "."
+         dotest cvsadm-2d3f "cat dir/dir2/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-2d4 "$testcvs co -d dir/dir2 2mod" \
+"$SPROG checkout: Updating dir/dir2
+U dir/dir2/file2"
+         dotest cvsadm-2d4b "cat CVS/Repository" "\."
+         dotest cvsadm-2d4f "cat dir/dir2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-2d5 "$testcvs co -d dir/dir2 1d1mod" \
+"$SPROG checkout: Updating dir/dir2
+U dir/dir2/file1"
+         dotest cvsadm-2d5b "cat CVS/Repository" "\."
+         dotest cvsadm-2d5f "cat dir/dir2/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-2d6 "$testcvs co -d dir/dir2 1d2mod" \
+"$SPROG checkout: Updating dir/dir2
+U dir/dir2/file2"
+         dotest cvsadm-2d6b "cat CVS/Repository" "\."
+         dotest cvsadm-2d6f "cat dir/dir2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-2d7 "$testcvs co -d dir/dir2 2d1mod" \
+"$SPROG checkout: Updating dir/dir2
+U dir/dir2/file1"
+         dotest cvsadm-2d7b "cat CVS/Repository" "\."
+         dotest cvsadm-2d7f "cat dir/dir2/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-2d8 "$testcvs co -d dir/dir2 2d2mod" \
+"$SPROG checkout: Updating dir/dir2
+U dir/dir2/file2"
+         dotest cvsadm-2d8b "cat CVS/Repository" "\."
+         dotest cvsadm-2d8f "cat dir/dir2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         ##################################################
+         ## And now, a few of those tests revisited to
+         ## test the behavior of the -N flag.
+         ##################################################
+
+         dotest cvsadm-N3 "$testcvs co -N 1mod" \
+"$SPROG checkout: Updating 1mod
+U 1mod/file1"
+         dotest cvsadm-N3b "cat CVS/Repository" "\."
+         dotest cvsadm-N3d "cat 1mod/CVS/Repository" "mod1"
+         rm -rf CVS 1mod
+
+         dotest cvsadm-N4 "$testcvs co -N 2mod" \
+"$SPROG checkout: Updating 2mod
+U 2mod/file2"
+         dotest cvsadm-N4b "cat CVS/Repository" "\."
+         dotest cvsadm-N4d "cat 2mod/CVS/Repository" "mod2/sub2"
+         rm -rf CVS 2mod
+
+         dotest cvsadm-N5 "$testcvs co -N 1d1mod" \
+"$SPROG checkout: Updating dir1d1
+U dir1d1/file1"
+         dotest cvsadm-N5b "cat CVS/Repository" "\."
+         dotest cvsadm-N5d "cat dir1d1/CVS/Repository" "mod1"
+         rm -rf CVS dir1d1
+
+         dotest cvsadm-N6 "$testcvs co -N 1d2mod" \
+"$SPROG checkout: Updating dir1d2
+U dir1d2/file2"
+         dotest cvsadm-N6b "cat CVS/Repository" "\."
+         dotest cvsadm-N6d "cat dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir1d2
+
+         dotest cvsadm-N7 "$testcvs co -N 2d1mod" \
+"$SPROG checkout: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+         dotest cvsadm-N7b "cat CVS/Repository" "\."
+         dotest cvsadm-N7d "cat dir2d1/CVS/Repository" "\."
+         dotest cvsadm-N7f "cat dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS dir2d1
+
+         dotest cvsadm-N8 "$testcvs co -N 2d2mod" \
+"$SPROG checkout: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+         dotest cvsadm-N8b "cat CVS/Repository" "\."
+         dotest cvsadm-N8d "cat dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-N8f "cat dir2d2/sub2d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir2d2
+
+         ## the ones in one-deep directories
+
+         dotest cvsadm-N1d3 "$testcvs co -N -d dir 1mod" \
+"$SPROG checkout: Updating dir/1mod
+U dir/1mod/file1"
+         dotest cvsadm-N1d3b "cat CVS/Repository" "\."
+         dotest cvsadm-N1d3d "cat dir/CVS/Repository" "\."
+         dotest cvsadm-N1d3f "cat dir/1mod/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-N1d4 "$testcvs co -N -d dir 2mod" \
+"$SPROG checkout: Updating dir/2mod
+U dir/2mod/file2"
+         dotest cvsadm-N1d4b "cat CVS/Repository" "\."
+         dotest cvsadm-N1d4d "cat dir/CVS/Repository" "mod2"
+         dotest cvsadm-N1d4f "cat dir/2mod/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         dotest cvsadm-N1d5 "$testcvs co -N -d dir 1d1mod" \
+"$SPROG checkout: Updating dir/dir1d1
+U dir/dir1d1/file1"
+         dotest cvsadm-N1d5b "cat CVS/Repository" "\."
+         dotest cvsadm-N1d5d "cat dir/CVS/Repository" "\."
+         dotest cvsadm-N1d5d "cat dir/dir1d1/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-N1d6 "$testcvs co -N -d dir 1d2mod" \
+"$SPROG checkout: Updating dir/dir1d2
+U dir/dir1d2/file2"
+         dotest cvsadm-N1d6b "cat CVS/Repository" "\."
+         dotest cvsadm-N1d6d "cat dir/CVS/Repository" "mod2"
+         dotest cvsadm-N1d6f "cat dir/dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         dotest cvsadm-N1d7 "$testcvs co -N -d dir 2d1mod" \
+"$SPROG checkout: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+         dotest cvsadm-N1d7b "cat CVS/Repository" "\."
+         dotest cvsadm-N1d7d "cat dir/CVS/Repository" "CVSROOT/Emptydir"
+         dotest cvsadm-N1d7f "cat dir/dir2d1/CVS/Repository" "\."
+         dotest cvsadm-N1d7h "cat dir/dir2d1/sub2d1/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         dotest cvsadm-N1d8 "$testcvs co -N -d dir 2d2mod" \
+"$SPROG checkout: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+         dotest cvsadm-N1d8b "cat CVS/Repository" "\."
+         dotest cvsadm-N1d8d "cat dir/CVS/Repository" "\."
+         dotest cvsadm-N1d8d "cat dir/dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-N1d8d "cat dir/dir2d2/sub2d2/CVS/Repository" \
+"mod2/sub2"
+         rm -rf CVS dir
+
+         ## the ones in two-deep directories
+
+         mkdir dir
+         dotest cvsadm-N2d3 "$testcvs co -N -d dir/dir2 1mod" \
+"$SPROG checkout: Updating dir/dir2/1mod
+U dir/dir2/1mod/file1"
+         dotest cvsadm-N2d3b "cat CVS/Repository" "\."
+         dotest cvsadm-N2d3f "cat dir/dir2/CVS/Repository" "\."
+         dotest cvsadm-N2d3h "cat dir/dir2/1mod/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-N2d4 "$testcvs co -N -d dir/dir2 2mod" \
+"$SPROG checkout: Updating dir/dir2/2mod
+U dir/dir2/2mod/file2"
+         dotest cvsadm-N2d4b "cat CVS/Repository" "\."
+         dotest cvsadm-N2d4f "cat dir/dir2/CVS/Repository" "mod2"
+         dotest cvsadm-N2d4h "cat dir/dir2/2mod/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-N2d5 "$testcvs co -N -d dir/dir2 1d1mod" \
+"$SPROG checkout: Updating dir/dir2/dir1d1
+U dir/dir2/dir1d1/file1"
+         dotest cvsadm-N2d5b "cat CVS/Repository" "\."
+         dotest cvsadm-N2d5f "cat dir/dir2/CVS/Repository" "\."
+         dotest cvsadm-N2d5h "cat dir/dir2/dir1d1/CVS/Repository" "mod1"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-N2d6 "$testcvs co -N -d dir/dir2 1d2mod" \
+"$SPROG checkout: Updating dir/dir2/dir1d2
+U dir/dir2/dir1d2/file2"
+         dotest cvsadm-N2d6b "cat CVS/Repository" "\."
+         dotest cvsadm-N2d6f "cat dir/dir2/CVS/Repository" "mod2"
+         dotest cvsadm-N2d6h "cat dir/dir2/dir1d2/CVS/Repository" "mod2/sub2"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-N2d7 "$testcvs co -N -d dir/dir2 2d1mod" \
+"$SPROG checkout: Updating dir/dir2/dir2d1/sub2d1
+U dir/dir2/dir2d1/sub2d1/file1"
+         dotest cvsadm-N2d7b "cat CVS/Repository" "\."
+         dotest cvsadm-N2d7f "cat dir/dir2/CVS/Repository" "CVSROOT/Emptydir"
+         dotest cvsadm-N2d7g "cat dir/dir2/dir2d1/CVS/Repository" "\."
+         dotest cvsadm-N2d7h "cat dir/dir2/dir2d1/sub2d1/CVS/Repository" \
+"mod1"
+         rm -rf CVS dir
+
+         mkdir dir
+         dotest cvsadm-N2d8 "$testcvs co -N -d dir/dir2 2d2mod" \
+"$SPROG checkout: Updating dir/dir2/dir2d2/sub2d2
+U dir/dir2/dir2d2/sub2d2/file2"
+         dotest cvsadm-N2d8b "cat CVS/Repository" "\."
+         dotest cvsadm-N2d8f "cat dir/dir2/CVS/Repository" "\."
+         dotest cvsadm-N2d8h "cat dir/dir2/dir2d2/CVS/Repository" "mod2"
+         dotest cvsadm-N2d8j "cat dir/dir2/dir2d2/sub2d2/CVS/Repository" \
+"mod2/sub2"
+         rm -rf CVS dir
+         # End of test that didn't work for remote prior to CVS 1.11.14.
+
+         ##################################################
+         ## That's enough of that, thank you very much.
+         ##################################################
+
+         dokeep
+         restore_adm
+
+         # remove our junk
+         cd ..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/1mod $CVSROOT_DIRNAME/1mod-2 \
+                            $CVSROOT_DIRNAME/2mod $CVSROOT_DIRNAME/2mod-2 \
+                            $CVSROOT_DIRNAME/mod1 $CVSROOT_DIRNAME/mod1-2 \
+                            $CVSROOT_DIRNAME/mod2 $CVSROOT_DIRNAME/mod2-2
+         ;;
+
+
+
+       emptydir)
+         # Various tests of the Emptydir (CVSNULLREPOS) code.  See also:
+         #   cvsadm: tests of Emptydir in various module definitions
+         #   basicb: Test that "Emptydir" is non-special in ordinary contexts
+
+         mkdir 1; cd 1
+         dotest emptydir-1 "${testcvs} co CVSROOT/modules" \
+"U CVSROOT/modules"
+         echo "# Module defs for emptydir tests" > CVSROOT/modules
+         echo "2d1mod -d dir2d1/sub/sub2d1 mod1" >> CVSROOT/modules
+         echo "2d1moda -d dir2d1/suba moda/modasub" >> CVSROOT/modules
+         echo "2d1modb -d dir2d1/suba mod1" >> CVSROOT/modules
+         echo "comb -a 2d1modb 2d1moda" >> CVSROOT/modules
+
+         dotest emptydir-2 "${testcvs} ci -m add-modules" \
+"${CPROG} commit: Examining CVSROOT
+${CVSROOT_DIRNAME}/CVSROOT/modules,v  <--  CVSROOT/modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+${SPROG} commit: Rebuilding administrative file database" \
+"${CPROG} commit: Examining CVSROOT"
+         rm -rf CVS CVSROOT
+
+         modify_repo mkdir $CVSROOT_DIRNAME/mod1 $CVSROOT_DIRNAME/moda
+         # Populate.  Not sure we really need to do this.
+         dotest emptydir-3 "$testcvs -q co -l ."
+         dotest emptydir-3a "${testcvs} co mod1 moda" \
+"${SPROG} checkout: Updating mod1
+${SPROG} checkout: Updating moda"
+         echo "file1" > mod1/file1
+         mkdir moda/modasub
+         dotest emptydir-3b "${testcvs} add moda/modasub" \
+"Directory ${CVSROOT_DIRNAME}/moda/modasub added to the repository"
+         echo "filea" > moda/modasub/filea
+         dotest emptydir-4 "${testcvs} add mod1/file1 moda/modasub/filea" \
+"${SPROG} add: scheduling file .mod1/file1. for addition
+${SPROG} add: scheduling file .moda/modasub/filea. for addition
+${SPROG} add: use \`${SPROG} commit' to add these files permanently"
+         dotest emptydir-5 "${testcvs} -q ci -m yup" \
+"$CVSROOT_DIRNAME/mod1/file1,v  <--  mod1/file1
+initial revision: 1\.1
+${CVSROOT_DIRNAME}/moda/modasub/filea,v  <--  moda/modasub/filea
+initial revision: 1\.1"
+         rm -rf mod1 moda CVS
+         # End Populate.
+
+         dotest emptydir-6 "${testcvs} co 2d1mod" \
+"${SPROG} checkout: Updating dir2d1/sub/sub2d1
+U dir2d1/sub/sub2d1/file1"
+         cd dir2d1
+         touch emptyfile
+         # It doesn't make any sense to add a file (or do much of anything
+         # else) in Emptydir; Emptydir is a placeholder indicating that
+         # the working directory doesn't correspond to anything in
+         # the repository.
+         dotest_fail emptydir-7 "${testcvs} add emptyfile" \
+"${SPROG} \[add aborted]: cannot add to \`${CVSROOT_DIRNAME}/CVSROOT/Emptydir'"
+         mkdir emptydir
+         dotest_fail emptydir-8 "${testcvs} add emptydir" \
+"${CPROG} \[add aborted]: cannot add to \`${CVSROOT_DIRNAME}/CVSROOT/Emptydir'"
+         cd ..
+         rm -rf CVS dir2d1
+
+         # OK, while we have an Emptydir around, test a few obscure
+         # things about it.
+         mkdir edir; cd edir
+         dotest emptydir-9 "${testcvs} -q co -l CVSROOT" \
+"U CVSROOT${DOTSTAR}"
+         cd CVSROOT
+         dotest_fail emptydir-10 "test -d Emptydir" ''
+         # This tests the code in find_dirs which skips Emptydir.
+         dotest emptydir-11 "${testcvs} -q -n update -d -P" ''
+         cd ../..
+         rm -r edir
+         cd ..
+
+         # Now start playing with moda.
+         mkdir 2; cd 2
+         dotest emptydir-12 "${testcvs} -q co 2d1moda" \
+"U dir2d1/suba/filea"
+         # OK, this is the crux of the matter.  This used to show "Emptydir",
+         # but everyone seemed to think it should show "moda".  This
+         # usually works better, but not always as shown by the following
+         # test.
+         dotest emptydir-13 "cat dir2d1/CVS/Repository" "moda"
+         dotest_fail emptydir-14 "${testcvs} co comb" \
+"${SPROG} checkout: existing repository ${CVSROOT_DIRNAME}/moda/modasub does 
not match ${CVSROOT_DIRNAME}/mod1
+${SPROG} checkout: ignoring module 2d1modb
+${SPROG} checkout: Updating dir2d1/suba"
+         dotest emptydir-15 "cat dir2d1/CVS/Repository" "moda"
+         cd ..
+
+         # Test the effect of a non-cvs directory already existing with the
+         # same name as one in the modules file.
+         mkdir 3; cd 3
+         mkdir dir2d1
+         dotest emptydir-16 "${testcvs} co 2d1mod" \
+"${SPROG} checkout: Updating dir2d1/sub/sub2d1
+U dir2d1/sub/sub2d1/file1"
+
+         if $remote; then
+           dotest emptydir-17 "cat dir2d1/CVS/Repository" "CVSROOT/Emptydir"
+         else
+           dotest_fail emptydir-17 "test -d dir2d1/CVS"
+         fi
+
+         dokeep
+         cd ..
+         rm -r 1 2 3
+         modify_repo rm -rf $CVSROOT_DIRNAME/mod1 $CVSROOT_DIRNAME/moda
+         # I guess for the moment the convention is going to be
+         # that we don't need to remove $CVSROOT_DIRNAME/CVSROOT/Emptydir
+         ;;
+
+
+
+       abspath)
+       
+         # These tests test the thituations thin thwitch thoo theck
+         # things thout twith thabsolute thaths.  Threally.
+
+         #
+         # CHECKOUTS
+         #
+
+         # Create a few modules to use
+         modify_repo mkdir $CVSROOT_DIRNAME/mod1 $CVSROOT_DIRNAME/mod2
+         dotest abspath-1a "${testcvs} co mod1 mod2" \
+"${SPROG} checkout: Updating mod1
+${SPROG} checkout: Updating mod2"
+
+         # Populate the module
+         echo "file1" > mod1/file1
+         echo "file2" > mod2/file2
+         cd mod1
+         dotest abspath-1ba "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+          cd ..
+          cd mod2
+         dotest abspath-1bb "${testcvs} add file2" \
+"${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+          cd ..
+
+         dotest abspath-1c "${testcvs} ci -m yup mod1 mod2" \
+"${CPROG} commit: Examining mod1
+${CPROG} commit: Examining mod2
+${CVSROOT_DIRNAME}/mod1/file1,v  <--  mod1/file1
+initial revision: 1.1
+${CVSROOT_DIRNAME}/mod2/file2,v  <--  mod2/file2
+initial revision: 1.1"
+         # Finished creating the module -- clean up.
+         rm -rf CVS mod1 mod2
+         # Done.
+         
+         # Try checking out the module in a local directory
+         if $remote; then
+           dotest_fail abspath-2a "${testcvs} co -d ${TESTDIR}/1 mod1" \
+"${SPROG} \[checkout aborted\]: absolute pathnames invalid for server 
(specified .${TESTDIR}/1.)"
+           dotest abspath-2a-try2 "${testcvs} co -d 1 mod1" \
+"${SPROG} checkout: Updating 1
+U 1/file1"
+         else
+           dotest abspath-2a "${testcvs} co -d ${TESTDIR}/1 mod1" \
+"${SPROG} checkout: Updating ${TESTDIR}/1
+U ${TESTDIR}/1/file1"
+         fi # remote workaround
+
+         dotest abspath-2b "cat ${TESTDIR}/1/CVS/Repository" "mod1"
+
+         # Done.  Clean up.
+         rm -r $TESTDIR/1
+
+
+         # Now try in a subdirectory.  We're not covering any more
+         # code here, but we might catch a future error if someone
+         # changes the checkout code.
+
+         # Since CVS 1.11.14, CVS will create leading directories specified
+         # via co -d.
+         # I am unsure that this wasn't the behavior prior to CVS 1.9, but the
+         # comment that used to be here leads me to believe it was not.
+         if $remote; then :; else
+           dotest abspath-3.1 "$testcvs -q co -d $TESTDIR/1/2 mod1" \
+"U $TESTDIR/1/2/file1"
+           rm -r $TESTDIR/1
+         fi
+         dotest abspath-3.2 "$testcvs -q co -d 1/2 mod1" \
+"U 1/2/file1"
+         rm -r 1
+
+         # We don't to mess with an existing directory just to traverse it,
+         # for example by creating a CVS directory, but currently we can't
+         # avoid this in client/server mode.
+         mkdir 1
+         if $remote; then
+           dotest abspath-3ar "$testcvs co -d 1/2 mod1" \
+"$SPROG checkout: Updating 1/2
+U 1/2/file1"
+           dotest abspath-3br "cat 1/CVS/Repository" .
+         else
+           dotest abspath-3a "$testcvs co -d $TESTDIR/1/2 mod1" \
+"$SPROG checkout: Updating $TESTDIR/1/2
+U $TESTDIR/1/2/file1"
+           dotest_fail abspath-3b "test -d ${TESTDIR}/1/CVS"
+         fi
+
+         dotest abspath-3c "cat ${TESTDIR}/1/2/CVS/Repository" mod1
+
+
+         # Done.  Clean up.
+         rm -rf ${TESTDIR}/1
+
+
+         # Now try someplace where we don't have permission.
+         mkdir ${TESTDIR}/barf
+         chmod -w ${TESTDIR}/barf
+         dotest_fail abspath-4r "${testcvs} co -d ${TESTDIR}/barf/sub mod1" \
+"${SPROG} \[checkout aborted\]: cannot make directory sub: Permission denied" \
+"${SPROG} \[checkout aborted\]: absolute pathnames invalid for server 
(specified .${TESTDIR}/barf/sub.)"
+         chmod +w ${TESTDIR}/barf
+         rmdir ${TESTDIR}/barf
+         # Done.  Nothing to clean up.
+
+
+         # Try checking out two modules into the same directory.
+         if $remote; then
+           dotest abspath-5ar "${testcvs} co -d 1 mod1 mod2" \
+"${SPROG} checkout: Updating 1/mod1
+U 1/mod1/file1
+${SPROG} checkout: Updating 1/mod2
+U 1/mod2/file2"
+         else
+           dotest abspath-5a "${testcvs} co -d ${TESTDIR}/1 mod1 mod2" \
+"${SPROG} checkout: Updating ${TESTDIR}/1/mod1
+U ${TESTDIR}/1/mod1/file1
+${SPROG} checkout: Updating ${TESTDIR}/1/mod2
+U ${TESTDIR}/1/mod2/file2"
+         fi # end remote workaround
+         dotest abspath-5b "cat ${TESTDIR}/1/CVS/Repository" "\."
+         dotest abspath-5c "cat ${TESTDIR}/1/mod1/CVS/Repository" "mod1"
+         dotest abspath-5d "cat ${TESTDIR}/1/mod2/CVS/Repository" "mod2"
+         # Done.  Clean up.
+         rm -rf $TESTDIR/1
+
+
+         # Try checking out the top-level module.
+         if $remote; then
+           dotest abspath-6ar "$testcvs co -d 1 ." \
+"$SPROG checkout: Updating 1
+$SPROG checkout: Updating 1/CVSROOT
+$DOTSTAR
+$SPROG checkout: Updating 1/mod1
+U 1/mod1/file1
+$SPROG checkout: Updating 1/mod2
+U 1/mod2/file2"
+         else
+           dotest abspath-6a "${testcvs} co -d ${TESTDIR}/1 ." \
+"${SPROG} checkout: Updating ${TESTDIR}/1
+${SPROG} checkout: Updating ${TESTDIR}/1/CVSROOT
+${DOTSTAR}
+${SPROG} checkout: Updating ${TESTDIR}/1/mod1
+U ${TESTDIR}/1/mod1/file1
+${SPROG} checkout: Updating ${TESTDIR}/1/mod2
+U ${TESTDIR}/1/mod2/file2"
+         fi # end of remote workaround
+         dotest abspath-6b "cat ${TESTDIR}/1/CVS/Repository" "\."
+         dotest abspath-6c "cat ${TESTDIR}/1/CVSROOT/CVS/Repository" "CVSROOT"
+         dotest abspath-6c "cat ${TESTDIR}/1/mod1/CVS/Repository" "mod1"
+         dotest abspath-6d "cat ${TESTDIR}/1/mod2/CVS/Repository" "mod2"
+         # Done.  Clean up.
+         rm -rf ${TESTDIR}/1
+
+         # Test that an absolute pathname to some other directory
+         # doesn't mess with the current working directory.
+         mkdir 1
+         cd 1
+         if $remote; then
+           dotest_fail abspath-7ar "${testcvs} -q co -d ../2 mod2" \
+"${SPROG} checkout: protocol error: .\.\./2. contains more leading \.\.
+${SPROG} \[checkout aborted\]: than the 0 which Max-dotdot specified"
+           cd ..
+           dotest abspath-7a-try2r "${testcvs} -q co -d 2 mod2" \
+"U 2/file2"
+           cd 1
+         else
+           dotest abspath-7a "${testcvs} -q co -d ${TESTDIR}/2 mod2" \
+"U ${TESTDIR}/2/file2"
+         fi # remote workaround
+         dotest abspath-7b "ls" ""
+         dotest abspath-7c "${testcvs} -q co mod1" \
+"U mod1/file1"
+         cd mod1
+         if $remote; then
+           cd ../..
+           dotest abspath-7dr "${testcvs} -q co -d 3 mod2" \
+"U 3/file2"
+           cd 1/mod1
+         else
+           dotest abspath-7d "${testcvs} -q co -d ${TESTDIR}/3 mod2" \
+"U ${TESTDIR}/3/file2"
+         fi # remote workaround
+         dotest abspath-7e "${testcvs} -q update -d"
+
+         #
+         # FIXME: do other functions here (e.g. update /tmp/foo)
+         #
+
+         # Finished with all tests.  Cleanup.
+         dokeep
+         cd ../..
+         rm -r 1 2 3
+         modify_repo rm -rf $CVSROOT_DIRNAME/mod1 $CVSROOT_DIRNAME/mod2
+         ;;
+
+
+
+       abspath2)
+         # More absolute path checks.  The following used to attempt to create
+         # directories in /:
+         #
+         # $ cvs -d:fork:/cvsroot co /foo
+         # cvs checkout: warning: cannot make directory CVS in /: Permission 
denied
+         # cvs [checkout aborted]: cannot make directory /foo: Permission 
denied
+         # $
+         #
+         # The -z9 in this test also checks for an old server bug where the
+         # server would block indefinitely attempting to read an EOF from the
+         # client in the compression buffer shutdown routine.
+         dotest_fail abspath2-1 "$testcvs -z9 co /foo" \
+"$CPROG \[checkout aborted\]: Absolute module reference invalid: \`/foo'" \
+"$SPROG \[server aborted\]: Absolute module reference invalid: \`/foo'
+$CPROG \[checkout aborted\]: end of file from server (consult above messages 
if any)"
+         ;;
+
+
+
+       toplevel)
+         # test the feature that cvs creates a CVS subdir also for
+         # the toplevel directory
+
+         # First set the TopLevelAdmin setting.
+         mkdir 1; cd 1
+         dotest toplevel-1a "${testcvs} -q co CVSROOT/config" \
+"U CVSROOT/config"
+         cd CVSROOT
+         echo "TopLevelAdmin=yes" >>config
+         dotest toplevel-1b "${testcvs} -q ci -m yes-top-level" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ../..
+         rm -r 1
+
+         mkdir 1; cd 1
+         dotest toplevel-1 "${testcvs} -q co -l ." ''
+         mkdir top-dir second-dir
+         dotest toplevel-2 "${testcvs} add top-dir second-dir" \
+"Directory ${CVSROOT_DIRNAME}/top-dir added to the repository
+Directory ${CVSROOT_DIRNAME}/second-dir added to the repository"
+         cd top-dir
+
+         touch file1
+         dotest toplevel-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest toplevel-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/top-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         cd ..
+
+         cd second-dir
+         touch file2
+         dotest toplevel-3s "${testcvs} add file2" \
+"${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest toplevel-4s "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/second-dir/file2,v  <--  file2
+initial revision: 1\.1"
+
+         cd ../..
+         rm -r 1; mkdir 1; cd 1
+         dotest toplevel-5 "${testcvs} co top-dir" \
+"${SPROG} checkout: Updating top-dir
+U top-dir/file1"
+
+         dotest toplevel-6 "${testcvs} update top-dir" \
+"${SPROG} update: Updating top-dir"
+         dotest toplevel-7 "${testcvs} update"  \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating top-dir"
+
+         dotest toplevel-8 "${testcvs} update -d top-dir" \
+"${SPROG} update: Updating top-dir"
+         # There is some sentiment that
+         #   "${SPROG} update: Updating \.
+          #   ${SPROG} update: Updating top-dir"
+         # is correct but it isn't clear why that would be correct instead
+         # of the remote CVS behavior (which also updates CVSROOT).
+         #
+         # The DOTSTAR matches of a bunch of lines like
+         # "U CVSROOT/checkoutlist".  Trying to match them more precisely
+         # seemed to cause trouble.  For example CVSROOT/cvsignore will
+         # be present or absent depending on whether we ran the "ignore"
+         # test or not.
+         dotest toplevel-9 "${testcvs} update -d" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating CVSROOT
+${DOTSTAR}
+${SPROG} update: Updating top-dir"
+
+         cd ..
+         rm -r 1; mkdir 1; cd 1
+         dotest toplevel-10 "${testcvs} co top-dir" \
+"${SPROG} checkout: Updating top-dir
+U top-dir/file1"
+
+         # This tests more or less the same thing, in a particularly
+         # "real life" example.
+         dotest toplevel-11 "${testcvs} -q update -d second-dir" \
+"U second-dir/file2"
+
+         # Now remove the CVS directory (people may do this manually,
+         # especially if they formed their habits with CVS
+         # 1.9 and older, which didn't create it.  Or perhaps the working
+         # directory itself was created with 1.9 or older).
+         rm -r CVS
+         # Now set the permissions so we can't recreate it.
+         if test -n "$remotehost"; then
+           # Cygwin again.
+           $CVS_RSH $remotehost "chmod -w $TESTDIR/1"
+         else
+           chmod -w ../1
+         fi
+         # Now see whether CVS has trouble because it can't create CVS.
+         # First string is for local, second is for remote.
+         dotest toplevel-12 "${testcvs} co top-dir" \
+"${SPROG} checkout: warning: cannot make directory CVS in \.: Permission denied
+${SPROG} checkout: Updating top-dir" \
+"${CPROG} checkout: warning: cannot make directory CVS in \.: Permission denied
+${CPROG} checkout: in directory \.:
+${CPROG} checkout: cannot open CVS/Entries for reading: No such file or 
directory
+${SPROG} checkout: Updating top-dir"
+
+         chmod +w ../1
+
+         dokeep
+         restore_adm
+         cd ..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/top-dir \
+                            $CVSROOT_DIRNAME/second-dir
+         ;;
+
+
+
+       toplevel2)
+         # Similar to toplevel, but test the case where TopLevelAdmin=no.
+
+         # First set the TopLevelAdmin setting.
+         mkdir 1; cd 1
+         dotest toplevel2-1a "${testcvs} -q co CVSROOT/config" \
+"U CVSROOT/config"
+         cd CVSROOT
+         echo "TopLevelAdmin=no" >>config
+         dotest toplevel2-1b "$testcvs -q ci -m no-top-level" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ../..
+         rm -r 1
+
+         # Now set up some directories and subdirectories
+         mkdir 1; cd 1
+         dotest toplevel2-1 "${testcvs} -q co -l ." ''
+         mkdir top-dir second-dir
+         dotest toplevel2-2 "${testcvs} add top-dir second-dir" \
+"Directory ${CVSROOT_DIRNAME}/top-dir added to the repository
+Directory ${CVSROOT_DIRNAME}/second-dir added to the repository"
+         cd top-dir
+
+         touch file1
+         dotest toplevel2-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest toplevel2-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/top-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         cd ..
+
+         cd second-dir
+         touch file2
+         dotest toplevel2-3s "${testcvs} add file2" \
+"${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest toplevel2-4s "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/second-dir/file2,v  <--  file2
+initial revision: 1\.1"
+
+         cd ../..
+         rm -r 1; mkdir 1; cd 1
+         dotest toplevel2-5 "${testcvs} co top-dir" \
+"${SPROG} checkout: Updating top-dir
+U top-dir/file1"
+
+         dotest toplevel2-6 "${testcvs} update top-dir" \
+"${SPROG} update: Updating top-dir"
+         dotest toplevel2-7 "${testcvs} update"  \
+"${SPROG} update: Updating top-dir"
+
+         dotest toplevel2-8 "${testcvs} update -d top-dir" \
+"${SPROG} update: Updating top-dir"
+         # Contrast this with toplevel-9, which has TopLevelAdmin=yes.
+         dotest toplevel2-9 "${testcvs} update -d" \
+"${SPROG} update: Updating top-dir"
+
+         cd ..
+         rm -r 1; mkdir 1; cd 1
+         dotest toplevel2-10 "${testcvs} co top-dir" \
+"${SPROG} checkout: Updating top-dir
+U top-dir/file1"
+         # This tests more or less the same thing, in a particularly
+         # "real life" example.  With TopLevelAdmin=yes, this command
+         # would give us second-dir and CVSROOT directories too.
+         dotest toplevel2-11 "${testcvs} -q update -d" ""
+
+         dokeep
+         cd ..
+         restore_adm
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/top-dir \
+                            $CVSROOT_DIRNAME/second-dir
+         ;;
+
+
+
+       rstar-toplevel)
+         # This test used to confirm a bug that existed in the r* commands
+         # run against the top-level project prior to CVS 1.11.18 & 1.12.10.
+         #
+         # The assertion failure was something like:
+         # do_recursion: Assertion \`strstr (repository, \"/\./\") == ((void 
\*)0)' failed\..*"
+         dotest rstar-toplevel-1 "$testcvs -q rlog ." \
+"
+RCS file: $CVSROOT_DIRNAME/CVSROOT$DOTSTAR"
+
+         dokeep
+       ;;
+
+
+
+       trailingslashes)
+         # Some tests of CVS's reactions to path specifications containing
+         # trailing slashes.
+         mkdir trailingslashes; cd trailingslashes
+         dotest trailingslashes-init-1 "$testcvs -Q co -ldt ."
+         dotest trailingslashes-init-2 "$testcvs -Q co -dt2 ."
+         cd t
+         echo "Ahh'll be baaack." >topfile
+         dotest trailingslashes-init-3 "$testcvs -Q add topfile"
+         dotest trailingslashes-init-4 "$testcvs -Q ci -mto-top"
+
+         # First, demonstrate the usual case.
+         cd ../t2
+         dotest trailingslashes-1 "$testcvs -q up CVSROOT"
+         dotest_fail trailingslashes-1a "test -f topfile"
+
+         # FIXCVS:
+         # Now the one that fails in remote mode.
+         # This highlights one of the failure cases mentioned in TODO item
+         # #205.
+         if $remote; then
+                 dotest trailingslashes-2 "$testcvs -q up CVSROOT/" \
+"U topfile"
+                 dotest trailingslashes-2a "test -f topfile"
+         else
+                 dotest trailingslashes-2 "$testcvs -q up CVSROOT/"
+                 dotest_fail trailingslashes-2a "test -f topfile"
+         fi
+
+         dokeep
+         cd ../..
+         rm -rf trailingslashes
+         modify_repo rm -rf $CVSROOT_DIRNAME/topfile,v
+         ;;
+
+
+
+        checkout_repository)
+          dotest_fail checkout_repository-1 \
+"${testcvs} co -d ${CVSROOT_DIRNAME} CVSROOT" \
+"${CPROG} \[checkout aborted\]: Cannot check out files into the repository 
itself" \
+"${SPROG} \[checkout aborted\]: absolute pathnames invalid for server 
(specified \`${CVSROOT_DIRNAME}')"
+
+         # The behavior of the client/server test below should be correct.
+         # The CVS client currently has no way of knowing that the client and
+         # server are the same machine and thus skips the $CVSROOT checks.
+         # I think checking for this case in CVS would be bloat since this
+         # should be a fairly rare occurance.
+         cd ${CVSROOT_DIRNAME}
+          dotest_fail checkout_repository-2 "${testcvs} co CVSROOT" \
+"${CPROG} \[checkout aborted\]: Cannot check out files into the repository 
itself" \
+"${SPROG} checkout: Updating CVSROOT
+${CPROG} checkout: move away \`CVSROOT/checkoutlist'; it is in the way
+C CVSROOT/checkoutlist
+${CPROG} checkout: move away \`CVSROOT/commitinfo'; it is in the way
+C CVSROOT/commitinfo
+${CPROG} checkout: move away \`CVSROOT/config'; it is in the way
+C CVSROOT/config
+${CPROG} checkout: move away \`CVSROOT/cvswrappers'; it is in the way
+C CVSROOT/cvswrappers
+${CPROG} checkout: move away \`CVSROOT/loginfo'; it is in the way
+C CVSROOT/loginfo
+${CPROG} checkout: move away \`CVSROOT/modules'; it is in the way
+C CVSROOT/modules
+${CPROG} checkout: move away \`CVSROOT/notify'; it is in the way
+C CVSROOT/notify
+${CPROG} checkout: move away \`CVSROOT/postadmin'; it is in the way
+C CVSROOT/postadmin
+${CPROG} checkout: move away \`CVSROOT/postproxy'; it is in the way
+C CVSROOT/postproxy
+${CPROG} checkout: move away \`CVSROOT/posttag'; it is in the way
+C CVSROOT/posttag
+${CPROG} checkout: move away \`CVSROOT/postwatch'; it is in the way
+C CVSROOT/postwatch
+${CPROG} checkout: move away \`CVSROOT/preproxy'; it is in the way
+C CVSROOT/preproxy
+${CPROG} checkout: move away \`CVSROOT/rcsinfo'; it is in the way
+C CVSROOT/rcsinfo
+${CPROG} checkout: move away \`CVSROOT/taginfo'; it is in the way
+C CVSROOT/taginfo
+${CPROG} checkout: move away \`CVSROOT/verifymsg'; it is in the way
+C CVSROOT/verifymsg"
+
+          dotest checkout_repository-3 \
+"${testcvs} co -p CVSROOT/modules >/dev/null" \
+"===================================================================
+Checking out CVSROOT/modules
+RCS:  ${CVSROOT_DIRNAME}/CVSROOT/modules,v
+VERS: 1\.[0-9]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*"
+
+         dokeep
+         cd $TESTDIR
+          ;;
+
+
+
+       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
+               pass 156
+           else
+               fail 156
+           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
+               pass 157
+           else
+               fail 157
+           fi
+           # Test handling of -m during ci
+           cd ..; rm -r a-dir
+           if ${testcvs} co a-dir >>${LOGFILE} 2>&1; then
+               pass 158
+           else
+               fail 158
+           fi
+           cd a-dir
+           echo testc >>test
+           if ${testcvs} ci -m "$message" >>${LOGFILE} 2>&1; then
+               pass 159
+           else
+               fail 159
+           fi
+           # Test handling of -m during rm/ci
+           rm test;
+           if ${testcvs} rm test >>${LOGFILE} 2>&1; then
+               pass 160
+           else
+               fail 160
+           fi
+           if ${testcvs} ci -m "$message" >>${LOGFILE} 2>&1; then
+               pass 161
+           else
+               fail 161
+           fi
+
+           dokeep
+           # Clean up
+           cd ..
+           rm -r a-dir
+           modify_repo rm -rf $CVSROOT_DIRNAME/a-dir
+         done
+         ;;
+
+
+
+       editor)
+         # More tests of log messages, in this case the ability to
+         # run an external editor.
+         # TODO:
+         #   * also test $EDITOR, $CVSEDITOR, &c.
+         #   * test what happens if up-to-date check fails.
+
+         # Our "editor" puts "x" at the start of each line, so we
+         # can see the "CVS:" lines.
+         cat >${TESTDIR}/editme <<EOF
+#!${TESTSHELL}
+sleep 1
+sed <\$1 -e 's/^/x/' >${TESTDIR}/edit.new
+mv ${TESTDIR}/edit.new \$1
+exit 0
+EOF
+         chmod +x ${TESTDIR}/editme
+
+         mkdir 1; cd 1
+         dotest editor-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest editor-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         touch file1 file2
+         dotest editor-3 "${testcvs} add file1 file2" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest editor-4 "${testcvs} -e ${TESTDIR}/editme -q ci" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+         dotest editor-5 "${testcvs} -q tag -b br" "T file1
+T file2"
+         dotest editor-6 "${testcvs} -q update -r br" ''
+         echo modify >>file1
+         dotest editor-7 "${testcvs} -e ${TESTDIR}/editme -q ci" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+         # OK, now we want to make sure "ci -r" puts in the branch
+         # where appropriate.  Note that we can check in on the branch
+         # without being on the branch, because there is not a revision
+         # already on the branch.  If there were a revision on the branch,
+         # CVS would correctly give an up-to-date check failed.
+         dotest editor-8 "${testcvs} -q update -A" "U file1"
+         echo add a line >>file2
+         dotest editor-9 "${testcvs} -q -e ${TESTDIR}/editme ci -rbr file2" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         dotest editor-log-file1 "${testcvs} log -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.2;
+xCVS: ----------------------------------------------------------------------
+xCVS: Enter Log.  Lines beginning with .CVS:. are removed automatically
+xCVS:
+xCVS: Committing in .
+xCVS:
+xCVS: Added Files:
+xCVS:  file1 file2
+xCVS: ----------------------------------------------------------------------
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+xCVS: ----------------------------------------------------------------------
+xCVS: Enter Log.  Lines beginning with .CVS:. are removed automatically
+xCVS:
+xCVS: Committing in .
+xCVS:
+xCVS: Modified Files:
+xCVS:  Tag: br
+xCVS:  file1
+xCVS: ----------------------------------------------------------------------
+============================================================================="
+
+         # The only difference between the two expect strings is the
+         # presence or absence of "Committing in ." for 1.1.2.1.
+         dotest editor-log-file2 "${testcvs} log -N file2" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+Working file: file2
+head: 1\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.2;
+xCVS: ----------------------------------------------------------------------
+xCVS: Enter Log.  Lines beginning with .CVS:. are removed automatically
+xCVS:
+xCVS: Committing in .
+xCVS:
+xCVS: Added Files:
+xCVS:  file1 file2
+xCVS: ----------------------------------------------------------------------
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+xCVS: ----------------------------------------------------------------------
+xCVS: Enter Log.  Lines beginning with .CVS:. are removed automatically
+xCVS:
+xCVS: Modified Files:
+xCVS:  Tag: br
+xCVS:  file2
+xCVS: ----------------------------------------------------------------------
+=============================================================================" 
"
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+Working file: file2
+head: 1\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.2;
+xCVS: ----------------------------------------------------------------------
+xCVS: Enter Log.  Lines beginning with .CVS:. are removed automatically
+xCVS:
+xCVS: Committing in .
+xCVS:
+xCVS: Added Files:
+xCVS:  file1 file2
+xCVS: ----------------------------------------------------------------------
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+xCVS: ----------------------------------------------------------------------
+xCVS: Enter Log.  Lines beginning with .CVS:. are removed automatically
+xCVS:
+xCVS: Committing in .
+xCVS:
+xCVS: Modified Files:
+xCVS:  Tag: br
+xCVS:  file2
+xCVS: ----------------------------------------------------------------------
+============================================================================="
+
+         # Test CVS's response to an unchanged log message
+         cat >${TESTDIR}/editme <<EOF
+#!${TESTSHELL}
+sleep 1
+exit 0
+EOF
+         chmod +x ${TESTDIR}/editme
+         dotest_fail editor-emptylog-1 "echo a |${testcvs} -e 
${TESTDIR}/editme ci -f file1" \
+"
+Log message unchanged or not specified
+a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs
+Action: (continue) ${CPROG} \[commit aborted\]: aborted by user"
+
+         # Test CVS's response to an empty log message
+         cat >${TESTDIR}/editme <<EOF
+#!${TESTSHELL}
+sleep 1
+cat /dev/null >\$1
+exit 0
+EOF
+         chmod +x ${TESTDIR}/editme
+         dotest_fail editor-emptylog-1a "echo a |${testcvs} -e 
${TESTDIR}/editme ci -f file1" \
+"
+Log message unchanged or not specified
+a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs
+Action: (continue) ${CPROG} \[commit aborted\]: aborted by user"
+
+         # Test CVS's response to a log message with one blank line
+         cat >${TESTDIR}/editme <<EOF
+#!${TESTSHELL}
+sleep 1
+echo >\$1
+exit 0
+EOF
+         chmod +x ${TESTDIR}/editme
+         dotest_fail editor-emptylog-1b "echo a |${testcvs} -e 
${TESTDIR}/editme ci -f file1" \
+"
+Log message unchanged or not specified
+a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs
+Action: (continue) ${CPROG} \[commit aborted\]: aborted by user"
+
+         # Test CVS's response to a log message with only comments
+         cat >${TESTDIR}/editme <<EOF
+#!${TESTSHELL}
+sleep 1
+cat \$1 >${TESTDIR}/edit.new
+mv ${TESTDIR}/edit.new \$1
+exit 0
+EOF
+         chmod +x ${TESTDIR}/editme
+         dotest_fail editor-emptylog-1c "echo a |${testcvs} -e 
${TESTDIR}/editme ci -f file1" \
+"
+Log message unchanged or not specified
+a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs
+Action: (continue) ${CPROG} \[commit aborted\]: aborted by user"
+
+         # Test CVS's response to a log message that is zero bytes
+         # in length. This caused core dumps in cvs 1.11.5 on Solaris
+         # hosts.
+         cd ..
+         dotest editor-emptylog-continue-1 "${testcvs} -q co CVSROOT/loginfo" \
+"U CVSROOT/loginfo"
+
+          cd CVSROOT
+         cat <<\EOF >>loginfo
+DEFAULT (echo Start-Log;cat;echo End-Log) >> $CVSROOT/CVSROOT/commitlog
+EOF
+         dotest editor-emptylog-continue-2 "$testcvs -Q ci -mloggem"
+
+         cd ../first-dir
+         cat >${TESTDIR}/editme <<EOF
+#!${TESTSHELL}
+sleep 1
+cp /dev/null \$1
+exit 1
+EOF
+         chmod +x ${TESTDIR}/editme
+         dotest editor-emptylog-continue-3 "echo c |${testcvs} -e 
${TESTDIR}/editme ci -f file1" \
+"${CPROG} commit: warning: editor session failed
+
+Log message unchanged or not specified
+a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs
+Action: (continue) ${CVSROOT_DIRNAME}/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         # The loginfo Log message should be an empty line and not "(null)"
+         # which is what some fprintf() implementations do with "%s"
+         # format and a NULL pointer...
+         if $remote; then
+           dotest editor-emptylog-continue-4r \
+"cat $CVSROOT_DIRNAME/CVSROOT/commitlog" \
+"Start-Log
+Update of $CVSROOT_DIRNAME/CVSROOT
+In directory $hostname:$TMPDIR/cvs-serv[0-9a-z]*
+
+Modified Files:
+       loginfo 
+Log Message:
+loggem
+End-Log
+Start-Log
+Update of $CVSROOT_DIRNAME/first-dir
+In directory $hostname:$TMPDIR/cvs-serv[0-9a-z]*
+
+Modified Files:
+       file1 
+Log Message:
+
+End-Log"
+          else
+           dotest editor-emptylog-continue-4 \
+"cat $CVSROOT_DIRNAME/CVSROOT/commitlog" \
+"Start-Log
+Update of $CVSROOT_DIRNAME/CVSROOT
+In directory $hostname:$TESTDIR/1/CVSROOT
+
+Modified Files:
+       loginfo 
+Log Message:
+loggem
+End-Log
+Start-Log
+Update of $CVSROOT_DIRNAME/first-dir
+In directory $hostname:$TESTDIR/1/first-dir
+
+Modified Files:
+       file1 
+Log Message:
+
+End-Log"
+         fi
+         # There should have an empty log message at this point
+         dotest editor-emptylog-continue-5 "${testcvs} log -N -r1.2 file1" \
+"
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.2
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 3;    selected revisions: 1
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: +0 -0;  
commitid: ${commitid};
+\*\*\* empty log message \*\*\*
+============================================================================="
+
+         # clean up
+         dokeep
+         # restore the default loginfo script
+         restore_adm
+         cd ../..
+         rm -r 1
+         rm $TESTDIR/editme
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       env)
+         # Test to see if the CVS_PID environment variable is being set
+         mkdir ${TESTDIR}/env
+         cd ${TESTDIR}/env
+         dotest env-1 "${testcvs} -Q co . >>${LOGFILE}" ''
+
+         cat > ${TESTDIR}/env/test-cvs-pid <<EOF
+#!${TESTSHELL}
+if test "x\$CVS_PID" != "x"; then
+  # In local mode, there is no directory with the pid in it for use.
+  # In remote mode the CVS_PID will be the parent process of the
+  # cvs process that runs the commitinfo script.
+  if test "x$remote" = "x:" ; then
+    ppid=\`pwd | sed -e 's,.*/cvs-serv,,'\`
+  else
+    # This assumes that the -l switch puts PPID in the banner and does
+    # not run the elements together such that whitespace surrounds the
+    # pid and ppid in the output. This could be made slightly simpler
+    # if all hosts had a 'ps' command that supported the -p switch,
+    # but Solaris 7 /usr/ucb/ps does not and that may be the one we use.
+    # It is because this is so messy that the CVS_PID feature exists.
+    pid=\$\$
+    pidcmd="ps -o pid,ppid -p \$pid || ps -el || ps -al"
+    if echo \$pidcmd | sh >pid.stdout 2> pid.stderr; then
+      ppid=\`cat pid.stdout |\\
+      awk '/PPID/ { for (i=1; i <= NF; i++) {
+                      if (\$i == "PPID") ppidx = i; 
+                      if (\$i == "PID") pidx = i;
+                   }
+                    next; 
+                  }
+                  { print \$pidx " " \$ppidx }' |\\
+      grep "^\$pid " |\\
+      awk '{ print \$NF }'\`
+    else
+      ppid=unkown
+    fi
+  fi
+  if test "x\$ppid" = "x\${CVS_PID}"; then
+    # The PID looks okay to me
+    # Clean up any temporary files
+    rm -f pid.stdout pid.stderr
+    exit 0
+  else
+    echo The environment variable CVS_PID is not properly set.
+    echo It should have been set to \'\$ppid\' but instead was \'\$CVS_PID\'
+    echo It is possible that this test is broken for your host.
+    echo Current pid: \$pid
+    [ -n "\$pidcmd" ] && echo "Command: \$pidcmd"
+    [ -s pid.stdout ] && echo Standard Out: && cat pid.stdout
+    [ -s pid.stderr ] && echo Standard Error: && cat pid.stderr
+    exit 1
+  fi
+else
+  echo The environment variable CVS_PID is not set.
+  exit 1
+fi
+EOF
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod +x ${TESTDIR}/env/test-cvs-pid"
+         else
+           chmod +x ${TESTDIR}/env/test-cvs-pid
+         fi
+         cd CVSROOT
+         echo "^env ${TESTDIR}/env/test-cvs-pid %r/%p %s" >>commitinfo
+         dotest env-2 "${testcvs} -q ci -m test-pid commitinfo" \
+"${CVSROOT_DIRNAME}/CVSROOT/commitinfo,v  <--  commitinfo
+new revision: 1\.2; previous revision: 1\.1
+${SPROG} commit: Rebuilding administrative file database"
+         cd ..
+         mkdir env
+         dotest env-3 "${testcvs} -q add env" \
+"Directory ${CVSROOT_DIRNAME}/env added to the repository"
+         cd env
+         echo testing >file1
+         dotest env-4 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest env-5 "${testcvs} -q commit -m test-pid" \
+"${CVSROOT_DIRNAME}/env/file1,v  <--  file1
+initial revision: 1\.1"
+
+         dokeep
+         # undo commitinfo changes
+         restore_adm
+         cd ../..
+         rm -fr $TESTDIR/env
+         modify_repo rm -rf $CVSROOT_DIRNAME/env
+         ;;
+
+
+
+       errmsg1)
+         modify_repo mkdir $CVSROOT_DIRNAME/1dir
+         mkdir 1
+         cd 1
+         dotest errmsg1-init-1 "$testcvs -Q co 1dir"
+         cd 1dir
+         touch foo
+         dotest errmsg-init-2 "$testcvs -Q add foo"
+         if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then
+             pass 164
+         else
+             fail 164
+         fi
+         cd ../..
+         mkdir 2
+         cd 2
+         if ${testcvs} -q co 1dir >>${LOGFILE}; then
+             pass 165
+         else
+             fail 165
+         fi
+         chmod a-w 1dir
+         cd ../1/1dir
+         rm foo;
+         if ${testcvs} rm foo >>${LOGFILE} 2>&1; then
+             pass 166
+         else
+             fail 166
+         fi
+         if ${testcvs} ci -m removed >>${LOGFILE} 2>&1; then
+             pass 167
+         else
+             fail 167
+         fi
+
+         cd ../../2/1dir
+         # The second case in the local and remote versions of errmsg1-168
+         # below happens on Cygwin under Windows, where write privileges
+         # aren't enforced properly.
+         if $remote; then
+           dotest errmsg1-168r "${testcvs} -q update" \
+"${SPROG} update: \`foo' is no longer in the repository
+$CPROG update: unable to remove \./foo: Permission denied" \
+"${SPROG} update: \`foo' is no longer in the repository"
+         else
+           dotest errmsg1-168 "${testcvs} -q update" \
+"${SPROG} update: \`foo' is no longer in the repository
+${SPROG} update: unable to remove foo: Permission denied" \
+"${SPROG} update: \`foo' is no longer in the repository"
+         fi
+
+         dokeep
+         cd ..
+         chmod u+w 1dir
+         cd ..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/1dir
+         ;;
+
+
+
+       errmsg2)
+         # More tests of various miscellaneous error handling,
+         # and cvs add behavior in general.
+         # See also test basicb-4a, concerning "cvs ci CVS".
+         # Too many tests to mention test the simple cases of
+         # adding files and directories.
+         # Test basicb-2a10 tests cvs -n add.
+
+         # First the usual setup; create a directory first-dir.
+         mkdir 1; cd 1
+         dotest errmsg2-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest errmsg2-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+         dotest_fail errmsg2-3 "${testcvs} add CVS" \
+"${CPROG} add: cannot add special file .CVS.; skipping"
+         touch file1
+         # For the most part add returns a failure exitstatus if
+         # there are any errors, even if the remaining files are
+         # processed without incident.  The "cannot add
+         # special file" message fits this pattern, at
+         # least currently.
+         dotest_fail errmsg2-4 "${testcvs} add CVS file1" \
+"${CPROG} add: cannot add special file .CVS.; skipping
+${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         # I'm not sure these tests completely convey the various strange
+         # behaviors that CVS had before it specially checked for "." and
+         # "..".  Suffice it to say that these are unlikely to work right
+         # without a special case.
+         dotest_fail errmsg2-5 "${testcvs} add ." \
+"${CPROG} add: cannot add special file .\..; skipping"
+         dotest_fail errmsg2-6 "${testcvs} add .." \
+"${CPROG} add: cannot add special file .\.\..; skipping"
+         # Make sure that none of the error messages left droppings
+         # which interfere with normal operation.
+         dotest errmsg2-7 "${testcvs} -q ci -m add-file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         mkdir sdir
+         cd ..
+         dotest errmsg2-8 "${testcvs} add first-dir/sdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/sdir added to the repository"
+         # while we're here... check commit with no CVS directory
+         dotest_fail errmsg2-8a "${testcvs} -q ci first-dir nonexistant" \
+"${CPROG} commit: nothing known about .nonexistant'
+${CPROG} \[commit aborted\]: correct above errors first!"
+         dotest_fail errmsg2-8b "$testcvs -q ci nonexistant first-dir" \
+"$CPROG commit: nothing known about .nonexistant'
+$CPROG \[commit aborted\]: correct above errors first!"
+         dotest errmsg2-8c "$testcvs -q ci first-dir"
+
+         cd first-dir
+
+         touch file10
+         mkdir sdir10
+         dotest errmsg2-10 "${testcvs} add file10 sdir10" \
+"${SPROG} add: scheduling file .file10. for addition
+Directory ${CVSROOT_DIRNAME}/first-dir/sdir10 added to the repository
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest errmsg2-11 "${testcvs} -q ci -m add-file10" \
+"$CVSROOT_DIRNAME/first-dir/file10,v  <--  file10
+initial revision: 1\.1"
+         # Try to see that there are no droppings left by
+         # any of the previous tests.
+         dotest errmsg2-12 "${testcvs} -q update" ""
+
+         # Now test adding files with '/' in the name, both one level
+         # down and more than one level down.
+         cd ..
+         mkdir first-dir/sdir10/ssdir
+         dotest errmsg2-13 "${testcvs} add first-dir/sdir10/ssdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/sdir10/ssdir added to the repository"
+
+         touch first-dir/sdir10/ssdir/ssfile
+         dotest errmsg2-14 \
+           "${testcvs} add first-dir/sdir10/ssdir/ssfile" \
+"${SPROG} add: scheduling file .first-dir/sdir10/ssdir/ssfile. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         touch first-dir/file15
+         dotest errmsg2-15 "${testcvs} add first-dir/file15" \
+"${SPROG} add: scheduling file .first-dir/file15. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+
+         # Now the case where we try to give it a directory which is not
+         # under CVS control.
+         mkdir bogus-dir
+         touch bogus-dir/file16
+         # FIXCVS: The first message, from local CVS, is nice.  The second one
+         # is not nice; would be good to fix remote CVS to give a clearer
+         # message (e.g. the one from local CVS).  But at least it is an
+         # error message.
+         dotest_fail errmsg2-16 "${testcvs} add bogus-dir/file16" \
+"${SPROG} add: in directory \`bogus-dir':
+${SPROG} \[add aborted\]: there is no version here; do .${SPROG} checkout. 
first" \
+"${CPROG} add: cannot open CVS/Entries for reading: No such file or directory
+${CPROG} \[add aborted\]: no repository"
+         rm -r bogus-dir
+
+         # One error condition we don't test for is trying to add a file
+         # or directory which already is there.
+
+         dotest errmsg2-17 "${testcvs} -q ci -m checkin" \
+"$CVSROOT_DIRNAME/first-dir/file15,v  <--  first-dir/file15
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/sdir10/ssdir/ssfile,v  <--  
first-dir/sdir10/ssdir/ssfile
+initial revision: 1\.1"
+         dotest errmsg2-18 "${testcvs} -Q tag test" ''
+
+         # trying to import the repository
+
+         if $remote; then :; else
+           cd ${CVSROOT_DIRNAME}
+           dotest_fail errmsg2-20 "${testcvs} import -mtest . A B" \
+"${SPROG} \[import aborted\]: attempt to import the repository"
+           dotest_fail errmsg2-21 "${testcvs} import -mtest first-dir A B" \
+"${SPROG} \[import aborted\]: attempt to import the repository"
+         fi
+
+         dokeep
+         cd ..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       errmsg3)
+         # Test the *PANIC* message caused by missing administration files
+         mkdir errmsg3
+         cd errmsg3
+         mkdir CVS
+         dotest_fail errmsg3-1 "${testcvs} -q up" \
+"${CPROG} update: in directory \`.':
+${CPROG} update: CVS directory found without administrative files\.
+${CPROG} update: Use CVS to create the CVS directory, or rename the
+${CPROG} update: directory if it is intended to store something
+${CPROG} update: besides CVS administrative files\.
+${CPROG} \[update aborted\]: \*PANIC\* administration files missing!"
+
+         dokeep
+         cd ..
+         rm -r errmsg3
+         ;;
+
+
+
+       close-stdout)
+         # Ensure that cvs update -p FILE > /dev/full fails
+         # Perform this test IFF /dev/full is a writable character device.
+         if test -w /dev/full && test -c /dev/full; then
+           mkdir close-stdout
+           cd close-stdout
+           echo a > file
+           dotest close-stdout-1 "$testcvs -Q import -m. closeout X Y" ''
+           dotest close-stdout-2 "$testcvs -Q co closeout" ''
+           # Match either a bare `write error' or
+           # `write error: No space left on device',
+           # since closeout.c can produce both.
+           dotest_fail close-stdout-3 \
+               "${testcvs} -Q update -p closeout/file > /dev/full" \
+               "${CPROG} \[update aborted\]: write error.*"
+
+           dokeep
+           cd ..
+           rm -r close-stdout
+           modify_repo rm -rf $CVSROOT_DIRNAME/closeout
+         else
+           skip close-stdout '/dev/full is not available'
+         fi
+         ;;
+
+
+
+       debug-log-nonfatal)
+         # Once upon a time, failure to create the debug log could be fatal.
+          if $remote; then :; else
+            remoteonly debug-log-nonfatal
+           continue
+         fi
+
+         mkdir $TESTDIR/unwritable
+         chmod a-w $TESTDIR/unwritable
+         if test -n "$CVS_CLIENT_LOG"; then
+              save_CVS_CLIENT_LOG=$CVS_CLIENT_LOG
+         fi
+         CVS_CLIENT_LOG=$TESTDIR/unwritable/cvsclientlog
+         export CVS_CLIENT_LOG
+
+         dotest debug-log-nonfatal-1 \
+"$testcvs -Q co -p CVSROOT/config >/dev/null" \
+"$CPROG checkout: opening to-server logfile 
$TESTDIR/unwritable/cvsclientlog.in: Permission denied
+$CPROG checkout: opening from-server logfile 
$TESTDIR/unwritable/cvsclientlog.out: Permission denied"
+
+         dokeep
+         rm -rf $TESTDIR/unwritable
+         unset CVS_CLIENT_LOG
+         if test -n "$save_CVS_CLIENT_LOG"; then
+             CVS_CLIENT_LOG=$save_CVS_CLIENT_LOG
+         fi
+         ;;
+
+
+
+       adderrmsg)
+         # Test some of the error messages the 'add' command can return and
+         # their reactions to '-q'.
+
+         # First the usual setup; create a directory first-dir.
+         mkdir 1; cd 1
+         dotest adderrmsg-init1 "${testcvs} -q co -l ." ''
+         mkdir adderrmsg-dir
+         dotest adderrmsg-init2 "${testcvs} add adderrmsg-dir" \
+"Directory ${CVSROOT_DIRNAME}/adderrmsg-dir added to the repository"
+          cd adderrmsg-dir
+
+         # try to add the admin dir
+         dotest_fail adderrmsg-1 "${testcvs} add CVS" \
+"${CPROG} add: cannot add special file .CVS.; skipping"
+         # might not want to see this message when you 'cvs add *'
+         dotest_fail adderrmsg-2 "${testcvs} -q add CVS" ""
+
+         # to test some other messages
+         touch file1
+         dotest adderrmsg-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+
+         # add it twice
+         dotest_fail adderrmsg-4 "${testcvs} add file1" \
+"${SPROG} add: \`file1' has already been entered"
+         dotest_fail adderrmsg-5 "${testcvs} -q add file1" ""
+
+         dotest adderrmsg-6 "${testcvs} -q ci -madd" \
+"$CVSROOT_DIRNAME/adderrmsg-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+         # file in Entries & repository
+         dotest_fail adderrmsg-7 "${testcvs} add file1" \
+"${SPROG} add: \`file1' already exists, with version number 1\.1"
+         dotest_fail adderrmsg-8 "${testcvs} -q add file1" ""
+
+         # clean up
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/adderrmsg-dir
+         ;;
+
+
+
+       opterrmsg)
+         # Test some option parsing error messages
+
+         # No init is necessary since these error messages are printed b4
+         # CVS looks for a sandbox or repository
+
+         # -z used to accept non-numeric arguments.  This bit someone who
+         # attempted `cvs -z -n up' when the -n was read as the argument to
+         # -z.
+         dotest_fail opterrmsg-1 "${testcvs} -z -n up" \
+"${CPROG}: gzip compression level must be between 0 and 9"
+
+         # Some general -z checks
+         dotest_fail opterrmsg-2 "${testcvs} -z -1 up" \
+"${CPROG}: gzip compression level must be between 0 and 9"
+         dotest_fail opterrmsg-3 "${testcvs} -z10 up" \
+"${CPROG}: gzip compression level must be between 0 and 9"
+         ;;
+
+
+
+       devcom)
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir 1
+         cd 1
+         dotest devcom-1 "$testcvs -q co first-dir"
+
+         cd first-dir
+         echo abb >abb
+         dotest devcom-2 "$testcvs add abb" \
+"$SPROG add: scheduling file \`abb' for addition
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+
+         dotest devcom-3 "$testcvs -q ci -m added" \
+"$CVSROOT_DIRNAME/first-dir/abb,v  <--  abb
+initial revision: 1\.1"
+
+         dotest_fail devcom-4 "$testcvs watch" "Usage$DOTSTAR"
+
+         dotest devcom-5 "$testcvs watch on"
+
+         echo abc >abc
+         dotest devcom-6 "$testcvs add abc" \
+"$SPROG add: scheduling file \`abc' for addition
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+
+         dotest devcom-7 "$testcvs -q ci -m added" \
+"$CVSROOT_DIRNAME/first-dir/abc,v  <--  abc
+initial revision: 1\.1"
+
+         cd ../..
+         mkdir 2
+         cd 2
+
+         dotest devcom-8 "$testcvs -q co first-dir" \
+"U first-dir/abb
+U first-dir/abc"
+
+         cd first-dir
+         dotest_fail devcom-9 "test -w abb"
+         dotest_fail devcom-9b "test -w abc"
+
+         dotest devcom-10 "$testcvs editors"
+         dotest devcom-11 "$testcvs edit abb"
+
+         # Here we test for the traditional ISO C ctime() date format.
+         # We assume the C locale; I guess that works provided we set
+         # LC_ALL at the start of this script but whether these
+         # strings should vary based on locale does not strike me as
+         # self-evident.
+         dotest devcom-12 "$testcvs editors" \
+"abb   ${username}     [SMTWF][uoehra][neduit] [JFAMSOND][aepuco][nbrylgptvc] 
[0-9 ][0-9] [0-9:]* [0-9][0-9][0-9][0-9] -0000   [-a-zA-Z_.0-9]* 
${TESTDIR}/2/first-dir"
+
+         echo aaaa >>abb
+         dotest devcom-13 "$testcvs ci -m modify abb" \
+"${CVSROOT_DIRNAME}/first-dir/abb,v  <--  abb
+new revision: 1\.2; previous revision: 1\.1"
+
+         # Unedit of a file not being edited should be a noop.
+         dotest devcom-14 "$testcvs unedit abb" ''
+
+         dotest devcom-15 "$testcvs editors" ""
+
+         dotest_fail devcom-16 "test -w abb"
+
+         dotest devcom-17 "$testcvs edit abc"
+
+         # Unedit of an unmodified file.
+         dotest devcom-18 "$testcvs unedit abc"
+         dotest devcom-19 "$testcvs edit abc"
+
+         echo changedabc >abc
+         # Try to unedit a modified file; cvs should ask for confirmation
+         dotest devcom-20 "echo no | $testcvs unedit abc" \
+"abc has been modified; revert changes? "
+
+         dotest devcom-21 "echo changedabc |$diff_u - abc"
+
+         # OK, now confirm the unedit
+         dotest devcom-22 "echo yes |$testcvs unedit abc" \
+"abc has been modified; revert changes? "
+
+         dotest devcom-23 "echo abc |$diff_u - abc"
+
+         dotest devcom-24 "$testcvs watchers" ''
+
+         # FIXME: This probably should be an error message instead
+         # of silently succeeding and printing nothing.
+         dotest devcom-a-nonexist "$testcvs watchers nonexist" ''
+
+         dotest devcom-a1 "$testcvs watch add" ''
+         dotest devcom-a2 "$testcvs watchers" \
+"abb   $username       edit    unedit  commit
+abc    $username       edit    unedit  commit"
+         dotest devcom-a3 "$testcvs watch remove -a unedit abb" ''
+         dotest devcom-a4 "$testcvs watchers abb" \
+"abb   $username       edit    commit"
+
+         # Check tagging and checking out while we have a CVS
+         # directory in the repository.
+         dotest devcom-t0 "${testcvs} -q tag tag" \
+'T abb
+T abc'
+         cd ../..
+         mkdir 3
+         cd 3
+
+         # Test commented out because the bug it tests for is not fixed
+         # The error is:
+         # cvs watchers: cannot open CVS/Entries for reading: No such file or 
directory
+         # cvs: ../../work/ccvs/src/fileattr.c:75: fileattr_read: Assertion 
`fileattr_stored_repos != ((void *)0)' failed.
+:        dotest devcom-t-nonexist "${testcvs} watchers nonexist" fixme
+
+         dotest devcom-t1 "${testcvs} -q co -rtag first-dir/abb" \
+'U first-dir/abb'
+         cd ..
+         # Since first-dir/abb is readonly, use -f.
+         rm -rf 3
+
+         # Test checking out the directory rather than the file.
+         mkdir 3
+         cd 3
+         dotest devcom-t2 "${testcvs} -q co -rtag first-dir" \
+'U first-dir/abb
+U first-dir/abc'
+         cd ..
+         # Since the files are readonly, use -f.
+         rm -rf 3
+
+         # Now do it again, after removing the val-tags file created
+         # by devcom-t1 to force CVS to search the repository
+         # containing CVS directories.
+         rm ${CVSROOT_DIRNAME}/CVSROOT/val-tags
+         mkdir 3
+         cd 3
+         dotest devcom-t3 "${testcvs} -q co -rtag first-dir" \
+'U first-dir/abb
+U first-dir/abc'
+         cd ..
+         # Since the files are readonly, use -f.
+         rm -rf 3
+
+         # Now remove all the file attributes
+         cd 2/first-dir
+         dotest devcom-b0 "${testcvs} watch off" ''
+         dotest devcom-b1 "${testcvs} watch remove" ''
+         # Test that CVS 1.6 and earlier can handle the repository.
+         dotest_fail devcom-b2 "test -d ${CVSROOT_DIRNAME}/first-dir/CVS"
+
+         # Now test watching just some, not all, files.
+         dotest devcom-some0 "${testcvs} watch on abc" ''
+         cd ../..
+         mkdir 3
+         cd 3
+         dotest devcom-some1 "${testcvs} -q co first-dir" 'U first-dir/abb
+U first-dir/abc'
+         dotest devcom-some2 "test -w first-dir/abb" ''
+         dotest_fail devcom-some3 "test -w first-dir/abc" ''
+
+         dokeep
+         cd ..
+         # Use -f because of the readonly files.
+         rm -rf 1 2 3
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       devcom2)
+         # More watch tests, most notably setting watches on
+         # files in various different states.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir 1
+         cd 1
+         dotest devcom2-1 "${testcvs} -q co first-dir" ''
+         cd first-dir
+
+         # This should probably be an error; setting a watch on a totally
+         # unknown file is more likely to be a typo than intentional.
+         # But that isn't the currently implemented behavior.
+         dotest devcom2-2 "${testcvs} watch on w1" ''
+
+         touch w1 w2 w3 nw1
+         dotest devcom2-3 "${testcvs} add w1 w2 w3 nw1" "${DOTSTAR}"
+         # Letting the user set the watch here probably can be considered
+         # a feature--although it leads to a few potentially strange
+         # consequences like one user can set the watch and another actually
+         # adds the file.
+         dotest devcom2-4 "${testcvs} watch on w2" ''
+         dotest devcom2-5 "${testcvs} -Q ci -m add-them"
+
+         # Note that this test differs in a subtle way from devcom-some0;
+         # in devcom-some0 the watch is creating a new fileattr file, and
+         # here we are modifying an existing one.
+         dotest devcom2-6 "${testcvs} watch on w3" ''
+
+         # Now test that all the watches got set on the correct files
+         # FIXME: CVS should have a way to report whether watches are
+         # set, I think.  The "check it out and see if it read-only" is
+         # sort of OK, but is complicated by CVSREAD and doesn't help
+         # if the file is added and not yet committed or some such.
+         # Probably "cvs status" should report "watch: on" if watch is on
+         # (and nothing if watch is off, so existing behavior is preserved).
+         cd ../..
+         mkdir 2
+         cd 2
+         dotest devcom2-7 "${testcvs} -q co first-dir" 'U first-dir/nw1
+U first-dir/w1
+U first-dir/w2
+U first-dir/w3'
+         dotest devcom2-8 "test -w first-dir/nw1" ''
+         dotest_fail devcom2-9 "test -w first-dir/w1" ''
+         dotest_fail devcom2-10 "test -w first-dir/w2" ''
+         dotest_fail devcom2-11 "test -w first-dir/w3" ''
+
+         cd first-dir
+         # OK, now we want to try files in various states with cvs edit.
+         dotest_fail devcom2-12 "$testcvs edit w4" \
+"${CPROG} edit: no such file w4; ignored"
+         # Try the same thing with a per-directory watch set.
+         dotest devcom2-13 "${testcvs} watch on" ''
+         dotest_fail devcom2-14 "$testcvs edit w5" \
+"${CPROG} edit: no such file w5; ignored"
+         dotest devcom2-15 "${testcvs} editors" ''
+         dotest devcom2-16 "${testcvs} editors w4" ''
+         # Make sure there are no droppings lying around
+         dotest devcom2-17 "cat ${CVSROOT_DIRNAME}/first-dir/CVS/fileattr" \
+"Fw1   _watched=
+Fw2    _watched=
+Fw3    _watched=
+Fnw1   _watched=
+D      _watched="
+         cd ..
+
+         # Do a little error testing
+         dotest devcom2-18 "${testcvs} -q co -d first+dir first-dir" \
+"U first${PLUS}dir/nw1
+U first${PLUS}dir/w1
+U first${PLUS}dir/w2
+U first${PLUS}dir/w3"
+         cd first+dir
+         dotest_fail devcom2-19 "${testcvs} edit" \
+"${CPROG} \[edit aborted\]: current directory (${TESTDIR}/2/first${PLUS}dir) 
contains an invalid character (${PLUS},>;=\\\\t\\\\n)"
+
+         # Make sure there are no droppings lying around
+         dotest devcom2-20 "cat ${CVSROOT_DIRNAME}/first-dir/CVS/fileattr" \
+"Fw1   _watched=
+Fw2    _watched=
+Fw3    _watched=
+Fnw1   _watched=
+D      _watched="
+
+         dokeep
+         cd ../..
+         # Use -f because of the readonly files.
+         rm -rf 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       devcom3)
+         # More watch tests, most notably handling of features designed
+         # for future expansion.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir 1
+         cd 1
+
+         # Set up logging via the postwatch script hook.  See the `info' test
+         # for a list of tests where other script hooks are tested.
+         dotest devcom3-init-1 "$testcvs -Q co CVSROOT"
+         cd CVSROOT
+         echo "ALL $TESTDIR/1/loggit %r %p %c" >>postwatch
+         dotest devcom3-init-2 "$testcvs -Q ci -mlog-watch"
+         cd .. # 1
+
+         cat >loggit <<EOF
+#!$TESTSHELL
+echo \${1+"\$@"} >>$TESTDIR/1/watch-log
+EOF
+         # #^@&!^@ Cygwin.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod +x $TESTDIR/1/loggit"
+         else
+           chmod +x loggit
+         fi
+       
+       
+
+         dotest devcom3-1 "$testcvs -q co first-dir"
+         cd first-dir
+
+         touch w1 w2
+         dotest devcom3-2 "${testcvs} add w1 w2" "${DOTSTAR}"
+         dotest devcom3-3 "${testcvs} watch on w1 w2" ''
+         dotest devcom3-4 "${testcvs} -Q ci -m add-them"
+
+         # OK, since we are about to delve into CVS's internals, make
+         # sure that we seem to be correct about how they work.
+         dotest devcom3-5 "cat ${CVSROOT_DIRNAME}/first-dir/CVS/fileattr" \
+"Fw1   _watched=
+Fw2    _watched="
+         # Now write a few more lines, just as if we were a newer version
+         # of CVS implementing some new feature.
+         cat <<'EOF' >>${CVSROOT_DIRNAME}/first-dir/CVS/fileattr
+Enew   line    here
address@hidden@#=&
+EOF
+         # Now get CVS to write to the fileattr file....
+         dotest devcom3-6 "${testcvs} watch off w1" ''
+         # ...and make sure that it hasn't clobbered our new lines.
+         # Note that writing these lines in another order would be OK
+         # too.
+         dotest devcom3-7 "cat ${CVSROOT_DIRNAME}/first-dir/CVS/fileattr" \
+"Fw2   _watched=
address@hidden@#=&
+Enew   line    here"
+
+         # See what CVS does when a file name is duplicated.  The
+         # behavior of all versions of CVS since file attributes were
+         # implemented is that it nukes the duplications.  This seems
+         # reasonable enough, although it means it isn't clear how
+         # useful duplicates would be for purposes of future
+         # expansion.  But in the interests of keeping behaviors
+         # predictable, might as well test for it, I guess.
+         echo 'Fw2     duplicate=' >>${CVSROOT_DIRNAME}/first-dir/CVS/fileattr
+         dotest devcom3-8 "${testcvs} watch on w1" ''
+         dotest devcom3-9 "cat ${CVSROOT_DIRNAME}/first-dir/CVS/fileattr" \
+"Fw2   _watched=
+Fw1    _watched=
+Enew   line    here
address@hidden@#=&"
+
+         # Now test disconnected "cvs edit" and the format of the 
+         # CVS/Notify file.
+         if $remote; then
+           CVS_SERVER_save=$CVS_SERVER
+           CVS_SERVER=$TESTDIR/cvs-none; export CVS_SERVER
+
+           # The ${DOTSTAR} below matches the exact CVS server error message,
+           # which in :fork: mode is:
+           # "$SPROG \[edit aborted\]: cannot exec $TESTDIR/cvs-none: 
${DOTSTAR}",
+           # but which is:
+           # "bash2: line 1: $TESTDIR/cvs-none: No such file or directory"
+           # when testing across an :ext:/ssh link to my Linux 2.4 box.
+           #
+           # I can't even test for the second part of the error message,
+           # from the client, which varies more consistently, usually either
+           # "end of file from server" (if the process doing the exec exits
+           # before the parent gets around to sending data to it) or
+           # "received broken pipe signal" (if it is the other way around),
+           # since HP-UX fails to output it.
+           dotest_fail devcom3-9ar "$testcvs edit w1 2>/dev/null"
+           dotest devcom3-9br "test -w w1"
+           dotest devcom3-9cr "cat CVS/Notify" \
+"Ew1   [SMTWF][uoehra][neduit] [JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] 
[0-9:]* [0-9][0-9][0-9][0-9] -0000   [-a-zA-Z_.0-9]* ${TESTDIR}/1/first-dir  
EUC"
+           CVS_SERVER=${CVS_SERVER_save}; export CVS_SERVER
+           if $proxy; then
+             dotest_fail devcom3-9dp "$testcvs -q update" \
+"This CVS server does not support disconnected \`cvs edit'\.  For now, remove 
all \`CVS/Notify' files in your workspace and try your command again\."
+             dotest devcom3-9ep "test -f CVS/Notify"
+             rm CVS/Notify
+             dotest devcom3-9hp "$testcvs watchers w1"
+           else
+             dotest devcom3-9dr "$testcvs -q update"
+             dotest_fail devcom3-9er "test -f CVS/Notify"
+             dotest devcom3-9fr "$testcvs watchers w1" \
+"w1    $username       tedit   tunedit tcommit"
+           fi
+           dotest devcom3-9gr "$testcvs unedit w1"
+           dotest devcom3-9hr "$testcvs watchers w1"
+         fi
+
+         cd ../..
+         # OK, now change the tab to a space, and see that CVS gives
+         # a reasonable error (this is database corruption but CVS should
+         # not lose its mind).
+         sed -e 's/Fw2 /Fw2 /' <$CVSROOT_DIRNAME/first-dir/CVS/fileattr \
+           >$CVSROOT_DIRNAME/first-dir/CVS/fileattr.new
+         modify_repo mv $CVSROOT_DIRNAME/first-dir/CVS/fileattr.new \
+                        $CVSROOT_DIRNAME/first-dir/CVS/fileattr
+         mkdir 2; cd 2
+         dotest_fail devcom3-10 "${testcvs} -Q co ." \
+"${SPROG} \[checkout aborted\]: file attribute database corruption: tab 
missing in ${CVSROOT_DIRNAME}/first-dir/CVS/fileattr"
+
+         notifyworks=false
+         if $remote; then
+           if $proxy; then :; else
+             notifyworks=:
+           fi
+         fi
+         if $notifyworks; then
+           dotest devcom3-postwatch-examine-1r "cat $TESTDIR/1/watch-log" \
+"$CVSROOT_DIRNAME first-dir watch
+$CVSROOT_DIRNAME first-dir watch
+$CVSROOT_DIRNAME first-dir watch
+$CVSROOT_DIRNAME first-dir update
+$CVSROOT_DIRNAME first-dir server"
+         else
+           dotest devcom3-postwatch-examine-1 "cat $TESTDIR/1/watch-log" \
+"$CVSROOT_DIRNAME first-dir watch
+$CVSROOT_DIRNAME first-dir watch
+$CVSROOT_DIRNAME first-dir watch"
+         fi
+
+         dokeep
+         restore_adm
+         cd ..
+         # Use -f because of the readonly files.
+         rm -rf 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       watch4)
+         # More watch tests, including adding directories.
+         mkdir 1; cd 1
+         dotest watch4-0a "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest watch4-0b "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+
+         cd first-dir
+         dotest watch4-1 "${testcvs} watch on" ''
+         # This is just like the 173 test
+         touch file1
+         dotest watch4-2 "$testcvs add file1" \
+"$SPROG add: scheduling file .file1. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         dotest watch4-3 "$testcvs -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         # Now test the analogous behavior for directories.
+         mkdir subdir
+         dotest watch4-4 "${testcvs} add subdir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/subdir added to the repository"
+         cd subdir
+         touch sfile
+         dotest watch4-5 "${testcvs} add sfile" \
+"${SPROG} add: scheduling file .sfile. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest watch4-6 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/subdir/sfile,v  <--  sfile
+initial revision: 1\.1"
+         cd ../../..
+         mkdir 2; cd 2
+         dotest watch4-7 "${testcvs} -q co first-dir" "U first-dir/file1
+U first-dir/subdir/sfile"
+         dotest_fail watch4-8 "test -w first-dir/file1" ''
+         dotest_fail watch4-9 "test -w first-dir/subdir/sfile" ''
+         cd first-dir
+         dotest watch4-10 "${testcvs} edit file1" ''
+         echo 'edited in 2' >file1
+         cd ../..
+
+         cd 1/first-dir
+
+            # NOTE: I'm leaving in '' as acceptable
+            #  to maintain partial compatibility with CVS versions
+            #  prior to the edit check patch.
+          editorsLineRE="file1 $username       [SMTWF][uoehra][neduit] 
[JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] [0-9:]* [0-9][0-9][0-9][0-9] -0000   
$hostname       $TESTDIR/2/first-dir"
+         dotest watch4-11 "$testcvs edit file1" "$editorsLineRE"
+
+         echo 'edited in 1' >file1
+         dotest watch4-12 "${testcvs} -q ci -m edit-in-1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         cd ../..
+         cd 2/first-dir
+         dotest watch4-13 "${testcvs} -q update" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1 and 1\.2 into file1
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in file1
+C file1"
+         if (echo yes | ${testcvs} unedit file1) >>${LOGFILE}; then
+           pass watch4-14
+         else
+           fail watch4-15
+         fi
+         # This could plausibly be defined to either go back to the revision
+         # which was cvs edit'd (the status quo), or back to revision 1.2
+         # (that is, the merge could update CVS/Base/file1).  We pick the
+         # former because it is easier to implement, not because we have
+         # thought much about which is better.
+         dotest watch4-16 "cat file1" ''
+         # Make sure CVS really thinks we are at 1.1.
+         dotest watch4-17 "${testcvs} -q update" "U file1"
+         dotest watch4-18 "cat file1" "edited in 1"
+         cd ../..
+
+         # As a sanity check, make sure we are in the right place.
+         dotest watch4-cleanup-1 "test -d 1"
+         dotest watch4-cleanup-1 "test -d 2"
+
+         dokeep
+         # Specify -f because of the readonly files.
+         rm -rf 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       watch5)
+         # This test was designed to catch a problem in server
+         # mode where an 'cvs edit'd file disappeared from the
+         # CVS/Base directory when 'cvs status' or 'cvs update'
+         # was called on the file after the file was touched.
+         #
+         # This test is still here to prevent the bug from
+         # being reintroduced.
+         #
+         # The rationale for having CVS/Base stay around is that
+         # CVS/Base should be there if "cvs edit" has been run (this
+         # may be helpful as a "cvs editors" analogue, it is
+         # client-side and based on working directory not username;
+         # but more importantly, it isn't clear why a "cvs status"
+         # would act like an unedit, and even if it does, it would
+         # need to make the file read-only again).
+
+         mkdir watch5; cd watch5
+         dotest watch5-0a "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest watch5-0b "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+
+         cd first-dir
+         dotest watch5-1 "${testcvs} watch on" ''
+         # This is just like the 173 test
+         touch file1
+         dotest watch5-2 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest watch5-3 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         dotest watch5-4 "${testcvs} edit file1" ''
+         dotest watch5-5 "test -f CVS/Base/file1" ''
+         if ${testcvs} status file1 >>${LOGFILE} 2>&1; then
+               pass watch5-6
+         else
+               fail watch5-6
+         fi
+         dotest watch5-7 "test -f CVS/Base/file1" ''
+
+         # Here's where the file used to dissappear
+         touch file1
+         if ${testcvs} status file1 >>${LOGFILE} 2>&1; then
+               pass watch5-8
+         else
+               fail watch5-8
+         fi
+         dotest watch5-10 "test -f CVS/Base/file1" ''
+
+         # Make sure update won't remove the file either
+         touch file1
+         dotest watch5-11 "${testcvs} -q up" ''
+         dotest watch5-12 "test -f CVS/Base/file1" ''
+
+         dokeep
+         cd ../..
+         rm -r watch5
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       watch6-0)
+
+         # Make sure that default attributes are being set properly.
+         # Specifying a directory has, it seems, never worked,
+         # and 1.12.10 broke it completely.
+         mkdir watch6-0; cd watch6-0
+
+         dotest watch6-0-setup-1 "$testcvs -Q co -ldtop ."
+         cd top
+         mkdir watch6-0
+         dotest watch6-0-setup-2 "$testcvs -Q add watch6-0"
+         cd watch6-0
+         dotest watch6-0-1 "$testcvs watch add"
+         dotest watch6-0-2 \
+"grep '^D' $CVSROOT_DIRNAME/watch6-0/CVS/fileattr >/dev/null"
+         dotest watch6-0-3 "$testcvs watch remove"
+         dotest_fail watch6-0-4 \
+"grep '^D' $CVSROOT_DIRNAME/watch6-0/CVS/fileattr 2>/dev/null >/dev/null"
+
+         dotest watch6-0-5 "$testcvs watch add ."
+         dotest watch6-0-6 \
+"grep '^D' $CVSROOT_DIRNAME/watch6-0/CVS/fileattr >/dev/null"
+         dotest watch6-0-7 "$testcvs watch remove ."
+         dotest_fail watch6-0-8 \
+"grep '^D' $CVSROOT_DIRNAME/watch6-0/CVS/fileattr 2>/dev/null >/dev/null"
+
+         # OK, basic add/remove work. Now, make sure it works with
+         # named directories.
+         mkdir dir1
+         mkdir dir2
+         mkdir dir3
+         echo afile>afile
+         $testcvs -Q add afile dir1 dir2 dir3
+         $testcvs -Q ci -m "Adding test files"
+
+         # Current directory should not be watched, but there should
+         # be a watch on the file, and on dir1 & dir2, but not on
+         # dir3.
+         dotest watch6-0-9 "$testcvs -Q watch add afile dir1 dir2"
+         dotest_fail watch6-0-10 \
+"grep '^D' $CVSROOT_DIRNAME/watch6-0/CVS/fileattr 2>/dev/null >/dev/null"
+         dotest watch6-0-11 \
+"grep '^Fafile' $CVSROOT_DIRNAME/watch6-0/CVS/fileattr >/dev/null"
+         dotest watch6-0-12 \
+"grep '^D' $CVSROOT_DIRNAME/watch6-0/dir1/CVS/fileattr >/dev/null"
+         dotest watch6-0-13 \
+"grep '^D' $CVSROOT_DIRNAME/watch6-0/dir2/CVS/fileattr >/dev/null"
+         dotest_fail watch6-0-12 \
+"grep '^D' $CVSROOT_DIRNAME/watch6-0/dir3/CVS/fileattr 2>/dev/null >/dev/null"
+
+         dokeep
+         cd ../../..
+         rm -rf watch6-0
+         modify_repo rm -rf $CVSROOT_DIRNAME/watch6-0
+         ;;
+
+
+
+       watch6)
+         # Check that `cvs watch on' does not reset the fileattr file.
+         mkdir watch6; cd watch6
+
+         dotest watch6-setup-1 "$testcvs -Q co -ldtop ."
+         cd top
+         mkdir watch6
+         dotest watch6-setup-2 "$testcvs -Q add watch6"
+
+         # I don't recall why I had these next 3 lines.
+         cd ..
+         dotest watch6-setup-3 "$testcvs -Q co watch6"
+         cd watch6
+
+         mkdir subdir
+         dotest watch6-setup-4 "$testcvs -Q add subdir"
+         cd subdir
+
+         # START watch add/remove sequence
+         dotest watch6-1 "$testcvs -Q watch add"
+         dotest watch6-2 \
+"grep '_watchers' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+
+         dotest watch6-3 "$testcvs watch on"
+         dotest watch6-4 \
+"grep '_watchers' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+         dotest watch6-5 \
+"grep '_watched' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+
+         dotest watch6-6 "$testcvs watch off"
+         dotest watch6-7 \
+"grep '_watchers' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+         dotest_fail watch6-8 \
+"grep '_watched' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+
+         dotest watch6-9 "$testcvs watch remove"
+         dotest_fail watch6-10 \
+"test -d $CVSROOT_DIRNAME/test-directory/subdir/CVS"
+         dotest_fail watch6-11 \
+"test -f $CVSROOT_DIRNAME/test-directory/subdir/CVS/fileattr"
+         # END watch add/remove sequence
+
+         echo Hi there >afile
+         dotest watch6-12 "$testcvs -Q add afile"
+         dotest watch6-13 "$testcvs ci -m 'A file' afile" \
+"$CVSROOT_DIRNAME/watch6/subdir/afile,v  <--  afile
+initial revision: 1.1"
+
+         # START watch add/remove sequence
+         dotest watch6-14 "$testcvs -Q watch add"
+         dotest watch6-15 \
+"grep '_watchers' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+
+         dotest watch6-16 "$testcvs watch on"
+         dotest watch6-17 \
+"grep '_watchers' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+         dotest watch6-18 \
+"grep '_watched' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+
+         dotest watch6-19 "$testcvs watch off"
+         dotest watch6-20 \
+"grep '_watchers' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+         dotest_fail watch6-21 \
+"grep '_watched' $CVSROOT_DIRNAME/watch6/subdir/CVS/fileattr >/dev/null"
+
+         dotest watch6-22 "$testcvs watch remove"
+         dotest_fail watch6-23 \
+"test -d $CVSROOT_DIRNAME/test-directory/subdir/CVS"
+         dotest_fail watch6-24 \
+"test -f $CVSROOT_DIRNAME/test-directory/subdir/CVS/fileattr"
+         # END watch add/remove sequence
+
+         if $keep; then
+           echo Keeping $TESTDIR and exiting due to --keep
+           exit 0
+         fi
+         cd ../../..
+         rm -r watch6
+         modify_repo rm -rf $CVSROOT_DIRNAME/watch6
+         ;;
+
+
+
+        edit-check)
+          # This tests the edit -c/-f and related features.
+
+         mkdir edit-check; cd edit-check
+         dotest edit-check-0a "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest edit-check-0b "$testcvs add first-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir added to the repository"
+
+         cd first-dir
+         dotest edit-check-1 "$testcvs watch on"
+
+          echo foo > file1
+          dotest edit-check-2a "$testcvs add -minitial file1" \
+"$SPROG [a-z]*: scheduling file .file1. for addition
+$SPROG [a-z]*: use .$SPROG commit. to add this file permanently"
+
+          dotest edit-check-2b "$testcvs commit -m 'c1' file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+          editorsLineRE="file1 $username       [SMTWF][uoehra][neduit] 
[JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] [0-9:]* [0-9][0-9][0-9][0-9] -0000   
$hostname       $TESTDIR/edit-check/first-dir"
+
+          R_editorsLineRE="first-dir/file1     $username       
[SMTWF][uoehra][neduit] [JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] [0-9:]* 
[0-9][0-9][0-9][0-9] -0000   $hostname       $TESTDIR/edit-check"
+          F3_editorsLineRE="second-dir/file3   $username       
[SMTWF][uoehra][neduit] [JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] [0-9:]* 
[0-9][0-9][0-9][0-9] -0000   $hostname       $TESTDIR/edit-check/first-dir"
+
+          A_editorsLineRE="file1       [-a-zA-Z0-9_]*  [SMTWF][uoehra][neduit] 
[JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] [0-9:]* [0-9][0-9][0-9][0-9] -0000   
$hostname       $TESTDIR[0-9]*/edit-check/first-dir"
+
+          AF_editorsLineRE="file[12]   [-a-zA-Z0-9_]*  [SMTWF][uoehra][neduit] 
[JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] [0-9:]* [0-9][0-9][0-9][0-9] -0000   
$hostname       $TESTDIR/edit-check/first-dir"
+
+          NF_editorsLineRE="   [-a-zA-Z0-9_]*  [SMTWF][uoehra][neduit] 
[JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] [0-9:]* [0-9][0-9][0-9][0-9] -0000   
$hostname       $TESTDIR/edit-check/first-dir"
+
+          dotest edit-check-3 "$testcvs edit file1"
+          dotest edit-check-4 "$testcvs edit file1" "$editorsLineRE"
+
+          dotest_fail edit-check-5a "$testcvs edit -c file1" \
+"$editorsLineRE
+$SPROG edit: Skipping file \`file1' due to existing editors\."
+
+          dotest edit-check-5b "$testcvs editors" "$editorsLineRE"
+
+          dotest edit-check-6a "$testcvs edit -c -f file1" "$editorsLineRE"
+          dotest edit-check-6b "$testcvs editors" "$editorsLineRE"
+
+          dotest edit-check-7a "cat file1" "foo"
+          echo "bar" > file1
+          dotest_fail edit-check-7b "$testcvs edit -c file1" \
+"$editorsLineRE
+$SPROG edit: Skipping file \`file1' due to existing editors\."
+          dotest edit-check-7c "cat file1" "bar"
+
+            # edit-check-8a has issues.  It copies the current (modified)
+            # version of the file into CVS/Base, so that edit-check-9a and
+            # edit-check-9b don't get the expected results.
+            #   Maybe unedit is *supposed* to return it to the state
+            # it was in before the edit (even if it was modified),
+            # but while that has a certain symetry, it doesn't seem
+            # to pass the intuitive-usability test.
+            #   This aspect of the general problem could
+            # be fixed by not overwriting pre-existing Base versions,
+            # but it still wouldn't fix it if the user manually
+            # modified the file before doing the first edit.
+            #   Because of the possibility that this is working as
+            # intended, I'm just commenting out the test, not fixing
+            # the issue.
+          #dotest edit-check-8a "${testcvs} edit -c -f file1" \
+          #   "${editorsLineRE}"
+          dotest edit-check-8b "$testcvs editors" "$editorsLineRE"
+
+          dotest edit-check-9a "echo yes | $testcvs unedit file1" \
+"file1 has been modified; revert changes? "
+          dotest edit-check-9b "$testcvs editors"
+          dotest edit-check-9c "cat file1" "foo"
+
+          dotest edit-check-10 "$testcvs edit -c file1"
+          dotest_fail edit-check-11 "$testcvs edit -c file1" \
+"$editorsLineRE
+$SPROG edit: Skipping file \`file1' due to existing editors\."
+
+          echo "morefoo" > file1
+          dotest edit-check-12a "$testcvs commit -m 'c2' -c file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+          dotest edit-check-12b "$testcvs editors file1"
+
+          chmod u+w file1
+          echo "morebar" > file1
+          dotest_fail edit-check-13a "$testcvs commit -m 'c3' -c file1" \
+"$SPROG [a-z]*: Valid edit does not exist for file1
+$SPROG \[[a-z]* aborted\]: correct above errors first!"
+          dotest edit-check-13b "$testcvs editors file1"
+
+          dotest edit-check-14a "$testcvs commit -m 'c4' -c -f file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2"
+          dotest edit-check-14b "$testcvs editors file1"
+
+          dotest edit-check-15 "$testcvs edit -c file1"
+          cd ..
+
+          dotest edit-check-16a "echo yes | $testcvs release -d first-dir" \
+"You have \[0\] altered files in this repository.
+Are you sure you want to release (and delete) directory \`first-dir': "
+          dotest edit-check-16b "$testcvs -q update -d first-dir" \
+             "U first-dir/file1"
+          cd first-dir
+          dotest edit-check-16c "$testcvs editors file1"
+
+          cd ..
+          dotest edit-check-17a "$testcvs edit -c"
+          dotest_fail edit-check-17b "$testcvs edit -c" \
+"$R_editorsLineRE
+$SPROG edit: Skipping file \`first-dir/file1' due to existing editors\."
+          dotest edit-check-17c "$testcvs edit -c -f" "$R_editorsLineRE"
+
+          echo "more changes" > first-dir/file1
+          dotest edit-check-18a "$testcvs -q commit -m 'c5' -c" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  first-dir/file1
+new revision: 1\.4; previous revision: 1\.3"
+          dotest edit-check-18b "$testcvs editors"
+
+          cd first-dir
+
+            # Manually fake another editor:
+
+            # Try to gaurantee a seperate name for an "other" user editting
+            # the file.
+          otherUser="dummyUser"
+          if [ x"$USER" = x"$otherUser" ]  ; then
+            otherUser="dummyUser2"
+          fi
+          if [ x"$LOGNAME" = x"$otherUser" ] ; then
+            otherUser="dummyUser3"
+          fi
+          tabChar='    '
+
+          backupFileattrName="$CVSROOT_DIRNAME/first-dir/CVS/bak.fileattr.$$"
+          mv $CVSROOT_DIRNAME/first-dir/CVS/fileattr $backupFileattrName
+
+          otherDir="`pwd | sed 's%/edit-check/%2/edit-check/%'`"
+          echo \
+"Ffile1${tabChar}_watched=;_editors=$otherUser>Sat Oct  6 04:25:00 2001 
-0000+`hostname`+$otherDir;_watchers=$otherUser>tedit+tunedit+tcommit
+D${tabChar}_watched=" > $CVSROOT_DIRNAME/first-dir/CVS/fileattr 
+
+          editFileattrName="$CVSROOT_DIRNAME/first-dir/CVS/edit.fileattr.$$"
+          cp $CVSROOT_DIRNAME/first-dir/CVS/fileattr $editFileattrName
+
+          O_editorsLineRE="file1       $otherUser      [SMTWF][uoehra][neduit] 
[JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] [0-9:]* [0-9][0-9][0-9][0-9] -0000   
$hostname       $TESTDIR[0-9]/edit-check/first-dir"
+
+          dotest edit-check-19a "$testcvs edit file1" "$O_editorsLineRE"
+          dotest edit-check-19b "$testcvs editors" \
+"$A_editorsLineRE
+$NF_editorsLineRE"
+
+          dotest edit-check-20a "$testcvs unedit file1"
+          dotest edit-check-20b "$testcvs editors" "$O_editorsLineRE"
+
+          dotest_fail edit-check-21a "$testcvs edit -c file1" \
+"$O_editorsLineRE
+$SPROG edit: Skipping file \`file1' due to existing editors\."
+          dotest edit-check-21b "$testcvs editors" "$O_editorsLineRE"
+
+          dotest edit-check-22a "$testcvs edit -c -f file1" "$O_editorsLineRE"
+          dotest edit-check-22b "$testcvs editors" \
+"$A_editorsLineRE
+$NF_editorsLineRE"
+
+          echo "Yet another change" >file1
+
+          dotest_fail edit-check-23a "$testcvs edit -c" \
+"$A_editorsLineRE
+$NF_editorsLineRE
+$SPROG edit: Skipping file \`file1' due to existing editors\."
+
+          dotest edit-check-23b "$testcvs editors" \
+"$A_editorsLineRE
+$NF_editorsLineRE"
+
+          dotest edit-check-24a "echo y | $testcvs unedit" \
+             "file1 has been modified; revert changes? "
+          dotest edit-check-24b "$testcvs editors" "$O_editorsLineRE"
+          dotest edit-check-24c "cat file1" "more changes"
+
+          dotest edit-check-25a "$testcvs unedit"
+          dotest edit-check-25b "$testcvs editors" "$O_editorsLineRE"
+          dotest_fail edit-check-25c "test -w file1"
+
+          dotest edit-check-26a "$testcvs edit file1" "$O_editorsLineRE"
+          dotest edit-check-26b "$testcvs editors file1" \
+"$A_editorsLineRE
+$NF_editorsLineRE"
+          dotest edit-check-26c "test -w file1"
+
+          echo "Yet more changes" >file1
+          dotest edit-check-27a "$testcvs -q commit -mmsg -c file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.5; previous revision: 1\.4"
+          dotest edit-check-27b "$testcvs editors" "$O_editorsLineRE"
+
+          chmod u+w file1
+          echo "unofficial change" >file1
+
+          dotest_fail edit-check-28a "$testcvs -q commit -mmsg -c" \
+"$SPROG commit: Valid edit does not exist for file1
+$SPROG \[commit aborted\]: correct above errors first!"
+          dotest edit-check-28b "$testcvs editors" "$O_editorsLineRE"
+
+          dotest edit-check-29a "$testcvs -q commit -mmsg -c -f" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.6; previous revision: 1\.5"
+          dotest edit-check-29b "$testcvs editors" "$O_editorsLineRE"
+          dotest edit-check-29c "cat file1" "unofficial change"
+
+          modify_repo cp "$backupFileattrName" \
+                        $CVSROOT_DIRNAME/first-dir/CVS/fileattr
+          dotest edit-check-30 "$testcvs editors"
+
+          # Make sure earlier unreported editors are reported properly
+          # with the edit-check code running.
+          if $remote; then
+            CVS_SERVER_SAVED=$CVS_SERVER
+            CVS_SERVER=$TESTDIR/cvs-none; export CVS_SERVER
+
+            # The $DOTSTAR matches the exact exec error message
+            # (which varies) and either "end of file from server"
+            # (if the process doing the exec exits before the parent
+            # gets around to sending data to it) or "broken pipe" (if it
+            # is the other way around).
+            dotest_fail edit-check-31ar "$testcvs edit file1" \
+"$SPROG \[edit aborted\]: cannot exec $TESTDIR/cvs-none: $DOTSTAR"
+            dotest edit-check-31br "test -w file1"
+            dotest edit-check-31cr "cat CVS/Notify" \
+"Efile1        [SMTWF][uoehra][neduit] [JFAMSOND][aepuco][nbrylgptvc] [0-9 
][0-9] [0-9:]* [0-9][0-9][0-9][0-9] -0000   [-a-zA-Z_.0-9]* 
$TESTDIR/edit-check/first-dir   EUC"
+            CVS_SERVER=$CVS_SERVER_SAVED; export CVS_SERVER
+
+            dotest_fail edit-check-31dr "$testcvs edit -c file1" \
+"$editorsLineRE
+$SPROG edit: Skipping file \`file1' due to existing editors\."
+            dotest edit-check-31er "$testcvs editors file1" "$editorsLineRE"
+            dotest edit-check-31fr "$testcvs unedit file1"
+          fi
+
+           # Make sure it isn't confused by handling multiple files at
+           # the same time:
+          echo file2Data >file2
+
+          dotest edit-check-32a "$testcvs add file2" \
+"$SPROG [a-z]*: scheduling file .file2. for addition
+$SPROG [a-z]*: use .$SPROG commit. to add this file permanently"
+
+          dotest edit-check-32b "$testcvs commit -m 'c1' file2" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+
+          mkdir second-dir
+          dotest edit-check-32c "$testcvs add second-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir/second-dir added to the repository"
+          cd second-dir
+          echo ThirdFile >file3
+
+          dotest edit-check-32d "$testcvs add file3" \
+"$SPROG [a-z]*: scheduling file .file3. for addition
+$SPROG [a-z]*: use .$SPROG commit. to add this file permanently"
+
+          dotest edit-check-32f "$testcvs commit -m 'c1' file3" \
+"$CVSROOT_DIRNAME/first-dir/second-dir/file3,v  <--  file3
+initial revision: 1\.1"
+          dotest_fail edit-check-32g "test -w file3"
+
+          cd ..
+
+          dotest edit-check-33a "$testcvs edit -c"
+
+          dotest edit-check-33b "$testcvs editors" \
+"$AF_editorsLineRE
+$AF_editorsLineRE
+$F3_editorsLineRE"
+          dotest edit-check-33c "test -w second-dir/file3"
+
+          dotest_fail edit-check-34a "$testcvs edit -c file1 file2" \
+"$AF_editorsLineRE
+$SPROG edit: Skipping file \`file1' due to existing editors\.
+$AF_editorsLineRE
+$SPROG edit: Skipping file \`file2' due to existing editors\."
+
+          dotest edit-check-34b "$testcvs editors file1 file2" \
+"$editorsLineRE
+$AF_editorsLineRE"
+
+          dotest edit-check-35a "$testcvs unedit file1"
+          dotest edit-check-35b "$testcvs editors" \
+"$AF_editorsLineRE
+$F3_editorsLineRE"
+          dotest edit-check-35c "test -w second-dir/file3"
+
+          dotest edit-check-36a "$testcvs unedit"
+          dotest edit-check-36b "$testcvs editors"
+          dotest_fail edit-check-36c "test -w second-dir/file3"
+
+         dokeep
+          cd ../..
+          rm -rf edit-check
+          rm -rf $CVSROOT_DIRNAME/first-dir
+          ;;
+
+
+
+       unedit-without-baserev)
+         mkdir 1; cd 1
+         module=x
+
+         file=m
+         echo foo > $file
+         dotest unedit-without-baserev-1 \
+           "$testcvs -Q import -m . $module X Y" ''
+         dotest unedit-without-baserev-2 "$testcvs -Q co $module" ''
+         cd $module
+
+         dotest unedit-without-baserev-3 "$testcvs -Q edit $file" ''
+
+         echo add a line >> $file
+         rm -f CVS/Baserev
+
+         # This will fail on most systems.
+         dotest unedit-without-baserev-4 "echo yes |${testcvs} -Q unedit 
$file" \
+"m has been modified; revert changes${QUESTION} ${CPROG} unedit: m not 
mentioned in CVS/Baserev
+${CPROG} unedit: run update to complete the unedit"
+
+         # SunOS4.1.4 systems make it this far, but with a corrupted
+         # CVS/Entries file.  Demonstrate the corruption!
+         dotest unedit-without-baserev-5 "cat CVS/Entries" \
+           "/$file/1\.1\.1\.1/${DOTSTAR}"
+
+         dotest unedit-without-baserev-6 "${testcvs} -q update" \
+"$SPROG update: warning: \`m' was lost
+U m"
+
+         # OK, those were the easy cases.  Now tackle the hard one
+         # (the reason that CVS/Baserev was invented rather than just
+         # getting the revision from CVS/Entries).  This is very
+         # similar to watch4-10 through watch4-18 but with Baserev
+         # missing.
+         cd ../..
+         mkdir 2; cd 2
+         dotest unedit-without-baserev-7 "${testcvs} -Q co x" ''
+         cd x
+
+         dotest unedit-without-baserev-10 "${testcvs} edit m" ''
+         echo 'edited in 2' >m
+         cd ../..
+
+         cd 1/x
+
+          editorsLineRE="m     $username       [SMTWF][uoehra][neduit] 
[JFAMSOND][aepuco][nbrylgptvc] [0-9 ][0-9] [0-9:]* [0-9][0-9][0-9][0-9] -0000   
$hostname       $TESTDIR/2/x"
+         dotest unedit-without-baserev-11 "$testcvs edit m" "$editorsLineRE"
+
+         echo 'edited in 1' >m
+         dotest unedit-without-baserev-12 "${testcvs} -q ci -m edit-in-1" \
+"$CVSROOT_DIRNAME/x/m,v  <--  m
+new revision: 1\.2; previous revision: 1\.1"
+         cd ../..
+         cd 2/x
+         dotest unedit-without-baserev-13 "${testcvs} -q update" \
+"RCS file: ${CVSROOT_DIRNAME}/x/m,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.2
+Merging differences between 1\.1\.1\.1 and 1\.2 into m
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in m
+C m"
+         rm CVS/Baserev
+         dotest unedit-without-baserev-14 "echo yes |${testcvs} unedit m" \
+"m has been modified; revert changes${QUESTION} ${CPROG} unedit: m not 
mentioned in CVS/Baserev
+${CPROG} unedit: run update to complete the unedit"
+         dotest unedit-without-baserev-15 "${testcvs} -q update" \
+"$SPROG update: warning: \`m' was lost
+U m"
+         # The following tests are kind of degenerate compared with
+         # watch4-16 through watch4-18 but might as well make sure that
+         # nothing seriously wrong has happened to the working directory.
+         dotest unedit-without-baserev-16 "cat m" 'edited in 1'
+         # Make sure CVS really thinks we are at 1.2.
+         dotest unedit-without-baserev-17 "${testcvs} -q update" ""
+         dotest unedit-without-baserev-18 "cat m" "edited in 1"
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         rm -r 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         ;;
+
+
+
+       ignore)
+         # On Windows, we can't check out CVSROOT, because the case
+         # insensitivity means that this conflicts with cvsroot.
+         mkdir ignore
+         cd ignore
+
+         dotest ignore-1 "${testcvs} -q co CVSROOT" "U CVSROOT/${DOTSTAR}"
+         cd CVSROOT
+         echo rootig.c >cvsignore
+         dotest ignore-2 "${testcvs} add cvsignore" "${SPROG}"' add: 
scheduling file `cvsignore'"'"' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+
+         dotest ignore-3 " ${testcvs} ci -m added" \
+"${CPROG} commit: Examining \.
+${CVSROOT_DIRNAME}/CVSROOT/cvsignore,v  <--  cvsignore
+initial revision: 1\.1
+${SPROG} commit: Rebuilding administrative file database"
+
+         cd ..
+         if echo "yes" | ${testcvs} release -d CVSROOT >>${LOGFILE} ; then
+             pass ignore-4
+         else
+             fail ignore-4
+         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 use sort because we can't predict the order in which
+         # the files will be listed.
+         dotest_sort ignore-5 "${testcvs} import -m m -I optig.c 
ignore/first-dir tag1 tag2" \
+'
+
+I ignore/first-dir/defig.o
+I ignore/first-dir/envig.c
+I ignore/first-dir/optig.c
+I ignore/first-dir/rootig.c
+N ignore/first-dir/bar.c
+N ignore/first-dir/foobar.c
+No conflicts created by this import'
+         dotest_sort ignore-6 "${testcvs} import -m m -I ! ignore/second-dir 
tag3 tag4" \
+'
+
+N ignore/second-dir/bar.c
+N ignore/second-dir/defig.o
+N ignore/second-dir/envig.c
+N ignore/second-dir/foobar.c
+N ignore/second-dir/optig.c
+N ignore/second-dir/rootig.c
+No conflicts created by this import'
+         cd ..
+         rm -r dir-to-import
+
+         mkdir 1
+         cd 1
+         dotest ignore-7 "${testcvs} -q co -dsecond-dir ignore/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'
+         dotest ignore-8 "${testcvs} -q co -dfirst-dir ignore/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 ignore-9 "${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_sort ignore-10 "${testcvs} -q update -I ! -I CVS" \
+"${QUESTION} defig.o
+${QUESTION} envig.c
+${QUESTION} notig.c
+${QUESTION} optig.c
+${QUESTION} rootig.c"
+
+         # Now test that commands other than update also print "? notig.c"
+         # where appropriate.  Only test this for remote, because local
+         # CVS only prints it on update.
+         rm optig.c
+         if $remote; then
+           dotest ignore-11r "$testcvs -q diff" "$QUESTION notig.c"
+
+           # Force the server to be contacted.  Ugh.  Having CVS
+           # contact the server for the sole purpose of checking
+           # the CVSROOT/cvsignore file does not seem like such a
+           # good idea, so I imagine this will continue to be
+           # necessary.  Oh well, at least we test CVS's ablity to
+           # handle a file with a modified timestamp but unmodified
+           # contents.
+           touch bar.c
+
+           dotest ignore-11ar "$testcvs -q ci -m commit-it" \
+"$QUESTION notig.c"
+         fi
+
+         # now test .cvsignore files
+         cd ..
+         echo notig.c >first-dir/.cvsignore
+         echo foobar.c >second-dir/.cvsignore
+         touch first-dir/notig.c second-dir/notig.c second-dir/foobar.c
+         dotest_sort ignore-12 "${testcvs} -qn update" \
+"${QUESTION} first-dir/.cvsignore
+${QUESTION} second-dir/.cvsignore
+${QUESTION} second-dir/notig.c"
+         dotest_sort ignore-13 "${testcvs} -qn update -I! -I CVS" \
+"${QUESTION} first-dir/.cvsignore
+${QUESTION} first-dir/defig.o
+${QUESTION} first-dir/envig.c
+${QUESTION} first-dir/rootig.c
+${QUESTION} second-dir/.cvsignore
+${QUESTION} second-dir/notig.c"
+
+         echo yes | dotest ignore-14 "${testcvs} release -d first-dir" \
+"${QUESTION} \.cvsignore
+You have \[0\] altered files in this repository.
+Are you sure you want to release (and delete) directory .first-dir': "
+
+         echo add a line >>second-dir/foobar.c
+         rm second-dir/notig.c second-dir/.cvsignore
+         echo yes | dotest ignore-15 "${testcvs} release -d second-dir" \
+"M foobar.c
+You have \[1\] altered files in this repository.
+Are you sure you want to release (and delete) directory .second-dir': "
+
+         dokeep
+         cd ../..
+         rm -r ignore
+         modify_repo rm -rf $CVSROOT_DIRNAME/ignore
+         ;;
+
+
+
+       ignore-on-branch)
+         # Test that CVS _doesn't_ ignore files on branches because they were
+         # added to the trunk.
+         mkdir ignore-on-branch; cd ignore-on-branch
+         modify_repo mkdir $CVSROOT_DIRNAME/ignore-on-branch
+
+         # create file1 & file2 on trunk
+         dotest ignore-on-branch-setup-1 "$testcvs -q co -dsetup 
ignore-on-branch" ''
+         cd setup
+         echo file1 >file1 
+         dotest ignore-on-branch-setup-2 "$testcvs -q add file1" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest ignore-on-branch-setup-3 "$testcvs -q ci -mfile1 file1" \
+"$CVSROOT_DIRNAME/ignore-on-branch/file1,v  <--  file1
+initial revision: 1\.1"
+         dotest ignore-on-branch-setup-4 "$testcvs -q tag -b branch" 'T file1'
+         echo file2 >file2 
+         dotest ignore-on-branch-setup-5 "$testcvs -q add file2" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest ignore-on-branch-setup-6 "$testcvs -q ci -mtrunk file2" \
+"$CVSROOT_DIRNAME/ignore-on-branch/file2,v  <--  file2
+initial revision: 1\.1"
+
+         cd ..
+
+         # Check out branch.
+         #
+         # - This was the original failure case - file2 would not be flagged
+         #   with a '?'
+         dotest ignore-on-branch-1 "$testcvs -q co -rbranch ignore-on-branch" \
+'U ignore-on-branch/file1'
+         cd ignore-on-branch
+         echo file2 on branch >file2 
+         dotest ignore-on-branch-2 "$testcvs -nq update" '? file2'
+
+         # Now set up for a join.  One of the original fixes for this would
+         # print out a 'U' and a '?' during a join which added a file.
+         if $remote; then
+           dotest ignore-on-branch-3 "$testcvs -q tag -b branch2" \
+'? file2
+T file1'
+         else
+           dotest ignore-on-branch-3 "$testcvs -q tag -b branch2" 'T file1'
+         fi
+         dotest ignore-on-branch-4 "$testcvs -q add file2" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest ignore-on-branch-5 "$testcvs -q ci -mbranch file2" \
+"$CVSROOT_DIRNAME/ignore-on-branch/file2,v  <--  file2
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+         dotest ignore-on-branch-6 "$testcvs -q up -rbranch2" \
+"${SPROG} update: \`file2' is no longer in the repository"
+         dotest ignore-on-branch-7 "$testcvs -q up -jbranch" 'U file2'
+
+         dokeep
+         cd ../..
+         rm -r ignore-on-branch
+         modify_repo rm -rf $CVSROOT_DIRNAME/ignore-on-branch
+         ;;
+
+
+
+       binfiles)
+         # Test cvs's ability to handle binary files.
+         # List of binary file tests:
+         #   * conflicts, "cvs admin": binfiles
+         #   * branching and joining: binfiles2
+         #   * adding and removing files: binfiles3
+         #   * -k wrappers: binwrap, binwrap2, binwrap3
+         #   * "cvs import" and wrappers: binwrap, binwrap2, binwrap3
+         #   * -k option to "cvs import": none yet, as far as I know.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir 1; cd 1
+         dotest binfiles-1 "${testcvs} -q co first-dir" ''
+         ${AWK} 'BEGIN { printf "address@hidden", 2, 10, 137, 13, 10 }' \
+           </dev/null | ${TR} '@' '\000' >binfile.dat
+         cat binfile.dat binfile.dat >binfile2.dat
+         cd first-dir
+         cp ../binfile.dat binfile
+         dotest binfiles-2 "${testcvs} add -kb binfile" \
+"${SPROG}"' add: scheduling file `binfile'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest binfiles-3 "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/binfile,v  <--  binfile
+initial revision: 1\.1"
+         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    ${CVSROOT_DIRNAME}/first-dir/binfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+
+         # Test that "-kk" does not override "-kb"
+         cd ../..
+         mkdir 2a; cd 2a
+         dotest binfiles-4 "${testcvs} -q co -kk first-dir" 'U 
first-dir/binfile'
+         cd first-dir
+         # 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    ${CVSROOT_DIRNAME}/first-dir/binfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+
+         # Test whether the default options from the RCS file are
+         # also used when operating on files instead of whole
+         # directories
+          cd ../..
+         rm -r 2a
+         mkdir 3; cd 3
+         dotest binfiles-5.5b0 "${testcvs} -q co first-dir/binfile" \
+'U first-dir/binfile'
+         cd first-dir
+         dotest binfiles-5.5b1 "${testcvs} status binfile" \
+"===================================================================
+File: binfile                  Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/binfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+         cd ../..
+         rm -r 3
+         # test that "-kk" does not override "-kb"
+         mkdir 3; cd 3
+         dotest binfiles-5.5b0 "${testcvs} -q co -kk first-dir/binfile" \
+'U first-dir/binfile'
+         cd first-dir
+         dotest binfiles-5.5b1 "${testcvs} status binfile" \
+"===================================================================
+File: binfile                  Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/binfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+         cd ../..
+         rm -r 3
+         cd 2/first-dir
+
+         cp ../../1/binfile2.dat binfile
+         dotest binfiles-6 "${testcvs} -q ci -m modify-it" \
+"$CVSROOT_DIRNAME/first-dir/binfile,v  <--  binfile
+new revision: 1\.2; previous revision: 1\.1"
+         cd ../../1/first-dir
+         dotest binfiles-7 "${testcvs} -q update" 'U binfile'
+         dotest binfiles-8 "cmp ../binfile2.dat binfile" ''
+
+         # Now test handling of conflicts with binary files.
+         cp ../binfile.dat binfile
+         dotest binfiles-con0 "${testcvs} -q ci -m modify-it" \
+"$CVSROOT_DIRNAME/first-dir/binfile,v  <--  binfile
+new revision: 1\.3; previous revision: 1\.2"
+         cd ../../2/first-dir
+         echo 'edits in dir 2' >binfile
+         dotest binfiles-con1 "${testcvs} -q update" \
+"$SPROG update: nonmergeable file needs merge
+$SPROG update: revision 1\.3 from repository is now in binfile
+$SPROG update: file from working directory is now in \.#binfile\.1\.2
+C binfile"
+
+         dotest_fail binfiles-con1b "$testcvs -q up" "C binfile"
+
+         dotest binfiles-con2 "cmp binfile ../../1/binfile.dat" ''
+         dotest binfiles-con3 "cat .#binfile.1.2" 'edits in dir 2'
+
+         cp ../../1/binfile2.dat binfile
+         dotest binfiles-con4 "$testcvs -q ci -m resolve-it" \
+"$CVSROOT_DIRNAME/first-dir/binfile,v  <--  binfile
+new revision: 1\.4; previous revision: 1\.3"
+         cd ../../1/first-dir
+         dotest binfiles-con5 "${testcvs} -q update" 'U binfile'
+
+         dotest binfiles-9 "${testcvs} -q update -A" ''
+         # "-kk" no longer does anything with "-kb"
+         dotest binfiles-10 "${testcvs} -q update -kk" ''
+         dotest binfiles-11 "${testcvs} -q update" ''
+         # "-kk" no longer does anything with "-kb"
+         dotest binfiles-12 "${testcvs} -q update -A" ''
+         dotest binfiles-13 "${testcvs} -q update -A" ''
+
+         cd ../..
+
+         mkdir 3
+         cd 3
+         dotest binfiles-13a0 "${testcvs} -q co -r HEAD first-dir" \
+'U first-dir/binfile'
+         cd first-dir
+         dotest binfiles-13a1 "${testcvs} status binfile" \
+"===================================================================
+File: binfile                  Status: Up-to-date
+
+   Working revision:   1\.4.*
+   Repository revision:        1\.4    ${CVSROOT_DIRNAME}/first-dir/binfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         HEAD (revision: 1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+         cd ../..
+         rm -r 3
+
+         cd 2/first-dir
+         echo 'this file is $''RCSfile$' >binfile
+         dotest binfiles-14a "${testcvs} -q ci -m modify-it" \
+"$CVSROOT_DIRNAME/first-dir/binfile,v  <--  binfile
+new revision: 1\.5; previous revision: 1\.4"
+         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\.5.*
+   Repository revision:        1\.5    ${CVSROOT_DIRNAME}/first-dir/binfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+         dotest binfiles-14d "${testcvs} admin -kv binfile" \
+"RCS file: ${CVSROOT_DIRNAME}/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\.5.*
+   Repository revision:        1\.5    ${CVSROOT_DIRNAME}/first-dir/binfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+         dotest binfiles-14g "${testcvs} -q update -A" 'U 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\.5.*
+   Repository revision:        1\.5    ${CVSROOT_DIRNAME}/first-dir/binfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kv"
+
+         # Do sticky options work when used with 'cvs update'?
+         echo "Not a binary file." > nibfile
+         dotest binfiles-sticky1 "${testcvs} -q add nibfile" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest binfiles-sticky2 "${testcvs} -q ci -m add-it nibfile" \
+"$CVSROOT_DIRNAME/first-dir/nibfile,v  <--  nibfile
+initial revision: 1\.1"
+         dotest binfiles-sticky3 "${testcvs} -q update -kb nibfile" \
+           'U nibfile'
+         dotest binfiles-sticky4 "${testcvs} -q status nibfile" \
+"===================================================================
+File: nibfile                  Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/nibfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+
+         # Now test that -A can clear the sticky option.
+         dotest binfiles-sticky5 "${testcvs} -q update -A nibfile" \
+"U nibfile"
+         dotest binfiles-sticky6 "${testcvs} -q status nibfile" \
+"===================================================================
+File: nibfile                  Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/nibfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest binfiles-15 "${testcvs} -q admin -kb nibfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/nibfile,v
+done"
+         dotest binfiles-16 "${testcvs} -q update nibfile" "U nibfile"
+         dotest binfiles-17 "${testcvs} -q status nibfile" \
+"===================================================================
+File: nibfile                  Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/nibfile,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+
+         dotest binfiles-o1 "${testcvs} admin -o1.3:: binfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/binfile,v
+deleting revision 1\.5
+deleting revision 1\.4
+done"
+         dotest binfiles-o2 "${testcvs} admin -o::1.3 binfile" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/binfile,v
+deleting revision 1\.2
+deleting revision 1\.1
+done"
+         dotest binfiles-o3 "${testcvs} -q log -h -N binfile" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/binfile,v
+Working file: binfile
+head: 1\.3
+branch:
+locks: strict
+access list:
+keyword substitution: v
+total revisions: 1
+============================================================================="
+
+         # Check that the contents were right.  This isn't the hard case
+         # (in which RCS_delete_revs does a diff), but might as well.
+         dotest binfiles-o4 "${testcvs} -q update binfile" "U binfile"
+         dotest binfiles-o5 "cmp binfile ../../1/binfile.dat" ""
+
+         dokeep
+         cd ../..
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -r 1 2
+         ;;
+
+
+
+       binfiles2)
+         # Test cvs's ability to handle binary files, particularly branching
+         # and joining.  The key thing we are worrying about is that CVS
+         # doesn't print "cannot merge binary files" or some such, in 
+         # situations where no merging is required.
+         # See also "join" which does this with non-binary files.
+         #
+         # Cases (we are merging from the branch to the trunk):
+         # binfile.dat) File added on branch, not on trunk.
+         #      File should be marked for addition.
+         # brmod) File modified on branch, not on trunk.
+         #      File should be copied over to trunk (no merging is needed).
+         # brmod-trmod) File modified on branch, also on trunk.
+         #      This is a conflict.  Present the user with both files and
+         #      let them figure it out.
+         # brmod-wdmod) File modified on branch, not modified in the trunk
+         #      repository, but modified in the (trunk) working directory.
+         #      This is also a conflict.
+
+         modify_repo mkdir ${CVSROOT_DIRNAME}/first-dir
+         mkdir 1; cd 1
+         dotest binfiles2-1 "${testcvs} -q co first-dir" ''
+         cd first-dir
+
+         # The most important thing here is that binfile, binfile2, &c
+         # each be distinct from each other.  We also make sure to include
+         # a few likely end-of-line patterns to make sure nothing is
+         # being munged as if in text mode.
+         ${AWK} 'BEGIN { printf "address@hidden", 2, 10, 137, 13, 10 }' \
+           </dev/null | ${TR} '@' '\000' >../binfile
+         # Use binfl2 rather than binfile2 because of a problem with Cygwin
+         # and Samba. that causes cat to report that the input and output file
+         # are the same when outputting to binfile3.  Why?  I don't know, but
+         # it is consistently reproducible.
+         cat ../binfile ../binfile >../binfl2
+         cat ../binfl2 ../binfile >../binfile3
+
+         # FIXCVS: unless a branch has at least one file on it,
+         # tag_check_valid won't know it exists.  So if brmod didn't
+         # exist, we would have to invent it.
+         cp ../binfile brmod
+         cp ../binfile brmod-trmod
+         cp ../binfile brmod-wdmod
+         dotest binfiles2-1a \
+"${testcvs} add -kb brmod brmod-trmod brmod-wdmod" \
+"${SPROG} add: scheduling file .brmod. for addition
+${SPROG} add: scheduling file .brmod-trmod. for addition
+${SPROG} add: scheduling file .brmod-wdmod. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest binfiles2-1b "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/brmod,v  <--  brmod
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-trmod,v  <--  brmod-trmod
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-wdmod,v  <--  brmod-wdmod
+initial revision: 1\.1"
+         dotest binfiles2-2 "${testcvs} -q tag -b br" 'T brmod
+T brmod-trmod
+T brmod-wdmod'
+         dotest binfiles2-3 "${testcvs} -q update -r br" ''
+         cp ../binfile binfile.dat
+         dotest binfiles2-4 "${testcvs} add -kb binfile.dat" \
+"${SPROG} add: scheduling file .binfile\.dat. for addition on branch .br.
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         cp ../binfl2 brmod
+         cp ../binfl2 brmod-trmod
+         cp ../binfl2 brmod-wdmod
+         dotest binfiles2-5 "${testcvs} -q ci -m br-changes" \
+"$CVSROOT_DIRNAME/first-dir/Attic/binfile\.dat,v  <--  binfile\.dat
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod,v  <--  brmod
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-trmod,v  <--  brmod-trmod
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-wdmod,v  <--  brmod-wdmod
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+         dotest binfiles2-6 "${testcvs} -q update -A" \
+"${SPROG} update: \`binfile\.dat' is no longer in the repository
+U brmod
+U brmod-trmod
+U brmod-wdmod"
+         dotest_fail binfiles2-7 "test -f binfile.dat" ''
+         dotest binfiles2-7-brmod "cmp ../binfile brmod"
+         cp ../binfile3 brmod-trmod
+         dotest binfiles2-7a "${testcvs} -q ci -m tr-modify" \
+"$CVSROOT_DIRNAME/first-dir/brmod-trmod,v  <--  brmod-trmod
+new revision: 1\.2; previous revision: 1\.1"
+         cp ../binfile3 brmod-wdmod
+
+         dotest binfiles2-8 "${testcvs} -q update -j br" \
+"U binfile\.dat
+U brmod
+${SPROG} update: nonmergeable file needs merge
+${SPROG} update: revision 1.1.2.1 from repository is now in brmod-trmod
+${SPROG} update: file from working directory is now in .#brmod-trmod.1.2
+C brmod-trmod
+M brmod-wdmod
+${SPROG} update: nonmergeable file needs merge
+${SPROG} update: revision 1.1.2.1 from repository is now in brmod-wdmod
+${SPROG} update: file from working directory is now in .#brmod-wdmod.1.1
+C brmod-wdmod"
+
+         dotest binfiles2-9 "cmp ../binfile binfile.dat"
+         dotest binfiles2-9-brmod "cmp ../binfl2 brmod"
+         dotest binfiles2-9-brmod-trmod "cmp ../binfl2 brmod-trmod"
+         dotest binfiles2-9-brmod-trmod "cmp ../binfl2 brmod-wdmod"
+         dotest binfiles2-9a-brmod-trmod "cmp ../binfile3 .#brmod-trmod.1.2"
+         dotest binfiles2-9a-brmod-wdmod "cmp ../binfile3 .#brmod-wdmod.1.1"
+
+         # Test that everything was properly scheduled.
+         dotest binfiles2-10 "${testcvs} -q ci -m checkin" \
+"$CVSROOT_DIRNAME/first-dir/binfile\.dat,v  <--  binfile\.dat
+new revision: 1\.2; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod,v  <--  brmod
+new revision: 1\.2; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-trmod,v  <--  brmod-trmod
+new revision: 1\.3; previous revision: 1\.2
+$CVSROOT_DIRNAME/first-dir/brmod-wdmod,v  <--  brmod-wdmod
+new revision: 1\.2; previous revision: 1\.1"
+
+         dotest_fail binfiles2-o1 "${testcvs} -q admin -o :1.2 brmod-trmod" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/brmod-trmod,v
+deleting revision 1\.2
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/brmod-trmod,v: can't remove 
branch point 1\.1
+${SPROG} admin: RCS file for .brmod-trmod. not modified\."
+         dotest binfiles2-o2 "${testcvs} -q admin -o 1.1.2.1: brmod-trmod" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/brmod-trmod,v
+deleting revision 1\.1\.2\.1
+done"
+         dotest binfiles2-o3 "${testcvs} -q admin -o :1.2 brmod-trmod" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/brmod-trmod,v
+deleting revision 1\.2
+deleting revision 1\.1
+done"
+         dotest binfiles2-o4 "${testcvs} -q log -N brmod-trmod" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/brmod-trmod,v
+Working file: brmod-trmod
+head: 1\.3
+branch:
+locks: strict
+access list:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+checkin
+============================================================================="
+
+         dokeep
+         cd ../..
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -r 1
+         ;;
+
+
+
+       binfiles3)
+         # More binary file tests, especially removing, adding, &c.
+         # See "binfiles" for a list of binary file tests.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir 1; cd 1
+         dotest binfiles3-1 "${testcvs} -q co first-dir" ''
+         ${AWK} 'BEGIN { printf "address@hidden", 2, 10, 137, 13, 10 }' \
+           </dev/null | ${TR} '@' '\000' >binfile.dat
+         cd first-dir
+         echo hello >file1
+         dotest binfiles3-2 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest binfiles3-3 "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         rm file1
+         dotest binfiles3-4 "${testcvs} rm file1" \
+"${SPROG} remove: scheduling .file1. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest binfiles3-5 "${testcvs} -q ci -m remove-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: delete; previous revision: 1\.1"
+         cp ../binfile.dat file1
+         dotest binfiles3-6 "${testcvs} add -kb file1" \
+"$SPROG add: Re-adding file .file1. after dead revision 1\.2\.
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         # The idea behind this test is to make sure that the file
+         # gets opened in binary mode to send to "cvs ci".
+         dotest binfiles3-6a "cat CVS/Entries" \
+"/file1/0/[A-Za-z0-9 :]*/-kb/
+D"
+         # TODO: This just tests the case where the old keyword
+         # expansion mode is the default (RCS_getexpand == NULL
+         # in checkaddfile()); should also test the case in which
+         # we are changing it from one non-default value to another.
+         dotest binfiles3-7 "$testcvs -q ci -m readd-it" \
+"$SPROG commit: changing keyword expansion mode to -kb
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2"
+         dotest binfiles3-8 "${testcvs} -q log -h -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.3
+branch:
+locks: strict
+access list:
+keyword substitution: b
+total revisions: 3
+============================================================================="
+
+         # OK, now test admin -o on a binary file.  See "admin"
+         # test for a more complete list of admin -o tests.
+         cp ${TESTDIR}/1/binfile.dat ${TESTDIR}/1/binfile4.dat
+         echo '%%$$##@@!!jjiiuull' | ${TR} j '\000' >>${TESTDIR}/1/binfile4.dat
+         cp ${TESTDIR}/1/binfile4.dat ${TESTDIR}/1/binfile5.dat
+         echo 'aawwee%$$##@@!!jjil' | ${TR} w '\000' 
>>${TESTDIR}/1/binfile5.dat
+
+         cp ../binfile4.dat file1
+         dotest binfiles3-9 "${testcvs} -q ci -m change" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.4; previous revision: 1\.3"
+         cp ../binfile5.dat file1
+         dotest binfiles3-10 "${testcvs} -q ci -m change" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.5; previous revision: 1\.4"
+         dotest binfiles3-11 "${testcvs} admin -o 1.3::1.5 file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+deleting revision 1\.4
+done"
+         dotest binfiles3-12 "${testcvs} -q update -r 1.3 file1" "U file1"
+         dotest binfiles3-13 "cmp file1 ${TESTDIR}/1/binfile.dat" ""
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       mcopy)
+         # See comment at "mwrap" test for list of other wrappers tests.
+         # Test cvs's ability to handle nonmergeable files specified with
+         # -m 'COPY' in wrappers.  Similar to the binfiles2 test,
+         # which tests the same thing for binary files
+         # (which are non-mergeable in the same sense).
+         #
+         # Cases (we are merging from the branch to the trunk):
+         # brmod) File modified on branch, not on trunk.
+         #      File should be copied over to trunk (no merging is needed).
+         # brmod-trmod) File modified on branch, also on trunk.
+         #      This is a conflict.  Present the user with both files and
+         #      let them figure it out.
+         # brmod-wdmod) File modified on branch, not modified in the trunk
+         #      repository, but modified in the (trunk) working directory.
+         #      This is also a conflict.
+
+         # For the moment, remote CVS can't pass wrappers from CVSWRAPPERS
+         # (see wrap_send).  So skip these tests for remote.
+         if $remote; then :; else
+
+           mkdir ${CVSROOT_DIRNAME}/first-dir
+           mkdir 1; cd 1
+           dotest mcopy-1 "${testcvs} -q co first-dir" ''
+           cd first-dir
+
+           # FIXCVS: unless a branch has at least one file on it,
+           # tag_check_valid won't know it exists.  So if brmod didn't
+           # exist, we would have to invent it.
+           echo 'brmod initial contents' >brmod
+           echo 'brmod-trmod initial contents' >brmod-trmod
+           echo 'brmod-wdmod initial contents' >brmod-wdmod
+           echo "* -m 'COPY'" >.cvswrappers
+           dotest mcopy-1a \
+"${testcvs} add .cvswrappers brmod brmod-trmod brmod-wdmod" \
+"${SPROG} add: scheduling file .\.cvswrappers. for addition
+${SPROG} add: scheduling file .brmod. for addition
+${SPROG} add: scheduling file .brmod-trmod. for addition
+${SPROG} add: scheduling file .brmod-wdmod. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+           dotest mcopy-1b "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/\.cvswrappers,v  <--  \.cvswrappers
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod,v  <--  brmod
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-trmod,v  <--  brmod-trmod
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-wdmod,v  <--  brmod-wdmod
+initial revision: 1\.1"
+
+           # NOTE: .cvswrappers files are broken (see comment in
+           # src/wrapper.c).  So doing everything via the environment
+           # variable is a workaround.  Better would be to test them
+           # both.
+           CVSWRAPPERS="* -m 'COPY'"
+           export CVSWRAPPERS
+           dotest mcopy-2 "${testcvs} -q tag -b br" 'T \.cvswrappers
+T brmod
+T brmod-trmod
+T brmod-wdmod'
+           dotest mcopy-3 "${testcvs} -q update -r br" ''
+           echo 'modify brmod on br' >brmod
+           echo 'modify brmod-trmod on br' >brmod-trmod
+           echo 'modify brmod-wdmod on br' >brmod-wdmod
+           dotest mcopy-5 "${testcvs} -q ci -m br-changes" \
+"$CVSROOT_DIRNAME/first-dir/brmod,v  <--  brmod
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-trmod,v  <--  brmod-trmod
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-wdmod,v  <--  brmod-wdmod
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+           dotest mcopy-6 "${testcvs} -q update -A" \
+"U brmod
+U brmod-trmod
+U brmod-wdmod"
+           dotest mcopy-7 "cat brmod brmod-trmod brmod-wdmod" \
+"brmod initial contents
+brmod-trmod initial contents
+brmod-wdmod initial contents"
+
+           echo 'modify brmod-trmod again on trunk' >brmod-trmod
+           dotest mcopy-7a "${testcvs} -q ci -m tr-modify" \
+"$CVSROOT_DIRNAME/first-dir/brmod-trmod,v  <--  brmod-trmod
+new revision: 1\.2; previous revision: 1\.1"
+           echo 'modify brmod-wdmod in working dir' >brmod-wdmod
+
+           dotest mcopy-8 "${testcvs} -q update -j br" \
+"U brmod
+${SPROG} update: nonmergeable file needs merge
+${SPROG} update: revision 1.1.2.1 from repository is now in brmod-trmod
+${SPROG} update: file from working directory is now in .#brmod-trmod.1.2
+C brmod-trmod
+M brmod-wdmod
+${SPROG} update: nonmergeable file needs merge
+${SPROG} update: revision 1.1.2.1 from repository is now in brmod-wdmod
+${SPROG} update: file from working directory is now in .#brmod-wdmod.1.1
+C brmod-wdmod"
+
+           dotest mcopy-9 "cat brmod brmod-trmod brmod-wdmod" \
+"modify brmod on br
+modify brmod-trmod on br
+modify brmod-wdmod on br"
+           dotest mcopy-9a "cat .#brmod-trmod.1.2 .#brmod-wdmod.1.1" \
+"modify brmod-trmod again on trunk
+modify brmod-wdmod in working dir"
+
+           # Test that everything was properly scheduled.
+           dotest mcopy-10 "${testcvs} -q ci -m checkin" \
+"$CVSROOT_DIRNAME/first-dir/brmod,v  <--  brmod
+new revision: 1\.2; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/brmod-trmod,v  <--  brmod-trmod
+new revision: 1\.3; previous revision: 1\.2
+$CVSROOT_DIRNAME/first-dir/brmod-wdmod,v  <--  brmod-wdmod
+new revision: 1\.2; previous revision: 1\.1"
+
+           dokeep
+           cd ../..
+           modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+           rm -r 1
+           unset CVSWRAPPERS
+         fi # end of tests to be skipped for remote
+         ;;
+
+
+
+       binwrap)
+         # Test the ability to specify binary-ness based on file name.
+         # See "mwrap" for a list of other wrappers tests.
+
+         mkdir dir-to-import
+         cd dir-to-import
+         touch foo.c foo.exe
+
+         # While we're here, test for rejection of duplicate tag names.
+         dotest_fail binwrap-0 \
+           "${testcvs} import -m msg -I ! first-dir dup dup" \
+"${CPROG} \[import aborted\]: tag .dup. was specified more than once"
+
+         if ${testcvs} import -m message -I ! -W "*.exe -k 'b'" \
+             first-dir tag1 tag2 >>${LOGFILE}; then
+           pass binwrap-1
+         else
+           fail binwrap-1
+         fi
+         cd ..
+         rm -r dir-to-import
+         dotest binwrap-2 "${testcvs} -q co first-dir" 'U first-dir/foo.c
+U first-dir/foo.exe'
+         dotest binwrap-3 "${testcvs} -q status first-dir" \
+"===================================================================
+File: foo\.c                   Status: Up-to-date
+
+   Working revision:   1\.1\.1\.1.*
+   Repository revision:        1\.1\.1\.1      
${CVSROOT_DIRNAME}/first-dir/foo\.c,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: foo\.exe                 Status: Up-to-date
+
+   Working revision:   1\.1\.1\.1.*
+   Repository revision:        1\.1\.1\.1      
${CVSROOT_DIRNAME}/first-dir/foo\.exe,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+
+         dokeep
+         rm -r first-dir
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       binwrap2)
+         # Test the ability to specify binary-ness based on file name.
+         # See "mwrap" for a list of other wrappers tests.
+
+         mkdir dir-to-import
+         cd dir-to-import
+         touch foo.c foo.exe
+
+         # Specify that all files are binary except *.c.
+         # The order seems to matter, with the earlier rules taking
+         # precedence.  I'm not sure whether that is good or not,
+         # but it is the current behavior.
+         if ${testcvs} import -m message -I ! \
+             -W "*.c -k 'o'" -W "* -k 'b'" \
+             first-dir tag1 tag2 >>${LOGFILE}; then
+           pass binwrap2-1
+         else
+           fail binwrap2-1
+         fi
+         cd ..
+         rm -r dir-to-import
+         dotest binwrap2-2 "${testcvs} -q co first-dir" 'U first-dir/foo.c
+U first-dir/foo.exe'
+         dotest binwrap2-3 "${testcvs} -q status first-dir" \
+"===================================================================
+File: foo\.c                   Status: Up-to-date
+
+   Working revision:   1\.1\.1\.1.*
+   Repository revision:        1\.1\.1\.1      
${CVSROOT_DIRNAME}/first-dir/foo\.c,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -ko
+
+===================================================================
+File: foo\.exe                 Status: Up-to-date
+
+   Working revision:   1\.1\.1\.1.*
+   Repository revision:        1\.1\.1\.1      
${CVSROOT_DIRNAME}/first-dir/foo\.exe,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     -kb"
+
+         dokeep
+         rm -r first-dir
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+        binwrap3)
+          # Test communication of file-specified -k wrappers between
+          # client and server, in `import':
+          #
+          #   1. Set up a directory tree, populate it with files.
+          #   2. Give each directory a different .cvswrappers file. 
+          #   3. Give the server its own .cvswrappers file.
+          #   4. Import the whole tree, see if the right files got set
+          #      to binary.
+          #
+          # The tree has a top ("0th") level, and two subdirs, sub1/
+          # and sub2/; sub2/ contains directory subsub/.  Every
+          # directory has a .cvswrappers file as well as regular
+          # files.
+          #
+          # In the file names, "foo-b.*" should end up binary, and
+          # "foo-t.*" should end up text.  Don't worry about the two
+          # letter extensions; they're just there to help me keep
+          # things straight.
+          #
+          # Here's the directory tree:
+          #
+          # ./
+          #    .cvswrappers
+          #    foo-b.c0
+          #    foo-b.sb
+          #    foo-t.c1
+          #    foo-t.st
+          #
+          #    sub1/             sub2/
+          #      .cvswrappers      .cvswrappers
+          #      foo-b.c1          foo-b.sb
+          #      foo-b.sb          foo-b.st
+          #      foo-t.c0          foo-t.c0
+          #      foo-t.st          foo-t.c1
+          #                        foo-t.c2
+          #                        foo-t.c3
+          #
+          #                        subsub/
+          #                          .cvswrappers
+          #                          foo-b.c3
+          #                          foo-b.sb
+          #                          foo-t.c0
+          #                          foo-t.c1
+          #                          foo-t.c2
+          #                          foo-t.st
+
+          binwrap3_line1="This is a test file "
+          binwrap3_line2="containing little of use "
+          binwrap3_line3="except this non-haiku"
+
+          binwrap3_text="${binwrap3_line1}${binwrap3_line2}${binwrap3_line3}"
+
+          cd ${TESTDIR}
+
+         # On Windows, we can't check out CVSROOT, because the case
+         # insensitivity means that this conflicts with cvsroot.
+         mkdir wnt
+         cd wnt
+
+          mkdir binwrap3 # the 0th dir
+          mkdir binwrap3/sub1
+          mkdir binwrap3/sub2
+          mkdir binwrap3/sub2/subsub
+          
+          echo "bar*" > binwrap3/.cvswrappers
+          echo "*.c0 -k 'b'" >> binwrap3/.cvswrappers
+          echo "whatever -k 'b'" >> binwrap3/.cvswrappers
+          echo ${binwrap3_text} > binwrap3/foo-b.c0
+          echo ${binwrap3_text} > binwrap3/bar-t.c0
+          echo ${binwrap3_text} > binwrap3/foo-b.sb
+          echo ${binwrap3_text} > binwrap3/foo-t.sb
+          echo ${binwrap3_text} > binwrap3/foo-t.c1
+          echo ${binwrap3_text} > binwrap3/foo-t.st
+
+          echo "bar* -k 'kv'" > binwrap3/sub1/.cvswrappers
+          echo "*.c1 -k 'b'" >> binwrap3/sub1/.cvswrappers
+          echo "whatever -k 'b'" >> binwrap3/sub1/.cvswrappers
+          echo ${binwrap3_text} > binwrap3/sub1/foo-b.c1
+          echo ${binwrap3_text} > binwrap3/sub1/bar-t.c1
+          echo ${binwrap3_text} > binwrap3/sub1/foo-b.sb
+          echo ${binwrap3_text} > binwrap3/sub1/foo-t.sb
+          echo ${binwrap3_text} > binwrap3/sub1/foo-t.c0
+          echo ${binwrap3_text} > binwrap3/sub1/foo-t.st
+
+          echo "bar*" > binwrap3/sub2/.cvswrappers
+          echo "*.st -k 'b'" >> binwrap3/sub2/.cvswrappers
+          echo ${binwrap3_text} > binwrap3/sub2/foo-b.sb
+          echo ${binwrap3_text} > binwrap3/sub2/foo-t.sb
+          echo ${binwrap3_text} > binwrap3/sub2/foo-b.st
+          echo ${binwrap3_text} > binwrap3/sub2/bar-t.st
+          echo ${binwrap3_text} > binwrap3/sub2/foo-t.c0
+          echo ${binwrap3_text} > binwrap3/sub2/foo-t.c1
+          echo ${binwrap3_text} > binwrap3/sub2/foo-t.c2
+          echo ${binwrap3_text} > binwrap3/sub2/foo-t.c3
+
+          echo "bar* -k 'kv'" > binwrap3/sub2/subsub/.cvswrappers
+          echo "*.c3 -k 'b'" >> binwrap3/sub2/subsub/.cvswrappers
+          echo "foo -k 'b'" >> binwrap3/sub2/subsub/.cvswrappers
+          echo "c0* -k 'b'" >> binwrap3/sub2/subsub/.cvswrappers
+          echo ${binwrap3_text} > binwrap3/sub2/subsub/foo-b.c3
+          echo ${binwrap3_text} > binwrap3/sub2/subsub/bar-t.c3
+          echo ${binwrap3_text} > binwrap3/sub2/subsub/foo-b.sb
+          echo ${binwrap3_text} > binwrap3/sub2/subsub/foo-t.sb
+          echo ${binwrap3_text} > binwrap3/sub2/subsub/foo-t.c0
+          echo ${binwrap3_text} > binwrap3/sub2/subsub/foo-t.c1
+          echo ${binwrap3_text} > binwrap3/sub2/subsub/foo-t.c2
+          echo ${binwrap3_text} > binwrap3/sub2/subsub/foo-t.st
+
+          # Now set up CVSROOT/cvswrappers, the easy way:
+         dotest binwrap3-1 "${testcvs} -q co CVSROOT" "U CVSROOT${DOTSTAR}"
+         cd CVSROOT
+          # This destroys anything currently in cvswrappers, but
+         # presumably other tests will take care of it themselves if
+         # they use cvswrappers:
+         echo "foo-t.sb" > cvswrappers
+         echo "foo*.sb  -k 'b'" >> cvswrappers
+         dotest binwrap3-2 "${testcvs} -q ci -m cvswrappers-mod" \
+"$CVSROOT_DIRNAME/CVSROOT/cvswrappers,v  <--  cvswrappers
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+          cd ..
+
+          # Avoid environmental interference
+          CVSWRAPPERS_save=${CVSWRAPPERS}
+          unset CVSWRAPPERS
+
+          # Do the import
+          cd binwrap3
+         # Not importing .cvswrappers tests whether the client is really
+         # letting the server know "honestly" whether the file is binary,
+         # rather than just letting the server see the .cvswrappers file.
+          dotest binwrap3-2a \
+"${testcvs} import -m . -I .cvswrappers binwrap3 tag1 tag2" \
+"[NI] ${DOTSTAR}"
+
+         # OK, now test "cvs add".
+          cd ..
+         rm -r binwrap3
+          dotest binwrap3-2b "${testcvs} co binwrap3" "${DOTSTAR}"
+          cd binwrap3
+         cd sub2
+         echo "*.newbin -k 'b'" > .cvswrappers
+         echo .cvswrappers >.cvsignore
+         echo .cvsignore >>.cvsignore
+         touch file1.newbin file1.txt
+         dotest binwrap3-2c "${testcvs} add file1.newbin file1.txt" \
+"${SPROG} add: scheduling file .file1\.newbin. for addition
+${SPROG} add: scheduling file .file1\.txt. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest binwrap3-2d "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/binwrap3/sub2/file1\.newbin,v  <--  file1\.newbin
+initial revision: 1\.1
+$CVSROOT_DIRNAME/binwrap3/sub2/file1\.txt,v  <--  file1\.txt
+initial revision: 1\.1"
+         cd ..
+
+          # Now check out the module and see which files are binary.
+          cd ..
+         rm -r binwrap3
+          dotest binwrap3-3 "${testcvs} co binwrap3" "${DOTSTAR}"
+          cd binwrap3
+
+          # Running "cvs status" and matching output is too
+          # error-prone, too likely to falsely fail.  Instead, we'll
+          # just grep the Entries lines:
+
+          dotest binwrap3-top1 "grep foo-b.c0 ./CVS/Entries" \
+                 "/foo-b.c0/1.1.1.1/[A-Za-z0-9         :]*/-kb/"
+
+          dotest binwrap3-top2 "grep foo-b.sb ./CVS/Entries" \
+                 "/foo-b.sb/1.1.1.1/[A-Za-z0-9         :]*/-kb/"
+
+          dotest binwrap3-top3 "grep foo-t.c1 ./CVS/Entries" \
+                 "/foo-t.c1/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-top4 "grep foo-t.st ./CVS/Entries" \
+                 "/foo-t.st/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-top5 "grep foo-t.sb ./CVS/Entries" \
+                 "/foo-t.sb/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-top6 "grep bar-t.c0 ./CVS/Entries" \
+                 "/bar-t.c0/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub1-1 "grep foo-b.c1 sub1/CVS/Entries" \
+                 "/foo-b.c1/1.1.1.1/[A-Za-z0-9         :]*/-kb/"
+
+          dotest binwrap3-sub1-2 "grep foo-b.sb sub1/CVS/Entries" \
+                 "/foo-b.sb/1.1.1.1/[A-Za-z0-9         :]*/-kb/"
+
+          dotest binwrap3-sub1-3 "grep foo-t.c0 sub1/CVS/Entries" \
+                 "/foo-t.c0/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub1-4 "grep foo-t.st sub1/CVS/Entries" \
+                 "/foo-t.st/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub1-5 "grep foo-t.sb sub1/CVS/Entries" \
+                 "/foo-t.sb/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub1-6 "grep bar-t.c1 sub1/CVS/Entries" \
+                 "/bar-t.c1/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub2-1 "grep foo-b.sb sub2/CVS/Entries" \
+                 "/foo-b.sb/1.1.1.1/[A-Za-z0-9         :]*/-kb/"
+
+          dotest binwrap3-sub2-2 "grep foo-b.st sub2/CVS/Entries" \
+                 "/foo-b.st/1.1.1.1/[A-Za-z0-9         :]*/-kb/"
+
+          dotest binwrap3-sub2-3 "grep foo-t.c0 sub2/CVS/Entries" \
+                 "/foo-t.c0/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub2-4 "grep foo-t.c1 sub2/CVS/Entries" \
+                 "/foo-t.c1/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub2-5 "grep foo-t.c2 sub2/CVS/Entries" \
+                 "/foo-t.c2/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub2-6 "grep foo-t.c3 sub2/CVS/Entries" \
+                 "/foo-t.c3/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub2-7 "grep foo-t.sb sub2/CVS/Entries" \
+                 "/foo-t.sb/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-sub2-8 "grep bar-t.st sub2/CVS/Entries" \
+                 "/bar-t.st/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-subsub1 "grep foo-b.c3 sub2/subsub/CVS/Entries" \
+                 "/foo-b.c3/1.1.1.1/[A-Za-z0-9         :]*/-kb/"
+
+          dotest binwrap3-subsub2 "grep foo-b.sb sub2/subsub/CVS/Entries" \
+                 "/foo-b.sb/1.1.1.1/[A-Za-z0-9         :]*/-kb/"
+
+          dotest binwrap3-subsub3 "grep foo-t.c0 sub2/subsub/CVS/Entries" \
+                 "/foo-t.c0/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-subsub4 "grep foo-t.c1 sub2/subsub/CVS/Entries" \
+                 "/foo-t.c1/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-subsub5 "grep foo-t.c2 sub2/subsub/CVS/Entries" \
+                 "/foo-t.c2/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-subsub6 "grep foo-t.st sub2/subsub/CVS/Entries" \
+                 "/foo-t.st/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-subsub7 "grep foo-t.sb sub2/subsub/CVS/Entries" \
+                 "/foo-t.sb/1.1.1.1/[A-Za-z0-9         :]*//"
+
+          dotest binwrap3-subsub8 "grep bar-t.c3 sub2/subsub/CVS/Entries" \
+                 "/bar-t.c3/1.1.1.1/[A-Za-z0-9         :]*//"
+
+         dotest binwrap3-sub2-add1 "grep file1.newbin sub2/CVS/Entries" \
+           "/file1.newbin/1.1/[A-Za-z0-9       :]*/-kb/"
+         dotest binwrap3-sub2-add2 "grep file1.txt sub2/CVS/Entries" \
+           "/file1.txt/1.1/[A-Za-z0-9  :]*//"
+
+          # Restore and clean up
+         dokeep
+          cd ..
+         rm -r binwrap3 CVSROOT
+         cd ..
+         rm -r wnt
+         modify_repo rm -rf $CVSROOT_DIRNAME/binwrap3
+          CVSWRAPPERS=${CVSWRAPPERS_save}
+          ;;
+
+
+
+       mwrap)
+         # Tests of various wrappers features:
+         # -m 'COPY' and cvs update: mwrap
+         # -m 'COPY' and joining: mcopy
+         # -k: binwrap, binwrap2
+         # -t/-f: hasn't been written yet.
+         # 
+         # Tests of different ways of specifying wrappers:
+         # CVSROOT/cvswrappers: mwrap
+         # -W: binwrap, binwrap2
+         # .cvswrappers in working directory, local: mcopy
+         # CVSROOT/cvswrappers, .cvswrappers remote: binwrap3
+         # CVSWRAPPERS environment variable: mcopy
+
+         # This test is similar to binfiles-con1; -m 'COPY' specifies
+         # non-mergeableness the same way that -kb does.
+
+         # On Windows, we can't check out CVSROOT, because the case
+         # insensitivity means that this conflicts with cvsroot.
+         mkdir wnt
+         cd wnt
+
+         dotest mwrap-c1 "${testcvs} -q co CVSROOT" "U CVSROOT${DOTSTAR}"
+         cd CVSROOT
+         echo "* -m 'COPY'" >>cvswrappers
+         dotest mwrap-c2 "${testcvs} -q ci -m wrapper-mod" \
+"$CVSROOT_DIRNAME/CVSROOT/cvswrappers,v  <--  cvswrappers
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ..
+         mkdir m1; cd m1
+         dotest mwrap-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest mwrap-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         touch aa
+         dotest mwrap-3 "${testcvs} add aa" \
+"${SPROG} add: scheduling file .aa. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest mwrap-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  aa
+initial revision: 1\.1"
+         cd ../..
+         mkdir m2; cd m2
+         dotest mwrap-5 "${testcvs} -q co first-dir" "U first-dir/aa"
+         cd first-dir
+         echo "changed in m2" >aa
+         dotest mwrap-6 "${testcvs} -q ci -m m2-mod" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  aa
+new revision: 1\.2; previous revision: 1\.1"
+         cd ../..
+         cd m1/first-dir
+         echo "changed in m1" >aa
+           dotest mwrap-7 "$testcvs -nq update" \
+"$SPROG update: nonmergeable file needs merge
+$SPROG update: revision 1\.2 from repository is now in aa
+$SPROG update: file from working directory is now in \.#aa\.1\.1
+C aa"
+         dotest mwrap-8 "$testcvs -q update" \
+"$SPROG update: nonmergeable file needs merge
+$SPROG update: revision 1\.2 from repository is now in aa
+$SPROG update: file from working directory is now in \.#aa\.1\.1
+C aa"
+         dotest mwrap-9 "cat aa" "changed in m2"
+         dotest mwrap-10 "cat .#aa.1.1" "changed in m1"
+
+         dokeep
+         restore_adm
+         cd ../..
+         rm -r CVSROOT
+         rm -r m1 m2
+         cd ..
+         rm -r wnt
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       info)
+         # Administrative file tests.
+         # Here is a list of where each administrative file is tested:
+         # loginfo: info
+         # modules: modules, modules2, modules3
+         # cvsignore: ignore
+         # verifymsg: info
+         # cvswrappers: mwrap
+         # taginfo: taginfo
+         # posttag: posttag
+         # postadmin: admin
+         # postwatch: devcom3
+         # config: config
+         # config2: MinCompressionLevel and MaxCompressionLevel in config
+
+         # On Windows, we can't check out CVSROOT, because the case
+         # insensitivity means that this conflicts with cvsroot.
+         mkdir wnt
+         cd wnt
+
+         dotest info-1 "$testcvs -q co CVSROOT" "U CVSROOT${DOTSTAR}"
+         cd CVSROOT
+         dotest info-2 "$testcvs -Q tag info-start"
+         sed -e's/%p/ALL/' <loginfo >tmploginfo
+         mv tmploginfo loginfo
+         echo "ALL sh -c \"echo 
x\${=MYENV}\${=OTHER}y\${=ZEE}=\$USER=\$COMMITID=\$SESSIONID=\$CVSROOT= 
>>$TESTDIR/testlog; cat >/dev/null\"" >> loginfo
+          # The following cases test the format string substitution
+          echo "ALL echo %{} >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %x >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo % >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %{sxVv} >>$TESTDIR/testlog2; cat >/dev/null" >> 
loginfo
+          echo "ALL echo %{v} >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %s %s >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %{V}AX >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "first-dir echo %sux >>$TESTDIR/testlog2; cat >/dev/null" \
+            >> loginfo
+         sed -e's/^UseNewInfoFmtStrings=yes$/#&/' <config >tmpconfig
+         mv tmpconfig config
+
+         # Might be nice to move this to crerepos tests; it should
+         # work to create a loginfo file if you didn't create one
+         # with "cvs init".
+         : dotest info-2 "$testcvs add loginfo" \
+"$SPROG add: scheduling file \`loginfo' for addition
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+
+         dotest_fail info-3 "$testcvs -q ci -m new-loginfo" \
+"$TESTDIR/cvsroot/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$CVSROOT_DIRNAME/CVSROOT/loginfo,v  <--  loginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database
+$SPROG commit: loginfo:[0-9]*: no such user variable \${=MYENV}
+$SPROG \[commit aborted\]: Unknown format character in info file ('').
+Info files are the hook files, verifymsg, taginfo, commitinfo, etc\."
+         cd ..
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest info-5 "$testcvs -q co first-dir" ''
+         cd first-dir
+         touch file1
+         dotest info-6 "$testcvs add file1" \
+"$SPROG add: scheduling file \`file1' for addition
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+         echo "cvs -s OTHER=not-this -s MYENV=env-" >>$HOME/.cvsrc
+         dotest info-6b "$testcvs -q -s OTHER=value ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$SPROG commit: loginfo:[0-9]*: no such user variable \${=ZEE}
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary .1. in
+all info files after each .%. which doesn.t represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\." \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary .1. in
+all info files after each .%. which doesn.t represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\.
+$SPROG commit: loginfo:[0-9]*: no such user variable \${=ZEE}"
+         echo line0 >>file1
+         dotest info-6c "$testcvs -q -sOTHER=foo ci -m mod-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1
+$SPROG commit: loginfo:[0-9]*: no such user variable \${=ZEE}
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary .1. in
+all info files after each .%. which doesn.t represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\." \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary .1. in
+all info files after each .%. which doesn.t represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\.
+$SPROG commit: loginfo:[0-9]*: no such user variable \${=ZEE}"
+         echo line1 >>file1
+         dotest info-7 "${testcvs} -q -s OTHER=value -s ZEE=z ci -m mod-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary .1. in
+all info files after each .%. which doesn.t represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\."
+
+         cd ..
+         dotest info-9 "cat $TESTDIR/testlog" \
+"xenv-valueyz=${username}=${commitid}=${commitid}=${CVSROOT_DIRNAME}="
+          dotest info-10 "cat $TESTDIR/testlog2" \
+'first-dir
+first-dir
+first-dir
+first-dir file1,,NONE,1.1
+first-dir 1.1
+first-dir file1 %s
+first-dir NONEAX
+first-dir file1ux
+first-dir
+first-dir
+first-dir
+first-dir file1,,1.1,1.2
+first-dir 1.2
+first-dir file1 %s
+first-dir 1.1AX
+first-dir file1ux
+first-dir
+first-dir
+first-dir
+first-dir file1,,1.2,1.3
+first-dir 1.3
+first-dir file1 %s
+first-dir 1.2AX
+first-dir file1ux'
+
+         # and make sure adding a '1' in the format strings really does ensure
+         # ensure backwards compatibility.
+         #
+         # these tests are identical to the above except for the loginfo setup
+         # and the project name
+         cd CVSROOT
+         dotest info-setup-intfmt-1 "$testcvs -q up -prinfo-start config 
>config"
+         dotest info-setup-intfmt-2 "$testcvs -q up -prinfo-start loginfo 
>loginfo"
+         sed -e's/%p/ALL/' <loginfo >tmploginfo
+         mv tmploginfo loginfo
+         echo "ALL sh -c \"echo 
x\${=MYENV}\${=OTHER}y\${=ZEE}=\$USER=\$CVSROOT= >>$TESTDIR/testlog; cat 
>/dev/null\"" >> loginfo
+          # The following cases test the format string substitution
+          echo "ALL echo %1{} >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %1x >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %1 >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %1{sxVv} >>$TESTDIR/testlog2; cat >/dev/null" >> 
loginfo
+          echo "ALL echo %1{v} >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %1s %%s >>$TESTDIR/testlog2; cat >/dev/null" >> 
loginfo
+          echo "ALL echo %1{V}AX >>$TESTDIR/testlog2; cat >/dev/null" >> 
loginfo
+          echo "third-dir echo %1sux >>$TESTDIR/testlog2; cat >/dev/null" \
+            >> loginfo
+
+         dotest info-setup-intfmt-2 "${testcvs} -q -s ZEE=garbage ci -m 
nuke-admin-for-info-intfmt" \
+"$TESTDIR/cvsroot/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$TESTDIR/cvsroot/CVSROOT/loginfo,v  <--  loginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary .1. in
+all info files after each .%. which doesn.t represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\."
+         cd ..
+
+         # delete the logs now so the results look more like the last tests
+         # (they won't include the config file update)
+         rm ${TESTDIR}/testlog ${TESTDIR}/testlog2
+
+         modify_repo mkdir $CVSROOT_DIRNAME/third-dir
+         dotest info-intfmt-5 "${testcvs} -q co third-dir" ''
+         cd third-dir
+         touch file1
+         dotest info-intfmt-6 "${testcvs} add file1" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         echo "cvs -s OTHER=not-this -s MYENV=env-" >>$HOME/.cvsrc
+         dotest info-intfmt-6b "${testcvs} -q -s OTHER=value ci -m add-it" \
+"${TESTDIR}/cvsroot/third-dir/file1,v  <--  file1
+initial revision: 1\.1
+${SPROG} commit: loginfo:[0-9]*: no such user variable \${=ZEE}
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\."
+         echo line0 >>file1
+         dotest info-intfmt-6c "${testcvs} -q -sOTHER=foo ci -m mod-it" \
+"${TESTDIR}/cvsroot/third-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1
+${SPROG} commit: loginfo:[0-9]*: no such user variable \${=ZEE}
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\."
+         echo line1 >>file1
+         dotest info-intfmt-7 "${testcvs} -q -s OTHER=value -s ZEE=z ci -m 
mod-it" \
+"${TESTDIR}/cvsroot/third-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\.
+${SPROG} commit: Using deprecated info format strings\.  Convert your scripts 
to use
+the new argument format and remove '1's from your info file format strings\."
+
+         cd ..
+         dotest info-intfmt-9 "cat $TESTDIR/testlog" 
"xenv-valueyz=${username}=${TESTDIR}/cvsroot="
+          dotest info-intfmt-10 "cat $TESTDIR/testlog2" \
+'third-dir
+third-dir
+third-dir
+third-dir file1,,NONE,1.1
+third-dir 1.1
+third-dir file1 %s
+third-dir NONEAX
+third-dir file1ux
+third-dir
+third-dir
+third-dir
+third-dir file1,,1.1,1.2
+third-dir 1.2
+third-dir file1 %s
+third-dir 1.1AX
+third-dir file1ux
+third-dir
+third-dir
+third-dir
+third-dir file1,,1.2,1.3
+third-dir 1.3
+third-dir file1 %s
+third-dir 1.2AX
+third-dir file1ux'
+
+         rm ${TESTDIR}/testlog ${TESTDIR}/testlog2
+
+         # test the new format strings too
+         cd CVSROOT
+         dotest info-setup-newfmt-1 "$testcvs -q up -prinfo-start loginfo 
>loginfo"
+         echo "ALL sh -c \"echo 
x\${=MYENV}\${=OTHER}y\${=ZEE}=\$USER=\$CVSROOT= >>$TESTDIR/testlog; cat 
>/dev/null\" %{sVv}" >> loginfo
+          # The following cases test the format string substitution
+          echo "ALL echo %p \"%{sTVv}\" >>$TESTDIR/testlog2; cat >/dev/null" 
>> loginfo
+          echo "ALL echo %{v} >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %s >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "ALL echo %{V}AX >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+          echo "second-dir echo %sux >>$TESTDIR/testlog2; cat >/dev/null" >> 
loginfo
+         dotest info-setup-newfmt-2 "$testcvs -q -s ZEE=garbage ci -m 
nuke-admin-for-info-newfmt" \
+"${CVSROOT_DIRNAME}/CVSROOT/loginfo,v  <--  loginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+${SPROG} commit: Rebuilding administrative file database"
+         cd ..
+
+         # delete the logs now so the results look more like the last tests
+         # (they won't include the config file update)
+         rm ${TESTDIR}/testlog ${TESTDIR}/testlog2
+
+         modify_repo mkdir $CVSROOT_DIRNAME/fourth-dir
+         dotest info-newfmt-1 "${testcvs} -q co fourth-dir" ''
+         cd fourth-dir
+         touch file1
+         dotest info-newfmt-2 "${testcvs} add file1" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         echo "cvs -s OTHER=not-this -s MYENV=env-" >>$HOME/.cvsrc
+         dotest info-newfmt-3 "$testcvs -q -s OTHER=value ci -m add-it" \
+"$TESTDIR/cvsroot/fourth-dir/file1,v  <--  file1
+initial revision: 1\.1
+$SPROG commit: loginfo:[0-9]*: no such user variable \${=ZEE}"
+         echo line0 >>file1
+         dotest info-newfmt-4 "$testcvs -q -sOTHER=foo ci -m mod-it" \
+"$TESTDIR/cvsroot/fourth-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1
+$SPROG commit: loginfo:[0-9]*: no such user variable \${=ZEE}"
+         echo line1 >>file1
+         dotest info-newfmt-5 "${testcvs} -q -s OTHER=value -s ZEE=z ci -m 
mod-it" \
+"${TESTDIR}/cvsroot/fourth-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2"
+
+         cd ..
+         dotest info-newfmt-6 "cat $TESTDIR/testlog" \
+"xenv-valueyz=${username}=${TESTDIR}/cvsroot="
+          dotest info-newfmt-7 "cat $TESTDIR/testlog2" \
+'fourth-dir file1  NONE 1\.1
+1\.1
+file1
+NONEAX
+fourth-dir file1  1\.1 1\.2
+1\.2
+file1
+1\.1AX
+fourth-dir file1  1\.2 1\.3
+1\.3
+file1
+1\.2AX'
+
+         # clean up after newfmt tests
+         cd CVSROOT
+         dotest info-cleanup-newfmt-1 "$testcvs -q up -prinfo-start loginfo 
>loginfo"
+         dotest info-cleanup-newfmt-2 "$testcvs -q ci -m nuke-loginfo" \
+"$CVSROOT_DIRNAME/CVSROOT/loginfo,v  <--  loginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         # clean up the logs
+         rm ${TESTDIR}/testlog ${TESTDIR}/testlog2
+
+         # Now test verifymsg
+         cat >${TESTDIR}/vscript <<EOF
+#!${TESTSHELL}
+echo vscript "\$@"
+if sed 1q < \$1 | grep '^BugId:[ ]*[0-9][0-9]*$' > /dev/null; then
+    exit 0
+elif sed 1q < \$1 | grep '^BugId:[ ]*new$' > /dev/null; then
+    echo A new bugid was found. >> \$1
+    exit 0
+else
+    echo "No BugId found."
+    sleep 1
+    exit 1
+fi
+EOF
+         cat >${TESTDIR}/vscript2 <<EOF
+#!${TESTSHELL}
+echo vscript2 "\$@"
+if test -f CVS/Repository; then
+       repo=\`cat CVS/Repository\`
+else
+       repo=\`pwd\`
+fi
+echo \$repo
+if echo "\$repo" |grep yet-another/ >/dev/null 2>&1; then
+       exit 1
+else
+       exit 0
+fi
+EOF
+         # Grumble, grumble, mumble, search for "Cygwin".
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod +x ${TESTDIR}/vscript*"
+         else
+           chmod +x ${TESTDIR}/vscript*
+         fi
+         echo "^first-dir/yet-another\\(/\\|\$\\) ${TESTDIR}/vscript2 %l 
%{sV}" >verifymsg
+         echo "^first-dir\\(/\\|\$\\) ${TESTDIR}/vscript %l %{sV}" >>verifymsg
+         echo "^missing-script\$ ${TESTDIR}/bogus %l" >>verifymsg
+         echo "^missing-var\$ ${TESTDIR}/vscript %l \${=Bogus}" >>verifymsg
+         # first test the directory independant verifymsg
+         dotest info-v1 "${testcvs} -q ci -m add-verification" \
+"$CVSROOT_DIRNAME/CVSROOT/verifymsg,v  <--  verifymsg
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         cd ../first-dir
+         echo line2 >>file1
+         dotest_fail info-v2 "${testcvs} -q ci -m bogus" \
+"vscript $tempname file1 1\.3
+No BugId found\.
+${SPROG} \[commit aborted\]: Message verification failed"
+
+         cat >${TESTDIR}/comment.tmp <<EOF
+BugId: 42
+and many more lines after it
+EOF
+         dotest info-v3 "${testcvs} -q ci -F ${TESTDIR}/comment.tmp" \
+"vscript $tempname file1 1\.3
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.4; previous revision: 1\.3"
+         rm ${TESTDIR}/comment.tmp
+
+         cd ..
+         mkdir another-dir
+         cd another-dir
+         touch file2
+         dotest_fail info-v4 \
+           "${testcvs} import -m bogus first-dir/another x y" \
+"vscript $tempname - Imported sources NONE
+No BugId found\.
+${SPROG} \[import aborted\]: Message verification failed"
+
+         # now verify that directory dependent verifymsgs work
+         dotest info-v5 \
+           "${testcvs} import -m bogus first-dir/yet-another x y" \
+"vscript2 $tempname - Imported sources NONE
+$TESTDIR/wnt/another-dir
+N first-dir/yet-another/file2
+
+No conflicts created by this import" \
+"vscript2 $tempname - Imported sources NONE
+$CVSROOT_DIRNAME/first-dir/yet-another
+N first-dir/yet-another/file2
+
+No conflicts created by this import"
+
+         # FIXCVS
+         #
+         # note that in the local case the error message is the same as
+         # info-v5
+         #
+         # This means that the verifymsg scripts cannot reliably and
+         # consistantly obtain information on which directory is being
+         # committed to.  Thus it is currently useless for them to be
+         # running in every dir.  They should either be run once or
+         # directory information should be passed.
+         if $remote; then
+           dotest_fail info-v6r \
+             "${testcvs} import -m bogus first-dir/yet-another/and-another x 
y" \
+"vscript2 $tempname - Imported sources NONE
+$CVSROOT_DIRNAME/first-dir/yet-another/and-another
+$SPROG \[import aborted\]: Message verification failed"
+         else
+           dotest info-v6 \
+             "${testcvs} import -m bogus first-dir/yet-another/and-another x 
y" \
+"vscript2 $tempname - Imported sources NONE
+$TESTDIR/wnt/another-dir
+N first-dir/yet-another/and-another/file2
+
+No conflicts created by this import"
+         fi
+
+         # check that errors invoking the script cause verification failure
+         #
+         # The second text below occurs on Cygwin, where I assume execvp
+         # does not return to let CVS print the error message when its
+         # argument does not exist.
+         dotest_fail info-v7 "${testcvs} import -m bogus missing-script x y" \
+"${SPROG} import: cannot exec ${TESTDIR}/bogus: No such file or directory
+${SPROG} \[import aborted\]: Message verification failed" \
+"${SPROG} \[import aborted\]: Message verification failed"
+
+         dotest_fail info-v8 "${testcvs} import -m bogus missing-var x y" \
+"${SPROG} import: verifymsg:4: no such user variable \${=Bogus}
+${SPROG} \[import aborted\]: Message verification failed"
+
+         rm file2
+         cd ..
+         rmdir another-dir
+
+         cd CVSROOT
+         echo "RereadLogAfterVerify=always" >>config
+         dotest info-rereadlog-1 "${testcvs} -q ci -m 
add-RereadLogAfterVerify=always" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ../first-dir
+         echo line3 >>file1
+         cat >${TESTDIR}/comment.tmp <<EOF
+BugId: new
+See what happens next.
+EOF
+         dotest info-reread-2 "${testcvs} -q ci -F ${TESTDIR}/comment.tmp" \
+"vscript $tempname file1 1\.4
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.5; previous revision: 1\.4"
+         dotest info-reread-3 "${testcvs} -q log -N -r1.5 file1" "
+.*
+BugId: new
+See what happens next.
+A new bugid was found.
+============================================================================="
+
+         cd ../CVSROOT
+         grep -v "RereadLogAfterVerify" config > config.new
+         mv config.new config
+         echo "RereadLogAfterVerify=stat" >>config
+         dotest info-reread-4 \
+"$testcvs -q ci -m add-RereadLogAfterVerify=stat" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ../first-dir
+         echo line4 >>file1
+         cat >${TESTDIR}/comment.tmp <<EOF
+BugId: new
+See what happens next with stat.
+EOF
+         dotest info-reread-5 "${testcvs} -q ci -F ${TESTDIR}/comment.tmp" \
+"vscript $tempname file1 1\.5
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.6; previous revision: 1\.5"
+         dotest info-reread-6 "${testcvs} -q log -N -r1.6 file1" "
+.*
+BugId: new
+See what happens next with stat.
+A new bugid was found.
+============================================================================="
+
+         cd ../CVSROOT
+         grep -v "RereadLogAfterVerify" config > config.new
+         mv config.new config
+         echo "RereadLogAfterVerify=never" >>config
+         dotest info-reread-7 \
+"$testcvs -q ci -m add-RereadLogAfterVerify=never" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ../first-dir
+         echo line5 >>file1
+         cat >${TESTDIR}/comment.tmp <<EOF
+BugId: new
+See what happens next.
+EOF
+         dotest info-reread-8 "${testcvs} -q ci -F ${TESTDIR}/comment.tmp" \
+"vscript $tempname file1 1\.6
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.7; previous revision: 1\.6"
+         dotest info-reread-6 "${testcvs} -q log -N -r1.7 file1" "
+.*
+BugId: new
+See what happens next.
+============================================================================="
+
+         cd ../CVSROOT
+         dotest info-reread-cleanup-1 "$testcvs -q up -prinfo-start config 
>config"
+         # Append the NULL format string until we remove the deprecation
+         # warning for lack of format strings.
+         echo 'DEFAULT false %n' >verifymsg
+         echo 'DEFAULT true %n' >>verifymsg
+         dotest info-multdef "${testcvs} -q ci -m multdef" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$CVSROOT_DIRNAME/CVSROOT/verifymsg,v  <--  verifymsg
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         cd ../CVSROOT
+         dotest info-reread--cleanup-1 \
+"$testcvs -q up -prinfo-start verifymsg >verifymsg"
+         dotest info-cleanup-verifymsg "$testcvs -q ci -m nuke-verifymsg" \
+"$SPROG commit: Multiple .DEFAULT. lines (1 and 2) in verifymsg file
+$CVSROOT_DIRNAME/CVSROOT/verifymsg,v  <--  verifymsg
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         dokeep
+         rm ${TESTDIR}/vscript*
+         cd ..
+
+         dotest info-cleanup-0 "$testcvs -n release -d CVSROOT" \
+"You have \[0\] altered files in this repository\."
+
+         dotest info-cleanup-1 \
+"echo yes |${testcvs} -q release -d CVSROOT >/dev/null"
+         dotest info-cleanup-2 \
+"echo yes |${testcvs} -q release -d first-dir >/dev/null"
+         dotest info-cleanup-3 \
+"echo yes |${testcvs} -q release -d third-dir >/dev/null"
+         dotest info-cleanup-4 \
+"echo yes |${testcvs} -q release -d fourth-dir >/dev/null"
+
+         dokeep
+         cd ..
+         rm -r wnt
+         rm $HOME/.cvsrc
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/third-dir \
+                            $CVSROOT_DIRNAME/fourth-dir
+         ;;
+
+
+
+       taginfo)
+         # Tests of the CVSROOT/taginfo file.  See the comment at the
+         # "info" tests for a full list of administrative file tests.
+
+         # all the oldfmt stuff can come out once we finish deprecating
+         # the old info file command line format stuff.
+         #
+         # grep the code for SUPPORT_OLD_INFO_FMT_STRINGS and see the stuff
+         # in configure.in about oldinfoformatsupport
+
+         mkdir 1; cd 1
+         dotest taginfo-init-1 "$testcvs -q co CVSROOT" "U CVSROOT/$DOTSTAR"
+         cd CVSROOT
+         dotest taginfo-init-2 "$testcvs -Q tag taginfo-start"
+         cat >$TESTDIR/1/loggit <<EOF
+#!$TESTSHELL
+if test "\$1" = rejectme; then
+  exit 1
+else
+  echo "\$@" >>$TESTDIR/1/taglog
+  exit 0
+fi
+EOF
+         # #^@&!^@ Cygwin.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod +x ${TESTDIR}/1/loggit"
+         else
+           chmod +x ${TESTDIR}/1/loggit
+         fi
+         echo "ALL ${TESTDIR}/1/loggit" >>taginfo
+         sed -e's/^UseNewInfoFmtStrings=yes$/#&/' <config >tmpconfig
+         mv tmpconfig config
+         sed -e's/%p/ALL/' <loginfo >tmploginfo
+         mv tmploginfo loginfo
+         dotest taginfo-2 "${testcvs} -q ci -m check-in-taginfo" \
+"$TESTDIR/cvsroot/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$CVSROOT_DIRNAME/CVSROOT/loginfo,v  <--  loginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$CVSROOT_DIRNAME/CVSROOT/taginfo,v  <--  taginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ..
+
+         # taginfo-3 used to rely on the top-level CVS directory
+         # being created to add "first-dir" to the repository.  Since
+         # that won't happen anymore, we create the directory in the
+         # repository.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest taginfo-3 "$testcvs -q co first-dir"
+
+         cd first-dir
+         echo first >file1
+         dotest taginfo-4 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest taginfo-5 "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary '1' in
+all info files after each '%' which doesn't represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\."
+         dotest taginfo-6 "${testcvs} -q tag tag1" \
+"${SPROG} tag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\.
+T file1"
+         dotest taginfo-7 "${testcvs} -q tag -b br" \
+"${SPROG} tag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\.
+T file1"
+         dotest taginfo-8 "$testcvs -q update -r br"
+         echo add text on branch >>file1
+         dotest taginfo-9 "${testcvs} -q ci -m modify-on-br" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary '1' in
+all info files after each '%' which doesn't represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\."
+         dotest taginfo-10 "${testcvs} -q tag -F -c brtag" \
+"${SPROG} tag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\.
+T file1"
+
+         dotest_fail taginfo-11 "${testcvs} -q tag rejectme" \
+"${SPROG} tag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\.
+${SPROG} tag: Pre-tag check failed
+${SPROG} \[tag aborted\]: correct the above errors first!"
+
+         # When we are using taginfo to allow/disallow, it would be
+         # convenient to be able to use "cvs -n tag" to test whether
+         # the allow/disallow functionality is working as expected.
+         dotest taginfo-12 "${testcvs} -nq tag rejectme" \
+"${SPROG} tag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\.
+T file1"
+
+         # But when taginfo is used for logging, it is a pain for -n
+         # to call taginfo, since taginfo doesn't know whether -n was
+         # specified or not.
+         dotest taginfo-13 "${testcvs} -nq tag would-be-tag" \
+"${SPROG} tag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\.
+T file1"
+
+         # Deleting: the cases are basically either the tag existed,
+         # or it didn't exist.
+         dotest taginfo-14 "${testcvs} -q tag -d tag1" \
+"${SPROG} tag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\.
+D file1"
+         dotest taginfo-15 "${testcvs} -q tag -d tag1" \
+"${SPROG} tag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\."
+
+         # Likewise with rtag.
+         dotest taginfo-16 "${testcvs} -q rtag tag1 first-dir" \
+"${SPROG} rtag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\."
+         dotest taginfo-17 "${testcvs} -q rtag -d tag1 first-dir" \
+"${SPROG} rtag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\."
+         dotest taginfo-18 "${testcvs} -q rtag -d tag1 first-dir" \
+"${SPROG} rtag: warning: taginfo line contains no format strings:
+    \"${TESTDIR}/1/loggit\"
+Filling in old defaults ('%t %o %p %{sv}'), but please be aware that this
+usage is deprecated\."
+
+         # The "br" example should be passing 1.1.2 or 1.1.0.2.
+         # But it turns out that is very hard to implement, since
+         # check_fileproc doesn't know what branch number it will
+         # get.  Probably the whole thing should be re-architected
+         # so that taginfo only allows/denies tagging, and a new
+         # hook, which is done from tag_fileproc, does logging.
+         # That would solve this, some more subtle races, and also
+         # the fact that it is nice for users to run "-n tag foo" to
+         # see whether a tag would be allowed.  Failing that,
+         # I suppose passing "1.1.branch" or "branch" for "br"
+         # would be an improvement.
+         dotest taginfo-examine-1 "cat ${TESTDIR}/1/taglog" \
+"tag1 add first-dir file1 1\.1
+br add first-dir file1 1\.1
+brtag mov first-dir file1 1\.1\.2\.1
+tag1 del first-dir file1 1\.1
+tag1 del first-dir
+tag1 add first-dir file1 1\.1
+tag1 del first-dir file1 1\.1
+tag1 del first-dir"
+
+         # now that we've tested the default operation, try a new
+         # style fmt string.
+         rm $TESTDIR/1/taglog
+         cd ..
+         cd CVSROOT
+         dotest taginfo-newfmt-init-1 \
+"$testcvs -q up -prtaginfo-start taginfo >taginfo"
+         echo "ALL $TESTDIR/1/loggit %r %t %o %b %p %{sTVv}" >>taginfo
+         dotest taginfo-newfmt-init-2 "$testcvs -q ci -m check-in-taginfo" \
+"$TESTDIR/cvsroot/CVSROOT/taginfo,v  <--  taginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"  \
+"$TESTDIR/cvsroot/CVSROOT/taginfo,v  <--  taginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary '1' in
+all info files after each '%' which doesn't represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\."
+
+         cat >${TESTDIR}/1/loggit <<EOF
+#!${TESTSHELL}
+if test "\$1" = rejectme; then
+  exit 1
+else
+  while test "\$#" -gt 0; do
+    echo "\$1" >>${TESTDIR}/1/taglog
+    shift
+  done
+  exit 0
+fi
+EOF
+
+         cd ..
+         cd first-dir
+         dotest taginfo-newfmt-2 "${testcvs} -q update -A" "U file1"
+         echo "bull pucky" >'file 2'
+         dotest taginfo-newfmt-2b "${testcvs} add 'file 2'" \
+"${SPROG} add: scheduling file .file 2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest taginfo-newfmt-2c "$testcvs -q ci -m add-it" \
+"$TESTDIR/cvsroot/first-dir/file 2,v  <--  file 2
+initial revision: 1\.1" \
+"$TESTDIR/cvsroot/first-dir/file 2,v  <--  file 2
+initial revision: 1\.1
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary '1' in
+all info files after each '%' which doesn't represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\."
+
+         dotest taginfo-newfmt-3 "${testcvs} -q tag tag1" \
+"T file 2
+T file1"
+         dotest taginfo-newfmt-4 "${testcvs} -q tag tag3" \
+"T file 2
+T file1"
+         dotest taginfo-newfmt-5 "$testcvs -q tag -rtag1 tag4" \
+"T file 2
+T file1"
+
+         dotest taginfo-newfmt-examine-1 "cat ${TESTDIR}/1/taglog" \
+"$TESTDIR/cvsroot
+tag1
+add
+N
+first-dir
+file 2
+
+NONE
+1\.1
+file1
+
+NONE
+1\.1
+$TESTDIR/cvsroot
+tag3
+add
+N
+first-dir
+file 2
+
+NONE
+1\.1
+file1
+
+NONE
+1\.1
+$TESTDIR/cvsroot
+tag4
+add
+N
+first-dir
+file 2
+tag1
+NONE
+1\.1
+file1
+tag1
+NONE
+1\.1"
+
+         # now update to use the new format strings (really, disable support
+         # of the old format) and run the whole gamut of tests again.
+         rm ${TESTDIR}/1/taglog
+         cd ..
+         cd CVSROOT
+         cat >${TESTDIR}/1/loggit <<EOF
+#!${TESTSHELL}
+if test "\$1" = rejectme; then
+  exit 1
+else
+  echo "\$@" >>${TESTDIR}/1/taglog
+  exit 0
+fi
+EOF
+         dotest taginfo-newfmt-init-7 \
+"$testcvs -q up -prtaginfo-start taginfo >taginfo"
+         echo "ALL ${TESTDIR}/1/loggit %{t} %b %{o} %p %{sTVv}" >>taginfo
+         echo "UseNewInfoFmtStrings=yes" >>config
+         dotest taginfo-newfmt-7 "$testcvs -q ci -m check-in-taginfo" \
+"$TESTDIR/cvsroot/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$TESTDIR/cvsroot/CVSROOT/taginfo,v  <--  taginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database" \
+"$TESTDIR/cvsroot/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$TESTDIR/cvsroot/CVSROOT/taginfo,v  <--  taginfo
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database
+$SPROG commit: warning:  Set to use deprecated info format strings\.  Establish
+compatibility with the new info file format strings (add a temporary '1' in
+all info files after each '%' which doesn't represent a literal percent)
+and set UseNewInfoFmtStrings=yes in CVSROOT/config\.  After that, convert
+individual command lines and scripts to handle the new format at your
+leisure\."
+
+         cd ../first-dir
+         dotest taginfo-newfmt-8 "${testcvs} -q tag tag1" ""
+         mkdir sdir
+         dotest taginfo-newfmt-8b "${testcvs} -q add sdir" \
+"Directory ${TESTDIR}/cvsroot/first-dir/sdir added to the repository"
+         touch sdir/file3
+         dotest taginfo-newfmt-8c "${testcvs} -q add sdir/file3" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest taginfo-newfmt-8d "${testcvs} -q ci -m added-sdir" \
+"${TESTDIR}/cvsroot/first-dir/sdir/file3,v  <--  sdir/file3
+initial revision: 1\.1"
+         dotest taginfo-newfmt-9 "${testcvs} -q tag -b br" \
+"T file 2
+W file1 : br already exists on branch 1\.1\.2\.1 : NOT MOVING tag to branch 
1\.1\.0\.4
+T sdir/file3"
+         dotest taginfo-newfmt-10 "${testcvs} -q update -r br" "U file1"
+         echo add more text on branch >>file1
+         dotest taginfo-newfmt-11 "${testcvs} -q ci -m modify-on-br" \
+"${TESTDIR}/cvsroot/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1"
+         dotest taginfo-newfmt-12 "${testcvs} -q tag -F -c brtag" \
+"T file 2
+T file1
+T sdir/file3"
+
+         # we are being called once for each directory.  I'm not sure
+         # I like this, but I'm also not sure how hard it would be to change,
+         # It seems like it would be more trouble than it is really worth
+         # to let a partial tag go through...
+         dotest_fail taginfo-newfmt-13 "${testcvs} -q tag rejectme" \
+"${SPROG} tag: Pre-tag check failed
+${SPROG} tag: Pre-tag check failed
+${SPROG} \[tag aborted\]: correct the above errors first!"
+
+         # When we are using taginfo to allow/disallow, it would be
+         # convenient to be able to use "cvs -n tag" to test whether
+         # the allow/disallow functionality is working as expected.
+         # see the comment before taginfo-newfmt-15 for notes on
+         # pretag and posttag proc
+         dotest taginfo-newfmt-14 "${testcvs} -nq tag rejectme" \
+"T file 2
+T file1
+T sdir/file3"
+
+         # But when taginfo is used for logging, it is a pain for -n
+         # to call taginfo, since taginfo doesn't know whether -n was
+         # specified or not. (this could be fixed pretty easily now
+         # with a new fmt string.  i suppose it would be better to
+         # have a pretag proc and a posttag proc, though.)
+         dotest taginfo-newfmt-15 "${testcvs} -nq tag would-be-tag" \
+"T file 2
+T file1
+T sdir/file3"
+
+         # Deleting: the cases are basically either the tag existed,
+         # or it didn't exist.
+         dotest taginfo-newfmt-16 "${testcvs} -q tag -d tag1" \
+"D file 2
+D file1"
+         dotest taginfo-newfmt-17 "${testcvs} -q tag -d tag1" ""
+
+         # Likewise with rtag.
+         dotest taginfo-newfmt-18 "${testcvs} -q rtag tag1 first-dir" ""
+         dotest taginfo-newfmt-19 "${testcvs} -q rtag -d tag1 first-dir" ""
+         dotest taginfo-newfmt-20 "${testcvs} -q rtag -d tag1 first-dir" ""
+
+         # The "br" example should be passing 1.1.2 or 1.1.0.2.
+         # But it turns out that is very hard to implement, since
+         # check_fileproc doesn't know what branch number it will
+         # get.  Probably the whole thing should be re-architected
+         # so that taginfo only allows/denies tagging, and a new
+         # hook, which is done from tag_fileproc, does logging.
+         # That would solve this, some more subtle races, and also
+         # the fact that it is nice for users to run "-n tag foo" to
+         # see whether a tag would be allowed.  Failing that,
+         # I suppose passing "1.1.branch" or "branch" for "br"
+         # would be an improvement.
+         dotest taginfo-newfmt-examine-2 "cat ${TESTDIR}/1/taglog" \
+"tag1 N add first-dir
+br T add first-dir file 2  NONE 1\.1
+br T add first-dir/sdir file3  NONE 1\.1
+brtag N mov first-dir file 2 br NONE 1\.1 file1 br 1\.1\.2\.1 1\.1\.2\.2
+brtag N mov first-dir/sdir file3 br NONE 1\.1
+tag1 ? del first-dir file 2 br 1\.1 1\.1 file1 br 1\.1 1\.1
+tag1 ? del first-dir/sdir
+tag1 ? del first-dir
+tag1 ? del first-dir/sdir
+tag1 N add first-dir file 2  NONE 1\.1 file1  NONE 1\.1
+tag1 N add first-dir/sdir file3  NONE 1\.1
+tag1 ? del first-dir file 2  1\.1 1\.1 file1  1\.1 1\.1
+tag1 ? del first-dir/sdir file3  1\.1 1\.1
+tag1 ? del first-dir
+tag1 ? del first-dir/sdir"
+
+         dokeep
+         restore_adm
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       posttag)
+         # Tests of the CVSROOT/taginfo file.  See the comment at the
+         # "info" tests for a full list of administrative file tests.
+
+         mkdir 1; cd 1
+
+         dotest posttag-init-1 "$testcvs -q co CVSROOT" "U CVSROOT/$DOTSTAR"
+
+         # now that we've tested the default operation, try a new
+         # style fmt string.
+         cd CVSROOT
+         echo "ALL $TESTDIR/1/loggit %r %t %o %b %p %{sVv}" >posttag
+         dotest posttag-init-2 "$testcvs -q ci -m check-in-taginfo" \
+"$TESTDIR/cvsroot/CVSROOT/posttag,v  <--  posttag
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ..
+
+         cat >$TESTDIR/1/loggit <<EOF
+#!$TESTSHELL
+if test "\$1" = rejectme; then
+    error=:
+else
+    error=false
+fi
+
+while [ -n "\$1" ]; do
+    echo "\$1" >>$TESTDIR/1/taglog
+    shift
+done
+
+if \$error; then
+  exit 1
+fi
+exit 0
+EOF
+         # #^@&!^@ Cygwin.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod +x $TESTDIR/1/loggit"
+         else
+           chmod +x $TESTDIR/1/loggit
+         fi
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest posttag-init-3 "$testcvs -q co first-dir"
+
+         cd first-dir
+         echo first >file1
+         echo "bull pucky" >'file 2'
+         dotest posttag-init-4 "$testcvs add file1 'file 2'" \
+"$SPROG add: scheduling file \`file1' for addition
+$SPROG add: scheduling file \`file 2' for addition
+$SPROG add: use \`$SPROG commit' to add these files permanently"
+         dotest posttag-init-5 "$testcvs -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/file 2,v  <--  file 2
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+         dotest posttag-1 "$testcvs -q tag tag1" \
+"T file 2
+T file1"
+         dotest posttag-2 "$testcvs -q tag tag3" \
+"T file 2
+T file1"
+
+         dotest posttag-3 "$testcvs -q tag rejectme" \
+"T file 2
+T file1"
+
+         dotest posttag-4 "$testcvs -q tag -d rejectme" \
+"D file 2
+D file1"
+
+         dotest posttag-examine-1 "cat $TESTDIR/1/taglog" \
+"$TESTDIR/cvsroot
+tag1
+add
+N
+first-dir
+file 2
+NONE
+1\.1
+file1
+NONE
+1\.1
+$TESTDIR/cvsroot
+tag3
+add
+N
+first-dir
+file 2
+NONE
+1\.1
+file1
+NONE
+1\.1
+$TESTDIR/cvsroot
+rejectme
+add
+N
+first-dir
+file 2
+NONE
+1.1
+file1
+NONE
+1.1
+$TESTDIR/cvsroot
+rejectme
+del
+?
+first-dir
+file 2
+1.1
+1.1
+file1
+1.1
+1.1"
+
+         dokeep
+         cd ../..
+         restore_adm
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       config)
+         # Tests of the CVSROOT/config file.  See the comment at the
+         # "info" tests for a full list of administrative file tests.
+
+         # See note in keywordexpand about config errors from a proxied
+         # primary.
+         if $noredirect; then
+           notnoredirect config
+           continue
+         fi
+
+         # On Windows, we can't check out CVSROOT, because the case
+         # insensitivity means that this conflicts with cvsroot.
+         mkdir wnt
+         cd wnt
+
+         dotest config-init-1 "$testcvs -q co CVSROOT" "U CVSROOT/$DOTSTAR"
+         cd CVSROOT
+         dotest config-init-2 "$testcvs -Q tag config-start"
+         echo 'bogus line' >>config
+         dotest config-3 "$testcvs -q ci -m change-to-bogus-line" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         dotest config-3a "$testcvs -Q update -jHEAD -jconfig-start" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: 
syntax error: missing \`=' between keyword and value
+RCS file: $CVSROOT_DIRNAME/CVSROOT/config,v
+retrieving revision 1.[0-9]*
+retrieving revision 1.[0-9]*
+Merging differences between 1.[0-9]* and 1.[0-9]* into config"
+         echo 'BogusOption=yes' >>config
+         if $proxy; then
+           dotest config-4p "$testcvs -q ci -m change-to-bogus-opt" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[99\]: syntax 
error: missing \`=' between keyword and value
+$SPROG [a-z]*: $CVSROOT_DIRNAME/CVSROOT/config \[99\]: syntax error: missing 
\`=' between keyword and value
+$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         else
+           dotest config-4 "$testcvs -q ci -m change-to-bogus-opt" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[1-9][0-9]*\]: 
syntax error: missing \`=' between keyword and value
+$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         fi
+
+         if $proxy; then
+           : # FIXME: don't try in proxy mode
+         else
+           # Now test the HistoryLogPath and HistorySearchPath options.
+           mkdir $TESTDIR/historylogs
+           echo >config \
+                'HistoryLogPath=$CVSROOT/../historylogs/%Y-%m-%d-%H-%M-%S'
+           echo 'HistorySearchPath=$CVSROOT/../historylogs/*' >>config
+
+           # The warning is left over from the previous test.
+           dotest config-5 "$testcvs -q ci -m set-HistoryLogPath" \
+"$SPROG [a-z]*: $CVSROOT_DIRNAME/CVSROOT/config \[98\]: unrecognized keyword 
\`BogusOption'
+$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+           echo '# noop' >> config
+           dotest config-6 "$testcvs -q ci -mlog-commit" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+           sleep 1
+           echo '# noop' >> config
+           dotest config-7 "$testcvs -q ci -mlog-commit" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+           # The log entry was intentionally split across multiple files.
+           dotest config-8 "ls -l $TESTDIR/historylogs/*" \
+"-rw-rw-r--.*$TESTDIR/historylogs/2[0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]-[0-2][0-9]-[0-5][0-9]-[0-5][0-9]
+-rw-rw-r--.*$TESTDIR/historylogs/2[0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]-[0-2][0-9]-[0-5][0-9]-[0-5][0-9]"
+
+           # Should still see both commits.
+           if $remote; then
+             dotest config-9r "$testcvs history -ea" \
+"M [0-9-]* [0-9:]* ${PLUS}0000 $username 1\.[0-9]* config CVSROOT == <remote>
+M [0-9-]* [0-9:]* ${PLUS}0000 $username 1\.[0-9]* config CVSROOT == <remote>"
+           else
+             dotest config-9 "$testcvs history -ea" \
+"M [0-9-]* [0-9:]* ${PLUS}0000 $username 1\.[0-9]* config CVSROOT == 
$TESTDIR/wnt/CVSROOT
+M [0-9-]* [0-9:]* ${PLUS}0000 $username 1\.[0-9]* config CVSROOT == 
$TESTDIR/wnt/CVSROOT"
+           fi
+
+           # Remove this now to see what kind of error messages we get.
+           rm -r $TESTDIR/historylogs
+         fi
+
+         dokeep
+         restore_adm
+         cd ../..
+         rm -r wnt
+         ;;
+
+
+
+       config2)
+         # Tests of the CVSROOT/config file.  See the comment at the
+         # "info" tests for a full list of administrative file tests.
+
+         # No point in testing compression effects in local mode.
+          if $remote; then :; else
+            remoteonly config2
+           continue
+         fi
+
+         # On Windows, we can't check out CVSROOT, because the case
+         # insensitivity means that this conflicts with cvsroot.
+         mkdir wnt
+         cd wnt
+
+         # Set MinCompressionLevel and MaxCompressionLevel in config.
+         dotest config2-init-1 "$testcvs -q co CVSROOT" "U CVSROOT/$DOTSTAR"
+         dotest config2-init-1b "$testcvs -Q tag initial"
+         cd CVSROOT
+         cat << EOF >> config
+MinCompressionLevel=5
+MaxCompressionLevel=6
+EOF
+         dotest config2-init-2 \
+"$testcvs -q ci -m set-compression-constraints" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         # Verify that the server reports forcing compression to an allowed
+         # level.
+
+         # Too high.
+         dotest config2-1 "$testcvs -z9 update" \
+"$SPROG server: Forcing compression level 6 (allowed: 5 <= z <= 6)\.
+$SPROG update: Updating \."
+         # Too low.
+         dotest config2-2 "$testcvs -z1 update" \
+"$SPROG server: Forcing compression level 5 (allowed: 5 <= z <= 6)\.
+$SPROG update: Updating \."
+         # From zero.
+         dotest config2-3 "$testcvs update" \
+"$SPROG server: Forcing compression level 5 (allowed: 5 <= z <= 6)\.
+$SPROG update: Updating \."
+         # Just right.
+         dotest config2-3 "$testcvs -z5 update" \
+"$SPROG update: Updating \."
+
+         # Check that compression may be forced to 0.
+         dotest config2-init-2b "$testcvs -z5 up -jHEAD -jinitial" "$DOTSTAR"
+         cat << EOF >> config
+MaxCompressionLevel=0
+EOF
+         dotest config2-init-3 "$testcvs -qz5 ci -m no-compression" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         # Too high.
+         dotest config2-5 "$testcvs -z9 update" \
+"$SPROG server: Forcing compression level 0 (allowed: 0 <= z <= 0)\.
+$SPROG update: Updating \."
+         # Just right.
+         dotest config2-6 "$testcvs update" \
+"$SPROG update: Updating \."
+
+         # And verify effect without restrictions.
+         dotest config2-init-3b "$testcvs up -jHEAD -jinitial" "$DOTSTAR"
+         dotest config2-init-4 "$testcvs -q ci -m change-to-comment" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         dotest config2-7 "$testcvs update" \
+"$SPROG update: Updating \."
+
+         dokeep
+         restore_adm
+         cd ../..
+         rm -r wnt
+         ;;
+
+       config3)
+         # Verify comments, white space, & [rootspecs] in CVSROOT/config
+         #
+         # `cvs server' `-c' option tested in `server' test
+         modify_repo mkdir $CVSROOT_DIRNAME/config3
+         mkdir config3
+         cd config3
+
+         dotest config3-init-1 "$testcvs -q co CVSROOT" "U CVSROOT/$DOTSTAR"
+         cd CVSROOT
+
+         # I break the usual sanity.sh indentation standard for here-docs
+         # mostly to test that leading white-space is now ignored.
+         dotest config3-init-1b "$testcvs -Q tag initial-config"
+
+         cat <<EOF >>config
+             # Ignore a comment with leading spaces.
+             GLOBAL-BAD-OPTION=WWW
+ 
+             [/ignore/this/root]
+             [/and/this/one]
+                 IGNORED-BAD-OPTION=YYY
+EOF
+         dotest config3-init-2 \
+"$testcvs -q ci -m test-root-specs" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         cd ..
+         dotest config3-1 "$testcvs co config3" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[0-9]*\]: 
unrecognized keyword \`GLOBAL-BAD-OPTION'
+$SPROG checkout: Updating config3"
+
+         cd CVSROOT
+         dotest config3-init-2a "$testcvs -Q up -jHEAD -jinitial-config" \
+"$DOTSTAR
+Merging differences between 1\.[0-9]* and 1\.[0-9]* into config"
+
+         cat <<EOF >>config
+             # Ignore a comment with leading spaces.
+
+             [/ignore/this/root]
+             [/and/this/one]
+                 IGNORED-BAD-OPTION=YYY
+                 # Ignore a comment with leading spaces.
+
+             [/some/other/root]
+
+             # Comments and blank lines do not affect fall-through behavior.
+
+             [$CVSROOT_DIRNAME]
+             [$SECONDARY_CVSROOT_DIRNAME]
+
+             # Comments and blank lines do not affect fall-through behavior.
+
+             [/yet/another/root]
+                 # Ignore a comment with leading spaces.
+                 PROCESS-BAD-OPTION=XXX
+EOF
+         dotest config3-init-3 \
+"$testcvs -q ci -m test-root-specs" \
+"$SPROG [a-z]*: $CVSROOT_DIRNAME/CVSROOT/config \[[0-9]*\]: unrecognized 
keyword \`GLOBAL-BAD-OPTION'
+$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[0-9]*\]: 
unrecognized keyword \`GLOBAL-BAD-OPTION'
+$SPROG [a-z]*: $CVSROOT_DIRNAME/CVSROOT/config \[[0-9]*\]: unrecognized 
keyword \`GLOBAL-BAD-OPTION'
+$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         cd ..
+         dotest config3-2 "$testcvs co config3" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[0-9]*\]: 
unrecognized keyword \`PROCESS-BAD-OPTION'
+$SPROG checkout: Updating config3"
+
+         # The next few tests make sure both global options and root
+         # specific options are processed by setting the history log and
+         # search paths in different locations and then verifying that
+         # both registered.  It also verifies that a key for a different
+         # root is ignored.
+         cd CVSROOT
+         dotest config3-init-3a "$testcvs -Q up -jHEAD -jinitial-config" \
+"$DOTSTAR
+Merging differences between 1\.[0-9]* and 1\.[0-9]* into config"
+
+         cat <<EOF >>config
+             HistoryLogPath=$TESTDIR/historylog
+
+             [/ignore/this/root]
+             [/and/this/one]
+                 IGNORED-BAD-OPTION=YYY
+
+             [/some/other/root]
+             [$CVSROOT_DIRNAME]
+             [$SECONDARY_CVSROOT_DIRNAME]
+             [/yet/another/root]
+                 HistorySearchPath=$TESTDIR/historylog
+
+             [/ignore/another/root]
+             [/and/this/one/too]
+                 ANOTHER-IGNORED-BAD-OPTION=ZZZ
+
+             [$CVSROOT_DIRNAME]
+             [$SECONDARY_CVSROOT_DIRNAME]
+                 LogHistory=TMAR
+EOF
+         dotest config3-init-4 \
+"$testcvs -q ci -m test-root-specs" \
+"$SPROG [a-z]*: $CVSROOT_DIRNAME/CVSROOT/config \[[0-9]*\]: unrecognized 
keyword \`PROCESS-BAD-OPTION'
+$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database" \
+"$SPROG [a-z]*: $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \[[0-9]*\]: 
unrecognized keyword \`PROCESS-BAD-OPTION'
+$SPROG [a-z]*: $CVSROOT_DIRNAME/CVSROOT/config \[[0-9]*\]: unrecognized 
keyword \`PROCESS-BAD-OPTION'
+$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         cd ..
+         dotest config3-3 "$testcvs co -d config3-2 config3" \
+"$SPROG checkout: Updating config3-2"
+
+         cd config3-2
+         touch newfile
+         dotest config3-4 "$testcvs -Q add newfile"
+         dotest config3-5 "$testcvs -q ci -madd-file" \
+"$CVSROOT_DIRNAME/config3/newfile,v  <--  newfile
+initial revision: 1\.1"
+
+         dotest config3-6 "$testcvs rtag testtag config3" \
+"$SPROG rtag: Tagging config3"
+
+         cd ..
+         dotest config3-7 "$testcvs history -ea" \
+"A [0-9-]* [0-9:]* ${PLUS}0000 $username 1\.1 newfile config3 == 
[-_/a-zA-Z0-9<>.]*
+T [0-9-]* [0-9:]* ${PLUS}0000 $username config3 \[testtag:A\]"
+
+         dokeep
+         restore_adm
+         cd ..
+         rm -r config3
+         modify_repo rm -rf $CVSROOT_DIRNAME/config3
+         ;;
+
+
+
+       config4)
+         # TmpDir
+         mkdir config4
+         cd config4
+
+         dotest config4-init-1 "$testcvs -q co CVSROOT" "U CVSROOT/$DOTSTAR"
+         cd CVSROOT
+         mkdir $TESTDIR/config4/tmp
+         echo "TmpDir=$TESTDIR/config4/tmp" >>config
+         echo "DEFAULT $TESTDIR/config4/verify %l" >>verifymsg
+         dotest config4-init-2 "$testcvs -q ci -m change-tmpdir" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$CVSROOT_DIRNAME/CVSROOT/verifymsg,v  <--  verifymsg
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         cat >$TESTDIR/config4/verify <<EOF
+#! /bin/sh
+echo \$1
+exit 0
+EOF
+         chmod a+x $TESTDIR/config4/verify
+         dotest config4-1 \
+"$testcvs -q ci -fmtest-tmpdir config" \
+"$TESTDIR/config4/tmp/$tempfile
+$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         dokeep
+         restore_adm
+         cd ../..
+         rm -r config4
+         modify_repo rm -rf $CVSROOT_DIRNAME/config4
+         ;;
+
+
+
+       compression)
+         # Try to reproduce some old compression buffer problems.
+
+         # No point in testing compression effects in local mode.
+          if $remote; then :; else
+            remoteonly config2
+           continue
+         fi
+
+         mkdir compression; cd compression
+         dotest compression-init1 "$testcvs -z6 -Q co -l -d toplevel ."
+         cd toplevel
+         mkdir compression
+         dotest compression-init2 "$testcvs -z6 -Q add compression"
+         cd ..
+
+         dotest compression-init3 "$testcvs -z6 -q co compression"
+         cd compression
+
+         # Want a big file
+         cat >big_file <<EOF
+a lot of data on a line to make a really big file once it is copied, copied,
+copied, the digital equivalent of a mile.
+EOF
+         # 1..14 creates about a 1MB file, the minimum required on the system
+         # I initially tested this on.  1..16 creates about a 4MB file.
+         for a in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do
+           cat big_file >tmp
+           cat big_file >>tmp
+           mv tmp big_file
+         done
+
+         dotest compression-1 "$testcvs -z6 -Q add big_file"
+
+         # The following command hung due to a bug in CVS 1.12.13.  This was
+         # because the command that grabbed more data to uncompress from the
+         # underlying buffer in zlib.c:compress_buffer_input would try to read
+         # the amount of data actually needed by the caller, even though this
+         # should be shorter in the underlying buffer (since it is
+         # compressed).  e.g., if the caller wanted 5 bytes, there may only be
+         # 3 bytes in the underlying buffer though it will uncompress to 5
+         # bytes, and a blocking read looking for 5 bytes will block
+         # indefinitely since the data will never become available.
+         #
+         # The only odd thing is that it worked at all, sometimes.  For
+         # example, I needed big_file to be at least 1MB in size to reproduce
+         # this here (for a in 1..14 - I used 1..16 above just 4 good measure
+         # - this compresses to c. 26k and should handle up 2 a c. 16k pg sz).
+         # My guess is that this has something to do with the amount the file
+         # gets compressed and how much other data preceded it in the data
+         # stream - the current buffer read will read as much data as is
+         # available, up to the system page size, in blocking or nonblocking
+         # modes.  So, when the compressed data is much less than the system
+         # page size, it is cached in full from previous reads and the
+         # blocking read request for more than the available data is never
+         # made.  The smallest file I could reproduce this with above
+         # compressed to just under 7k, on a system with a 4k page size.  In
+         # this case, the call to compress_buffer_input decompressed all the
+         # available buffered data, causing a read request for maybe half a
+         # megabyte, with only 3k left to read, and the server blocked
+         # waiting for the nonexistent data.  The same could happen with a
+         # smaller file if its compressed data happened to cross a page
+         # boundry or if timing issues caused a similar effect.
+         dotest compression-2 "$testcvs -z6 -q ci -mForce-zerror." \
+"$CVSROOT_DIRNAME/compression/big_file,v  <--  big_file
+initial revision: 1\.1"
+
+         dokeep
+         cd ../..
+         rm -r compression
+         modify_repo rm -rf $CVSROOT_DIRNAME/compression
+         ;;
+
+
+
+       serverpatch)
+         # Test remote CVS handling of unpatchable files.  This isn't
+         # much of a test for local CVS.
+         # We test this with some keyword expansion games, but the situation
+         # also arises if the user modifies the file while CVS is running.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         mkdir 1
+         cd 1
+         dotest serverpatch-1 "$testcvs -q co first-dir"
+
+         cd first-dir
+
+         # Add a file with an RCS keyword.
+         echo '$''Name$' > file1
+         echo '1' >> file1
+         dotest serverpatch-2 "$testcvs add file1" \
+"$SPROG add: scheduling file \`file1' for addition
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+
+         dotest serverpatch-3 "${testcvs} -q commit -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+         # Tag the file.
+         dotest serverpatch-4 "${testcvs} -q tag tag file1" 'T file1'
+
+         # Check out a tagged copy of the file.
+         cd ../..
+         mkdir 2
+         cd 2
+         dotest serverpatch-5 "${testcvs} -q co -r tag first-dir" \
+'U first-dir/file1'
+
+         # Remove the tag.  This will leave the tag string in the
+         # expansion of the Name keyword.
+         dotest serverpatch-6 "${testcvs} -q update -A first-dir" ''
+
+         # Modify and check in the first copy.
+         cd ../1/first-dir
+         echo '2' >> file1
+         dotest serverpatch-7 "${testcvs} -q ci -mx file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+
+         # Now update the second copy.  When using remote CVS, the
+         # patch will fail, forcing the file to be refetched.
+         cd ../../2/first-dir
+         dotest serverpatch-8 "$testcvs -q update" 'U file1' \
+"$CPROG update: checksum failure after patch to \`\./file1'; will refetch
+$CPROG client: refetching unpatchable files
+U file1"
+
+         dokeep
+         cd ../..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       log)
+         # Test selecting revisions with cvs log.
+         # See also log2 tests for more tests.
+         # See also branches-14.3 for logging with a branch off of a branch.
+         # See also multibranch-14 for logging with several branches off the
+         #   same branchpoint.
+         # Tests of each option to cvs log:
+         #   -h: admin-19a-log
+         #   -N: log, log2, admin-19a-log
+         #   -b, -r: log
+         #   -d: logopt, rcs
+         #   -s: logopt, rcs3
+         #   -R: logopt, rcs3
+         #   -w, -t: not tested yet (TODO)
+
+         # Check in a file with a few revisions and branches.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest log-1 "$testcvs -q co first-dir"
+         cd first-dir
+         echo 'first revision' > file1
+         echo 'first revision' > file2
+         dotest log-2 "${testcvs} add file1 file2" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+
+         # While we're at it, check multi-line comments, input from file,
+         # and trailing whitespace trimming
+         echo 'line 1     '     >${TESTDIR}/comment.tmp
+         echo '     '          >>${TESTDIR}/comment.tmp
+         echo 'line 2  '       >>${TESTDIR}/comment.tmp
+         echo '        '       >>${TESTDIR}/comment.tmp
+         echo '          '     >>${TESTDIR}/comment.tmp
+         dotest log-3 "${testcvs} -q commit -F ${TESTDIR}/comment.tmp" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+         rm -f ${TESTDIR}/comment.tmp
+
+         echo 'second revision' > file1
+         echo 'second revision' > file2
+         dotest log-4 "${testcvs} -q ci -m2 file1 file2" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.2; previous revision: 1\.1"
+
+         dotest log-5 "${testcvs} -q tag -b branch file1" 'T file1'
+         dotest log-5a "${testcvs} -q tag tag1 file2" 'T file2'
+
+         echo 'third revision' > file1
+         echo 'third revision' > file2
+         dotest log-6 "${testcvs} -q ci -m3 file1 file2" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.3; previous revision: 1\.2"
+
+         dotest log-6a "${testcvs} -q tag tag2 file2" 'T file2'
+
+         dotest log-7 "${testcvs} -q update -r branch" \
+"U file1
+${SPROG} update: \`file2' is no longer in the repository"
+
+         echo 'first branch revision' > file1
+         dotest log-8 "${testcvs} -q ci -m1b file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2\.2\.1; previous revision: 1\.2"
+
+         dotest log-9 "${testcvs} -q tag tag file1" 'T file1'
+
+         echo 'second branch revision' > file1
+         dotest log-10 "${testcvs} -q ci -m2b file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2\.2\.2; previous revision: 1\.2\.2\.1"
+
+         # Set up a bunch of shell variables to make the later tests
+         # easier to describe.=
+         log_header1="
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.3
+branch:
+locks: strict
+access list:"
+         rlog_header1="
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+head: 1\.3
+branch:
+locks: strict
+access list:"
+         log_tags1='symbolic names:
+       tag: 1\.2\.2\.1
+       branch: 1\.2\.0\.2'
+         log_keyword='keyword substitution: kv'
+         log_dash='----------------------------
+revision'
+         log_date="date: ${ISO8601DATE};  author: ${username};  state: Exp;"
+         log_lines="  lines: ${PLUS}1 -1;"
+         log_commitid="  commitid: ${commitid};"
+         log_rev1="${log_dash} 1\.1
+${log_date}${log_commitid}
+line 1
+
+line 2"
+         log_rev2="${log_dash} 1\.2
+${log_date}${log_lines}${log_commitid}
+branches:  1\.2\.2;
+2"
+         log_rev3="${log_dash} 1\.3
+${log_date}${log_lines}${log_commitid}
+3"
+         log_rev1b="${log_dash} 1\.2\.2\.1
+${log_date}${log_lines}${log_commitid}
+1b"
+         log_rev2b="${log_dash} 1\.2\.2\.2
+${log_date}${log_lines}${log_commitid}
+2b"
+         
log_trailer='============================================================================='
+
+         # Now, finally, test the log output.
+
+         dotest log-11 "${testcvs} log file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 5
+description:
+${log_rev3}
+${log_rev2}
+${log_rev1}
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+
+         dotest log-12 "${testcvs} log -N file1" \
+"${log_header1}
+${log_keyword}
+total revisions: 5;    selected revisions: 5
+description:
+${log_rev3}
+${log_rev2}
+${log_rev1}
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+
+         dotest log-13 "${testcvs} log -b file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 3
+description:
+${log_rev3}
+${log_rev2}
+${log_rev1}
+${log_trailer}"
+
+         dotest log-14 "${testcvs} log -r file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+
+         dotest log-14a "${testcvs} log -rHEAD file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+
+         # The user might not realize that "-r" must not take a space.
+         # In the error message, HEAD is a file name, not a tag name (which
+         # might be confusing itself).
+         dotest_fail log-14b "${testcvs} log -r HEAD file1" \
+"${SPROG} log: nothing known about HEAD
+${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+
+#        Check that unusual syntax works correctly.
+
+         dotest log-14c "${testcvs} log -r: file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+         dotest log-14d "${testcvs} log -r, file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+         dotest log-14e "${testcvs} log -r. file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+         dotest log-14f "${testcvs} log -r:: file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         dotest log-15 "${testcvs} log -r1.2 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2}
+${log_trailer}"
+
+         dotest log-16 "${testcvs} log -r1.2.2 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+
+         # This test would fail with the old invocation of rlog, but it
+         # works with the builtin log support.
+         dotest log-17 "${testcvs} log -rbranch file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+
+         dotest log-18 "${testcvs} log -r1.2.2. file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2b}
+${log_trailer}"
+
+         # Multiple -r options are undocumented; see comments in
+         # cvs.texinfo about whether they should be deprecated.
+         dotest log-18a "${testcvs} log -r1.2.2.2 -r1.3:1.3 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev2b}
+${log_trailer}"
+
+         # This test would fail with the old invocation of rlog, but it
+         # works with the builtin log support.
+         dotest log-19 "${testcvs} log -rbranch. file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2b}
+${log_trailer}"
+
+         dotest log-20 "${testcvs} log -r1.2: file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev2}
+${log_trailer}"
+
+         dotest log-20a "${testcvs} log -r1.2:: file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+
+         dotest log-21 "${testcvs} log -r:1.2 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2}
+${log_rev1}
+${log_trailer}"
+
+         dotest log-21a "${testcvs} log -r::1.2 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2}
+${log_rev1}
+${log_trailer}"
+
+         dotest log-22 "${testcvs} log -r1.1:1.2 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2}
+${log_rev1}
+${log_trailer}"
+
+         dotest log-22a "${testcvs} log -r1.1::1.2 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2}
+${log_trailer}"
+
+         dotest log-22b "${testcvs} log -r1.1::1.3 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev2}
+${log_trailer}"
+
+         dotest log-23 "${testcvs} log -rfoo:: file1" \
+"${SPROG} log: warning: no revision .foo. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         dotest log-24 "${testcvs} log -rfoo::1.3 file1" \
+"${SPROG} log: warning: no revision .foo. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         dotest log-25 "${testcvs} log -r::foo file1" \
+"${SPROG} log: warning: no revision .foo. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         dotest log-26 "${testcvs} log -r1.1::foo file1" \
+"${SPROG} log: warning: no revision .foo. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         # Test BASE pseudotag
+         dotest log-27 "${testcvs} log -rBASE file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2b}
+${log_trailer}"
+
+         dotest log-28 "${testcvs} -q up -r1.2 file1" "U file1"
+         dotest log-29 "${testcvs} log -rBASE file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2}
+${log_trailer}"
+
+         dotest log-30 "${testcvs} -q up -rbranch file1" "U file1"
+
+         # Now the same tests but with rlog
+
+         dotest log-r11 "${testcvs} rlog first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 5
+description:
+${log_rev3}
+${log_rev2}
+${log_rev1}
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+
+         dotest log-r12 "${testcvs} rlog -N first-dir/file1" \
+"${rlog_header1}
+${log_keyword}
+total revisions: 5;    selected revisions: 5
+description:
+${log_rev3}
+${log_rev2}
+${log_rev1}
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+
+         dotest log-r13 "${testcvs} rlog -b first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 3
+description:
+${log_rev3}
+${log_rev2}
+${log_rev1}
+${log_trailer}"
+
+         dotest log-r14 "${testcvs} rlog -r first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+
+         dotest log-r14a "${testcvs} rlog -rHEAD first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+
+         dotest_fail log-r14b "${testcvs} rlog -r HEAD first-dir/file1" \
+"${SPROG} rlog: cannot find module .HEAD. - ignored
+${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+
+         dotest log-r14c "${testcvs} rlog -r: first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+         dotest log-r14d "${testcvs} rlog -r, first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+         dotest log-r14e "${testcvs} rlog -r. first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+         dotest log-r14f "${testcvs} rlog -r:: first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         dotest log-r15 "${testcvs} rlog -r1.2 first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2}
+${log_trailer}"
+
+         dotest log-r16 "${testcvs} rlog -r1.2.2 first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+
+         dotest log-r17 "${testcvs} rlog -rbranch first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+
+         dotest log-r18 "${testcvs} rlog -r1.2.2. first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2b}
+${log_trailer}"
+
+         dotest log-r18a "${testcvs} rlog -r1.2.2.2 -r1.3:1.3 first-dir/file1" 
\
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev2b}
+${log_trailer}"
+
+         dotest log-r19 "${testcvs} rlog -rbranch. first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2b}
+${log_trailer}"
+
+         dotest log-r20 "${testcvs} rlog -r1.2: first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev2}
+${log_trailer}"
+
+         dotest log-r20a "${testcvs} rlog -r1.2:: first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev3}
+${log_trailer}"
+
+         dotest log-r21 "${testcvs} rlog -r:1.2 first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2}
+${log_rev1}
+${log_trailer}"
+
+         dotest log-r21a "${testcvs} rlog -r::1.2 first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2}
+${log_rev1}
+${log_trailer}"
+
+         dotest log-r22 "${testcvs} rlog -r1.1:1.2 first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev2}
+${log_rev1}
+${log_trailer}"
+
+         dotest log-r22a "${testcvs} rlog -r1.1::1.2 first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 1
+description:
+${log_rev2}
+${log_trailer}"
+
+         dotest log-r22b "${testcvs} rlog -r1.1::1.3 first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev2}
+${log_trailer}"
+
+         dotest log-r23 "${testcvs} rlog -rfoo:: first-dir/file1" \
+"${SPROG} rlog: warning: no revision .foo. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         dotest log-r24 "${testcvs} rlog -rfoo::1.3 first-dir/file1" \
+"${SPROG} rlog: warning: no revision .foo. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         dotest log-r25 "${testcvs} rlog -r::foo first-dir/file1" \
+"${SPROG} rlog: warning: no revision .foo. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         dotest log-r26 "${testcvs} rlog -r1.1::foo first-dir/file1" \
+"${SPROG} rlog: warning: no revision .foo. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         # Test BASE pseudotag
+         dotest log-r27 "${testcvs} rlog -rBASE first-dir/file1" \
+"${SPROG} rlog: warning: no revision .BASE. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         dotest log-r28 "${testcvs} -q up -r1.2 file1" "U file1"
+         dotest log-r29 "${testcvs} rlog -rBASE first-dir/file1" \
+"${SPROG} rlog: warning: no revision .BASE. in 
.${CVSROOT_DIRNAME}/first-dir/file1,v.
+${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 0
+description:
+${log_trailer}"
+
+         # Test when head is dead
+
+         dotest log-d0 "${testcvs} -q up -A" \
+"U file1
+U file2"
+         dotest log-d1 "${testcvs} -q rm -f file1" \
+"${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+         dotest log-d2 "${testcvs} -q ci -m4" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: delete; previous revision: 1\.3"
+
+         log_header1="
+RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+Working file: file1
+head: 1\.4
+branch:
+locks: strict
+access list:"
+         rlog_header1="
+RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+head: 1\.4
+branch:
+locks: strict
+access list:"
+         log_header2="
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+Working file: file2
+head: 1\.3
+branch:
+locks: strict
+access list:"
+         rlog_header2="
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+head: 1\.3
+branch:
+locks: strict
+access list:"
+         log_tags2='symbolic names:
+       tag2: 1\.3
+       tag1: 1\.2'
+         log_rev4="${log_dash} 1\.4
+date: ${ISO8601DATE};  author: ${username};  state: dead;  lines: ${PLUS}0 -0; 
 commitid: ${commitid};
+4"
+         log_rev22="${log_dash} 1\.2
+${log_date}${log_lines}${log_commitid}
+2"
+
+         dotest log-d3 "${testcvs} log -rbranch file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+         dotest log-rd3 "${testcvs} rlog -rbranch first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+         dotest log-d4 "${testcvs} -q log -rbranch" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}
+${SPROG} log: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v.
+${log_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3;    selected revisions: 0
+description:
+${log_trailer}"
+         dotest log-d4a "${testcvs} -q log -t -rbranch" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6
+description:
+${log_trailer}
+${SPROG} log: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v.
+${log_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3
+description:
+${log_trailer}"
+         dotest log-d4b "${testcvs} -q log -tS -rbranch" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_trailer}
+${SPROG} log: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v."
+         dotest log-d4c "${testcvs} -q log -h -rbranch" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6
+${log_trailer}
+${SPROG} log: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v.
+${log_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3
+${log_trailer}"
+         dotest log-d4d "${testcvs} -q log -hS -rbranch" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+${log_trailer}
+${SPROG} log: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v."
+         dotest log-d4e "$testcvs -q log -R -rbranch" \
+"$CVSROOT_DIRNAME/first-dir/Attic/file1,v
+$CVSROOT_DIRNAME/first-dir/file2,v"
+         dotest log-d4f "${testcvs} -q log -R -S -rbranch" \
+"${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+${SPROG} log: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v."
+         dotest log-rd4 "${testcvs} -q rlog -rbranch first-dir" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}
+${SPROG} rlog: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v.
+${rlog_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3;    selected revisions: 0
+description:
+${log_trailer}"
+         dotest log-rd4a "${testcvs} -q rlog -t -rbranch first-dir" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6
+description:
+${log_trailer}
+${SPROG} rlog: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v.
+${rlog_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3
+description:
+${log_trailer}"
+         dotest log-rd4b "${testcvs} -q rlog -St -rbranch first-dir" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_trailer}
+${SPROG} rlog: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v."
+         dotest log-rd4c "${testcvs} -q rlog -h -rbranch first-dir" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6
+${log_trailer}
+${SPROG} rlog: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v.
+${rlog_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3
+${log_trailer}"
+         dotest log-rd4d "${testcvs} -q rlog -Sh -rbranch first-dir" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+${log_trailer}
+${SPROG} rlog: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v."
+         dotest log-rd4e "${testcvs} -q rlog -R -rbranch first-dir" \
+"${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+${CVSROOT_DIRNAME}/first-dir/file2,v"
+         dotest log-rd4f "${testcvs} -q rlog -R -S -rbranch first-dir" \
+"${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+${SPROG} rlog: warning: no revision .branch. in 
.${CVSROOT_DIRNAME}/first-dir/file2,v."
+         dotest log-d5 "${testcvs} log -r1.2.2.1:1.2.2.2 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+         dotest log-rd5 "${testcvs} rlog -r1.2.2.1:1.2.2.2 first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}"
+         dotest log-d6 "${testcvs} -q log -r1.2.2.1:1.2.2.2" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}
+${log_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3;    selected revisions: 0
+description:
+${log_trailer}"
+         dotest log-rd6 "${testcvs} -q rlog -r1.2.2.1:1.2.2.2 first-dir" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev2b}
+${log_rev1b}
+${log_trailer}
+${rlog_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3;    selected revisions: 0
+description:
+${log_trailer}"
+         dotest log-d7 "${testcvs} log -r1.2:1.3 file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev2}
+${log_trailer}"
+         dotest log-rd7 "${testcvs} -q rlog -r1.2:1.3 first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev2}
+${log_trailer}"
+         dotest log-d8 "${testcvs} -q log -rtag1:tag2" \
+"${SPROG} log: warning: no revision .tag1. in 
.${CVSROOT_DIRNAME}/first-dir/Attic/file1,v.
+${SPROG} log: warning: no revision .tag2. in 
.${CVSROOT_DIRNAME}/first-dir/Attic/file1,v.
+${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 0
+description:
+${log_trailer}
+${log_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev22}
+${log_trailer}"
+         dotest log-d8a "${testcvs} -q log -rtag1:tag2 -S" \
+"${SPROG} log: warning: no revision .tag1. in 
.${CVSROOT_DIRNAME}/first-dir/Attic/file1,v.
+${SPROG} log: warning: no revision .tag2. in 
.${CVSROOT_DIRNAME}/first-dir/Attic/file1,v.
+${log_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev22}
+${log_trailer}"
+         dotest log-rd8 "${testcvs} -q rlog -rtag1:tag2 first-dir" \
+"${SPROG} rlog: warning: no revision .tag1. in 
.${CVSROOT_DIRNAME}/first-dir/Attic/file1,v.
+${SPROG} rlog: warning: no revision .tag2. in 
.${CVSROOT_DIRNAME}/first-dir/Attic/file1,v.
+${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 6;    selected revisions: 0
+description:
+${log_trailer}
+${rlog_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev22}
+${log_trailer}"
+         dotest log-rd8a "${testcvs} -q rlog -rtag1:tag2 -S first-dir" \
+"${SPROG} rlog: warning: no revision .tag1. in 
.${CVSROOT_DIRNAME}/first-dir/Attic/file1,v.
+${SPROG} rlog: warning: no revision .tag2. in 
.${CVSROOT_DIRNAME}/first-dir/Attic/file1,v.
+${rlog_header2}
+${log_tags2}
+${log_keyword}
+total revisions: 3;    selected revisions: 2
+description:
+${log_rev3}
+${log_rev22}
+${log_trailer}"
+
+         dotest log-d99 "${testcvs} -q up -rbranch" \
+"U file1
+${SPROG} update: \`file2' is no longer in the repository"
+
+         # Now test outdating revisions
+
+         dotest log-o0 "${testcvs} admin -o 1.2.2.2:: file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+done"
+         dotest log-o1 "${testcvs} admin -o ::1.2.2.1 file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+done"
+         dotest log-o2 "${testcvs} admin -o 1.2.2.1:: file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file1,v
+deleting revision 1\.2\.2\.2
+done"
+         dotest log-o3 "${testcvs} log file1" \
+"${log_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 5
+description:
+${log_rev4}
+${log_rev3}
+${log_rev2}
+${log_rev1}
+${log_rev1b}
+${log_trailer}"
+         dotest log-ro3 "${testcvs} rlog first-dir/file1" \
+"${rlog_header1}
+${log_tags1}
+${log_keyword}
+total revisions: 5;    selected revisions: 5
+description:
+${log_rev4}
+${log_rev3}
+${log_rev2}
+${log_rev1}
+${log_rev1b}
+${log_trailer}"
+         dotest log-o4 "${testcvs} -q update -p -r 1.2.2.1 file1" \
+"first branch revision"
+
+         dokeep
+         cd ..
+         rm -r first-dir
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       log2)
+         # More "cvs log" tests, for example the file description.
+
+         # Check in a file
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest log2-1 "$testcvs -q co first-dir"
+         cd first-dir
+         echo 'first revision' > file1
+         dotest log2-2 "${testcvs} add -m file1-is-for-testing file1" \
+"${SPROG}"' add: scheduling file `file1'\'' for addition
+'"${SPROG}"' add: use .'"${SPROG}"' commit. to add this file permanently'
+         dotest log2-3 "${testcvs} -q commit -m 1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         # Setting the file description with add -m doesn't yet work
+         # client/server, so skip log2-4 for remote.
+         if $remote; then :; else
+
+           dotest log2-4 "${testcvs} log -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+file1-is-for-testing
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+1
+============================================================================="
+
+         fi # end of tests skipped for remote
+
+         dotest log2-5 "${testcvs} admin -t-change-description file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+         dotest log2-6 "${testcvs} log -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+change-description
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+1
+============================================================================="
+
+         echo 'longer description' >${TESTDIR}/descrip
+         echo 'with two lines' >>${TESTDIR}/descrip
+         dotest log2-7 "${testcvs} admin -t${TESTDIR}/descrip file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+         dotest_fail log2-7a "${testcvs} admin -t${TESTDIR}/nonexist file1" \
+"${CPROG} \[admin aborted\]: can't stat ${TESTDIR}/nonexist: No such file or 
directory"
+         dotest log2-8 "${testcvs} log -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+longer description
+with two lines
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+1
+============================================================================="
+
+         # TODO: `cvs admin -t "my message" file1' is a request to
+         # read the message from stdin and to operate on two files.
+         # Should test that there is an error because "my message"
+         # doesn't exist.
+
+         dotest log2-9 "echo change from stdin | ${testcvs} admin -t -q file1" 
""
+         dotest log2-10 "${testcvs} log -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+change from stdin
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+1
+============================================================================="
+
+         dokeep
+         cd ..
+         rm $TESTDIR/descrip
+         rm -r first-dir
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       logopt)
+         # Some tests of log.c's option parsing and such things.
+         mkdir 1; cd 1
+         dotest logopt-1 "$testcvs -q co -l ." ''
+         mkdir first-dir
+         dotest logopt-2 "$testcvs add first-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir added to the repository"
+         cd first-dir
+         echo hi >file1
+         dotest logopt-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest logopt-4 "${testcvs} -q ci -m add file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         cd ..
+
+         dotest logopt-5 "${testcvs} log -R -d 2038-01-01" \
+"${SPROG} log: Logging \.
+${SPROG} log: Logging first-dir
+${CVSROOT_DIRNAME}/first-dir/file1,v"
+         dotest logopt-6 "${testcvs} log -d 2038-01-01 -R" \
+"${SPROG} log: Logging \.
+${SPROG} log: Logging first-dir
+${CVSROOT_DIRNAME}/first-dir/file1,v"
+         dotest logopt-6a "${testcvs} log -Rd 2038-01-01" \
+"${SPROG} log: Logging \.
+${SPROG} log: Logging first-dir
+${CVSROOT_DIRNAME}/first-dir/file1,v"
+         dotest logopt-7 "${testcvs} log -s Exp -R" \
+"${SPROG} log: Logging \.
+${SPROG} log: Logging first-dir
+${CVSROOT_DIRNAME}/first-dir/file1,v"
+
+         dokeep
+         cd ..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       ann)
+         # Tests of "cvs annotate".  See also:
+         #   basica-10  A simple annotate test
+         #   rcs        Annotate and the year 2000
+         #   keywordlog Annotate and $Log.
+         mkdir 1; cd 1
+         dotest ann-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest ann-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         cat >file1 <<EOF
+this
+is
+the
+ancestral
+file
+EOF
+         dotest ann-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest ann-4 "${testcvs} -q ci -m add file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         cat >file1 <<EOF
+this
+is
+a
+file
+
+with
+a
+blank
+line
+EOF
+         dotest ann-5 "${testcvs} -q ci -m modify file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         dotest ann-6 "${testcvs} -q tag -b br" "T file1"
+         cat >file1 <<EOF
+this
+is
+a
+trunk file
+
+with
+a
+blank
+line
+EOF
+         dotest ann-7 "${testcvs} -q ci -m modify file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2"
+         dotest ann-8 "${testcvs} -q update -r br" "U file1"
+         cat >file1 <<EOF
+this
+is
+a
+file
+
+with
+a
+blank
+line
+and some
+branched content
+EOF
+         dotest ann-9 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2\.2\.1; previous revision: 1\.2"
+         # Note that this annotates the trunk despite the presence
+         # of a sticky tag in the current directory.  This is
+         # fairly bogus, but it is the longstanding behavior for
+         # whatever that is worth.
+         dotest ann-10 "${testcvs} ann" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          ($username8 *[0-9a-zA-Z-]*): this
+1\.1          ($username8 *[0-9a-zA-Z-]*): is
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.3          ($username8 *[0-9a-zA-Z-]*): trunk file
+1\.2          ($username8 *[0-9a-zA-Z-]*): 
+1\.2          ($username8 *[0-9a-zA-Z-]*): with
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.2          ($username8 *[0-9a-zA-Z-]*): blank
+1\.2          ($username8 *[0-9a-zA-Z-]*): line"
+         dotest ann-10w1 "${testcvs} ann -w 1" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          ($username1 *[0-9a-zA-Z-]*): this
+1\.1          ($username1 *[0-9a-zA-Z-]*): is
+1\.2          ($username1 *[0-9a-zA-Z-]*): a
+1\.3          ($username1 *[0-9a-zA-Z-]*): trunk file
+1\.2          ($username1 *[0-9a-zA-Z-]*): 
+1\.2          ($username1 *[0-9a-zA-Z-]*): with
+1\.2          ($username1 *[0-9a-zA-Z-]*): a
+1\.2          ($username1 *[0-9a-zA-Z-]*): blank
+1\.2          ($username1 *[0-9a-zA-Z-]*): line"
+         if test $userlen -lt 80; then
+           dotest ann-10wmax "${testcvs} ann -w $userlen" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          ($username *[0-9a-zA-Z-]*): this
+1\.1          ($username *[0-9a-zA-Z-]*): is
+1\.2          ($username *[0-9a-zA-Z-]*): a
+1\.3          ($username *[0-9a-zA-Z-]*): trunk file
+1\.2          ($username *[0-9a-zA-Z-]*): 
+1\.2          ($username *[0-9a-zA-Z-]*): with
+1\.2          ($username *[0-9a-zA-Z-]*): a
+1\.2          ($username *[0-9a-zA-Z-]*): blank
+1\.2          ($username *[0-9a-zA-Z-]*): line"
+         fi
+         dotest ann-11 "${testcvs} ann -r br" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          ($username8 *[0-9a-zA-Z-]*): this
+1\.1          ($username8 *[0-9a-zA-Z-]*): is
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.1          ($username8 *[0-9a-zA-Z-]*): file
+1\.2          ($username8 *[0-9a-zA-Z-]*): 
+1\.2          ($username8 *[0-9a-zA-Z-]*): with
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.2          ($username8 *[0-9a-zA-Z-]*): blank
+1\.2          ($username8 *[0-9a-zA-Z-]*): line
+1\.2\.2\.1      ($username8 *[0-9a-zA-Z-]*): and some
+1\.2\.2\.1      ($username8 *[0-9a-zA-Z-]*): branched content"
+         # FIXCVS: shouldn't "-r 1.2.0.2" be the same as "-r br"?
+         dotest ann-12 "${testcvs} ann -r 1.2.0.2 file1" ""
+         dotest ann-13 "${testcvs} ann -r 1.2.2 file1" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          ($username8 *[0-9a-zA-Z-]*): this
+1\.1          ($username8 *[0-9a-zA-Z-]*): is
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.1          ($username8 *[0-9a-zA-Z-]*): file
+1\.2          ($username8 *[0-9a-zA-Z-]*): 
+1\.2          ($username8 *[0-9a-zA-Z-]*): with
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.2          ($username8 *[0-9a-zA-Z-]*): blank
+1\.2          ($username8 *[0-9a-zA-Z-]*): line
+1\.2\.2\.1      ($username8 *[0-9a-zA-Z-]*): and some
+1\.2\.2\.1      ($username8 *[0-9a-zA-Z-]*): branched content"
+         dotest_fail ann-14 "$testcvs ann -r bill-clintons-chastity file1" \
+"$SPROG \[annotate aborted\]: no such tag \`bill-clintons-chastity'"
+
+         # Now get rid of the working directory and test rannotate
+
+         cd ../..
+         rm -r 1
+         dotest ann-r10 "${testcvs} rann first-dir" \
+"
+Annotations for first-dir/file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          ($username8 *[0-9a-zA-Z-]*): this
+1\.1          ($username8 *[0-9a-zA-Z-]*): is
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.3          ($username8 *[0-9a-zA-Z-]*): trunk file
+1\.2          ($username8 *[0-9a-zA-Z-]*): 
+1\.2          ($username8 *[0-9a-zA-Z-]*): with
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.2          ($username8 *[0-9a-zA-Z-]*): blank
+1\.2          ($username8 *[0-9a-zA-Z-]*): line"
+         dotest ann-r11 "${testcvs} rann -r br first-dir" \
+"
+Annotations for first-dir/file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          ($username8 *[0-9a-zA-Z-]*): this
+1\.1          ($username8 *[0-9a-zA-Z-]*): is
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.1          ($username8 *[0-9a-zA-Z-]*): file
+1\.2          ($username8 *[0-9a-zA-Z-]*): 
+1\.2          ($username8 *[0-9a-zA-Z-]*): with
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.2          ($username8 *[0-9a-zA-Z-]*): blank
+1\.2          ($username8 *[0-9a-zA-Z-]*): line
+1\.2\.2\.1      ($username8 *[0-9a-zA-Z-]*): and some
+1\.2\.2\.1      ($username8 *[0-9a-zA-Z-]*): branched content"
+         dotest ann-r12 "${testcvs} rann -r 1.2.0.2 first-dir/file1" ""
+         dotest ann-r13 "${testcvs} rann -r 1.2.2 first-dir/file1" \
+"
+Annotations for first-dir/file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          ($username8 *[0-9a-zA-Z-]*): this
+1\.1          ($username8 *[0-9a-zA-Z-]*): is
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.1          ($username8 *[0-9a-zA-Z-]*): file
+1\.2          ($username8 *[0-9a-zA-Z-]*): 
+1\.2          ($username8 *[0-9a-zA-Z-]*): with
+1\.2          ($username8 *[0-9a-zA-Z-]*): a
+1\.2          ($username8 *[0-9a-zA-Z-]*): blank
+1\.2          ($username8 *[0-9a-zA-Z-]*): line
+1\.2\.2\.1      ($username8 *[0-9a-zA-Z-]*): and some
+1\.2\.2\.1      ($username8 *[0-9a-zA-Z-]*): branched content"
+         dotest_fail ann-r14 "$testcvs rann -r bill-clintons-chastity 
first-dir/file1" \
+"$SPROG \[rannotate aborted\]: no such tag \`bill-clintons-chastity'"
+
+         dokeep
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       ann-id)
+         # Demonstrate that cvs-1.9.28.1 improperly expands rcs keywords in
+         # the output of `cvs annotate' -- it uses values from the previous
+         # delta.  In this case, `1.1' instead of `1.2', even though it puts
+         # the proper version number on the prefix to each line of output.
+         mkdir 1; cd 1
+         dotest ann-id-1 "$testcvs -q co -l ."
+         module=x
+         mkdir $module
+         dotest ann-id-2 "${testcvs} add $module" \
+"Directory ${CVSROOT_DIRNAME}/$module added to the repository"
+         cd $module
+
+         file=m
+         echo '$Id''$' > $file
+
+         dotest ann-id-3 "$testcvs add $file" \
+"$SPROG add: scheduling file .$file. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         dotest ann-id-4 "$testcvs -Q ci -m . $file"
+
+         echo line2 >> $file
+         dotest ann-id-5 "$testcvs -Q ci -m . $file"
+
+         # The version number after $file,v should be `1.2'.
+         # 1.9.28.1 puts `1.1' there.
+         dotest ann-id-6 "$testcvs -Q ann $file" \
+"
+Annotations for $file
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1.2          ($username8 *[0-9a-zA-Z-]*): "'\$'"Id: $file,v 1.1 [0-9/]* 
[0-9:]* $username Exp "'\$'"
+1.2          ($username8 *[0-9a-zA-Z-]*): line2"
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         ;;
+
+
+
+       crerepos)
+         # Various tests relating to creating repositories, operating
+         # on repositories created with old versions of CVS, etc.
+
+         CVS_SERVER_save=$CVS_SERVER
+
+         # Because this test is all about -d options and such, it
+         # at least to some extent needs to be different for remote vs.
+         # local.
+         if $remote; then
+
+           # Use :ext: rather than :fork:.  Most of the tests use :fork:,
+           # so we want to make sure that we test :ext: _somewhere_.
+           # Make sure 'rsh' works first.
+           require_rsh "$CVS_RSH"
+           if test $? -eq 77; then
+               skip crerepos "$skipreason"
+               continue
+           fi
+
+            # Make sure server ignores real $HOME/.cvsrc:
+            cat >$TESTDIR/cvs-setHome <<EOF
+#!$TESTSHELL
+HOME=$HOME
+export HOME
+exec $CVS_SERVER "\$@"
+EOF
+            chmod a+x $TESTDIR/cvs-setHome
+
+           # Note that we set CVS_SERVER at the beginning.
+           CVS_SERVER=$TESTDIR/cvs-setHome; export CVS_SERVER
+           CREREPOS_ROOT=:ext:$host$TESTDIR/crerepos
+         else # local
+           CREREPOS_ROOT=$TESTDIR/crerepos
+         fi
+
+         # First, if the repository doesn't exist at all...
+         dotest_fail crerepos-1 \
+"${testcvs} -d ${TESTDIR}/crerepos co cvs-sanity" \
+"${SPROG} \[checkout aborted\]: ${TESTDIR}/crerepos/CVSROOT: .*"
+         mkdir crerepos
+
+         # The repository exists but CVSROOT doesn't.
+         dotest_fail crerepos-2 \
+"${testcvs} -d ${TESTDIR}/crerepos co cvs-sanity" \
+"${SPROG} \[checkout aborted\]: ${TESTDIR}/crerepos/CVSROOT: .*"
+         mkdir crerepos/CVSROOT
+
+         # Checkout of nonexistent module
+         dotest_fail crerepos-3 \
+"${testcvs} -d ${TESTDIR}/crerepos co cvs-sanity" \
+"${SPROG} checkout: cannot find module .cvs-sanity. - ignored"
+
+         # Now test that CVS works correctly without a modules file
+         # or any of that other stuff.  In particular, it *must*
+         # function if administrative files added to CVS recently (since
+         # CVS 1.3) do not exist, because the repository might have
+         # been created with an old version of CVS.
+         mkdir 1; cd 1
+         dotest crerepos-4 \
+"${testcvs} -q -d ${TESTDIR}/crerepos co CVSROOT" \
+''
+         dotest crerepos-5 \
+"echo yes | $testcvs -d $TESTDIR/crerepos release -d CVSROOT" \
+"You have \[0\] altered files in this repository\.
+Are you sure you want to release (and delete) directory \`CVSROOT': "
+         rm -rf CVS
+         cd ..
+         # The directory 1 should be empty
+         dotest crerepos-6 "rmdir 1"
+
+         if $remote; then
+           # Test that CVS rejects a relative path in CVSROOT.
+           mkdir 1; cd 1
+           # Note that having the client reject the pathname (as :fork:
+           # does), does _not_ test for the bugs we are trying to catch
+           # here.  The point is that malicious clients might send all
+           # manner of things and the server better protect itself.
+           dotest_fail crerepos-6a-r \
+"${testcvs} -q -d :ext:`hostname`:../crerepos get ." \
+"${CPROG} checkout: CVSROOT may only specify a positive, non-zero, integer 
port (not .\.\..)\.
+${CPROG} checkout: Perhaps you entered a relative pathname${QUESTION}
+${CPROG} \[checkout aborted\]: Bad CVSROOT: .:ext:${hostname}:\.\./crerepos.\."
+           cd ..
+           rm -r 1
+
+           mkdir 1; cd 1
+           dotest_fail crerepos-6b-r \
+"${testcvs} -d :ext:`hostname`:crerepos init" \
+"${CPROG} init: CVSROOT requires a path spec:
+${CPROG} init: 
:(gserver|kserver|pserver):\[\[user\]\[:address@hidden:\[port\]\]/path
+${CPROG} init: \[:(ext|server):address@hidden:\]/path
+${CPROG} \[init aborted\]: Bad CVSROOT: .:ext:${hostname}:crerepos.\."
+           cd ..
+           rm -r 1
+         else # local
+           # Test that CVS rejects a relative path in CVSROOT.
+
+           mkdir 1; cd 1
+           # Set CVS_RSH=false since ocassionally (e.g. when CVS_RSH=ssh on
+           # some systems) some rsh implementations will block because they
+           # can look up '..' and want to ask the user about the unknown host
+           # key or somesuch.  Which error message we get depends on whether
+           # false finishes running before we try to talk to it or not.
+           dotest_fail crerepos-6a "CVS_RSH=false ${testcvs} -q -d ../crerepos 
get ." \
+"${SPROG} \[checkout aborted\]: end of file from server (consult above 
messages if any)" \
+"${SPROG} \[checkout aborted\]: received broken pipe signal"
+           cd ..
+           rm -r 1
+
+           mkdir 1; cd 1
+           dotest_fail crerepos-6b "${testcvs} -d crerepos init" \
+"${SPROG} init: CVSROOT must be an absolute pathname (not .crerepos.)
+${SPROG} init: when using local access method\.
+${SPROG} \[init aborted\]: Bad CVSROOT: .crerepos.\."
+           cd ..
+           rm -r 1
+         fi # end of tests to be skipped for remote
+
+         # CVS should have created a history file.  If the administrator 
+         # doesn't need it and wants to save on disk space, they just
+         # delete it and set LogHistory = the empty string in config.
+         dotest crerepos-7 "test -f $TESTDIR/crerepos/CVSROOT/history"
+
+         # Now test mixing repositories.  This kind of thing tends to
+         # happen accidentally when people work with several repositories.
+         mkdir 1; cd 1
+         dotest crerepos-8 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest crerepos-9 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         touch file1
+         dotest crerepos-10 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest crerepos-11 "${testcvs} -q ci -m add-it" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         cd ../..
+         rm -r 1
+
+         mkdir 1; cd 1
+         dotest crerepos-12 "$testcvs -d $CREREPOS_ROOT -q co -l ."
+         mkdir crerepos-dir
+         dotest crerepos-13 "$testcvs add crerepos-dir" \
+"Directory $TESTDIR/crerepos/crerepos-dir added to the repository"
+         cd crerepos-dir
+         touch cfile
+         dotest crerepos-14 "${testcvs} add cfile" \
+"${SPROG} add: scheduling file .cfile. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest crerepos-15 "${testcvs} -q ci -m add-it" \
+"$TESTDIR/crerepos/crerepos-dir/cfile,v  <--  cfile
+initial revision: 1\.1"
+         cd ../..
+         rm -r 1
+
+         mkdir 1; cd 1
+         dotest crerepos-16 "${testcvs} co first-dir" \
+"${SPROG} checkout: Updating first-dir
+U first-dir/file1"
+         dotest crerepos-17 "${testcvs} -d ${CREREPOS_ROOT} co crerepos-dir" \
+"${SPROG} checkout: Updating crerepos-dir
+U crerepos-dir/cfile"
+         dotest crerepos-18 "${testcvs} update" \
+"${SPROG} update: Updating first-dir
+${SPROG} update: Updating crerepos-dir"
+
+         cd ..
+
+          CVS_SERVER=$CVS_SERVER_save; export CVS_SERVER
+
+         if $keep; then
+           echo Keeping ${TESTDIR} and exiting due to --keep
+           exit 0
+         fi
+
+         dokeep
+          rm -f $TESTDIR/cvs-setHome
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         rm -rf $TESTDIR/crerepos
+         ;;
+
+
+
+       rcs)
+         # Test ability to import an RCS file.  Note that this format
+         # is fixed--files written by RCS5, and other software which
+         # implements this format, will be out there "forever" and
+         # CVS must always be able to import such files.
+
+         # See tests admin-13, admin-25 and rcs-8a for exporting RCS files.
+
+         # Save the timezone and set it to UTC for these tests to make the
+         # value more predicatable.
+         save_TZ=$TZ
+         TZ=UTC0; export TZ
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+
+         # Currently the way to import an RCS file is to copy it
+         # directly into the repository.
+         #
+         # This file was written by RCS 5.7, and then the dates were
+         # hacked so that we test year 2000 stuff.  Note also that
+         # "author" names are just strings, as far as importing
+         # RCS files is concerned--they need not correspond to user
+         # IDs on any particular system.
+         #
+         # I also tried writing a file with the RCS supplied with
+         # HPUX A.09.05.  According to "man rcsintro" this is
+         # "Revision Number: 3.0; Release Date: 83/05/11".  There
+         # were a few minor differences like whitespace but at least
+         # in simple cases like this everything else seemed the same
+         # as the file written by RCS 5.7 (so I won't try to make it
+         # a separate test case).
+
+         cat <<EOF >$TESTDIR/file1,v
+head   1.3;
+access;
+symbols;
+locks; strict;
+comment        @# @;
+
+
+1.3
+date   ${RAWRCSDATE2000A};     author kingdon; state Exp;
+branches;
+next   1.2;
+
+1.2
+date   ${RAWRCSDATE1996A};     author kingdon; state Exp;
+branches;
+next   1.1;
+
+1.1
+date   ${RAWRCSDATE1996B};     author kingdon; state Exp;
+branches;
+next   ;
+
+
+desc
address@hidden is for testing CVS
+@
+
+
+1.3
+log
address@hidden second line; modify twelfth line
+@
+text
address@hidden is the first line
+This is the third line
+This is the fourth line
+This is the fifth line
+This is the sixth line
+This is the seventh line
+This is the eighth line
+This is the ninth line
+This is the tenth line
+This is the eleventh line
+This is the twelfth line (and what a line it is)
+This is the thirteenth line
+@
+
+
+1.2
+log
address@hidden more lines
+@
+text
address@hidden 1
+This is the second line
+d11 1
+a11 1
+This is the twelfth line
+@
+
+
+1.1
+log
address@hidden file1
+@
+text
address@hidden 12
+@
+EOF
+         modify_repo mv $TESTDIR/file1,v $CVSROOT_DIRNAME/first-dir/file1,v
+
+         dotest rcs-1 "$testcvs -q co first-dir" 'U first-dir/file1'
+         cd first-dir
+         dotest rcs-2 "$testcvs -q log" "
+RCS file: $CVSROOT_DIRNAME/first-dir/file1,v
+Working file: file1
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+file1 is for testing CVS
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE2000A};  author: kingdon;  state: Exp;  lines: ${PLUS}1 -2;
+delete second line; modify twelfth line
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE1996A};  author: kingdon;  state: Exp;  lines: ${PLUS}12 -0;
+add more lines
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE1996B};  author: kingdon;  state: Exp;
+add file1
+============================================================================="
+
+         # Note that the dates here are chosen so that (a) we test
+         # at least one date after 2000, (b) we will notice if the
+         # month and day are getting mixed up with each other.
+         # TODO: also test that year isn't getting mixed up with month
+         # or day, for example 01-02-03.
+
+         # ISO8601 format.  There are many, many, other variations
+         # specified by ISO8601 which we should be testing too.
+         dotest rcs-3 "${testcvs} -q log -d '1996-12-11<'" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 3;    selected revisions: 1
+description:
+file1 is for testing CVS
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE2000A};  author: kingdon;  state: Exp;  lines: ${PLUS}1 -2;
+delete second line; modify twelfth line
+============================================================================="
+
+         # RFC822 format (as amended by RFC1123).
+         dotest rcs-4 "${testcvs} -q log -d '<3 Apr 2000 00:00'" \
+"
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 3;    selected revisions: 2
+description:
+file1 is for testing CVS
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE1996A};  author: kingdon;  state: Exp;  lines: ${PLUS}12 -0;
+add more lines
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE1996B};  author: kingdon;  state: Exp;
+add file1
+============================================================================="
+
+         # Intended behavior for "cvs annotate" is that it displays the
+         # last two digits of the year.  Make sure it does that rather
+         # than some bogosity like "100".
+         dotest rcs-4a "${testcvs} annotate file1" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1          (kingdon  24-Nov-96): This is the first line
+1\.2          (kingdon  24-Nov-96): This is the third line
+1\.2          (kingdon  24-Nov-96): This is the fourth line
+1\.2          (kingdon  24-Nov-96): This is the fifth line
+1\.2          (kingdon  24-Nov-96): This is the sixth line
+1\.2          (kingdon  24-Nov-96): This is the seventh line
+1\.2          (kingdon  24-Nov-96): This is the eighth line
+1\.2          (kingdon  24-Nov-96): This is the ninth line
+1\.2          (kingdon  24-Nov-96): This is the tenth line
+1\.2          (kingdon  24-Nov-96): This is the eleventh line
+1\.3          (kingdon  24-Nov-00): This is the twelfth line (and what a line 
it is)
+1\.2          (kingdon  24-Nov-96): This is the thirteenth line"
+
+         # Probably should split this test into two at this point (file1
+         # above this line and file2 below), as the two share little
+         # data/setup.
+
+         # OK, here is another one.  This one was written by hand based on
+         # doc/RCSFILES and friends.  One subtle point is that none of
+         # the lines end with newlines; that is a feature which we
+         # should be testing.
+         cat <<EOF >$TESTDIR/file2,v
+head                           1.5                 ;
+     branch        1.2.6;
+access ;
+symbols branch:1.2.6;
+locks;
+testofanewphrase @without newphrase we'd have trouble extending @@ all@ ;
+1.5 date 71.01.01.01.00.00; author joe; state bogus; branches; next 1.4;
+1.4 date 71.01.01.00.00.05; author joe; state bogus; branches; next 1.3;
+1.3 date 70.12.31.15.00.05; author joe; state bogus; branches; next 1.2;
+1.2 date 70.12.31.12.15.05; author me; state bogus; branches 1.2.6.1; next 1.1;
+1.1 date 70.12.31.11.00.05; author joe; state bogus; branches; next; newph;
+1.2.6.1 date 71.01.01.08.00.05; author joe; state Exp; branches; next;
+desc @@
+1.5 log @@ newphrase1; newphrase2 42; text @head revision@
+1.4 log @@ text @d1 1
+a1 1
+new year revision@
+1.3 log @@ text @d1 1
+a1 1
+old year revision@
+1.2 log @@ text @d1 1
+a1 1
+mid revision@ 1.1
+
+log           @@ text @d1 1
+a1 1
+start revision@
+1.2.6.1 log @@ text @d1 1
+a1 1
+branch revision@
+EOF
+         modify_repo mv $TESTDIR/file2,v $CVSROOT_DIRNAME/first-dir/file2,v
+         # ' Match the single quote in above here doc -- for font-lock mode.
+
+         # First test the default branch.
+         dotest rcs-5 "${testcvs} -q update file2" "U file2"
+         dotest rcs-6 "cat file2" "branch revision"
+
+         # Check in a revision on the branch to force CVS to
+         # interpret every revision in the file.
+         dotest rcs-6a "${testcvs} -q update -r branch file2" ""
+         echo "next branch revision" > file2
+         dotest rcs-6b "${testcvs} -q ci -m mod file2" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.2\.6\.2; previous revision: 1\.2\.6\.1"
+
+         # Now get rid of the default branch, it will get in the way.
+         dotest rcs-7 "${testcvs} admin -b file2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+         # But we do want to make sure that "cvs admin" leaves the newphrases
+         # in the file.
+         # The extra whitespace regexps are for the RCS library, which does
+         # not preserve whitespace in the dogmatic manner of RCS 5.7. -twp
+         dotest rcs-8 \
+"grep testofanewphrase ${CVSROOT_DIRNAME}/first-dir/file2,v" \
+"testofanewphrase[      ][     address@hidden newphrase we'd have trouble 
extending @@ address@hidden   ]*;"
+         # The easiest way to test for newphrases in deltas and deltatexts
+         # is to just look at the whole file, I guess.
+         dotest rcs-8a "cat ${CVSROOT_DIRNAME}/first-dir/file2,v" \
+"head  1\.5;
+access;
+symbols
+       branch:1.2.6;
+locks;
+
+testofanewphrase       @without newphrase we'd have trouble extending @@ all@;
+
+1\.5
+date   71\.01\.01\.01\.00\.00; author joe;     state bogus;
+branches;
+next   1\.4;
+
+1\.4
+date   71\.01\.01\.00\.00\.05; author joe;     state bogus;
+branches;
+next   1\.3;
+
+1\.3
+date   70\.12\.31\.15\.00\.05; author joe;     state bogus;
+branches;
+next   1\.2;
+
+1\.2
+date   70\.12\.31\.12\.15\.05; author me;      state bogus;
+branches
+       1\.2\.6\.1;
+next   1\.1;
+
+1\.1
+date   70\.12\.31\.11\.00\.05; author joe;     state bogus;
+branches;
+next   ;
+newph  ;
+
+1\.2\.6\.1
+date   71\.01\.01\.08\.00\.05; author joe;     state Exp;
+branches;
+next   1\.2\.6\.2;
+
+1\.2\.6\.2
+date   [0-9.]*;        author ${username};     state Exp;
+branches;
+next   ;
+commitid       ${commitid};
+
+
+desc
+@@
+
+
+1\.5
+log
+@@
+newphrase1     ;
+newphrase2     42;
+text
address@hidden revision@
+
+
+1\.4
+log
+@@
+text
address@hidden 1
+a1 1
+new year revision@
+
+
+1\.3
+log
+@@
+text
address@hidden 1
+a1 1
+old year revision@
+
+
+1\.2
+log
+@@
+text
address@hidden 1
+a1 1
+mid revision@
+
+
+1\.1
+log
+@@
+text
address@hidden 1
+a1 1
+start revision@
+
+
+1\.2\.6\.1
+log
+@@
+text
address@hidden 1
+a1 1
+branch revision@
+
+
+1\.2\.6\.2
+log
address@hidden
+@
+text
address@hidden 1
+a1 1
+next branch revision
+@"
+
+         dotest rcs-9 "${testcvs} -q update -p -D '1970-12-31 11:30 UT' file2" 
\
+"start revision"
+
+         dotest rcs-10 "${testcvs} -q update -p -D '1970-12-31 12:30 UT' 
file2" \
+"mid revision"
+
+         dotest rcs-11 "${testcvs} -q update -p -D '1971-01-01 00:30 UT' 
file2" \
+"new year revision"
+
+         # Same test as rcs-10, but with am/pm.
+         dotest rcs-12 "${testcvs} -q update -p -D 'December 31, 1970 12:30pm 
UT' file2" \
+"mid revision"
+
+         # Same test as rcs-11, but with am/pm.
+         dotest rcs-13 "${testcvs} -q update -p -D 'January 1, 1971 12:30am 
UT' file2" \
+"new year revision"
+
+         # OK, now make sure cvs log doesn't have any trouble with the
+         # newphrases and such.
+         dotest rcs-14 "${testcvs} -q log file2" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+Working file: file2
+head: 1\.5
+branch:
+locks:
+access list:
+symbolic names:
+       branch: 1\.2\.6
+keyword substitution: kv
+total revisions: 7;    selected revisions: 7
+description:
+----------------------------
+revision 1\.5
+date: 1971-01-01 01:00:00 [+-]0000;  author: joe;  state: bogus;  lines: 
${PLUS}1 -1;
+\*\*\* empty log message \*\*\*
+----------------------------
+revision 1\.4
+date: 1971-01-01 00:00:05 [+-]0000;  author: joe;  state: bogus;  lines: 
${PLUS}1 -1;
+\*\*\* empty log message \*\*\*
+----------------------------
+revision 1\.3
+date: 1970-12-31 15:00:05 [+-]0000;  author: joe;  state: bogus;  lines: 
${PLUS}1 -1;
+\*\*\* empty log message \*\*\*
+----------------------------
+revision 1\.2
+date: 1970-12-31 12:15:05 [+-]0000;  author: me;  state: bogus;  lines: 
${PLUS}1 -1;
+branches:  1\.2\.6;
+\*\*\* empty log message \*\*\*
+----------------------------
+revision 1\.1
+date: 1970-12-31 11:00:05 [+-]0000;  author: joe;  state: bogus;
+\*\*\* empty log message \*\*\*
+----------------------------
+revision 1\.2\.6\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+mod
+----------------------------
+revision 1\.2\.6\.1
+date: 1971-01-01 08:00:05 [+-]0000;  author: joe;  state: Exp;  lines: 
${PLUS}1 -1;
+\*\*\* empty log message \*\*\*
+============================================================================="
+         # Now test each date format for "cvs log -d".
+         # Earlier than 1971-01-01
+         dotest rcs-15 "${testcvs} -q log -d '<1971-01-01 00:00 GMT' file2 \
+           | grep revision" \
+"total revisions: 7;   selected revisions: 3
+revision 1\.3
+revision 1\.2
+revision 1\.1"
+         # Later than 1971-01-01
+         dotest rcs-16 "${testcvs} -q log -d '1971-01-01 00:00 GMT<' file2 \
+           | grep revision" \
+"total revisions: 7;   selected revisions: 4
+revision 1\.5
+revision 1\.4
+revision 1\.2\.6\.2
+revision 1\.2\.6\.1"
+         # Alternate syntaxes for later and earlier; multiple -d options
+         dotest rcs-17 "${testcvs} -q log -d '>1971-01-01 00:00 GMT' \
+           -d '1970-12-31 12:15 GMT>' file2 | grep revision" \
+"total revisions: 7;   selected revisions: 5
+revision 1\.5
+revision 1\.4
+revision 1\.1
+revision 1\.2\.6\.2
+revision 1\.2\.6\.1"
+         # Range, and single date
+         dotest rcs-18 "${testcvs} -q log -d '1970-12-31 11:30 GMT' \
+           -d '1971-01-01 00:00:05 GMT<1971-01-01 01:00:01 GMT' \
+           file2 | grep revision" \
+"total revisions: 7;   selected revisions: 2
+revision 1\.5
+revision 1\.1"
+         # Alternate range syntax; equality
+         dotest rcs-19 "${testcvs} -q log \
+           -d '1971-01-01 01:00:01 GMT>=1971-01-01 00:00:05 GMT' \
+           file2 | grep revision" \
+"total revisions: 7;   selected revisions: 2
+revision 1\.5
+revision 1\.4"
+
+         dokeep
+         TZ=$save_TZ
+         cd ..
+         rm -r first-dir
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       rcs2)
+         # More date tests.  Might as well do this as a separate
+         # test from "rcs", so that we don't need to perturb the
+         # "written by RCS 5.7" RCS file.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         # Significance of various dates:
+         # * At least one Y2K standard refers to recognizing 9 Sep 1999
+         #   (as an example of a pre-2000 date, I guess).
+         # * At least one Y2K standard refers to recognizing 1 Jan 2001
+         #   (as an example of a post-2000 date, I guess).
+         # * Many Y2K standards refer to 2000 being a leap year.
+         cat <<EOF >$TESTDIR/file1,v
+head 1.7; access; symbols; locks; strict;
+1.7 date 2004.08.31.01.01.01; author sue; state; branches; next 1.6;
+1.6 date 2004.02.29.01.01.01; author sue; state; branches; next 1.5;
+1.5 date 2003.02.28.01.01.01; author sue; state; branches; next 1.4;
+1.4 date 2001.01.01.01.01.01; author sue; state; branches; next 1.3;
+1.3 date 2000.02.29.01.01.01; author sue; state; branches; next 1.2;
+1.2 date 99.09.09.01.01.01; author sue; state; branches; next 1.1;
+1.1 date 98.09.10.01.01.01; author sue; state; branches; next;
+desc @a test file@
+1.7 log @@ text @head revision@
+1.6 log @@ text @d1 1
+a1 1
+2004 was a great year for leaping@
+1.5 log @@ text @d1 1
+a1 1
+2003 wasn't@
+1.4 log @@ text @d1 1
+a1 1
+two year hiatus@
+1.3 log @@ text @d1 1
+a1 1
+2000 is also a good year for leaping@
+1.2 log @@ text @d1 1
+a1 1
+Tonight we're going to party like it's a certain year@
+1.1 log @@ text @d1 1
+a1 1
+Need to start somewhere@
+EOF
+         modify_repo mv $TESTDIR/file1,v $CVSROOT_DIRNAME/first-dir/file1,v
+         # ' Match the 3rd single quote in the here doc -- for font-lock mode.
+
+         dotest rcs2-1 "${testcvs} -q co first-dir" 'U first-dir/file1'
+         cd first-dir
+
+         # 9 Sep 1999
+         dotest rcs2-2 "${testcvs} -q update -p -D '1999-09-09 11:30 UT' 
file1" \
+"Tonight we're going to party like it's a certain year"
+         # 1 Jan 2001.
+         dotest rcs2-3 "${testcvs} -q update -p -D '2001-01-01 11:30 UT' 
file1" \
+"two year hiatus"
+         # 29 Feb 2000
+         dotest rcs2-4 "${testcvs} -q update -p -D '2000-02-29 11:30 UT' 
file1" \
+"2000 is also a good year for leaping"
+         # 29 Feb 2003 is invalid
+         dotest_fail rcs2-5 "${testcvs} -q update -p -D '2003-02-29 11:30 UT' 
file1" \
+"$CPROG \[update aborted\]: Can't parse date/time: \`2003-02-29 11:30 UT'"
+
+         dotest rcs2-6 "${testcvs} -q update -p -D 2007-01-07 file1" \
+"head revision"
+         # This assumes that the clock of the machine running the tests
+         # is set to at least the year 1998 or so.  There don't seem
+         # to be a lot of ways to test the relative date code (short
+         # of something like LD_LIBRARY_PRELOAD'ing in our own
+         # getttimeofday, or hacking the CVS source with testing
+         # features, which always seems to be problematic since then
+         # someone feels like documenting them and things go downhill
+         # from there).
+         # 
+         # These tests can be expected to fail 3 times every 400 years
+         # starting Feb. 29, 2096 (because 8 years from that date would
+         # be Feb. 29, 2100, which is an invalid date -- 2100 isn't a
+         # leap year because it's divisible by 100 but not by 400).
+
+         dotest rcs2-7 "${testcvs} -q update -p -D '96 months' file1" \
+"head revision"
+         dotest rcs2-8 "${testcvs} -q update -p -D '8 years' file1" \
+"head revision"
+
+         dokeep
+         cd ..
+         rm -r first-dir
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       rcs3)
+         # More RCS file tests, in particular at least some of the
+         # error handling issues.
+         mkdir ${CVSROOT_DIRNAME}/first-dir
+         cat <<EOF >$TESTDIR/file1,v
+head 1.1; access; symbols; locks; expand o; 1.1 date 2007.03.20.04.03.02
+; author jeremiah ;state ;  branches; next;desc@@1.1log@@address@hidden@
+EOF
+         modify_repo mv $TESTDIR/file1,v $CVSROOT_DIRNAME/first-dir/file1,v
+         mkdir 1; cd 1
+         # CVS requires whitespace between "desc" and its value.
+         # The rcsfile(5) manpage doesn't really seem to answer the
+         # question one way or the other (it has a grammar but almost
+         # nothing about lexical analysis).
+         dotest_fail rcs3-1 "${testcvs} -q co first-dir" \
+"${SPROG} \[checkout aborted\]: EOF while looking for value in RCS file 
${CVSROOT_DIRNAME}/first-dir/file1,v"
+         cat <<EOF >$TESTDIR/file1,v
+head 1.1; access; symbols; locks; expand o; 1.1 date 2007.03.20.04.03.02
+; author jeremiah ;state ;  branches; next;desc @@1.1log@@address@hidden@
+EOF
+         modify_repo mv $TESTDIR/file1,v $CVSROOT_DIRNAME/first-dir/file1,v
+         # Whitespace issues, likewise.
+         dotest_fail rcs3-2 "${testcvs} -q co first-dir" \
+"${SPROG} \[checkout aborted\]: unexpected '.x6c' reading revision number in 
RCS file ${CVSROOT_DIRNAME}/first-dir/file1,v"
+         cat <<EOF >$TESTDIR/file1,v
+head 1.1; access; symbols; locks; expand o; 1.1 date 2007.03.20.04.03.02
+; author jeremiah ;state ;  branches; next;desc @@1.1 log@@address@hidden@
+EOF
+         modify_repo mv $TESTDIR/file1,v $CVSROOT_DIRNAME/first-dir/file1,v
+         # Charming array of different messages for similar
+         # whitespace issues (depending on where the whitespace is).
+         dotest_fail rcs3-3 "${testcvs} -q co first-dir" \
+"${SPROG} \[checkout aborted\]: EOF while looking for value in RCS file 
${CVSROOT_DIRNAME}/first-dir/file1,v"
+         cat <<EOF >$TESTDIR/file1,v
+head 1.1; access; symbols; locks; expand o; 1.1 date 2007.03.20.04.03.02
+; author jeremiah ;state ;  branches; next;desc @@1.1 log @@text @head@
+EOF
+         modify_repo mv $TESTDIR/file1,v $CVSROOT_DIRNAME/first-dir/file1,v
+         dotest rcs3-4 "${testcvs} -q co first-dir" 'U first-dir/file1'
+
+         # Ouch, didn't expect this one.  FIXCVS.  Or maybe just remove
+         # the feature, if this is a -s problem?
+         dotest_fail rcs3-5 "${testcvs} log -s nostate first-dir/file1" \
+"${DOTSTAR}ssertion.*failed${DOTSTAR}" "${DOTSTAR}failed assertion${DOTSTAR}"
+         cd first-dir
+         dotest_fail rcs3-5a "${testcvs} log -s nostate file1" \
+"${DOTSTAR}ssertion.*failed${DOTSTAR}" "${DOTSTAR}failed assertion${DOTSTAR}"
+         cd ..
+
+         # See remote code above for rationale for cd.
+         cd first-dir
+         dotest rcs3-6 "${testcvs} log -R file1" \
+"${CVSROOT_DIRNAME}/first-dir/file1,v"
+
+         # OK, now put an extraneous '\0' at the end.
+         mv $CVSROOT_DIRNAME/first-dir/file1,v $TESTDIR/file1,v
+         ${AWK} </dev/null 'BEGIN { printf "@%c", 10 }' | ${TR} '@' '\000' \
+           >>$TESTDIR/file1,v
+         modify_repo mv $TESTDIR/file1,v $CVSROOT_DIRNAME/first-dir/file1,v
+         dotest_fail rcs3-7 "${testcvs} log -s nostate file1" \
+"${SPROG} \[log aborted\]: unexpected '.x0' reading revision number in RCS 
file ${CVSROOT_DIRNAME}/first-dir/file1,v"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       rcs4)
+         # Fix a bug that shows up when checking out files by date with the
+         # "-D date" command line option.  There is code in the original to
+         # handle a special case.  If the date search finds revision 1.1 it
+         # is supposed to check whether revision 1.1.1.1 has the same date
+         # stamp, which would indicate that the file was originally brought
+         # in with "cvs import".  In that case it is supposed to return the
+         # vendor branch version 1.1.1.1.
+         # 
+         # However, there is a bug in the code. It actually compares
+         # the date of revision 1.1 for equality with the date given
+         # on the command line -- clearly wrong. This commit fixes
+         # the coding bug.
+         # 
+         # There is an additional bug which is _not_ fixed yet. 
+         # The date comparison should not be a strict
+         # equality test. It should allow a fudge factor of, say, 2-3
+         # seconds. Old versions of CVS created the two revisions
+         # with two separate invocations of the RCS "ci" command. We
+         # have many old files in the tree in which the dates of
+         # revisions 1.1 and 1.1.1.1 differ by 1 second.
+
+         # Need a predictable time zone.
+         save_TZ=$TZ
+         TZ=UTC0; export TZ
+
+          mkdir rcs4
+          cd rcs4
+
+         mkdir imp-dir
+         cd imp-dir
+         echo 'OpenMunger sources' >file1
+
+         # choose a time in the past to demonstrate the problem
+         touch -t 200012010123 file1
+
+         dotest_sort rcs4-1 \
+"${testcvs} import -d -m add rcs4-dir openmunger openmunger-1_0" \
+'
+
+N rcs4-dir/file1
+No conflicts created by this import'
+         echo 'OpenMunger sources release 1.1 extras' >>file1
+         touch -t 200112011234 file1
+         dotest_sort rcs4-2 \
+"${testcvs} import -d -m add rcs4-dir openmunger openmunger-1_1" \
+'
+
+No conflicts created by this import
+U rcs4-dir/file1'
+         cd ..
+         # Next checkout the new module
+         dotest rcs4-3 \
+"${testcvs} -q co rcs4-dir" \
+'U rcs4-dir/file1'
+         cd rcs4-dir
+         echo 'local change' >> file1
+
+         # commit a local change
+         dotest rcs4-4 "${testcvs} -q commit -m hack file1" \
+"$CVSROOT_DIRNAME/rcs4-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         # now see if we get version 1.1 or 1.1.1.1 when we ask for
+         # a checkout by time... it really should be 1.1.1.1 as
+          # that was indeed the version that was visible at the target
+         # time.
+         dotest rcs4-5 \
+"${testcvs} -q update -D 'October 1, 2001 UTC' file1" \
+'U file1'
+         dotest rcs4-6 \
+"${testcvs} -q status file1" \
+'===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1\.1\.1.*
+   Repository revision:        1\.1\.1\.1      
'${CVSROOT_DIRNAME}'/rcs4-dir/file1,v
+   Commit Identifier:  '${commitid}'
+   Sticky Tag:         (none)
+   Sticky Date:                2001\.10\.01\.00\.00\.00
+   Sticky Options:     (none)'
+
+         dokeep
+         TZ=$save_TZ
+         cd ../..
+          rm -r rcs4
+          modify_repo rm -rf $CVSROOT_DIRNAME/rcs4-dir
+         ;;
+
+
+
+       rcs5)
+         # Some tests of the $Log keyword and log message without a trailing
+         # EOL.  This used to look ugly and, in the worst case, could cause
+         # a seg fault due to a buffer overflow.
+         #
+         # Note that it should not be possible to create this situation via a
+         # CVS server (and any client), since the server itself inserts the
+         # trailing EOL onto log messages that are missing one.  Still, we
+         # shouldn't segfault due to a corrupt RCS file and I think that a log
+         # message without the trailing EOL doesn't actually violate the RCS
+         # spec, though it doesn't appear to be possible to create such a log
+         # message using RCS 5.7.
+
+         modify_repo mkdir $CVSROOT_DIRNAME/rcs5
+         cat <<\EOF >$TESTDIR/file1,v
+head 1.1;
+access;
+symbols;
+locks;
+expand kv;
+
+1.1 date 2007.03.20.04.03.02; author jeremiah; state Ext;  branches; next;
+
+desc
+@@
+
+1.1
+log
address@hidden always had very fine wine@
+text
address@hidden
+/*
+EOF
+echo ' * History: $''Log$' >>$TESTDIR/file1,v
+         cat <<\EOF >>$TESTDIR/file1,v
+ */
+line5
+@
+EOF
+         modify_repo mv $TESTDIR/file1,v $CVSROOT_DIRNAME/rcs5/file1,v
+
+          mkdir rcs5
+          cd rcs5
+         dotest rcs5-1 "$testcvs -Q co rcs5"
+         dotest rcs5-2 "cat rcs5/file1" \
+"line1
+/\\*
+ \\* History: "'\$'"Log: file1,v "'\$'"
+ \\* History: Revision 1\.1  2007/03/20 04:03:02  jeremiah
+ \\* History: he always had very fine wine
+ \\* History:
+ \\*/
+line5"
+
+         cd ..
+          rm -r rcs5
+          modify_repo rm -rf $CVSROOT_DIRNAME/rcs5
+         ;;
+
+
+
+       lockfiles)
+         # Tests of CVS lock files.
+         # TODO-maybe: Add a test where we arrange for a loginfo
+         # script (or some such) to ensure that locks are in place
+         # so then we can see how they are behaving.
+
+         if $proxy; then
+           # don't even try
+           continue
+         fi
+
+         mkdir 1; cd 1
+         mkdir sdir
+         mkdir sdir/ssdir
+         echo file >sdir/ssdir/file1
+         dotest lockfiles-1 \
+"${testcvs} -Q import -m import-it first-dir bar baz" ""
+         cd ..
+
+         mkdir 2; cd 2
+         dotest lockfiles-2 "${testcvs} -q co first-dir" \
+"U first-dir/sdir/ssdir/file1"
+         dotest lockfiles-3 "${testcvs} -Q co CVSROOT" ""
+         cd CVSROOT
+         echo "LockDir=${TESTDIR}/locks" >>config
+         dotest lockfiles-4 "${testcvs} -q ci -m config-it" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+         cd ../first-dir/sdir/ssdir
+         # The error message appears twice because Lock_Cleanup only
+         # stops recursing after the first attempt.
+         dotest_fail lockfiles-5 "${testcvs} -q update" \
+"${SPROG} \[update aborted\]: cannot stat ${TESTDIR}/locks: No such file or 
directory"
+         mkdir ${TESTDIR}/locks
+         # Grumble, mumble.  Cygwin.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod u=rwx,g=r,o= ${TESTDIR}/locks"
+         else
+           chmod u=rwx,g=r,o= ${TESTDIR}/locks
+         fi
+         save_umask=`umask`
+         umask 0077
+         CVSUMASK=0077; export CVSUMASK
+         dotest lockfiles-6 "${testcvs} -q update" ""
+         # TODO: should also be testing that CVS continues to honor the
+         # umask and CVSUMASK normally.  In the case of the umask, CVS
+         # doesn't seem to use it for much (although it perhaps should).
+         dotest lockfiles-7 "ls ${TESTDIR}/locks/first-dir/sdir/ssdir" ""
+
+         # The policy is that when CVS creates new lock directories, they
+         # inherit the permissions from the parent directory.  CVSUMASK
+         # isn't right, because typically the reason for LockDir is to
+         # use a different set of permissions.
+         #
+         # Bah!  Cygwin!
+         if test -n "$remotehost"; then
+           dotest lockfiles-7a "$CVS_RSH $remotehost 'ls -ld 
${TESTDIR}/locks/first-dir'" \
+"drwxr-----.*first-dir"
+           dotest lockfiles-7b "$CVS_RSH $remotehost 'ls -ld 
${TESTDIR}/locks/first-dir/sdir/ssdir'" \
+"drwxr-----.*first-dir/sdir/ssdir"
+         else
+           dotest lockfiles-7a "ls -ld ${TESTDIR}/locks/first-dir" \
+"drwxr-----.*first-dir"
+           dotest lockfiles-7b "ls -ld ${TESTDIR}/locks/first-dir/sdir/ssdir" \
+"drwxr-----.*first-dir/sdir/ssdir"
+         fi
+
+         cd ../../..
+         dotest lockfiles-8 "${testcvs} -q update" ""
+         dotest lockfiles-9 "${testcvs} -q co -l ." ""
+
+         ###
+         ### There are race conditions in the following tests, but hopefully
+         ### the 5 seconds the first process waits to remove the lockdir and
+         ### the 30 seconds CVS waits betweens checks will be significant
+         ### enough to render the case moot.
+         ###
+         # Considers the following cases:
+         #
+         #                    Lock Present
+         # Operation          Allowed (case #)
+         #
+         #                    Read      Promotable   Write
+         #                    _______   __________   ______
+         # Read              |Yes (1)   Yes (2)      No (3)
+         # Promotable Read   |Yes (4)   No (5)       No (6)
+         # Write             |No (7)    No (8)       No (9)
+         #
+         # Tests do not appear in same ordering as table:
+         # 1. Read when read locks are present...
+         # 2. Read when promotable locks are present...
+         # 3. Don't read when write locks present...
+         # 4. Read but don't write when read locks are present... (fail
+         #    commit up-to-date check with promotable lock present).
+         # 5. Don't allow promotable read when promotable locks are present...
+         #    (fail to perform commit up-to-date check with promotable lock
+         #     present).
+         # 6. Don't allow promotable read when write locks are present...
+         #    (fail to perform commit up-to-date check with promotable lock
+         #     present).
+         # 7. Don't write when read locks are present...
+         # 8. Don't write when promotable locks are present...
+         # 9. Don't write when write locks are present...
+
+         # 3. Don't read when write locks present...
+         mkdir "$TESTDIR/locks/first-dir/#cvs.lock"
+         (sleep 5; rmdir "$TESTDIR/locks/first-dir/#cvs.lock")&
+         dotest lockfiles-10 "$testcvs -q co -l first-dir" \
+"$SPROG checkout: \[[0-9:]*\] waiting for $username's lock in 
$CVSROOT_DIRNAME/first-dir
+$SPROG checkout: \[[0-9:]*\] obtained lock in $CVSROOT_DIRNAME/first-dir"
+
+         # 1. Read when read locks are present...
+         touch "$TESTDIR/locks/first-dir/#cvs.rfl.test.lock"
+         dotest lockfiles-11 "$testcvs -q co -l first-dir"
+         rm "$TESTDIR/locks/first-dir/#cvs.rfl.test.lock"
+
+         # 2. Read when promotable locks are present...
+         cd ..
+         mkdir 3; cd 3
+         touch "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.pfl.test.lock"
+         dotest lockfiles-12 "$testcvs -q co first-dir" \
+"U first-dir/sdir/ssdir/file1"
+         rm "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.pfl.test.lock"
+
+         # 7. Don't write when read locks are present...
+         echo I always have trouble coming up with witty text for the test 
files >>first-dir/sdir/ssdir/file1
+         touch "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.rfl.test.lock"
+         (sleep 5; rm 
"$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.rfl.test.lock")&
+         dotest lockfiles-13 "$testcvs -q ci -mconflict first-dir" \
+"$SPROG commit: \[[0-9:]*\] waiting for $username's lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$SPROG commit: \[[0-9:]*\] obtained lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$CVSROOT_DIRNAME/first-dir/sdir/ssdir/file1,v  <--  first-dir/sdir/ssdir/file1
+new revision: 1\.2; previous revision: 1\.1"
+
+         # 4. Read but don't write when read locks are present... (fail
+         #    commit up-to-date check with promotable lock present).
+         cd ../2
+         echo something that would render readers all full of smiles 
>>first-dir/sdir/ssdir/file1
+         touch "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.rfl.test.lock"
+         dotest_fail lockfiles-14 "$testcvs -q ci -mnot-up-to-date first-dir" \
+"$SPROG commit: Up-to-date check failed for \`first-dir/sdir/ssdir/file1'
+$SPROG \[commit aborted\]: correct above errors first!"
+         rm "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.rfl.test.lock"
+
+         # 5. Don't allow promotable read when promotable locks are present...
+         #    (fail to perform commit up-to-date check with promotable lock
+         #     present).
+         touch "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.pfl.test.lock"
+         (sleep 5; rm 
"$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.pfl.test.lock")&
+         dotest_fail lockfiles-15 "$testcvs -q ci -mnot-up-to-date first-dir" \
+"$SPROG commit: \[[0-9:]*\] waiting for $username's lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$SPROG commit: \[[0-9:]*\] obtained lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$SPROG commit: Up-to-date check failed for \`first-dir/sdir/ssdir/file1'
+$SPROG \[commit aborted\]: correct above errors first!"
+
+         # 6. Don't allow promotable read when write locks are present...
+         #    (fail to perform commit up-to-date check with promotable lock
+         #     present).
+         mkdir "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.lock"
+         (sleep 5; rmdir "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.lock")&
+         dotest_fail lockfiles-16 "$testcvs -q ci -mnot-up-to-date first-dir" \
+"$SPROG commit: \[[0-9:]*\] waiting for $username's lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$SPROG commit: \[[0-9:]*\] obtained lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$SPROG commit: Up-to-date check failed for \`first-dir/sdir/ssdir/file1'
+$SPROG \[commit aborted\]: correct above errors first!"
+
+         # 8. Don't write when promotable locks are present...
+         dotest lockfiles-17 "$testcvs -Q up -C first-dir/sdir/ssdir"
+         echo the kinds of smiles that light faces for miles 
>>first-dir/sdir/ssdir/file1
+         touch "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.pfl.test.lock"
+         (sleep 5; rm 
"$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.pfl.test.lock")&
+         dotest lockfiles-18 "$testcvs -q ci -mnot-up-to-date first-dir" \
+"$SPROG commit: \[[0-9:]*\] waiting for $username's lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$SPROG commit: \[[0-9:]*\] obtained lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$CVSROOT_DIRNAME/first-dir/sdir/ssdir/file1,v  <--  first-dir/sdir/ssdir/file1
+new revision: 1\.3; previous revision: 1\.2"
+
+         # 9. Don't write when write locks are present...
+         echo yet this poem would probably only give longfellow bile 
>>first-dir/sdir/ssdir/file1
+         mkdir "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.lock"
+         (sleep 5; rmdir "$TESTDIR/locks/first-dir/sdir/ssdir/#cvs.lock")&
+         dotest lockfiles-19 "$testcvs -q ci -mnot-up-to-date first-dir" \
+"$SPROG commit: \[[0-9:]*\] waiting for $username's lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$SPROG commit: \[[0-9:]*\] obtained lock in 
$CVSROOT_DIRNAME/first-dir/sdir/ssdir
+$CVSROOT_DIRNAME/first-dir/sdir/ssdir/file1,v  <--  first-dir/sdir/ssdir/file1
+new revision: 1\.4; previous revision: 1\.3"
+
+         # 10. Don't write when history locks are present...
+         echo have you ever heard a poem quite so vile\? 
>>first-dir/sdir/ssdir/file1
+         mkdir "$TESTDIR/locks/CVSROOT/#cvs.history.lock"
+         (sleep 5; rmdir "$TESTDIR/locks/CVSROOT/#cvs.history.lock")&
+         dotest lockfiles-20 "$testcvs -q ci -mnot-up-to-date first-dir" \
+"$CVSROOT_DIRNAME/first-dir/sdir/ssdir/file1,v  <--  first-dir/sdir/ssdir/file1
+new revision: 1\.5; previous revision: 1\.4
+$SPROG commit: \[[0-9:]*\] waiting for $username's lock in 
$CVSROOT_DIRNAME/CVSROOT
+$SPROG commit: \[[0-9:]*\] obtained lock in $CVSROOT_DIRNAME/CVSROOT"
+
+         dotest lockfiles-21 "$testcvs -Q tag newtag first-dir"
+
+         rm $CVSROOT_DIRNAME/CVSROOT/val-tags
+         mkdir "$TESTDIR/locks/CVSROOT/#cvs.val-tags.lock"
+         (sleep 5; rmdir "$TESTDIR/locks/CVSROOT/#cvs.val-tags.lock")&
+         dotest lockfiles-22 "$testcvs -q up -r newtag first-dir" \
+"$SPROG update: \[[0-9:]*\] waiting for $username's lock in 
$CVSROOT_DIRNAME/CVSROOT
+$SPROG update: \[[0-9:]*\] obtained lock in $CVSROOT_DIRNAME/CVSROOT"
+
+         cd CVSROOT
+         dotest lockfiles-cleanup-1 "$testcvs -q up -pr1.1 config >config" ""
+         dotest lockfiles-cleanup-2 "$testcvs -q ci -m config-it" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+         dokeep
+         cd ../..
+         # Restore umask.
+         umask $save_umask
+         unset CVSUMASK
+         rm -r $TESTDIR/locks
+         rm -r 1 2 3
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       backuprecover)
+         # Tests to make sure we get the expected behavior
+         # when we recover a repository from an old backup
+         #
+         # Details:
+         #   Backup will be older than some developer's workspaces
+         #     This means the first attempt at an update will fail
+         #     The workaround for this is to replace the CVS
+         #       directories with those from a "new" checkout from
+         #       the recovered repository.  Due to this, multiple
+         #       merges should cause conflicts (the same data
+         #       will be merged more than once).
+         #     A workspace updated before the date of the recovered
+         #       copy will not need any extra attention
+         #
+         # Note that backuprecover-15 is probably a failure case
+         #   If nobody else had a more recent update, the data would be lost
+         #     permanently
+         #   Granted, the developer should have been notified not to do this
+         #     by now, but still...
+         #
+         mkdir backuprecover; cd backuprecover
+         mkdir 1; cd 1
+         dotest backuprecover-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest backuprecover-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+         mkdir dir
+         dotest backuprecover-3 "${testcvs} add dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir added to the repository"
+         touch file1 dir/file2
+         dotest backuprecover-4 "${testcvs} -q add file1 dir/file2" \
+"${SPROG} add: use \`${SPROG} commit' to add these files permanently"
+         dotest backuprecover-5 "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dir/file2,v  <--  dir/file2
+initial revision: 1\.1"
+         echo "Line one" >>file1
+         echo "  is the place" >>file1
+         echo "    we like to begin" >>file1
+         echo "Anything else" >>file1
+         echo "  looks like" >>file1
+         echo "    a sin" >>file1
+         echo "File 2" >>dir/file2
+         echo "  is the place" >>dir/file2
+         echo "    the rest of it goes"  >>dir/file2
+         echo "Why I don't use" >>dir/file2
+         echo "  something like 'foo'" >>dir/file2
+         echo "    God only knows" >>dir/file2
+         dotest backuprecover-6 "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dir/file2,v  <--  dir/file2
+new revision: 1\.2; previous revision: 1\.1"
+
+         # Simulate the lazy developer
+         # (he did some work but didn't check it in...)
+         cd ../..
+         mkdir 2; cd 2
+         dotest backuprecover-7 "${testcvs} -Q co first-dir" ''
+         cd first-dir
+         sed -e "s/looks like/just looks like/" file1 >tmp; mv tmp file1
+         sed -e "s/don't use/don't just use/" dir/file2 >tmp; mv tmp dir/file2
+
+         # developer 1 is on a roll
+         cd ../../1/first-dir
+         echo "I need some more words" >>file1
+         echo "  to fill up this space" >>file1
+         echo "    anything else would be a disgrace" >>file1
+         echo "My rhymes cross many boundries" >>dir/file2
+         echo "  this time it's files" >>dir/file2
+         echo "    a word that fits here would be something like dials" 
>>dir/file2
+         dotest backuprecover-8 "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2
+$CVSROOT_DIRNAME/first-dir/dir/file2,v  <--  dir/file2
+new revision: 1\.3; previous revision: 1\.2"
+
+         # Save a backup copy
+         cp -R $CVSROOT_DIRNAME/first-dir $TESTDIR/backup
+
+         # Simulate developer 3
+         cd ../..
+         mkdir 3; cd 3
+         dotest backuprecover-9a "${testcvs} -Q co first-dir" ''
+         cd first-dir
+         echo >>file1
+         echo >>dir/file2
+         echo "Developer 1 makes very lame rhymes" >>file1
+         echo "  I think he should quit and become a mime" >>file1
+         echo "What the %*^# kind of rhyme crosses a boundry?" >>dir/file2
+         echo "  I think you should quit and get a job in the foundry" 
>>dir/file2
+         dotest backuprecover-9b "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.4; previous revision: 1\.3
+$CVSROOT_DIRNAME/first-dir/dir/file2,v  <--  dir/file2
+new revision: 1\.4; previous revision: 1\.3"
+
+         # Developer 4 so we can simulate a conflict later...
+         cd ../..
+         mkdir 4; cd 4
+         dotest backuprecover-10 "${testcvs} -Q co first-dir" ''
+         cd first-dir
+         sed -e "s/quit and/be fired so he can/" dir/file2 >tmp; mv tmp 
dir/file2
+
+         # And back to developer 1
+         cd ../../1/first-dir
+         dotest backuprecover-11 "${testcvs} -Q update" ''
+         echo >>file1
+         echo >>dir/file2
+         echo "Oh yeah, well rhyme this" >>file1
+         echo "  developer three" >>file1
+         echo "    you want opposition" >>file1
+         echo "      you found some in me!" >>file1
+         echo "I'll give you mimes" >>dir/file2
+         echo "  and foundries galore!"  >>dir/file2
+         echo "    your head will spin" >>dir/file2
+         echo "      once you find what's in store!" >>dir/file2
+         dotest backuprecover-12 "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.5; previous revision: 1\.4
+$CVSROOT_DIRNAME/first-dir/dir/file2,v  <--  dir/file2
+new revision: 1\.5; previous revision: 1\.4"
+
+         # developer 3'll do a bit of work that never gets checked in
+         cd ../../3/first-dir
+         dotest backuprecover-13 "${testcvs} -Q update" ''
+         sed -e "s/very/some extremely/" file1 >tmp; mv tmp file1
+         dotest backuprecover-14 "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.6; previous revision: 1\.5"
+         echo >>file1
+         echo "Tee hee hee hee" >>file1
+         echo >>dir/file2
+         echo "Find what's in store?" >>dir/file2
+         echo "  Oh, I'm so sure!" >>dir/file2
+         echo "    You've got an ill, and I have the cure!"  >>dir/file2
+
+         # Slag the original and restore it a few revisions back
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         modify_repo mv $TESTDIR/backup $CVSROOT_DIRNAME/first-dir
+
+         # Have developer 1 try an update and lose some data
+         #
+         # Feel free to imagine the horrific scream of despair
+         cd ../../1/first-dir
+         dotest backuprecover-15 "${testcvs} update" \
+"${SPROG} update: Updating .
+U file1
+${SPROG} update: Updating dir
+U dir/file2"
+
+         # Developer 3 tries the same thing (he has an office)
+         # but fails without losing data since all of his files have
+         # uncommitted changes
+         cd ../../3/first-dir
+         dotest_fail backuprecover-16 "${testcvs} update" \
+"${SPROG} update: Updating \.
+${SPROG} \[update aborted\]: could not find desired version 1\.6 in 
${CVSROOT_DIRNAME}/first-dir/file1,v"
+
+         # create our workspace fixin' script
+         cd ../..
+         echo \
+"#!$TESTSHELL
+
+# This script will copy the CVS database dirs from the checked out
+# version of a newly recovered repository and replace the CVS
+# database dirs in a workspace with later revisions than those in the
+# recovered repository
+cd repos-first-dir
+DATADIRS=\`find . -name CVS -print\`
+cd ../first-dir
+find . -name CVS -print | xargs rm -rf
+for file in \${DATADIRS}; do
+       cp -R ../repos-first-dir/\${file} \${file}
+done" >fixit
+
+         # We only need to fix the workspaces of developers 3 and 4
+         # (1 lost all her data and 2 has an update date from
+         # before the date the backup was made)
+         cd 3
+         dotest backuprecover-17 \
+               "${testcvs} -Q co -d repos-first-dir first-dir" ''
+         cd ../4
+         dotest backuprecover-18 \
+               "${testcvs} -Q co -d repos-first-dir first-dir" ''
+         sh ../fixit
+         cd ../3; sh ../fixit
+
+         # (re)commit developer 3's stuff
+         cd first-dir
+         dotest backuprecover-19 "${testcvs} -q ci -mrecover/merge" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.4; previous revision: 1\.3
+$CVSROOT_DIRNAME/first-dir/dir/file2,v  <--  dir/file2
+new revision: 1\.4; previous revision: 1\.3"
+
+         # and we should get a conflict on developer 4's stuff
+         cd ../../4/first-dir
+         dotest backuprecover-20 "${testcvs} update" \
+"${SPROG} update: Updating \.
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.3
+retrieving revision 1\.4
+Merging differences between 1\.3 and 1\.4 into file1
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in file1
+C file1
+${SPROG} update: Updating dir
+RCS file: ${CVSROOT_DIRNAME}/first-dir/dir/file2,v
+retrieving revision 1\.3
+retrieving revision 1\.4
+Merging differences between 1\.3 and 1\.4 into file2
+rcsmerge: warning: conflicts during merge
+${SPROG} update: conflicts found in dir/file2
+C dir/file2"
+         sed -e \
+"/^<<<<<<</,/^=======/d
+/^>>>>>>>/d" file1 >tmp; mv tmp file1
+         sed -e \
+"/^<<<<<<</,/^=======/d
+/^>>>>>>>/d
+s/quit and/be fired so he can/" dir/file2 >tmp; mv tmp dir/file2
+         dotest backuprecover-21 "${testcvs} -q ci -mrecover/merge" \
+"$CVSROOT_DIRNAME/first-dir/dir/file2,v  <--  dir/file2
+new revision: 1\.5; previous revision: 1\.4"
+
+         # go back and commit developer 2's stuff to prove it can still be done
+         cd ../../2/first-dir
+         dotest backuprecover-22 "${testcvs} -Q update" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.2
+retrieving revision 1\.4
+Merging differences between 1\.2 and 1\.4 into file1
+RCS file: ${CVSROOT_DIRNAME}/first-dir/dir/file2,v
+retrieving revision 1\.2
+retrieving revision 1\.5
+Merging differences between 1\.2 and 1\.5 into file2"
+         dotest backuprecover-23 "${testcvs} -q ci -mtest" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.5; previous revision: 1\.4
+$CVSROOT_DIRNAME/first-dir/dir/file2,v  <--  dir/file2
+new revision: 1\.6; previous revision: 1\.5"
+
+         # and restore the data to developer 1
+         cd ../../1/first-dir
+         dotest backuprecover-24 "${testcvs} -Q update" ''
+
+         dokeep
+         cd ../../..
+         rm -r backuprecover
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+        sshstdio)
+          # CVS_RSH=ssh can have a problem with a non-blocking stdio
+          # in some cases. So, this test is all about testing :ext:
+          # with CVS_RSH=ssh. The problem is that not all machines
+          # will necessarily have ssh available, so be prepared to
+          # skip this test.
+
+         if $proxy; then
+            notproxy sshstdio
+           continue
+         fi
+
+          if $remote; then :; else
+            remoteonly sshstdio
+           continue
+         fi
+
+         require_ssh
+         if test $? -eq 77; then
+            skip sshstdio "$skipreason"
+           continue
+         fi
+
+         SSHSTDIO_ROOT=:ext:$host$CVSROOT_DIRNAME
+
+          mkdir sshstdio; cd sshstdio
+          dotest sshstdio-1 "$testcvs -d $SSHSTDIO_ROOT -q co -l ."
+          mkdir first-dir
+          dotest sshstdio-2 "$testcvs add first-dir" \
+  "Directory $CVSROOT_DIRNAME/first-dir added to the repository"
+          cd first-dir
+          
a='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+          
c='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+          # Generate 1024 lines of $a
+          cnt=0
+          echo $a > aaa
+          while [ $cnt -lt 5 ] ; do
+            cnt=`expr $cnt + 1` ;
+            mv aaa aaa.old
+            cat aaa.old aaa.old aaa.old aaa.old > aaa
+          done
+          dotest sshstdio-3 "$testcvs -q add aaa" \
+"$SPROG add: use .$SPROG commit. to add this file permanently"
+          dotest sshstdio-4 "$testcvs -q ci -mcreate aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+initial revision: 1\.1"
+          # replace lines 1, 512, 513, 1024 with $c
+          sed 510q < aaa > aaa.old
+          (echo $c; cat aaa.old; echo $c; \
+           echo $c; cat aaa.old; echo $c) > aaa
+          dotest sshstdio-5 "$testcvs -q ci -mmodify-it aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.2; previous revision: 1\.1"
+          cat > wrapper.sh <<EOF
+#!$TESTSHELL
+exec "\$@" 2>&1 < /dev/null | cat
+EOF
+          chmod +x wrapper.sh
+          ./wrapper.sh \
+           $testcvs -z5 -Q diff --side-by-side -W 500 -r 1.1 -r 1.2 \
+             aaa > wrapper.dif
+  
+          $testcvs -z5 -Q diff --side-by-side -W 500 -r 1.1 -r 1.2 \
+             aaa > good.dif
+  
+          dotest sshstdio-6 "$diff_u wrapper.dif good.dif"
+
+         dokeep
+          cd ../..
+          CVS_RSH=$save_CVS_RSH; export CVS_RSH
+          rm -r sshstdio
+          rm -rf $CVSROOT_DIRNAME/first-dir
+          ;;
+
+
+
+       parseroot2)
+         # Test some :ext: roots for consistancy.
+         if $remote; then :; else
+           remoteonly parseroot2
+           continue
+         fi
+
+         require_rsh "$CVS_RSH"
+         if test $? -eq 77; then
+           skip parseroot2 "$skipreason"
+           continue
+         fi
+
+         # Test checking out and subsequently updating with some different
+         # CVSROOTs.
+
+         # A standard case, hostname:dirname.
+         mkdir parseroot2; cd parseroot2
+         save_CVSROOT=$CVSROOT
+         CVSROOT=$host:$CVSROOT_DIRNAME
+         dotest parseroot2-1 "$testcvs -Q co CVSROOT"
+         cd CVSROOT
+         dotest parseroot2-2 "$testcvs -Q up"
+         cd ..
+
+         # A degenerate remote case, just the server name and the directory
+         # name, with no :'s to help parsing.  It can be mistaken for a
+         # relative directory name.
+         rm -r CVSROOT
+         CVSROOT=$host$CVSROOT_DIRNAME
+         dotest parseroot2-3 "$testcvs -Q co CVSROOT"
+         cd CVSROOT
+         dotest parseroot2-4 "$testcvs -Q up"
+
+         dokeep
+         cd ../..
+         CVSROOT=$save_CVSROOT
+         rm -r parseroot2
+         ;;
+
+
+
+       parseroot3)
+         # Test some :ext: roots for consistancy.
+         if $remote; then :; else
+           remoteonly parseroot3
+           continue
+         fi
+
+         require_rsh "$CVS_RSH"
+         if test $? -eq 77; then
+           skip parseroot3 "$skipreason"
+           continue
+         fi
+
+         # Test checking out and subsequently updating with some different
+         # CVSROOTs.
+
+         # A standard case, hostname:dirname.
+         mkdir parseroot3; cd parseroot3
+         save_CVSROOT=$CVSROOT
+         save_CVS_RSH=$CVS_RSH
+         save_CVS_SERVER=$CVS_SERVER
+         unset CVS_RSH
+         unset CVS_SERVER
+         
CVSROOT=":ext;CVS_RSH=$save_CVS_RSH;CVS_SERVER=$save_CVS_SERVER:$host:$CVSROOT_DIRNAME"
+         dotest parseroot3-1 "$testcvs -Q co CVSROOT"
+         cd CVSROOT
+         dotest parseroot3-2 "$testcvs -Q up"
+         cd ..
+
+         # Initial checkout.
+         rm -r CVSROOT
+         
CVSROOT=":ext;cvs_RSH=$save_CVS_RSH;CVS_Server=$save_CVS_SERVER:$host$CVSROOT_DIRNAME"
+         dotest parseroot3-3 "$testcvs -Q co CVSROOT"
+         cd CVSROOT
+         dotest parseroot3-4 "$testcvs -Q up"
+         cd ..
+
+         # Checkout bogus values for Redirect
+         rm -r CVSROOT
+         
CVSROOT=":ext;Redirect=bogus;CVS_RSH=$save_CVS_RSH;CVS_SERVER=$save_CVS_SERVER:$host$CVSROOT_DIRNAME"
+         dotest parseroot3-5 "$testcvs -Q co CVSROOT" \
+"$SPROG checkout: CVSROOT: unrecognized value \`bogus' for \`Redirect'"
+         cd CVSROOT
+         # FIXCVS: parse_cvsroot is called more often that is
+         # desirable.    
+         dotest parseroot3-6 "$testcvs -Q up" \
+"$SPROG update: CVSROOT: unrecognized value \`bogus' for \`Redirect'"
+         cd ..
+
+         # Checkout good values for Redirect
+         rm -r CVSROOT
+         
CVSROOT=":EXT;Redirect=no;CVS_RSH=$save_CVS_RSH;CVS_SERVER=$save_CVS_SERVER:$host$CVSROOT_DIRNAME"
+         dotest parseroot3-7 "$testcvs -Q co CVSROOT"
+         cd CVSROOT
+         dotest parseroot3-8 "$testcvs -Q up"
+         cd ..
+
+         dotest parseroot3-9 "$testcvs -Q co -ldtop ."
+         dotest parseroot3-10 "test -d top"
+         dotest parseroot3-11 "test -d top/CVS"
+         dotest parseroot3-10 "cat top/CVS/Root" "$CVSROOT"
+
+         dokeep
+         cd ..
+         CVSROOT=$save_CVSROOT
+         CVS_RSH=$save_CVS_RSH
+         CVS_SERVER=$save_CVS_SERVER
+         export CVS_RSH CVS_SERVER
+         rm -r parseroot3
+         ;;
+
+
+
+       history)
+         # CVSROOT/history tests:
+         # history: various "cvs history" invocations
+         # basic2: Generating the CVSROOT/history file via CVS commands.
+
+         # Put in some data for the history file (discarding what was
+         # there before).  Note that this file format is fixed; the
+         # user may wish to analyze data from a previous version of
+         # CVS.  If we phase out this format, it should be done
+         # slowly and carefully.
+
+         if $proxy; then
+           # don't even try
+           continue
+         fi
+
+         cat <<EOF >$CVSROOT_DIRNAME/CVSROOT/history
+O3395c677|anonymous|<remote>/*0|ccvs||ccvs
+O3396c677|anonymous|<remote>/src|ccvs||src
+O3397c677|kingdon|<remote>/*0|ccvs||ccvs
+M339cafae|nk|<remote>|ccvs/src|1.229|sanity.sh
+M339cafff|anonymous|<remote>|ccvs/src|1.23|Makefile
+M339dc339|kingdon|~/work/*0|ccvs/src|1.231|sanity.sh
+W33a6eada|anonymous|<remote>*4|ccvs/emx||Makefile.in
+C3b235f50|kingdon|<remote>|ccvs/emx|1.3|README
+M3b23af50|kingdon|~/work/*0|ccvs/doc|1.281|cvs.texinfo
+EOF
+
+         dotest history-1 "${testcvs} history -e -a" \
+"O 1997-06-04 19:48 ${PLUS}0000 anonymous ccvs     =ccvs= <remote>/\*
+O 1997-06-05 14:00 ${PLUS}0000 anonymous ccvs     =src=  <remote>/\*
+M 1997-06-10 01:38 ${PLUS}0000 anonymous 1\.23  Makefile    ccvs/src == 
<remote>
+W 1997-06-17 19:51 ${PLUS}0000 anonymous       Makefile\.in ccvs/emx == 
<remote>/emx
+O 1997-06-06 08:12 ${PLUS}0000 kingdon   ccvs     =ccvs= <remote>/\*
+M 1997-06-10 21:12 ${PLUS}0000 kingdon   1\.231 sanity\.sh   ccvs/src == 
~/work/ccvs/src
+C 2001-06-10 11:51 ${PLUS}0000 kingdon   1\.3   README      ccvs/emx == 
<remote>
+M 2001-06-10 17:33 ${PLUS}0000 kingdon   1\.281 cvs\.texinfo ccvs/doc == 
~/work/ccvs/doc
+M 1997-06-10 01:36 ${PLUS}0000 nk        1\.229 sanity\.sh   ccvs/src == 
<remote>"
+
+         dotest history-2 "${testcvs} history -e -a -D '10 Jun 1997 13:00 UT'" 
\
+"W 1997-06-17 19:51 ${PLUS}0000 anonymous       Makefile\.in ccvs/emx == 
<remote>/emx
+M 1997-06-10 21:12 ${PLUS}0000 kingdon   1\.231 sanity\.sh   ccvs/src == 
~/work/ccvs/src
+C 2001-06-10 11:51 ${PLUS}0000 kingdon   1\.3   README      ccvs/emx == 
<remote>
+M 2001-06-10 17:33 ${PLUS}0000 kingdon   1\.281 cvs\.texinfo ccvs/doc == 
~/work/ccvs/doc"
+
+         dotest history-3 "${testcvs} history -e -a -D '10 Jun 2001 13:00 UT'" 
\
+"M 2001-06-10 17:33 ${PLUS}0000 kingdon 1\.281 cvs\.texinfo ccvs/doc == 
~/work/ccvs/doc"
+
+         dotest history-4 "${testcvs} history -ac sanity.sh" \
+"M 1997-06-10 21:12 ${PLUS}0000 kingdon 1\.231 sanity\.sh ccvs/src == 
~/work/ccvs/src
+M 1997-06-10 01:36 ${PLUS}0000 nk      1\.229 sanity\.sh ccvs/src == <remote>"
+
+         dotest history-5 "${testcvs} history -a -xCGUWAMR README sanity.sh" \
+"M 1997-06-10 21:12 ${PLUS}0000 kingdon 1\.231 sanity\.sh ccvs/src == 
~/work/ccvs/src
+C 2001-06-10 11:51 ${PLUS}0000 kingdon 1\.3   README    ccvs/emx == <remote>
+M 1997-06-10 01:36 ${PLUS}0000 nk      1\.229 sanity\.sh ccvs/src == <remote>"
+
+         dotest history-6 "${testcvs} history -xCGUWAMR -a -f README -f 
sanity.sh" \
+"M 1997-06-10 21:12 ${PLUS}0000 kingdon 1\.231 sanity\.sh ccvs/src == 
~/work/ccvs/src
+C 2001-06-10 11:51 ${PLUS}0000 kingdon 1\.3   README    ccvs/emx == <remote>
+M 1997-06-10 01:36 ${PLUS}0000 nk      1\.229 sanity\.sh ccvs/src == <remote>"
+
+         dotest history-7 "${testcvs} history -xCGUWAMR -a -f sanity.sh 
README" \
+"M 1997-06-10 21:12 ${PLUS}0000 kingdon 1\.231 sanity\.sh ccvs/src == 
~/work/ccvs/src
+C 2001-06-10 11:51 ${PLUS}0000 kingdon 1\.3   README    ccvs/emx == <remote>
+M 1997-06-10 01:36 ${PLUS}0000 nk      1\.229 sanity\.sh ccvs/src == <remote>"
+
+         dotest history-8 "${testcvs} history -ca -D '1970-01-01 00:00 UT'" \
+"M 1997-06-10 01:36 ${PLUS}0000 nk        1\.229 sanity.sh   ccvs/src == 
<remote>
+M 1997-06-10 01:38 ${PLUS}0000 anonymous 1\.23  Makefile    ccvs/src == 
<remote>
+M 1997-06-10 21:12 ${PLUS}0000 kingdon   1\.231 sanity.sh   ccvs/src == 
~/work/ccvs/src
+M 2001-06-10 17:33 ${PLUS}0000 kingdon   1\.281 cvs.texinfo ccvs/doc == 
~/work/ccvs/doc"
+
+         dotest history-9 "${testcvs} history -acl" \
+"M 2001-06-10 17:33 ${PLUS}0000 kingdon   1\.281 cvs.texinfo ccvs/doc == 
~/work/ccvs/doc
+M 1997-06-10 01:38 ${PLUS}0000 anonymous 1\.23  Makefile    ccvs/src == 
<remote>
+M 1997-06-10 21:12 ${PLUS}0000 kingdon   1\.231 sanity.sh   ccvs/src == 
~/work/ccvs/src"
+
+         dotest history-10 "${testcvs} history -lca -D '1970-01-01 00:00 UT'" \
+"M 2001-06-10 17:33 ${PLUS}0000 kingdon   1\.281 cvs.texinfo ccvs/doc == 
~/work/ccvs/doc
+M 1997-06-10 01:38 ${PLUS}0000 anonymous 1\.23  Makefile    ccvs/src == 
<remote>
+M 1997-06-10 21:12 ${PLUS}0000 kingdon   1\.231 sanity.sh   ccvs/src == 
~/work/ccvs/src"
+
+         dotest history-11 "${testcvs} history -aw" \
+"O 1997-06-04 19:48 ${PLUS}0000 anonymous ccvs =ccvs= <remote>/\*
+O 1997-06-05 14:00 ${PLUS}0000 anonymous ccvs =src=  <remote>/\*
+O 1997-06-06 08:12 ${PLUS}0000 kingdon   ccvs =ccvs= <remote>/\*"
+
+         dotest history-12 "${testcvs} history -aw -D'1970-01-01 00:00 UT'" \
+"O 1997-06-04 19:48 ${PLUS}0000 anonymous ccvs =ccvs= <remote>/\*
+O 1997-06-05 14:00 ${PLUS}0000 anonymous ccvs =src=  <remote>/\*
+O 1997-06-06 08:12 ${PLUS}0000 kingdon   ccvs =ccvs= <remote>/\*"
+         ;;
+
+
+
+       big)
+
+         # Test ability to operate on big files.  Intention is to
+         # test various realloc'ing code in RCS_deltas, rcsgetkey,
+         # etc.  "big" is currently defined to be 1000 lines (64000
+         # bytes), which in terms of files that users will use is not
+         # large, merely average, but my reasoning is that this
+         # should be big enough to make sure realloc'ing is going on
+         # and that raising it a lot would start to stress resources
+         # on machines which run the tests, without any significant
+         # benefit.
+
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest big-1 "$testcvs -q co first-dir"
+         cd first-dir
+         for i in 0 1 2 3 4 5 6 7 8 9; do
+           for j in 0 1 2 3 4 5 6 7 8 9; do
+             for k in 0 1 2 3 4 5 6 7 8 9; do
+               echo \
+"This is line ($i,$j,$k) which goes into the file file1 for testing" >>file1
+             done
+           done
+         done
+         dotest big-2 "$testcvs add file1" \
+"$SPROG add: scheduling file .file1. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         dotest big-3 "$testcvs -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         cd ..
+         mkdir 2
+         cd 2
+         dotest big-4 "$testcvs -q get first-dir" "U first-dir/file1"
+         cd ../first-dir
+         echo "add a line to the end" >>file1
+
+         dotest_fail big-4b "$testcvs -q diff -u" \
+"Index: file1
+===================================================================
+RCS file: $CVSROOT_DIRNAME/first-dir/file1,v
+retrieving revision 1\.1
+diff -u -r1\.1 file1
+--- file1      $RFCDATE        1\.1
+$PLUS$PLUS$PLUS file1  $RFCDATE
+@@ -998,3 ${PLUS}998,4 @@
+ This is line (9,9,7) which goes into the file file1 for testing
+ This is line (9,9,8) which goes into the file file1 for testing
+ This is line (9,9,9) which goes into the file file1 for testing
+${PLUS}add a line to the end"
+
+         dotest big-5 "$testcvs -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         cd ../2/first-dir
+         # The idea here is particularly to test the Rcs-diff response
+         # and the reallocing thereof, for remote.
+         dotest big-6 "$testcvs -q update" "U file1"
+
+         dokeep
+         cd ../..
+         rm -r first-dir 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       modes)
+         # Test repository permissions (CVSUMASK and so on).
+         # Although the tests in this section "cheat" by testing
+         # repository permissions, which are sort of not a user-visible
+         # sort of thing, the modes do have user-visible consequences,
+         # such as whether a second user can check out the files.  But
+         # it would be awkward to test the consequences, so we don't.
+
+         # Solaris /bin/sh doesn't support export -n.  I'm not sure
+         # what we can do about this, other than hope that whoever
+         # is running the tests doesn't have CVSUMASK set.
+         #export -n CVSUMASK # if unset, defaults to 002
+
+         save_umask=`umask`
+         umask 077
+         mkdir 1; cd 1
+         dotest modes-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest modes-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         touch aa
+         dotest modes-3 "${testcvs} add aa" \
+"${SPROG} add: scheduling file .aa. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest modes-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  aa
+initial revision: 1\.1"
+         # Yawn.  Cygwin.
+         if test -n "$remotehost"; then
+           dotest modes-5remotehost "$CVS_RSH $remotehost 'ls -l 
${CVSROOT_DIRNAME}/first-dir/aa,v'" \
+"-r--r--r-- .*"
+         else
+           dotest modes-5 "ls -l ${CVSROOT_DIRNAME}/first-dir/aa,v" \
+"-r--r--r-- .*"
+         fi
+
+         # Test for whether we can set the execute bit.
+         chmod +x aa
+         echo change it >>aa
+         dotest modes-6 "${testcvs} -q ci -m set-execute-bit" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  aa
+new revision: 1\.2; previous revision: 1\.1"
+         # If CVS let us update the execute bit, it would be set here.
+         # But it doesn't, and as far as I know that is longstanding
+         # CVS behavior.
+         #
+         # Yeah, yeah.  Search for "Cygwin".
+         if test -n "$remotehost"; then
+           dotest modes-7remotehost "$CVS_RSH $remotehost 'ls -l 
${CVSROOT_DIRNAME}/first-dir/aa,v'" \
+"-r--r--r-- .*"
+         else
+           dotest modes-7 "ls -l ${CVSROOT_DIRNAME}/first-dir/aa,v" \
+"-r--r--r-- .*"
+         fi
+
+         # OK, now manually change the modes and see what happens.
+         #
+         # Cygwin, already.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod g=r,o= 
${CVSROOT_DIRNAME}/first-dir/aa,v"
+         else
+           chmod g=r,o= ${CVSROOT_DIRNAME}/first-dir/aa,v
+         fi
+         echo second line >>aa
+         dotest modes-7a "${testcvs} -q ci -m set-execute-bit" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  aa
+new revision: 1\.3; previous revision: 1\.2"
+         # Cygwin.
+         if test -n "$remotehost"; then
+           dotest modes-7bremotehost "$CVS_RSH $remotehost 'ls -l 
${CVSROOT_DIRNAME}/first-dir/aa,v'" \
+"-r--r----- .*"
+         else
+           dotest modes-7b "ls -l ${CVSROOT_DIRNAME}/first-dir/aa,v" \
+"-r--r----- .*"
+         fi
+
+         # Check admin --execute
+         # Cygwin, already.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost \
+"chmod ugo=r ${CVSROOT_DIRNAME}/first-dir/aa,v"
+         else
+           chmod ugo=r ${CVSROOT_DIRNAME}/first-dir/aa,v
+         fi
+         dotest modes-execute-1 "${testcvs} admin --execute aa" \
+"RCS file: $CVSROOT_DIRNAME/first-dir/aa,v
+done"
+         # Cygwin.
+         if test -n "$remotehost"; then
+           dotest modes-execute-2r \
+"$CVS_RSH $remotehost 'ls -l ${CVSROOT_DIRNAME}/first-dir/aa,v'" \
+"-r-xr-xr-x .*"
+         else
+           dotest modes-execute-2 "ls -l ${CVSROOT_DIRNAME}/first-dir/aa,v" \
+"-r-xr-xr-x .*"
+         fi
+
+         # Test if admin --no-execute removes the execute bit:
+         dotest modes-execute-3 "${testcvs} admin --no-execute aa" \
+"RCS file: $CVSROOT_DIRNAME/first-dir/aa,v
+done"
+         # Cygwin.
+         if test -n "$remotehost"; then
+           dotest modes-execue-4r \
+"$CVS_RSH $remotehost 'ls -l ${CVSROOT_DIRNAME}/first-dir/aa,v'" \
+"-r--r--r-- .*"
+         else
+           dotest modes-execute-4 \
+"ls -l ${CVSROOT_DIRNAME}/first-dir/aa,v" \
+"-r--r--r-- .*"
+         fi
+
+         CVSUMASK=007
+         export CVSUMASK
+         touch ab
+         # Might as well test the execute bit too.
+         chmod +x ab
+         dotest modes-8 "$testcvs add ab" \
+"$SPROG add: scheduling file .ab. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         dotest modes-9 "$testcvs -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/ab,v  <--  ab
+initial revision: 1\.1"
+
+         # The ssh-wrapper script set up by this script forwards CVSUMASK to
+         # the server.  In practice it would be set on the server in some
+         # other manner (for instance, by the `env' command, or as an option
+         # in the xinted.conf file).
+         #
+         # I don't recall why, but I used to look for:
+         #
+         #   dotest modes-10remotehost \
+         #   "$CVS_RSH $remotehost 'ls -l $CVSROOT_DIRNAME/first-dir/ab,v'" \
+         #   "-r--r--r--.*"
+         #
+         # here when $remotehost was set.  I'm not sure why.  Maybe this was
+         # one of the innumerable Cygwin issues?
+         dotest modes-10 "ls -l $CVSROOT_DIRNAME/first-dir/ab,v" \
+"-r-xr-x---.*"
+
+         # Checkout --no-execute
+         # Test if admin --no-execute removes the execute bit:
+         dotest modes-execute-5 "${testcvs} admin --no-execute ab" \
+"RCS file: $CVSROOT_DIRNAME/first-dir/ab,v
+done"
+         # Cygwin.
+         if test -n "$remotehost"; then
+           dotest modes-execue-6r \
+"$CVS_RSH $remotehost 'ls -l ${CVSROOT_DIRNAME}/first-dir/ab,v'" \
+"-r--r----- .*"
+         else
+           dotest modes-execute-6 \
+"ls -l ${CVSROOT_DIRNAME}/first-dir/ab,v" \
+"-r--r----- .*"
+         dotest modes-execute-7 "${testcvs} admin --execute ab" \
+"RCS file: $CVSROOT_DIRNAME/first-dir/ab,v
+done"
+          fi
+         # Cygwin.
+         if test -n "$remotehost"; then
+           dotest modes-execue-8r \
+"$CVS_RSH $remotehost 'ls -l ${CVSROOT_DIRNAME}/first-dir/ab,v'" \
+"-r-xr-x--- .*"
+         else
+           dotest modes-execute-8 "ls -l ${CVSROOT_DIRNAME}/first-dir/ab,v" \
+"-r-xr-x--- .*"
+          fi
+
+         # OK, now add a file on a branch.  Check that the mode gets
+         # set the same way (it is a different code path in CVS).
+         dotest modes-11 "${testcvs} -q tag -b br" 'T aa
+T ab'
+         dotest modes-12 "${testcvs} -q update -r br" ''
+         touch ac
+         dotest modes-13 "${testcvs} add ac" \
+"${SPROG} add: scheduling file .ac. for addition on branch .br.
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         # Not sure it really makes sense to refer to a "previous revision"
+         # when we are just now adding the file; as far as I know
+         # that is longstanding CVS behavior, for what it's worth.
+         dotest modes-14 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/Attic/ac,v  <--  ac
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         # ssh-wrapper forwards CVSUMASK.  See modes-10 for notes.
+         dotest modes-15 \
+"ls -l ${CVSROOT_DIRNAME}/first-dir/Attic/ac,v" \
+"-r--r-----.*"
+
+         dokeep
+         cd ../..
+         # Restore umask.
+         umask $save_umask
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       modes2)
+         # More tests of file permissions in the working directory
+         # and that sort of thing.
+
+         # The usual setup, file first-dir/aa with two revisions.
+         mkdir 1; cd 1
+         dotest modes2-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest modes2-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         touch aa
+         dotest modes2-3 "${testcvs} add aa" \
+"${SPROG} add: scheduling file .aa. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest modes2-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  aa
+initial revision: 1\.1"
+         echo "more money" >> aa
+         dotest modes2-5 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  aa
+new revision: 1\.2; previous revision: 1\.1"
+
+         # OK, here is the test.  The idea is to see what
+         # No_Difference does if it can't open the file.
+         # If we don't change the st_mtime, CVS doesn't even try to read
+         # the file.  Note that some versions of "touch" require that we
+         # do this while the file is still writable.
+         touch aa
+         chmod a= aa
+         # Don't try this when permissions are broken, as with Cygwin.
+         if ${LS} ${CVSROOT_DIRNAME}/first-dir >/dev/null 2>&1; then :; else
+           dotest_fail modes2-6 "${testcvs} -q update -r 1.1 aa" \
+"${CPROG} \[update aborted\]: cannot open file aa for comparing: Permission 
denied" \
+"${CPROG} \[update aborted\]: reading aa: Permission denied"
+         fi
+
+         dokeep
+         chmod u+rwx aa
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       modes3)
+         # Repository permissions.  Particularly, what happens if we
+         # can't read/write in the repository.
+         # TODO: the case where we can access the repository, just not
+         # the attic (may that one can remain a fatal error, seems less
+         # useful for access control).
+         mkdir 1; cd 1
+         dotest modes3-1 "$testcvs -q co -l ."
+         mkdir first-dir second-dir
+         dotest modes3-2 "${testcvs} add first-dir second-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository
+Directory ${CVSROOT_DIRNAME}/second-dir added to the repository"
+         touch first-dir/aa second-dir/ab
+         dotest modes3-3 "${testcvs} add first-dir/aa second-dir/ab" \
+"${SPROG} add: scheduling file .first-dir/aa. for addition
+${SPROG} add: scheduling file .second-dir/ab. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest modes3-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  first-dir/aa
+initial revision: 1\.1
+$CVSROOT_DIRNAME/second-dir/ab,v  <--  second-dir/ab
+initial revision: 1\.1"
+         # quiet down this one as it will be noisy in proxy mode
+         modify_repo chmod a= $CVSROOT_DIRNAME/first-dir >/dev/null 2>&1
+         if ${LS} ${CVSROOT_DIRNAME}/first-dir >/dev/null 2>&1; then
+           # Avoid this test under Cygwin since permissions work differently
+           # there.
+           #
+           # This test also gets avoided under Mac OS X since the system `ls'
+           # is broken and exits with a 0 status despite the permission
+           # denied error.
+           if test -n "$remotehost"; then
+             cygwin_hack=false
+           else
+             cygwin_hack=:
+           fi
+         else
+           cygwin_hack=false
+         fi
+
+         cd $TESTDIR/1
+         if $cygwin_hack; then :; else
+           dotest modes3-5 "${testcvs} update" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating first-dir
+${SPROG} update: cannot open directory ${CVSROOT_DIRNAME}/first-dir: 
Permission denied
+${SPROG} update: skipping directory first-dir
+${SPROG} update: Updating second-dir"
+         fi
+
+         # OK, I can see why one might say the above case could be a
+         # fatal error, because normally users without access to first-dir
+         # won't have it in their working directory.  But the next
+         # one is more of a problem if it is fatal.
+         #
+         # The second text string below is for Cygwin again, and again it
+         # should really be XFAIL under Cygwin, but for now deal with the
+         # passing opendir by accepting the alternate string.
+         rm -r first-dir
+         dotest modes3-6 "${testcvs} update -dP" \
+"${SPROG} update: Updating .
+${SPROG} update: Updating CVSROOT
+U ${DOTSTAR}
+${SPROG} update: Updating first-dir
+${SPROG} update: cannot open directory ${CVSROOT_DIRNAME}/first-dir: 
Permission denied
+${SPROG} update: skipping directory first-dir
+${SPROG} update: Updating second-dir" \
+"${SPROG} update: Updating .
+${SPROG} update: Updating CVSROOT
+U ${DOTSTAR}
+${SPROG} update: Updating first-dir
+${SPROG} update: Updating second-dir"
+
+         dokeep
+         cd ..
+         rm -r 1
+         # quiet down this one as it will be noisy in proxy mode
+         modify_repo chmod u+rwx $CVSROOT_DIRNAME/first-dir 2>/dev/null
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir \
+                            $CVSROOT_DIRNAME/second-dir
+         ;;
+
+
+
+       stamps)
+         # Test timestamps.
+         mkdir 1; cd 1
+         dotest stamps-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest stamps-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         touch aa
+         echo '$''Id$' >kw
+         # Cygwin, *cough*, puts the year in the time column until the minute
+         # is no longer the current minute.  Sleep 60 seconds to avoid this
+         # problem.
+         sleep 60
+         ls -l aa >${TESTDIR}/1/stamp.aa.touch
+         ls -l kw >${TESTDIR}/1/stamp.kw.touch
+         # "sleep 1" would suffice if we could assume ls --full-time, but
+         # that is as far as I know unique to GNU ls.  Is there some POSIX.2
+         # way to get the timestamp of a file, including the seconds?
+         sleep 60
+         dotest stamps-3 "${testcvs} add aa kw" \
+"${SPROG} add: scheduling file .aa. for addition
+${SPROG} add: scheduling file .kw. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         ls -l aa >${TESTDIR}/1/stamp.aa.add
+         ls -l kw >${TESTDIR}/1/stamp.kw.add
+         # "cvs add" should not muck with the timestamp.
+         dotest stamps-4aa \
+"$diff_u $TESTDIR/1/stamp.aa.touch $TESTDIR/1/stamp.aa.add"
+         dotest stamps-4kw \
+"$diff_u $TESTDIR/1/stamp.kw.touch $TESTDIR/1/stamp.kw.add"
+         sleep 60
+         dotest stamps-5 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  aa
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/kw,v  <--  kw
+initial revision: 1\.1"
+         # Cygwin, *cough*, puts the year in the time column until the minute
+         # is no longer the current minute.  Sleep 60 seconds to avoid this
+         # problem.
+         sleep 60
+         ls -l aa >${TESTDIR}/1/stamp.aa.ci
+         ls -l kw >${TESTDIR}/1/stamp.kw.ci
+         # If there are no keywords, "cvs ci" leaves the timestamp alone
+         # If there are, it sets the timestamp to the date of the commit.
+         # I'm not sure how logical this is, but it is intentional.
+         # If we wanted to get fancy we would make sure the time as
+         # reported in "cvs log kw" matched stamp.kw.ci.  But that would
+         # be a lot of work.
+         dotest stamps-6aa \
+"$diff_u $TESTDIR/1/stamp.aa.add $TESTDIR/1/stamp.aa.ci"
+         dotest_fail stamps-6kw \
+"cmp $TESTDIR/1/stamp.kw.add $TESTDIR/1/stamp.kw.ci >/dev/null"
+         cd ../..
+         sleep 60
+         mkdir 2
+         cd 2
+         dotest stamps-7 "${testcvs} -q get first-dir" "U first-dir/aa
+U first-dir/kw"
+         cd first-dir
+         ls -l aa >${TESTDIR}/1/stamp.aa.get
+         ls -l kw >${TESTDIR}/1/stamp.kw.get
+         # On checkout, CVS should set the timestamp to the date that the
+         # file was committed.  Could check that the time as reported in
+         # "cvs log aa" matches stamp.aa.get, but that would be a lot of
+         # work.
+         dotest_fail stamps-8aa \
+"cmp $TESTDIR/1/stamp.aa.ci $TESTDIR/1/stamp.aa.get >/dev/null"
+         dotest stamps-8kw \
+"$diff_u $TESTDIR/1/stamp.kw.ci $TESTDIR/1/stamp.kw.get"
+
+         # Now we want to see what "cvs update" does.
+         sleep 60
+         echo add a line >>aa
+         echo add a line >>kw
+         dotest stamps-9 "${testcvs} -q ci -m change-them" \
+"$CVSROOT_DIRNAME/first-dir/aa,v  <--  aa
+new revision: 1\.2; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/kw,v  <--  kw
+new revision: 1\.2; previous revision: 1\.1"
+         
+         # Cygwin, *cough*, puts the year in the time column until the minute
+         # is no longer the current minute.  Sleep 60 seconds to avoid this
+         # problem.
+         sleep 60
+         ls -l aa >${TESTDIR}/1/stamp.aa.ci2
+         ls -l kw >${TESTDIR}/1/stamp.kw.ci2
+         cd ../..
+         cd 1/first-dir
+         sleep 60
+         dotest stamps-10 "${testcvs} -q update" 'U aa
+U kw'
+         # this doesn't serve any function other than being able to
+         # look at it manually, as we have no machinery for dates being
+         # newer or older than other dates.
+         date >$TESTDIR/1/stamp.debug.update
+         ls -l aa >$TESTDIR/1/stamp.aa.update
+         ls -l kw >$TESTDIR/1/stamp.kw.update
+         # stamp.aa.update and stamp.kw.update should both be approximately
+         # the same as stamp.debug.update.  Perhaps we could be testing
+         # this in a more fancy fashion by "touch stamp.before" before
+         # stamps-10, "touch stamp.after" after, and then using ls -t
+         # to check them.  But for now we just make sure that the *.update
+         # stamps differ from the *.ci2 ones.
+         # As for the rationale, this is so that if one updates and gets
+         # a new revision, then "make" will be sure to regard those files
+         # as newer than .o files which may be sitting around.
+         dotest_fail stamps-11aa \
+"cmp $TESTDIR/1/stamp.aa.update $TESTDIR/1/stamp.aa.ci2 >/dev/null"
+         dotest_fail stamps-11kw \
+"cmp $TESTDIR/1/stamp.kw.update $TESTDIR/1/stamp.kw.ci2 >/dev/null"
+
+         dokeep
+         cd ../..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       perms)
+         mkdir 1; cd 1
+         dotest perms-init-1 "$testcvs -Q co CVSROOT"
+         cd CVSROOT
+         echo 'PreservePermissions=yes' >> ${CVSROOT_DIRNAME}/CVSROOT/config
+         dotest perms-init-2 "$testcvs -Q ci -mperms"
+         cd ..
+
+         dotest perms-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest perms-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+
+         touch foo
+         chmod 431 foo
+         dotest perms-3 "${testcvs} add foo" \
+"${SPROG} add: scheduling file .foo. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest perms-4 "${testcvs} -q ci -m ''" \
+"$CVSROOT_DIRNAME/first-dir/foo,v  <--  foo
+initial revision: 1\.1"
+
+         # Test checking out files with different permissions.
+         cd ../..
+         mkdir 2; cd 2
+         dotest perms-5 "${testcvs} -q co first-dir" "U first-dir/foo"
+         cd first-dir
+         if $remote; then :; else
+           # PreservePermissions not yet implemented for remote.
+           dotest perms-6 "ls -l foo" "-r---wx--x .* foo"
+         fi
+
+         dokeep
+         cd ../1/CVSROOT
+         restore_adm
+         cd ../..
+         rm -rf 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       symlinks)
+         # short cut around checking out and committing CVSROOT
+         rm -f $CVSROOT_DIRNAME/CVSROOT/config
+         echo 'PreservePermissions=yes' >> $CVSROOT_DIRNAME/CVSROOT/config
+         chmod 444 $CVSROOT_DIRNAME/CVSROOT/config
+
+         mkdir 1; cd 1
+         dotest symlinks-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest symlinks-2 "$testcvs add first-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir added to the repository"
+         cd first-dir
+
+         dotest symlinks-2.1 "ln -s $TESTDIR/fumble slink"
+         dotest symlinks-3 "$testcvs add slink" \
+"$SPROG add: scheduling file .slink. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         if $remote; then
+           # Remote doesn't implement PreservePermissions, and in its
+           # absence the correct behavior is to follow the symlink.
+           dotest_fail symlinks-4r "$testcvs -q ci -m ''" \
+"$SPROG \[commit aborted\]: reading slink: No such file or directory"
+         else
+           dotest symlinks-4 "$testcvs -q ci -m ''" \
+"$CVSROOT_DIRNAME/first-dir/slink,v  <--  slink
+initial revision: 1\.1"
+
+           # Test checking out symbolic links.
+           cd ../..
+           mkdir 2; cd 2
+           dotest symlinks-5 "$testcvs -q co first-dir" "U first-dir/slink"
+           cd first-dir
+           dotest symlinks-6 "ls -l slink" \
+"l[rwx\-]* .* slink -> $TESTDIR/fumble"
+         fi
+
+         dokeep
+         cd ../..
+         rm -rf 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         restore_adm
+         ;;
+
+
+
+       symlinks2)
+         # Symlinks in working directory without PreservePermissions.
+         # Also see: symlinks: with PreservePermissions
+         # rcslib-symlink-*: symlinks in repository.
+         mkdir 1; cd 1
+         dotest symlinks2-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest symlinks2-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         echo nonsymlink > slink
+         dotest symlinks2-3 "${testcvs} add slink" \
+"${SPROG} add: scheduling file .slink. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest symlinks2-4 "${testcvs} -q ci -m ''" \
+"$CVSROOT_DIRNAME/first-dir/slink,v  <--  slink
+initial revision: 1\.1"
+         rm slink
+         # Choose name cvslog.* so it is in default ignore list.
+         echo second file >cvslog.file2
+         dotest symlinks2-5 "ln -s cvslog.file2 slink" ""
+         dotest symlinks2-6 "${testcvs} -q ci -m linkify" \
+"$CVSROOT_DIRNAME/first-dir/slink,v  <--  slink
+new revision: 1\.2; previous revision: 1\.1"
+         dotest symlinks2-7 "${testcvs} -q update -r 1.1 slink" "U slink"
+         dotest symlinks2-8 "cat slink" "nonsymlink"
+         dotest symlinks2-9 "ls -l slink" "-[-rwx]* .* slink"
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       hardlinks)
+         # short cut around checking out and committing CVSROOT
+         rm -f ${CVSROOT_DIRNAME}/CVSROOT/config
+         echo 'PreservePermissions=yes' > ${CVSROOT_DIRNAME}/CVSROOT/config
+         chmod 444 ${CVSROOT_DIRNAME}/CVSROOT/config
+
+         mkdir 1; cd 1
+         dotest hardlinks-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest hardlinks-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+
+         # Make up some ugly filenames, to test that they get
+         # encoded properly in the delta nodes.  Note that `dotest' screws
+         # up if some arguments have embedded spaces.
+         if touch aaaa
+         then
+           pass hardlinks-2.1
+         else
+           fail hardlinks-2.1
+         fi
+
+         if ln aaaa b.b.b.b
+         then
+           pass hardlinks-2.2
+         else
+           fail hardlinks-2.2
+         fi
+
+         if ln aaaa 'dd dd dd'
+         then
+           pass hardlinks-2.3
+         else
+           fail hardlinks-2.3
+         fi
+
+         dotest hardlinks-3 "${testcvs} add [abd]*" \
+"${SPROG} add: scheduling file .aaaa. for addition
+${SPROG} add: scheduling file .b\.b\.b\.b. for addition
+${SPROG} add: scheduling file .dd dd dd. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest hardlinks-4 "${testcvs} -q ci -m ''" \
+"$CVSROOT_DIRNAME/first-dir/aaaa,v  <--  aaaa
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/b\.b\.b\.b,v  <--  b\.b\.b\.b
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/dd dd dd,v  <--  dd dd dd
+initial revision: 1\.1"
+         # Test checking out hardlinked files.
+         cd ../..
+         mkdir 2; cd 2
+         if $remote; then
+           # Remote does not implement PreservePermissions.
+           dotest hardlinks-5r "${testcvs} -q co first-dir" \
+"U first-dir/aaaa
+U first-dir/b\.b\.b\.b
+U first-dir/dd dd dd"
+           cd first-dir
+           dotest hardlinks-6r "ls -l [abd]*" \
+"-[rwx\-]* *1 .* aaaa
+-[rwx\-]* *1 .* b\.b\.b\.b
+-[rwx\-]* *1 .* dd dd dd"
+         else
+           dotest hardlinks-5 "${testcvs} -q co first-dir" \
+"U first-dir/aaaa
+U first-dir/b\.b\.b\.b
+U first-dir/dd dd dd"
+           cd first-dir
+           # To make sure that the files are properly hardlinked, it
+           # would be nice to do `ls -i' and make sure all the inodes
+           # match.  But I think that would require expr to support
+           # tagged regexps, and I don't think we can rely on that.
+           # So instead we just see that each file has the right
+           # number of links. -twp
+           dotest hardlinks-6 "ls -l [abd]*" \
+"-[rwx\-]* *3 .* aaaa
+-[rwx\-]* *3 .* b\.b\.b\.b
+-[rwx\-]* *3 .* dd dd dd"
+         fi
+
+         dokeep
+         cd ../..
+         rm -rf 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         restore_adm
+         ;;
+
+
+
+       sticky)
+         # More tests of sticky tags, particularly non-branch sticky tags.
+         # See many tests (e.g. multibranch) for ordinary sticky tag
+         # operations such as adding files on branches.
+         # See "head" test for interaction between stick tags and HEAD.
+         mkdir 1; cd 1
+         dotest sticky-1 "$testcvs -q co -l ."
+         mkdir first-dir
+         dotest sticky-2 "$testcvs add first-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir added to the repository"
+         cd first-dir
+
+         touch file1
+         dotest sticky-3 "$testcvs add file1" \
+"$SPROG add: scheduling file .file1. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         dotest sticky-4 "$testcvs -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         dotest sticky-5 "$testcvs -q tag tag1" "T file1"
+         echo add a line >>file1
+         dotest sticky-6 "$testcvs -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         dotest sticky-7 "$testcvs -q update -r tag1" "U file1"
+         dotest sticky-8 "cat file1" ''
+         dotest sticky-9 "$testcvs -q update" ''
+         dotest sticky-10 "cat file1" ''
+         touch file2
+         dotest_fail sticky-11 "$testcvs add file2" \
+"$SPROG add: cannot add file on non-branch tag \`tag1'"
+         dotest sticky-12 "$testcvs -q update -A" "U file1
+$QUESTION file2" "$QUESTION file2
+U file1"
+         dotest sticky-13 "${testcvs} add file2" \
+"$SPROG add: scheduling file .file2. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         dotest sticky-14 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+
+         # Now back to tag1
+         dotest sticky-15 "${testcvs} -q update -r tag1" "U file1
+$SPROG update: \`file2' is no longer in the repository"
+
+         rm file1
+         dotest sticky-16 "${testcvs} rm file1" \
+"$SPROG remove: scheduling .file1. for removal
+$SPROG remove: use .$SPROG commit. to remove this file permanently"
+         # Hmm, this command seems to silently remove the tag from
+         # the file.  This appears to be intentional.
+         # The silently part especially strikes me as odd, though.
+         dotest sticky-17 "$testcvs -q ci -m remove-it" ""
+         dotest sticky-18 "$testcvs -q update -A" "U file1
+U file2"
+         dotest sticky-19 "$testcvs -q update -r tag1" \
+"${SPROG} update: \`file1' is no longer in the repository
+${SPROG} update: \`file2' is no longer in the repository"
+         dotest sticky-20 "$testcvs -q update -A" "U file1
+U file2"
+
+         # Now try with a numeric revision.
+         dotest sticky-21 "$testcvs -q update -r 1.1 file1" "U file1"
+         dotest sticky-22 "$testcvs rm -f file1" \
+"$SPROG remove: cannot remove file .file1. which has a numeric sticky tag of 
.1\.1."
+         # The old behavior was that remove allowed this and then commit
+         # gave an error, which was somewhat hard to clear.  I mean, you
+         # could get into a long elaborate discussion of this being a
+         # conflict and two ways to resolve it, but I don't really see
+         # why CVS should have a concept of conflict that arises, not from
+         # parallel development, but from CVS's own sticky tags.
+
+         # Ditto with a sticky date.
+         #
+         # I'm kind of surprised that the "file1 was lost" doesn't crop
+         # up elsewhere in the testsuite.  It is a long-standing
+         # discrepency between local and remote CVS and should probably
+         # be cleaned up at some point.
+         dotest sticky-23 "$testcvs -q update -Dnow file1" \
+"$SPROG update: warning: \`file1' was lost
+U file1" "U file1"
+         dotest sticky-24 "$testcvs rm -f file1" \
+"$SPROG remove: cannot remove file .file1. which has a sticky date of 
.[0-9.]*."
+
+         dotest sticky-25 "$testcvs -q update -A" \
+"$SPROG update: warning: \`file1' was lost
+U file1" "U file1"
+
+         dokeep
+         restore_adm
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       keyword)
+         # Test keyword expansion.
+         # Various other tests relate to our ability to correctly
+         # set the keyword expansion mode.
+         # "binfiles" tests "cvs admin -k".
+         # "binfiles" and "binfiles2" test "cvs add -k".
+         # "rdiff" tests "cvs co -k".
+         # "binfiles" (and this test) test "cvs update -k".
+         # "binwrap" tests setting the mode from wrappers.
+         # "keyword2" tests "cvs update -kk -j" with text and binary files
+         # I don't think any test is testing "cvs import -k".
+         # Other keyword expansion tests:
+         #   keywordlog - $Log.
+         mkdir 1; cd 1
+         dotest keyword-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest keyword-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+
+         echo '$''Author$' > file1
+         echo '$''Date$' >> file1
+         echo '$''Header$' >> file1
+         echo '$''Id$' >> file1
+         echo '$''Locker$' >> file1
+         echo '$''Name$' >> file1
+         echo '$''RCSfile$' >> file1
+         echo '$''Revision$' >> file1
+         echo '$''Source$' >> file1
+         echo '$''State$' >> file1
+         echo '$''Nonkey$' >> file1
+         # Omit the trailing dollar sign
+         echo '$''Date' >> file1
+         # Put two keywords on one line
+         echo '$''State$' '$''State$' >> file1
+         # Use a header for Log
+         echo 'xx $''Log$' >> file1
+
+         dotest keyword-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest keyword-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         dotest keyword-5 "cat file1" \
+'\$'"Author: ${username} "'\$'"
+"'\$'"Date: [0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9] 
[0-9][0-9]:[0-9][0-9]:[0-9][0-9] "'\$'"
+"'\$'"Header: ${CVSROOT_DIRNAME}/first-dir/file1,v 1\.1 [0-9/]* [0-9:]* 
${username} Exp "'\$'"
+"'\$'"Id: file1,v 1\.1 [0-9/]* [0-9:]* ${username} Exp "'\$'"
+"'\$'"Locker:  "'\$'"
+"'\$'"Name:  "'\$'"
+"'\$'"RCSfile: file1,v "'\$'"
+"'\$'"Revision: 1\.1 "'\$'"
+"'\$'"Source: ${CVSROOT_DIRNAME}/first-dir/file1,v "'\$'"
+"'\$'"State: Exp "'\$'"
+"'\$'"Nonkey"'\$'"
+"'\$'"Date
+"'\$'"State: Exp "'\$'" "'\$'"State: Exp "'\$'"
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.1  [0-9/]* [0-9:]*  ${username}
+xx add
+xx"
+
+         # Use cvs admin to lock the RCS file in order to check -kkvl
+         # vs. -kkv.  CVS does not normally lock RCS files, but some
+         # people use cvs admin to enforce reserved checkouts.
+         dotest keyword-6 "${testcvs} admin -l file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+1\.1 locked
+done"
+
+         dotest keyword-7 "${testcvs} update -kkv file1" "U file1"
+         dotest keyword-8 "cat file1" \
+'\$'"Author: ${username} "'\$'"
+"'\$'"Date: [0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9] 
[0-9][0-9]:[0-9][0-9]:[0-9][0-9] "'\$'"
+"'\$'"Header: ${CVSROOT_DIRNAME}/first-dir/file1,v 1\.1 [0-9/]* [0-9:]* 
${username} Exp "'\$'"
+"'\$'"Id: file1,v 1\.1 [0-9/]* [0-9:]* ${username} Exp "'\$'"
+"'\$'"Locker:  "'\$'"
+"'\$'"Name:  "'\$'"
+"'\$'"RCSfile: file1,v "'\$'"
+"'\$'"Revision: 1\.1 "'\$'"
+"'\$'"Source: ${CVSROOT_DIRNAME}/first-dir/file1,v "'\$'"
+"'\$'"State: Exp "'\$'"
+"'\$'"Nonkey"'\$'"
+"'\$'"Date
+"'\$'"State: Exp "'\$'" "'\$'"State: Exp "'\$'"
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.1  [0-9/]* [0-9:]*  ${username}
+xx add
+xx"
+
+         dotest keyword-9 "${testcvs} update -kkvl file1" "U file1"
+         dotest keyword-10 "cat file1" \
+'\$'"Author: ${username} "'\$'"
+"'\$'"Date: ${RCSKEYDATE} "'\$'"
+"'\$'"Header: ${CVSROOT_DIRNAME}/first-dir/file1,v 1\.1 ${RCSKEYDATE} 
${username} Exp ${username} "'\$'"
+"'\$'"Id: file1,v 1\.1 ${RCSKEYDATE} ${username} Exp ${username} "'\$'"
+"'\$'"Locker: ${username} "'\$'"
+"'\$'"Name:  "'\$'"
+"'\$'"RCSfile: file1,v "'\$'"
+"'\$'"Revision: 1\.1 "'\$'"
+"'\$'"Source: ${CVSROOT_DIRNAME}/first-dir/file1,v "'\$'"
+"'\$'"State: Exp "'\$'"
+"'\$'"Nonkey"'\$'"
+"'\$'"Date
+"'\$'"State: Exp "'\$'" "'\$'"State: Exp "'\$'"
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.1  ${RCSKEYDATE}  ${username}
+xx add
+xx"
+
+         dotest keyword-11 "${testcvs} update -kk file1" "U file1"
+         dotest keyword-12 "cat file1" \
+'\$'"Author"'\$'"
+"'\$'"Date"'\$'"
+"'\$'"Header"'\$'"
+"'\$'"Id"'\$'"
+"'\$'"Locker"'\$'"
+"'\$'"Name"'\$'"
+"'\$'"RCSfile"'\$'"
+"'\$'"Revision"'\$'"
+"'\$'"Source"'\$'"
+"'\$'"State"'\$'"
+"'\$'"Nonkey"'\$'"
+"'\$'"Date
+"'\$'"State"'\$'" "'\$'"State"'\$'"
+xx "'\$'"Log"'\$'"
+xx Revision 1\.1  ${RCSKEYDATE}  ${username}
+xx add
+xx"
+
+         dotest keyword-13 "${testcvs} update -kv file1" "U file1"
+         dotest keyword-14 "cat file1" \
+"${username}
+${RCSKEYDATE}
+${CVSROOT_DIRNAME}/first-dir/file1,v 1\.1 ${RCSKEYDATE} ${username} Exp
+file1,v 1\.1 ${RCSKEYDATE} ${username} Exp
+
+
+file1,v
+1\.1
+${CVSROOT_DIRNAME}/first-dir/file1,v
+Exp
+"'\$'"Nonkey"'\$'"
+"'\$'"Date
+Exp Exp
+xx file1,v
+xx Revision 1\.1  ${RCSKEYDATE}  ${username}
+xx add
+xx"
+
+         dotest keyword-15 "${testcvs} update -ko file1" "U file1"
+         dotest keyword-16 "cat file1" \
+'\$'"Author"'\$'"
+"'\$'"Date"'\$'"
+"'\$'"Header"'\$'"
+"'\$'"Id"'\$'"
+"'\$'"Locker"'\$'"
+"'\$'"Name"'\$'"
+"'\$'"RCSfile"'\$'"
+"'\$'"Revision"'\$'"
+"'\$'"Source"'\$'"
+"'\$'"State"'\$'"
+"'\$'"Nonkey"'\$'"
+"'\$'"Date
+"'\$'"State"'\$'" "'\$'"State"'\$'"
+xx "'\$'"Log"'\$'
+
+         # Test the Name keyword.  First go back to normal expansion.
+
+         dotest keyword-17 "${testcvs} update -A file1" "U file1"
+
+         echo '$''Name$' > file1
+         dotest keyword-18 "${testcvs} ci -m modify file1" \
+"${CVSROOT_DIRNAME}/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         dotest keyword-19 "${testcvs} -q tag tag1" "T file1"
+         echo "change" >> file1
+         dotest keyword-20 "${testcvs} -q ci -m mod2 file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2"
+         # FIXCVS - These unpatchable files are happening because the tag
+         # associated with the current base version of the file in the
+         # sandbox is not available in these cases.  See the note in the
+         # patch_file function in update.c.
+         dotest keyword-21 "${testcvs} -q update -r tag1" "U file1" \
+"$CPROG update: checksum failure after patch to \`\./file1'; will refetch
+$CPROG client: refetching unpatchable files
+U file1"
+
+         dotest keyword-22 "cat file1" '\$'"Name: tag1 "'\$'
+
+         if $remote; then
+           # Like serverpatch-8.  Not sure there is anything much we
+           # can or should do about this.
+           dotest keyword-23r "${testcvs} update -A file1" \
+"$CPROG update: checksum failure after patch to \`\./file1'; will refetch
+$CPROG client: refetching unpatchable files
+U file1"
+         else
+           dotest keyword-23 "${testcvs} update -A file1" "U file1"
+         fi
+         dotest keyword-24 "cat file1" '\$'"Name:  "'\$'"
+change"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       keywordlog)
+         # Test the Log keyword.
+         mkdir 1; cd 1
+         dotest keywordlog-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest keywordlog-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+         echo initial >file1
+         dotest keywordlog-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+
+         # See "rmadd" for a list of other tests of cvs ci -r.
+         dotest keywordlog-4 "${testcvs} -q ci -r 1.3 -m add file1" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.3"
+
+         cd ../..
+         mkdir 2; cd 2
+         dotest keywordlog-4a "${testcvs} -q co first-dir" "U first-dir/file1"
+         cd ../1/first-dir
+
+         echo 'xx $''Log$' >> file1
+         cat >${TESTDIR}/comment.tmp <<EOF
+First log line
+Second log line
+EOF
+         # As with rmadd-25, "cvs ci -r" sets a sticky tag.
+         dotest_fail keywordlog-4b \
+"${testcvs} ci -F ${TESTDIR}/comment.tmp file1" \
+"${SPROG} commit: sticky tag .1\.3. for file .file1. is not a branch
+${SPROG} \[commit aborted\]: correct above errors first!"
+         dotest keywordlog-4c "${testcvs} -q update -A" "M file1"
+
+         dotest keywordlog-5 "${testcvs} ci -F ${TESTDIR}/comment.tmp file1" \
+"${CVSROOT_DIRNAME}/first-dir/file1,v  <--  file1
+new revision: 1\.4; previous revision: 1\.3"
+         rm -f ${TESTDIR}/comment.tmp
+         dotest keywordlog-6 "${testcvs} -q tag -b br" "T file1"
+         dotest keywordlog-7 "cat file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx"
+
+         cd ../../2/first-dir
+         dotest keywordlog-8 "${testcvs} -q update" "U file1"
+         dotest keywordlog-9 "cat file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx"
+         cd ../../1/first-dir
+
+         echo "change" >> file1
+         dotest keywordlog-10 "${testcvs} ci -m modify file1" \
+"${CVSROOT_DIRNAME}/first-dir/file1,v  <--  file1
+new revision: 1\.5; previous revision: 1\.4"
+         dotest keywordlog-11 "cat file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.5  ${RCSKEYDATE}  ${username}
+xx modify
+xx
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx
+change"
+
+         cd ../../2/first-dir
+         dotest keywordlog-12 "${testcvs} -q update" "U file1"
+         dotest keywordlog-13 "cat file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.5  ${RCSKEYDATE}  ${username}
+xx modify
+xx
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx
+change"
+
+         cd ../../1/first-dir
+         dotest keywordlog-14 "${testcvs} -q update -r br" "U file1"
+         echo br-change >>file1
+         dotest keywordlog-15 "${testcvs} -q ci -m br-modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.4\.2\.1; previous revision: 1\.4"
+         dotest keywordlog-16 "cat file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.4\.2\.1  ${RCSKEYDATE}  ${username}
+xx br-modify
+xx
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx
+br-change"
+         cd ../../2/first-dir
+         dotest keywordlog-17 "${testcvs} -q update -r br" "U file1"
+         dotest keywordlog-18 "cat file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.4\.2\.1  ${RCSKEYDATE}  ${username}
+xx br-modify
+xx
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx
+br-change"
+         cd ../..
+         dotest keywordlog-19 "${testcvs} -q co -p -r br first-dir/file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.4\.2\.1  ${RCSKEYDATE}  ${username}
+xx br-modify
+xx
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx
+br-change"
+         dotest keywordlog-20 "${testcvs} -q co -p first-dir/file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.5  ${RCSKEYDATE}  ${username}
+xx modify
+xx
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx
+change"
+         dotest keywordlog-21 "${testcvs} -q co -p -r 1.4 first-dir/file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx"
+
+         cd 2/first-dir
+         # OK, the basic rule for keyword expansion is that it
+         # happens on checkout.  And the rule for annotate is that
+         # it annotates a checked-in revision, rather than a checked-out
+         # file.  So, although it is kind of confusing that the latest
+         # revision does not appear in the annotated output, and the
+         # annotated output does not quite match what you'd get with
+         # update or checkout, the behavior is more or less logical.
+         # The same issue occurs with annotate and other keywords,
+         # I think, although it is particularly noticeable for $Log.
+         dotest keywordlog-22 "${testcvs} ann -r br file1" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.3          ($username8 *[0-9a-zA-Z-]*): initial
+1\.4\.2\.1      ($username8 *[0-9a-zA-Z-]*): xx "'\$'"Log: file1,v "'\$'"
+1\.4\.2\.1      ($username8 *[0-9a-zA-Z-]*): xx Revision 1\.4  ${RCSKEYDATE}  
$username
+1\.4\.2\.1      ($username8 *[0-9a-zA-Z-]*): xx First log line
+1\.4\.2\.1      ($username8 *[0-9a-zA-Z-]*): xx Second log line
+1\.4\.2\.1      ($username8 *[0-9a-zA-Z-]*): xx
+1\.4\.2\.1      ($username8 *[0-9a-zA-Z-]*): br-change"
+         dotest keywordlog-23 "${testcvs} ann -r HEAD file1" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.3          ($username8 *[0-9a-zA-Z-]*): initial
+1\.5          ($username8 *[0-9a-zA-Z-]*): xx "'\$'"Log: file1,v "'\$'"
+1\.5          ($username8 *[0-9a-zA-Z-]*): xx Revision 1\.4  ${RCSKEYDATE}  
$username
+1\.5          ($username8 *[0-9a-zA-Z-]*): xx First log line
+1\.5          ($username8 *[0-9a-zA-Z-]*): xx Second log line
+1\.5          ($username8 *[0-9a-zA-Z-]*): xx
+1\.5          ($username8 *[0-9a-zA-Z-]*): change"
+         cd ../..
+
+         #
+         # test the operation of 'admin -o' in conjunction with keywords
+         # (especially Log - this used to munge the RCS file for all time)
+         #
+
+         dotest keywordlog-24 \
+"${testcvs} admin -oHEAD 1/first-dir/file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+deleting revision 1\.5
+done"
+
+         dotest keywordlog-25 \
+"${testcvs} -q co -p first-dir/file1" \
+"initial
+xx "'\$'"Log: file1,v "'\$'"
+xx Revision 1\.4  ${RCSKEYDATE}  ${username}
+xx First log line
+xx Second log line
+xx"
+
+         # Now test the behavior when the comment leader exceeds the
+         # configured maximum.
+         mkdir 3; cd 3
+         dotest keywordlog-26 "$testcvs -Q co first-dir"
+
+         cd first-dir
+         sed 's/xx \$/1234567890123456789 $/' <file1 >tmp
+         mv tmp file1
+         dotest keywordlog-27 "$testcvs -Q ci -mrevision-5"
+         dotest keywordlog-28 "cat file1" \
+"initial
+1234567890123456789 "'\$'"Log: file1,v "'\$'"
+1234567890123456789 Revision 1\.5  $RCSKEYDATE  $username
+1234567890123456789 revision-5
+1234567890123456789
+xx Revision 1\.4  $RCSKEYDATE  $username
+xx First log line
+xx Second log line
+xx"
+
+         sed 's/1234567890123456789 \$/12345678901234567890 $/' <file1 >tmp
+         mv tmp file1
+         dotest keywordlog-29 "$testcvs -Q ci -mrevision-6" \
+"$SPROG commit: Skipping "'`$''Log$'"' keyword due to excessive comment 
leader\."
+         dotest keywordlog-30 "cat file1" \
+"initial
+12345678901234567890 "'\$'"Log: file1,v "'\$'"
+1234567890123456789 Revision 1\.5  $RCSKEYDATE  $username
+1234567890123456789 revision-5
+1234567890123456789
+xx Revision 1\.4  $RCSKEYDATE  $username
+xx First log line
+xx Second log line
+xx"
+
+         # Check that the Log-related config options work.
+         cd ..
+         dotest keywordlog-31 "$testcvs -Q co CVSROOT"
+         cd CVSROOT
+         echo "UseArchiveCommentLeader=TrUe" >>config
+         dotest keywordlog-32 "$testcvs -Q ci -mset-UseArchiveCommentLeader"
+
+         cd ../first-dir
+         dotest keywordlog-33 "$testcvs -Q ci -fmrevision-7 file1"
+         dotest keywordlog-34 "cat file1" \
+"initial
+12345678901234567890 "'\$'"Log: file1,v "'\$'"
+# Revision 1\.7  $RCSKEYDATE  $username
+# revision-7
+#
+1234567890123456789 Revision 1\.5  $RCSKEYDATE  $username
+1234567890123456789 revision-5
+1234567890123456789
+xx Revision 1\.4  $RCSKEYDATE  $username
+xx First log line
+xx Second log line
+xx"
+
+         cd ../CVSROOT
+         echo "MaxCommentLeaderLength=1k" >>config
+         dotest keywordlog-35 "$testcvs -Q ci -mset-MaxCommentLeaderLength"
+
+         cd ../first-dir
+         dotest keywordlog-36 "$testcvs -Q ci -fmrevision-8 file1"
+         dotest keywordlog-37 "cat file1" \
+"initial
+12345678901234567890 "'\$'"Log: file1,v "'\$'"
+12345678901234567890 Revision 1\.8  $RCSKEYDATE  $username
+12345678901234567890 revision-8
+12345678901234567890
+# Revision 1\.7  $RCSKEYDATE  $username
+# revision-7
+#
+1234567890123456789 Revision 1\.5  $RCSKEYDATE  $username
+1234567890123456789 revision-5
+1234567890123456789
+xx Revision 1\.4  $RCSKEYDATE  $username
+xx First log line
+xx Second log line
+xx"
+
+         dokeep
+         cd ../..
+         restore_adm
+         rm -r 1 2 3
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       keywordname)
+         # Test the Name keyword.
+         # See the keyword test for a descriptions of some other tests that
+         # test keyword expansion modes.
+         mkdir keywordname; cd keywordname
+         mkdir 1; cd 1
+         dotest keywordname-init-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest keywordname-init-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+
+         echo '$'"Name$" >file1
+         echo '$'"Name$" >file2
+         dotest keywordname-init-3 "${testcvs} add file1 file2" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+
+         # See "rmadd" for a list of other tests of cvs ci -r.
+         dotest keywordname-init-4 "${testcvs} -q ci -r 1.3 -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.3
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.3"
+
+         dotest keywordname-init-6 "${testcvs} -q up -A"
+         dotest keywordname-init-7 "${testcvs} -q tag -b br" \
+"T file1
+T file2"
+
+         echo new data >>file1
+         dotest keywordname-init-8 "${testcvs} -q ci -mchange" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.4; previous revision: 1\.3"
+
+         # First check out a branch.
+         #
+         # There used to be a bug where static tags would be substituted for
+         # Name keywords but not branch tags.
+         #
+         # FIXCVS - BUG
+         # Why shouldn't the non-update case not cause a substitution?
+         # An update -kk or -A will unsub and sub keywords without updates
+         # being required.
+         # FIXCVS - see note above keyword-21
+         dotest keywordname-update-1 "${testcvs} -q up -rbr" "U file1" \
+"$CPROG update: checksum failure after patch to \`\./file1'; will refetch
+$CPROG client: refetching unpatchable files
+U file1"
+         dotest keywordname-update-2 "cat file1" '\$'"Name: br "'\$'
+         dotest keywordname-update-3 "cat file2" '\$'"Name:  "'\$'
+
+         # Now verify that updating to the trunk leaves no substitution for
+         # $Name
+         dotest keywordname-update-4 "${testcvs} -q tag firsttag" \
+"T file1
+T file2"
+         # FIXCVS - see note above keyword-21
+         dotest keywordname-update-5 "${testcvs} -q up -A" "U file1" \
+"$CPROG update: checksum failure after patch to \`\./file1'; will refetch
+$CPROG client: refetching unpatchable files
+U file1"
+         dotest keywordname-update-6 "cat file1" \
+'\$'"Name:  "'\$'"
+new data"
+         dotest keywordname-update-7 "cat file2" '\$'"Name:  "'\$'
+
+         # But updating to a static tag does cause a substitution
+         # FIXCVS - see same note above
+         dotest keywordname-update-8 "${testcvs} -q up -rfirsttag" "U file1" \
+"$CPROG update: checksum failure after patch to \`\./file1'; will refetch
+$CPROG client: refetching unpatchable files
+U file1"
+         dotest keywordname-update-9 "cat file1" '\$'"Name: firsttag "'\$'
+         dotest keywordname-update-10 "cat file2" '\$'"Name:  "'\$'
+
+         # And reverify the trunk update when the change is actually removed.
+         dotest keywordname-update-11 "${testcvs} -q up -A" "U file1" \
+"$CPROG update: checksum failure after patch to \`./file1'; will refetch
+$CPROG client: refetching unpatchable files
+U file1"
+         dotest keywordname-update-12 "cat file1" \
+'\$'"Name:  "'\$'"
+new data"
+         dotest keywordname-update-13 "cat file2" '\$'"Name:  "'\$'
+
+         cd ../..
+
+         # now verify that a fresh checkout substitutes all the $Name fields
+         mkdir 2; cd 2
+         dotest keywordname-checkout-1 \
+"${testcvs} -q co -rfirsttag first-dir" \
+"U first-dir/file1
+U first-dir/file2"
+         cd first-dir
+         dotest keywordname-checkout-2 "cat file1" '\$'"Name: firsttag "'\$'
+         dotest keywordname-checkout-3 "cat file2" '\$'"Name: firsttag "'\$'
+
+         dokeep
+         cd ../../..
+         rm -r keywordname
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       keyword2)
+         # Test merging on files with keywords:
+         #   without -kk
+         #   with -kk
+         #     on text files
+         #     on binary files
+         # Note:  This test assumes that CVS has already passed the binfiles
+         #    test sequence
+         # Note2:  We are testing positive on binary corruption here
+         #    we probably really DON'T want to 'cvs update -kk' a binary 
file...
+         mkdir 1; cd 1
+         dotest keyword2-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest keyword2-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+
+         echo '$''Revision$' >> file1
+         echo "I" >>file1
+         echo "like" >>file1
+         echo "long" >>file1
+         echo "files!" >>file1
+         echo "" >>file1
+         echo "a test line for our times" >>file1
+         echo "" >>file1
+         echo "They" >>file1
+         echo "make" >>file1
+         echo "diff" >>file1
+         echo "look like it" >>file1
+         echo "did a much better" >>file1
+         echo "job." >>file1
+         dotest keyword2-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+
+         ${AWK} 'BEGIN { printf "%c%c%c%sRevision: 1.1 address@hidden", \
+           2, 10, 137, "$", 13, 10 }' \
+           </dev/null | ${TR} '@' '\000' >../binfile.dat
+         cp ../binfile.dat .
+         dotest keyword2-5 "${testcvs} add -kb binfile.dat" \
+"${SPROG} add: scheduling file .binfile\.dat. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+
+         dotest keyword2-6 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/binfile\.dat,v  <--  binfile\.dat
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+         dotest keyword2-7 "${testcvs} -q tag -b branch" \
+"T binfile\.dat
+T file1"
+
+         sed -e 's/our/the best of and the worst of/' file1 >f; mv f file1
+         dotest keyword2-8 "${testcvs} -q ci -m change" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+
+         dotest keyword2-9 "${testcvs} -q update -r branch" 'U file1'
+
+         echo "what else do we have?" >>file1
+         dotest keyword2-10 "${testcvs} -q ci -m change" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         # Okay, first a conflict in file1 - should be okay with binfile.dat
+         dotest keyword2-11 "${testcvs} -q update -A -j branch" \
+"U file1
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.1
+Merging differences between 1\.1 and 1\.1\.2\.1 into file1
+rcsmerge: warning: conflicts during merge"
+
+         dotest_fail keyword2-12 "${testcvs} diff file1" \
+"Index: file1
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.2
+diff -r1\.2 file1
+0a1
+> <<<<<<< file1
+1a3,5
+> =======
+> \\\$""Revision: 1\.1\.2\.1 \\\$
+> >>>>>>> 1\.1\.2\.1
+14a19
+> what else do we have${QUESTION}"
+
+         # Here's the problem... shouldn't -kk a binary file...
+         rm file1
+         dotest keyword2-13 "${testcvs} -q update -A -kk -j branch" \
+"${SPROG} update: warning: \`file1' was lost
+U file1
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.1
+retrieving revision 1\.1\.2\.1
+Merging differences between 1\.1 and 1\.1\.2\.1 into file1"
+
+         # binfile won't get checked in, but it is now corrupt and could
+         # have been checked in if it had changed on the branch...
+         dotest keyword2-14 "${testcvs} -q ci -m change" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2"
+
+         # "-kk" no longer corrupts binary files
+         dotest keyword2-15 "cmp binfile.dat ../binfile.dat" ''
+
+         # Okay, restore everything and make CVS try and merge a binary file...
+         # "-kk" no longer affects binary files
+         dotest keyword2-16 "${testcvs} -q update -A" \
+"U file1"
+         dotest keyword2-17 "${testcvs} -q tag -b branch2" \
+"T binfile\.dat
+T file1"
+         dotest keyword2-18 "${testcvs} -q update -r branch2" ''
+
+         ${AWK} 'BEGIN { printf "address@hidden", 2, 10, 137, 13, 10 }' \
+           </dev/null | ${TR} '@' '\000' >>binfile.dat
+         dotest keyword2-19 "$testcvs -q ci -m badbadbad" \
+"$CVSROOT_DIRNAME/first-dir/binfile\.dat,v  <--  binfile\.dat
+new revision: 1\.1\.4\.1; previous revision: 1\.1"
+         # "-kk" no longer affects binary files
+
+         # XXXX: do not ask, why we get the "U binfile.dat" line twice
+         #       looks like a bug!
+         dotest keyword2-20 "${testcvs} -q update -A -kk -j branch2" \
+"U binfile\.dat
+U binfile\.dat
+U file1"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       head)
+         # Testing handling of the HEAD special tag.
+         # There are many cases involving added and removed files
+         # which we don't yet try to deal with.
+         # TODO: We also could be paying much closer attention to
+         # "head of the trunk" versus "head of the default branch".
+         # That is what "cvs import" is doing here (but I didn't really
+         # fully follow through on writing the tests for that case).
+         mkdir imp-dir
+         cd imp-dir
+         echo 'imported contents' >file1
+         # It may seem like we don't do much with file2, but do note that
+         # the "cvs diff" invocations do also diff file2 (and come up empty).
+         echo 'imported contents' >file2
+         dotest_sort head-1 "${testcvs} import -m add first-dir tag1 tag2" \
+"
+
+N first-dir/file1
+N first-dir/file2
+No conflicts created by this import"
+         cd ..
+         rm -r imp-dir
+         mkdir 1
+         cd 1
+         dotest head-2 "${testcvs} -q co first-dir" \
+"U first-dir/file1
+U first-dir/file2"
+         cd first-dir
+         echo 'add a line on trunk' >> file1
+         dotest head-3 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         dotest head-4 "${testcvs} -q tag trunktag" "T file1
+T file2"
+         echo 'add a line on trunk after trunktag' >> file1
+         dotest head-5 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3; previous revision: 1\.2"
+         dotest head-6 "${testcvs} -q tag -b br1" "T file1
+T file2"
+         dotest head-7 "${testcvs} -q update -r br1" ""
+         echo 'modify on branch' >>file1
+         dotest head-8 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3\.2\.1; previous revision: 1\.3"
+         dotest head-9 "${testcvs} -q tag brtag" "T file1
+T file2"
+         echo 'modify on branch after brtag' >>file1
+         dotest head-10 "${testcvs} -q ci -m modify" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.3\.2\.2; previous revision: 1\.3\.2\.1"
+         # With no sticky tags, HEAD is the head of the trunk.
+         dotest head-trunk-setup "${testcvs} -q update -A" "U file1"
+         dotest head-trunk-update "${testcvs} -q update -r HEAD -p file1" \
+"imported contents
+add a line on trunk
+add a line on trunk after trunktag"
+         # and diff thinks so too.  Case (a) from the comment in
+         # cvs.texinfo (Common options).
+         dotest_fail head-trunk-diff "${testcvs} -q diff -c -r HEAD -r br1" \
+"Index: file1
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.3
+retrieving revision 1\.3\.2\.2
+diff -c -r1\.3 -r1\.3\.2\.2
+\*\*\* file1   ${RFCDATE}      1\.3
+--- file1      ${RFCDATE}      1\.3\.2\.2
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1,3 \*\*\*\*
+--- 1,5 ----
+  imported contents
+  add a line on trunk
+  add a line on trunk after trunktag
+${PLUS} modify on branch
+${PLUS} modify on branch after brtag"
+
+         # With a branch sticky tag, HEAD is the head of the trunk.
+         dotest head-br1-setup "${testcvs} -q update -r br1" "U file1"
+         dotest head-br1-update "${testcvs} -q update -r HEAD -p file1" \
+"imported contents
+add a line on trunk
+add a line on trunk after trunktag"
+         # But diff thinks that HEAD is "br1".  Case (b) from cvs.texinfo.
+         # Probably people are relying on it.
+         dotest head-br1-diff "${testcvs} -q diff -c -r HEAD -r br1" ""
+
+         # With a nonbranch sticky tag on a branch,
+         # HEAD is the head of the trunk
+         dotest head-brtag-setup "${testcvs} -q update -r brtag" "U file1"
+         dotest head-brtag-update "${testcvs} -q update -r HEAD -p file1" \
+"imported contents
+add a line on trunk
+add a line on trunk after trunktag"
+
+         # CVS 1.9 and older thought that HEAD is "brtag" (this was
+         # noted as "strange, maybe accidental").  But "br1" makes a
+         # whole lot more sense.
+         dotest head-brtag-diff "${testcvs} -q diff -c -r HEAD -r br1" ""
+
+         # With a nonbranch sticky tag on the trunk, HEAD is the head
+         # of the trunk, I think.
+         dotest head-trunktag-setup "${testcvs} -q update -r trunktag" \
+"U file1"
+         dotest head-trunktag-check "cat file1" "imported contents
+add a line on trunk"
+         dotest head-trunktag-update "${testcvs} -q update -r HEAD -p file1" \
+"imported contents
+add a line on trunk
+add a line on trunk after trunktag"
+         # Like head-brtag-diff, there is a non-branch sticky tag.
+         dotest_fail head-trunktag-diff \
+           "${testcvs} -q diff -c -r HEAD -r br1" \
+"Index: file1
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.3
+retrieving revision 1\.3\.2\.2
+diff -c -r1\.3 -r1\.3\.2\.2
+\*\*\* file1   ${RFCDATE}      1\.3
+--- file1      ${RFCDATE}      1\.3\.2\.2
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1,3 \*\*\*\*
+--- 1,5 ----
+  imported contents
+  add a line on trunk
+  add a line on trunk after trunktag
+${PLUS} modify on branch
+${PLUS} modify on branch after brtag"
+
+         # Also might test what happens if we setup with update -r
+         # HEAD.  In general, if sticky tags matter, does the
+         # behavior of "update -r <foo>" (without -p) depend on the
+         # sticky tags before or after the update?
+
+         # Note that we are testing both the case where this deletes
+         # a revision (file1) and the case where it does not (file2)
+         dotest_fail head-o0a "${testcvs} admin -o ::br1" \
+"${SPROG} admin: Administrating \.
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+${SPROG} admin: cannot remove revision 1\.3\.2\.1 because it has tags
+${SPROG} admin: RCS file for .file1. not modified\.
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+         dotest head-o0b "${testcvs} tag -d brtag" \
+"${SPROG} tag: Untagging \.
+D file1
+D file2"
+         dotest head-o1 "${testcvs} admin -o ::br1" \
+"${SPROG} admin: Administrating \.
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+deleting revision 1\.3\.2\.1
+done
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       tagdate)
+         # Test combining -r and -D.
+         #
+         # Note that this is not a complete test.  It relies on the fact
+         # that update, checkout and export have a LOT of shared code.
+         # Notice:
+         #     1)  checkout is never tested at all with -r -D
+         #     2)  update never uses an argument to '-D' besides 'now'
+         #             (this test does not provide enough data to prove
+         #             that 'cvs update' with both a '-r' and a '-D'
+         #             specified does not ignore '-D': a 'cvs up
+         #             -r<branch> -Dnow' and a 'cvs up -r<branch>'
+         #             should specify the same file revision).
+         #     3)  export uses '-r<branch> -D<when there was a different
+         #             revision>', hopefully completing this behavior test
+         #             for checkout and update as well.
+         #
+         mkdir 1; cd 1
+         save_TZ=$TZ
+         TZ=UTC0; export TZ
+         dotest tagdate-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest tagdate-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+
+         echo trunk-1 >file1
+         dotest tagdate-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest tagdate-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         date_T1=`getrlogdate -r1.1 first-dir/file1`
+
+         dotest tagdate-5 "${testcvs} -q tag -b br1" "T file1"
+         dotest tagdate-6 "${testcvs} -q tag -b br2" "T file1"
+         echo trunk-2 >file1
+         dotest tagdate-7 "${testcvs} -q ci -m modify-on-trunk" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1"
+         date_T2=`getrlogdate -r1.2 first-dir/file1`
+
+         # We are testing -r -D where br1 is a (magic) branch without
+         # any revisions.  First the case where br2 doesn't have any
+         # revisions either:
+         dotest tagdate-8 "${testcvs} -q update -p -r br1 -D now" "trunk-1"
+         dotest tagdate-9 "${testcvs} -q update -r br2" "U file1"
+         echo br2-1 >file1
+         dotest tagdate-10 "${testcvs} -q ci -m modify-on-br2" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.4\.1; previous revision: 1\.1"
+         date_T3=`getrlogdate -r1.1.4.1 first-dir/file1`
+
+         # Then the case where br2 does have revisions:
+         dotest tagdate-11 "${testcvs} -q update -p -r br1 -D now" "trunk-1"
+
+         # Joins from dates on the head used to be prohibited.
+         dotest tagdate-12 "$testcvs -q update -j:yesterday -j:now"
+         dotest tagdate-12b "$testcvs -Q update -C"
+         # And check export
+
+         echo br2-2 >file1
+         dotest tagdate-13 "${testcvs} -q ci -m modify-2-on-br2" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.4\.2; previous revision: 1\.1\.4\.1"
+         date_T4=`getrlogdate -r1.1.4.2 first-dir/file1`
+
+         # Test diff -r<tag>:<date> with two revisions specified.
+         dotest_fail tagdate-13b \
+"$testcvs -q diff -u -rbr2:'$date_T3' -rbr2:now file1" \
+"Index: file1
+===================================================================
+RCS file: $CVSROOT_DIRNAME/first-dir/file1,v
+retrieving revision 1\.1\.4\.1
+retrieving revision 1\.1\.4\.2
+diff -u -r1\.1\.4\.1 -r1\.1\.4\.2
+--- file1      $RFCDATE        1\.1\.4\.1
++++ file1      $RFCDATE        1\.1\.4\.2
+@@ -1 ${PLUS}1 @@
+-br2-1
+${PLUS}br2-2"
+
+         # Tag a date on a branch.
+         dotest tagdate-13c "$testcvs -q tag -rbr2:'$date_T3' tagdate" \
+"T file1"
+         dotest tagdate-13d "$testcvs -q update -rtagdate" "U file1"
+         dotest tagdate-13e "cat file1" "br2-1"
+
+         # This one should fail, though currently without an error message,
+         # since a date on a static tag is meaningless.
+         dotest tagdate-13f "$testcvs -q tag -rtagdate:'$date_T3' tagdate"
+
+         # and restore to using the trunk for future tests.
+         dotest tagdate-13g "$testcvs -q up -rbr2" "U file1"
+
+         cd ../..
+         mkdir 2; cd 2
+         dotest tagdate-14 \
+"$testcvs -q export -r br2 -D'$date_T3' first-dir" \
+"U first-dir/file1"
+         dotest tagdate-14b "cat first-dir/file1" "br2-1"
+         dotest tagdate-15 \
+"$testcvs -q export -rbr2:'$date_T3' -dsecond-dir first-dir" \
+"U second-dir/file1"
+         dotest tagdate-15b "cat second-dir/file1" "br2-1"
+
+         # Now for annotate
+         cd ../1/first-dir
+         dotest tagdate-16 "${testcvs} annotate -rbr2 -D'$date_T3'" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1\.4\.1      ($username8 *[0-9a-zA-Z-]*): br2-1"
+
+         dotest tagdate-17 "${testcvs} annotate -rbr2 -Dnow" \
+"
+Annotations for file1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1\.1\.4\.2      ($username8 *[0-9a-zA-Z-]*): br2-2"
+
+         # Now check to see what happens when we add files to br2 and trunk
+         echo br2-1 > file3
+         dotest tagdate-18 "${testcvs} add file3" \
+"${SPROG} add: scheduling file \`file3' for addition on branch \`br2'
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+         dotest tagdate-19 "${testcvs} -q ci -m add file3" \
+"$CVSROOT_DIRNAME/first-dir/Attic/file3,v  <--  file3
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+         date_T5=`getrlogdate -r1.1 first-dir/file3`
+         date_T6=`getrlogdate -r1.1.2.1 first-dir/file3`
+
+         cd ../..
+         mkdir 3; cd 3
+         dotest tagdate-20 "${testcvs} -Q co first-dir" ''
+         cd first-dir
+         echo trunk-1 > file2
+         dotest tagdate-21 "${testcvs} add file2" \
+"${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest tagdate-22 "${testcvs} -q ci -m add file2" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+         date_T7=`getrlogdate -r1.1 first-dir/file2`
+         echo "trunk-2" >file2
+         dotest tagdate-23 "${testcvs} -q ci -m update file2" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.2; previous revision: 1\.1"
+         date_T8=`getrlogdate -r1.2 first-dir/file2`
+
+         cd ../../1/first-dir
+         echo br2-1 > file2
+         dotest tagdate-24 "${testcvs} add file2" \
+"${SPROG} add: scheduling file \`file2' for addition on branch \`br2'
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+         dotest tagdate-25 "${testcvs} -q ci -m add file2" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.2\.2\.2; previous revision: 1\.2\.2\.1"
+         date_T9=`getrlogdate -r1.2.2.2 first-dir/file2`
+         cd ../..
+
+         # Time  Rev     Branch  Comments
+         # T0            trunk   first-dir created
+         # T1    1.1     trunk   first-dir/file1 committed "trunk-1"
+         #               br1     branch created
+         #               br2     branch created
+         # T2    1.2     trunk   first-dir/file1 committed "trunk-2"
+         # T3    1.1.4.1 br2     first-dir/file1 committed "br2-1"
+         # +60s
+         # T4    1.1.4.2 br2     first-dir/file1 committed "br2-2"
+         # T5    1.1     trunk   first-dir/file3 dead
+         # T6    1.1.2.1 br2     first-dir/file3 committed "br2-1"
+         # T7    1.1     trunk   first-dir/file2 committed "trunk-1"
+         # T8    1.2     trunk   first-dir/file2 committed "trunk-2"
+         # T8    1.2.2.1 br2     first-dir/file2 dead
+         # T9    1.2.2.2 br2     first-dir/file2 committed "br2-1"
+         # 
+
+         mkdir 4; cd 4
+         (echo Dates for tagdate-26-* are:;\
+          echo "  date_T1='$date_T1'";\
+          echo "  date_T2='$date_T2'";\
+          echo "  date_T3='$date_T3'";\
+          echo "  date_T4='$date_T4'";\
+          echo "  date_T5='$date_T5'";\
+          echo "  date_T6='$date_T6'";\
+          echo "  date_T7='$date_T7'";\
+          echo "  date_T8='$date_T8'";\
+          echo "  date_T9='$date_T9'") >>$LOGFILE
+         dotest tagdate-26-trunk-t1 \
+"${testcvs} co -D'$date_T1' -d first-dir-trunk-t1 first-dir" \
+"${SPROG} checkout: Updating first-dir-trunk-t1
+U first-dir-trunk-t1/file1"
+         dotest tagdate-26-br2-t1 \
+"${testcvs} co -r br2 -D'$date_T1' -d first-dir-br2-t1 first-dir" \
+"${SPROG} checkout: Updating first-dir-br2-t1
+U first-dir-br2-t1/file1"
+         dotest tagdate-26-trunk-t2 \
+"${testcvs} co -D'$date_T2' -d first-dir-trunk-t2 first-dir" \
+"${SPROG} checkout: Updating first-dir-trunk-t2
+U first-dir-trunk-t2/file1"
+         dotest tagdate-26-br2-t2 \
+"${testcvs} co -r br2 -D'$date_T2' -d first-dir-br2-t2 first-dir" \
+"${SPROG} checkout: Updating first-dir-br2-t2
+U first-dir-br2-t2/file1"
+         dotest tagdate-26-br2-t3 \
+"${testcvs} co -r br2 -D'$date_T3' -d first-dir-br2-t3 first-dir" \
+"${SPROG} checkout: Updating first-dir-br2-t3
+U first-dir-br2-t3/file1"
+         dotest tagdate-26-br2-t4 \
+"${testcvs} co -r br2 -D'$date_T4' -d first-dir-br2-t4 first-dir" \
+"${SPROG} checkout: Updating first-dir-br2-t4
+U first-dir-br2-t4/file1"
+         dotest tagdate-26-br2-t6 \
+"${testcvs} co -r br2 -D'$date_T6' -d first-dir-br2-t6 first-dir" \
+"${SPROG} checkout: Updating first-dir-br2-t6
+U first-dir-br2-t6/file1
+U first-dir-br2-t6/file3"
+         dotest tagdate-26-trunk-t7 \
+"${testcvs} co -D'$date_T7' -d first-dir-trunk-t7 first-dir" \
+"${SPROG} checkout: Updating first-dir-trunk-t7
+U first-dir-trunk-t7/file1
+U first-dir-trunk-t7/file2"
+         dotest tagdate-26-br2-t7 \
+"${testcvs} co -r br2 -D'$date_T7' -d first-dir-br2-t7 first-dir" \
+"${SPROG} checkout: Updating first-dir-br2-t7
+U first-dir-br2-t7/file1
+U first-dir-br2-t7/file3"
+         dotest tagdate-26-trunk-t8 \
+"${testcvs} co -D'$date_T8' -d first-dir-trunk-t8 first-dir" \
+"${SPROG} checkout: Updating first-dir-trunk-t8
+U first-dir-trunk-t8/file1
+U first-dir-trunk-t8/file2"
+         dotest tagdate-26-br2-t8 \
+"${testcvs} co -r br2 -D'$date_T8' -d first-dir-br2-t8 first-dir" \
+"${SPROG} checkout: Updating first-dir-br2-t8
+U first-dir-br2-t8/file1
+U first-dir-br2-t8/file3"
+         dotest tagdate-26-br2-t9 \
+"${testcvs} co -r br2 -D'$date_T9' -d first-dir-br2-t9 first-dir" \
+"${SPROG} checkout: Updating first-dir-br2-t9
+U first-dir-br2-t9/file1
+U first-dir-br2-t9/file2
+U first-dir-br2-t9/file3"
+         dotest tagdate-27-trunk-t1 \
+"${testcvs} status first-dir-trunk-t1" \
+"${SPROG} status: Examining first-dir-trunk-t1
+===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1[^.]*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                ${RCSDELTADATE}
+   Sticky Options:     (none)"
+         dotest tagdate-27-br2-t1 \
+"${testcvs} status first-dir-br2-t1" \
+"${SPROG} status: Examining first-dir-br2-t1
+===================================================================
+File: file1                    Status: Needs Patch
+
+   Working revision:   1\.1[^.]*
+   Repository revision:        1\.1\.4\.2      
${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest tagdate-27-trunk-t2 \
+"${testcvs} status first-dir-trunk-t2" \
+"${SPROG} status: Examining first-dir-trunk-t2
+===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.2[^.]*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                ${RCSDELTADATE}
+   Sticky Options:     (none)"
+         dotest tagdate-27-br2-t2 \
+"${testcvs} status first-dir-br2-t2" \
+"${SPROG} status: Examining first-dir-br2-t2
+===================================================================
+File: file1                    Status: Needs Patch
+
+   Working revision:   1\.1[^.]*
+   Repository revision:        1\.1\.4\.2      
${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest tagdate-27-br2-t3 \
+"${testcvs} status first-dir-br2-t3" \
+"${SPROG} status: Examining first-dir-br2-t3
+===================================================================
+File: file1                    Status: Needs Patch
+
+   Working revision:   1\.1\.4\.1[^.]*
+   Repository revision:        1\.1\.4\.2      
${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest tagdate-27-br2-t4 \
+"${testcvs} status first-dir-br2-t4" \
+"${SPROG} status: Examining first-dir-br2-t4
+===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1\.4\.2[^.]*
+   Repository revision:        1\.1\.4\.2      
${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest tagdate-27-br2-t6 \
+"${testcvs} status first-dir-br2-t6" \
+"${SPROG} status: Examining first-dir-br2-t6
+===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1\.4\.2[^.]*
+   Repository revision:        1\.1\.4\.2      
${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Up-to-date
+
+   Working revision:   1\.1\.2\.1[^.]*
+   Repository revision:        1\.1\.2\.1      
${CVSROOT_DIRNAME}/first-dir/Attic/file3,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest tagdate-27-trunk-t7 \
+"${testcvs} status first-dir-trunk-t7" \
+"${SPROG} status: Examining first-dir-trunk-t7
+===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.2[^.]*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                ${RCSDELTADATE}
+   Sticky Options:     (none)
+
+===================================================================
+File: file2                    Status: Up-to-date
+
+   Working revision:   1\.1[^.]*
+   Repository revision:        1\.1    ${CVSROOT_DIRNAME}/first-dir/file2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                ${RCSDELTADATE}
+   Sticky Options:     (none)"
+         dotest tagdate-27-br2-t7 \
+"${testcvs} status first-dir-br2-t7" \
+"${SPROG} status: Examining first-dir-br2-t7
+===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1\.4\.2[^.]*
+   Repository revision:        1\.1\.4\.2      
${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Up-to-date
+
+   Working revision:   1\.1\.2\.1[^.]*
+   Repository revision:        1\.1\.2\.1      
${CVSROOT_DIRNAME}/first-dir/Attic/file3,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest tagdate-27-trunk-t8 \
+"${testcvs} status first-dir-trunk-t8" \
+"${SPROG} status: Examining first-dir-trunk-t8
+===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.2[^.]*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                ${RCSDELTADATE}
+   Sticky Options:     (none)
+
+===================================================================
+File: file2                    Status: Up-to-date
+
+   Working revision:   1\.2[^.]*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/file2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                ${RCSDELTADATE}
+   Sticky Options:     (none)"
+         dotest tagdate-27-br2-t8 \
+"${testcvs} status first-dir-br2-t8" \
+"${SPROG} status: Examining first-dir-br2-t8
+===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1\.4\.2[^.]*
+   Repository revision:        1\.1\.4\.2      
${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Up-to-date
+
+   Working revision:   1\.1\.2\.1[^.]*
+   Repository revision:        1\.1\.2\.1      
${CVSROOT_DIRNAME}/first-dir/Attic/file3,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         dotest tagdate-27-br2-t9 \
+"${testcvs} status first-dir-br2-t9" \
+"${SPROG} status: Examining first-dir-br2-t9
+===================================================================
+File: file1                    Status: Up-to-date
+
+   Working revision:   1\.1\.4\.2[^.]*
+   Repository revision:        1\.1\.4\.2      
${CVSROOT_DIRNAME}/first-dir/file1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.4)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file2                    Status: Up-to-date
+
+   Working revision:   1\.2\.2\.2[^.]*
+   Repository revision:        1\.2\.2\.2      
${CVSROOT_DIRNAME}/first-dir/file2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.2\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file3                    Status: Up-to-date
+
+   Working revision:   1\.1\.2\.1[^.]*
+   Repository revision:        1\.1\.2\.1      
${CVSROOT_DIRNAME}/first-dir/Attic/file3,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         br2 (branch: 1\.1\.2)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+
+         # Now check the contents of the files
+         dotest tagdate-28-trunk-t1 'cat first-dir-trunk-t1/file1' 'trunk-1'
+         dotest tagdate-28-br2-t1 'cat first-dir-br2-t1/file1' 'trunk-1'
+         dotest tagdate-28-trunk-t2 'cat first-dir-trunk-t2/file1' 'trunk-2'
+         dotest tagdate-28-br2-t2 'cat first-dir-br2-t2/file1' 'trunk-1'
+         dotest tagdate-28-br2-t3 'cat first-dir-br2-t3/file1' 'br2-1'
+         dotest tagdate-28-br2-t4 'cat first-dir-br2-t4/file1' 'br2-2'
+         dotest tagdate-28-br2-t6a 'cat first-dir-br2-t6/file1' "br2-2"
+         dotest tagdate-28-br2-t6b 'cat first-dir-br2-t6/file3' "br2-1"
+         dotest tagdate-28-trunk-t7a 'cat first-dir-trunk-t7/file1' "trunk-2"
+         dotest tagdate-28-trunk-t7b 'cat first-dir-trunk-t7/file2' "trunk-1"
+         dotest tagdate-28-br2-t7a 'cat first-dir-br2-t7/file1' "br2-2"
+         dotest tagdate-28-br2-t7b 'cat first-dir-br2-t7/file3' "br2-1"
+         dotest tagdate-28-trunk-t8a 'cat first-dir-trunk-t8/file1' "trunk-2"
+         dotest tagdate-28-trunk-t8b 'cat first-dir-trunk-t8/file2' "trunk-2"
+         dotest tagdate-28-br2-t8a 'cat first-dir-br2-t8/file1' "br2-2"
+         dotest tagdate-28-br2-t8c 'cat first-dir-br2-t8/file3' "br2-1"
+         dotest tagdate-28-br2-t9a 'cat first-dir-br2-t9/file1' "br2-2"
+         dotest tagdate-28-br2-t9b 'cat first-dir-br2-t9/file2' "br2-1"
+         dotest tagdate-28-br2-t9c 'cat first-dir-br2-t9/file3' "br2-1"
+         cd ..
+
+         unset date_T1 date_T2 date_T3 date_T4 date_T5
+         unset date_T6 date_T7 date_T8 date_T9
+         TZ=$save_TZ
+
+         dokeep
+         rm -r 1 2 3 4
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       multibranch2)
+         # Commit the first delta on branch A when there is an older
+         # branch, B, that already has a delta.  A and B come from the
+         # same branch point.  Then verify that branches A and B are
+         # in the right order.
+         mkdir 1; cd 1
+         dotest multibranch2-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest multibranch2-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+         cd first-dir
+
+         echo trunk-1 >file1
+         echo trunk-1 >file2
+         dotest multibranch2-3 "${testcvs} add file1 file2" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest multibranch2-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+         dotest multibranch2-5 "${testcvs} -q tag -b A" "T file1
+T file2"
+         dotest multibranch2-6 "${testcvs} -q tag -b B" "T file1
+T file2"
+
+         dotest multibranch2-7 "${testcvs} -q update -r B" ''
+         echo branch-B >file1
+         echo branch-B >file2
+         dotest multibranch2-8 "${testcvs} -q ci -m modify-on-B" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.4\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.1\.4\.1; previous revision: 1\.1"
+
+         dotest multibranch2-9 "${testcvs} -q update -r A" 'U file1
+U file2'
+         echo branch-A >file1
+         # When using cvs-1.9.20, this commit gets a failed assertion in rcs.c.
+         dotest multibranch2-10 "${testcvs} -q ci -m modify-on-A" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         dotest multibranch2-11 "${testcvs} -q log file1" \
+"
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       B: 1\.1\.0\.4
+       A: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+branches:  1\.1\.2;  1\.1\.4;
+add
+----------------------------
+revision 1\.1\.4\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+modify-on-B
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  lines: ${PLUS}1 -1;  
commitid: ${commitid};
+modify-on-A
+============================================================================="
+
+         # This one is more concise.
+         dotest multibranch2-12 "${testcvs} -q log -r1.1 file1" \
+"
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       B: 1\.1\.0\.4
+       A: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 3;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+branches:  1\.1\.2;  1\.1\.4;
+add
+============================================================================="
+
+         # OK, try very much the same thing except we run update -j to
+         # bring the changes from B to A.  Probably tests many of the
+         # same code paths but might as well keep it separate, I guess.
+
+         dotest multibranch2-13 "${testcvs} -q update -r B" "U file1
+U file2"
+         dotest multibranch2-14 "${testcvs} -q update -r A -j B file2" \
+"U file2
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+retrieving revision 1.1
+retrieving revision 1.1.4.1
+Merging differences between 1.1 and 1.1.4.1 into file2"
+         dotest multibranch2-15 "${testcvs} -q ci -m commit-on-A file2" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       tag8k)
+         # In cvs-1.9.27, there is a bug that can cause an abort.
+         # It happens when you commit a change to a ,v file that has
+         # just the right amount of tag/branch info to align one of the
+         # semicolons in the branch info to be on a 8k-byte boundary.
+         # The result: rcsbuf_getkey got an abort.  This failure doesn't
+         # corrupt the ,v file -- that would be really serious.  But it
+         # does leave stale write locks that have to be removed manually.
+
+         mkdir 1
+         cd 1
+
+         module=x
+
+         : > junk
+         dotest tag8k-1 "$testcvs -Q import -m . $module X Y" ''
+         dotest tag8k-2 "$testcvs -Q co $module" ''
+         cd $module
+
+         file=m
+         : > $file
+         dotest tag8k-3 "$testcvs add $file" \
+"$SPROG add: scheduling file .$file. for addition
+$SPROG add: use .$SPROG commit. to add this file permanently"
+         dotest tag8k-4 "$testcvs -Q ci -m . $file"
+
+         # It seems there have to be at least two versions.
+         echo a > $file
+         dotest tag8k-5 "$testcvs -Q ci -m . $file"
+
+         # Add just under 8K worth of tags.
+         
t=TAG---------------------------------------------------------------------
+         t=$t$t
+         t=$t$t$t$t$t
+         # Now $t is 720 bytes long.
+
+         # Apply some tags with that long prefix.
+         dotest tag8k-6  "$testcvs -Q tag $t-0 $file" ''
+         dotest tag8k-7  "$testcvs -Q tag $t-1 $file" ''
+         dotest tag8k-8  "$testcvs -Q tag $t-2 $file" ''
+         dotest tag8k-9  "$testcvs -Q tag $t-3 $file" ''
+         dotest tag8k-10 "$testcvs -Q tag $t-4 $file" ''
+         dotest tag8k-11 "$testcvs -Q tag $t-5 $file" ''
+         dotest tag8k-12 "$testcvs -Q tag $t-6 $file" ''
+         dotest tag8k-13 "$testcvs -Q tag $t-7 $file" ''
+         dotest tag8k-14 "$testcvs -Q tag $t-8 $file" ''
+         dotest tag8k-15 "$testcvs -Q tag $t-9 $file" ''
+         dotest tag8k-16 "$testcvs -Q tag $t-a $file" ''
+
+         # Extract the author value.
+         name=`sed -n 's/.*;   author \([^;]*\);.*/\1/p' 
${CVSROOT_DIRNAME}/$module/$file,v|sed 1q`
+
+         # Form a suffix string of length (16 - length($name)).
+         # CAREFUL: this will lose if $name is longer than 16.
+         sed_pattern=`echo $name|sed s/././g`
+         suffix=`echo 1234567890123456|sed s/$sed_pattern//`
+
+         # Add a final tag with length chosen so that it will push the
+         # offset of the `;' in the 2nd occurrence of `;\tauthor' in the
+         # ,v file to exactly 8192.
+         dotest tag8k-17 "$testcvs -Q tag "x8bytes-$suffix" $file" ''
+
+         # This commit would fail with 1.9.27.
+         echo a >> $file
+         dotest tag8k-18 "$testcvs -Q ci -m . $file"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         ;;
+
+
+
+       admin)
+         # More "cvs admin" tests.
+         # The basicb-21 test tests rejecting an invalid option.
+         # For -l and -u, see "reserved" and "keyword" tests.
+         # "binfiles" test has a test of "cvs admin -k".
+         # "log2" test has tests of -t and -q options to cvs admin.
+         # "rcs" tests -b option also.
+         # For -o, see:
+         #   admin-22-o1 through admin-23 (various cases not involving ::)
+         #   binfiles2-o* (:rev, rev on trunk; rev:, deleting entire branch)
+         #   basicb-o* (attempt to delete all revisions)
+         #   basica-o1 through basica-o3 (basic :: usage)
+         #   head-o1 (::branch, where this deletes a revision or is noop)
+         #   branches-o1 (::branch, similar, with different branch topology)
+         #   log-o1 (1.3.2.1::)
+         #   binfiles-o1 (1.3:: and ::1.3; binary files)
+         #   binfiles3-9 (binary files)
+         #   Also could be testing:
+         #     1.3.2.6::1.3.2.8
+         #     1.3.2.6::1.3.2
+         #     1.3.2.1::1.3.2.6
+         #     1.3::1.3.2.6 (error?  or synonym for ::1.3.2.6?)
+         # -n: admin, tagf tests.
+
+         # Test the postadmin hook as a side effect of the rest of the tests.
+         # See the `info' test for notes on where other script hooks are
+         # tested.
+         mkdir 2; cd 2
+         dotest admin-init-1 "$testcvs -Q co CVSROOT"
+         cd CVSROOT
+         echo "ALL $TESTDIR/2/loggit %r %p %c" >>postadmin
+         dotest admin-init-2 "$testcvs -Q ci -mlog-admin"
+         cd .. # 2
+
+         cat >loggit <<EOF
+#!$TESTSHELL
+echo \${1+"\$@"} >>$TESTDIR/2/admin-log
+EOF
+         # #^@&!^@ Cygwin.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod +x $TESTDIR/2/loggit"
+         else
+           chmod +x loggit
+         fi
+         cd .. # $TESTDIR
+
+
+         mkdir 1; cd 1
+         dotest admin-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest admin-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+
+         dotest_fail admin-3 "${testcvs} -q admin -i file1" \
+"${CPROG} admin: the -i option to admin is not supported
+${CPROG} admin: run add or import to create an RCS file
+${CPROG} \[admin aborted\]: specify ${CPROG} -H admin for usage information"
+         dotest_fail admin-4 "${testcvs} -q log file1" \
+"${SPROG} log: nothing known about file1"
+         dotest_fail admin-4a "${testcvs} -q admin file1" \
+"${SPROG} admin: nothing known about file1"
+
+         # Set up some files, file2 a plain one and file1 with a revision
+         # on a branch.
+         touch file1 file2
+         dotest admin-5 "${testcvs} add file1 file2" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest admin-6 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+         dotest admin-7 "${testcvs} -q tag -b br" "T file1
+T file2"
+         dotest admin-8 "${testcvs} -q update -r br" ""
+         echo 'add a line on the branch' >> file1
+         echo 'add a file on the branch' >> file3
+         dotest admin-9a "${testcvs} -q add file3" \
+"${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest admin-9b "${testcvs} -q ci -m modify-on-branch" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/Attic/file3,v  <--  file3
+new revision: 1\.1\.2\.1; previous revision: 1\.1"
+         dotest admin-10 "${testcvs} -q update -A" \
+"U file1
+${SPROG} update: \`file3' is no longer in the repository"
+
+         # Check that we can administer files in the repository that
+         # aren't in the working directory.
+         dotest admin-10-1 "${testcvs} admin ." \
+"${SPROG} admin: Administrating .
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+         dotest admin-10-2 "${testcvs} -q admin file3" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file3,v
+done"
+
+         # Try to recurse with a numeric revision arg.
+         # If we wanted to comprehensive about this, we would also test
+         # this for -l, -u, and all the different -o syntaxes.
+         dotest_fail admin-10a "${testcvs} -q admin -b1.1.2" \
+"${CPROG} admin: while processing more than one file:
+${CPROG} \[admin aborted\]: attempt to specify a numeric revision"
+         dotest_fail admin-10b "${testcvs} -q admin -m1.1:bogus file1 file2" \
+"${CPROG} admin: while processing more than one file:
+${CPROG} \[admin aborted\]: attempt to specify a numeric revision"
+
+         # try a bad symbolic revision
+         dotest_fail admin-10c "${testcvs} -q admin -bBOGUS" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/file1,v: Symbolic name BOGUS is 
undefined.
+${SPROG} admin: RCS file for .file1. not modified\.
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/file2,v: Symbolic name BOGUS is 
undefined.
+${SPROG} admin: RCS file for .file2. not modified\."
+
+         # Note that -s option applies to the new default branch, not
+         # the old one.
+         # Also note that the implementation of -a via "rcs" requires
+         # no space between -a and the argument.  However, we expect
+         # to change that once CVS parses options.
+         dotest admin-11 "${testcvs} -q admin -afoo,bar -abaz \
+-b1.1.2 -cxx -U -sfoo file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+         dotest admin-11a "${testcvs} log -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch: 1\.1\.2
+locks:
+access list:
+       foo
+       bar
+       baz
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.2;
+add
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: foo;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+modify-on-branch
+============================================================================="
+         dotest admin-12 "${testcvs} -q admin -bbr file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+         dotest admin-12a "${testcvs} log -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch: 1\.1\.2
+locks:
+access list:
+       foo
+       bar
+       baz
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.2;
+add
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: foo;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+modify-on-branch
+============================================================================="
+
+         # "cvs log" doesn't print the comment leader.  RCS 5.7 will print
+         # the comment leader only if one specifies "-V4" to rlog.  So it
+         # seems like the only way to test it is by looking at the RCS file
+         # directly.  This also serves as a test of exporting RCS files
+         # (analogous to the import tests in "rcs").
+         # Rather than try to write a rigorous check for whether the
+         # file CVS exports is valid, we just write a simpler
+         # test for what CVS actually exports, and figure we can revise
+         # the check as needed (within the confines of the RCS5 format as
+         # documented in RCSFILES).
+         # Note that we must accept either 2 or 4 digit year.
+         dotest admin-13 "cat ${CVSROOT_DIRNAME}/first-dir/file1,v" \
+"head  1\.1;
+branch 1\.1\.2;
+access
+       foo
+       bar
+       baz;
+symbols
+       br:1\.1\.0\.2;
+locks;
+comment        @xx@;
+
+
+1\.1
+date   ${RCSDELTADATE};        author ${username};     state Exp;
+branches
+       1\.1\.2\.1;
+next   ;
+commitid       ${commitid};
+
+1\.1\.2\.1
+date   ${RCSDELTADATE};        author ${username};     state foo;
+branches;
+next   ;
+commitid       ${commitid};
+
+
+desc
+@@
+
+
+1\.1
+log
address@hidden
+@
+text
+@@
+
+
+1\.1\.2\.1
+log
address@hidden
+@
+text
address@hidden 1
+add a line on the branch
+@"
+         dotest_fail admin-14-1 "${testcvs} -q admin \
+-m1.1.1.1:changed-bogus-log-message file2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+$SPROG admin: $CVSROOT_DIRNAME/first-dir/file2,v: no such revision 1\.1\.1\.1
+$SPROG admin: RCS file for .file2. not modified."
+         dotest admin-14-2 "${testcvs} -q log file2" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+Working file: file2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       br: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add
+============================================================================="
+
+         dotest admin-14-3 "${testcvs} -q admin -aauth3 -aauth2,foo \
+-soneone:1.1 -m1.1:changed-log-message -ntagone: file2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+         dotest admin-15 "${testcvs} -q log file2" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+Working file: file2
+head: 1\.1
+branch:
+locks: strict
+access list:
+       auth3
+       auth2
+       foo
+symbolic names:
+       tagone: 1\.1
+       br: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: oneone;  commitid: 
${commitid};
+changed-log-message
+============================================================================="
+
+         dotest admin-16 "${testcvs} -q admin \
+-A${CVSROOT_DIRNAME}/first-dir/file2,v -b -L -Nbr:1.1 file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+         dotest admin-17 "${testcvs} -q log file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+       foo
+       bar
+       baz
+       auth3
+       auth2
+symbolic names:
+       br: 1\.1
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.2;
+add
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: foo;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+modify-on-branch
+============================================================================="
+
+         dotest_fail admin-18 "${testcvs} -q admin -nbr:1.1.2 file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/file1,v: symbolic name br already 
bound to 1\.1
+${SPROG} admin: RCS file for .file1. not modified\."
+         dotest admin-19 "${testcvs} -q admin -ebaz -ebar,auth3 -nbr file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+         dotest admin-20 "${testcvs} -q log file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+       foo
+       auth2
+symbolic names:
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.2;
+add
+----------------------------
+revision 1.1.2.1
+date: ${ISO8601DATE};  author: ${username};  state: foo;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+modify-on-branch
+============================================================================="
+
+         # OK, this is starting to get ridiculous, in terms of
+         # testing a feature (access lists) which doesn't do anything
+         # useful, but what about nonexistent files and
+         # relative pathnames in admin -A?
+         dotest_fail admin-19a-nonexist \
+"${testcvs} -q admin -A${TESTDIR}/foo/bar file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+${SPROG} admin: Couldn't open rcs file .${TESTDIR}/foo/bar.: No such file or 
directory
+${SPROG} \[admin aborted\]: cannot continue"
+
+         # In the remote case, we are cd'd off into the temp directory
+         # and so these tests give "No such file or directory" errors.
+         if $remote; then :; else
+           dotest admin-19a-admin "${testcvs} -q admin 
-A../../cvsroot/first-dir/file2,v file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+           dotest admin-19a-log "${testcvs} -q log -h -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+       foo
+       auth2
+       auth3
+keyword substitution: kv
+total revisions: 2
+============================================================================="
+         fi # end of tests skipped for remote
+
+         # Now test that plain -e works right.
+         dotest admin-19a-2 "${testcvs} -q admin -e file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+         dotest admin-19a-3 "${testcvs} -q log -h -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 2
+============================================================================="
+
+         # Put the access list back, to avoid special cases later.
+         dotest admin-19a-4 "${testcvs} -q admin -afoo,auth2 file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+
+         # Add another revision to file2, so we can delete one.
+         echo 'add a line' >> file2
+         dotest admin-21 "${testcvs} -q ci -m modify file2" \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.2; previous revision: 1\.1"
+         dotest admin-22 "${testcvs} -q admin -o1.1 file2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+deleting revision 1\.1
+done"
+         # Test admin -o.  More variants that we could be testing:
+         # * REV: [on branch]
+         # * REV1:REV2 [deleting whole branch]
+         # * high branch numbers (e.g. 1.2.2.3.2.3)
+         # ... and probably others.  See RCS_delete_revs for ideas.
+
+         echo first rev > aaa
+         dotest admin-22-o1 "${testcvs} add aaa" \
+"${SPROG} add: scheduling file .aaa. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest admin-22-o2 "${testcvs} -q ci -m first aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+initial revision: 1\.1"
+         echo second rev >> aaa
+         dotest admin-22-o3 "${testcvs} -q ci -m second aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.2; previous revision: 1\.1"
+         echo third rev >> aaa
+         dotest admin-22-o4 "${testcvs} -q ci -m third aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.3; previous revision: 1\.2"
+         echo fourth rev >> aaa
+         dotest admin-22-o5 "${testcvs} -q ci -m fourth aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.4; previous revision: 1\.3"
+         echo fifth rev >>aaa
+         dotest admin-22-o6 "${testcvs} -q ci -m fifth aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.5; previous revision: 1\.4"
+         echo sixth rev >> aaa
+         dotest admin-22-o7 "${testcvs} -q ci -m sixth aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.6; previous revision: 1\.5"
+         dotest admin-22-o8 "${testcvs} admin -l1.6 aaa" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+1\.6 locked
+done"
+         dotest admin-22-o9 "${testcvs} log -r1.6 aaa" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+Working file: aaa
+head: 1\.6
+branch:
+locks: strict
+       ${username}: 1\.6
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 6;    selected revisions: 1
+description:
+----------------------------
+revision 1\.6  locked by: ${username};
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+sixth
+============================================================================="
+         dotest_fail admin-22-o10 "${testcvs} admin -o1.5: aaa" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/aaa,v: can't remove locked 
revision 1\.6
+${SPROG} admin: RCS file for .aaa. not modified\."
+         dotest admin-22-o11 "${testcvs} admin -u aaa" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+1\.6 unlocked
+done"
+         dotest admin-22-o12 "${testcvs} admin -o1.5: aaa" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+deleting revision 1\.6
+deleting revision 1\.5
+done"
+         dotest admin-22-o13 "${testcvs} log aaa" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+Working file: aaa
+head: 1\.4
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 4;    selected revisions: 4
+description:
+----------------------------
+revision 1\.4
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+fourth
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+third
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+second
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+first
+============================================================================="
+
+         dotest admin-22-o14 "${testcvs} tag -b -r1.3 br1 aaa" "T aaa"
+         dotest admin-22-o15 "${testcvs} update -rbr1 aaa" "U aaa"
+         echo new branch rev >> aaa
+         dotest admin-22-o16 "${testcvs} ci -m new-branch aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.3\.2\.1; previous revision: 1\.3"
+         dotest_fail admin-22-o17 "${testcvs} admin -o1.2:1.4 aaa" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+deleting revision 1\.4
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/aaa,v: can't remove branch point 
1\.3
+${SPROG} admin: RCS file for .aaa. not modified\."
+         dotest admin-22-o18 "${testcvs} update -p -r1.4 aaa" \
+"===================================================================
+Checking out aaa
+RCS:  ${CVSROOT_DIRNAME}/first-dir/aaa,v
+VERS: 1\.4
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+first rev
+second rev
+third rev
+fourth rev"
+         echo second branch rev >> aaa
+         dotest admin-22-o19 "${testcvs} ci -m branch-two aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.3\.2\.2; previous revision: 1\.3\.2\.1"
+         echo third branch rev >> aaa
+         dotest admin-22-o20 "${testcvs} ci -m branch-three aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.3\.2\.3; previous revision: 1\.3\.2\.2"
+         echo fourth branch rev >> aaa
+         dotest admin-22-o21 "${testcvs} ci -m branch-four aaa" \
+"$CVSROOT_DIRNAME/first-dir/aaa,v  <--  aaa
+new revision: 1\.3\.2\.4; previous revision: 1\.3\.2\.3"
+         dotest admin-22-o22 "${testcvs} admin -o:1.3.2.3 aaa" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+deleting revision 1\.3\.2\.1
+deleting revision 1\.3\.2\.2
+deleting revision 1\.3\.2\.3
+done"
+         dotest admin-22-o23 "${testcvs} log aaa" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+Working file: aaa
+head: 1\.4
+branch:
+locks: strict
+access list:
+symbolic names:
+       br1: 1\.3\.0\.2
+keyword substitution: kv
+total revisions: 5;    selected revisions: 5
+description:
+----------------------------
+revision 1\.4
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+fourth
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+branches:  1\.3\.2;
+third
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+second
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+first
+----------------------------
+revision 1\.3\.2\.4
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}4 -0;  
commitid: ${commitid};
+branch-four
+============================================================================="
+
+         dotest admin-22-o24 "${testcvs} -q update -p -r 1.3.2.4 aaa" \
+"first rev
+second rev
+third rev
+new branch rev
+second branch rev
+third branch rev
+fourth branch rev"
+
+         # The bit here about how there is a "tagone" tag pointing to
+         # a nonexistent revision is documented by rcs.  I dunno, I
+         # wonder whether the "cvs admin -o" should give a warning in
+         # this case.
+         dotest admin-23 "${testcvs} -q log file2" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+Working file: file2
+head: 1\.2
+branch:
+locks: strict
+access list:
+       auth3
+       auth2
+       foo
+symbolic names:
+       tagone: 1\.1
+       br: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+modify
+============================================================================="
+
+         dotest admin-25 "cat ${CVSROOT_DIRNAME}/first-dir/file1,v" \
+"head  1\.1;
+access
+       foo
+       auth2;
+symbols;
+locks; strict;
+comment        @xx@;
+
+
+1\.1
+date   ${RCSDELTADATE};        author ${username};     state Exp;
+branches
+       1\.1\.2\.1;
+next   ;
+commitid       ${commitid};
+
+1\.1\.2\.1
+date   ${RCSDELTADATE};        author ${username};     state foo;
+branches;
+next   ;
+commitid       ${commitid};
+
+
+desc
+@@
+
+
+1\.1
+log
address@hidden
+@
+text
+@@
+
+
+1\.1\.2\.1
+log
address@hidden
+@
+text
address@hidden 1
+add a line on the branch
+@"
+
+         # Tests of cvs admin -n.  Make use of the results of
+         # admin-1 through admin-25.
+         # FIXME: We probably shouldn't make use of those results;
+         # this test is way too long as it is.
+
+         # tagtwo should be a revision
+         #
+         dotest admin-26-1 "${testcvs} admin -ntagtwo:tagone file2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+         
+         # br1 should be a branch
+         #
+         dotest admin-26-2 "${testcvs} admin -nbr1:br file2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+         
+         # Attach some tags using RCS versions
+         #
+         dotest admin-26-3 "${testcvs} admin -ntagthree:1.1 file2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+
+         dotest admin-26-4 "${testcvs} admin -nbr2:1.1.2 file2"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+
+         dotest admin-26-5 "${testcvs} admin -nbr4:1.1.0.2 file2"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+         
+         # Check results so far
+         #
+         dotest admin-26-6 "${testcvs} status -v file2" \
+"===================================================================
+File: file2                    Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT_DIRNAME}/first-dir/file2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       br4                             (branch: 1\.1\.2)
+       br2                             (branch: 1\.1\.2)
+       tagthree                        (revision: 1\.1)
+       br1                             (branch: 1\.1\.2)
+       tagtwo                          (revision: 1\.1)
+       tagone                          (revision: 1\.1)
+       br                              (branch: 1\.1\.2)"
+
+         
+         # Add a couple more revisions
+         #
+         echo "nuthr_line" >> file2
+         dotest admin-27-1 "${testcvs} commit -m nuthr_line file2"  \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.3; previous revision: 1\.2"
+
+         echo "yet_another" >> file2
+         dotest admin-27-2 "${testcvs} commit -m yet_another file2"  \
+"$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+new revision: 1\.4; previous revision: 1\.3"
+         
+         # Fail trying to reattach existing tag with -n
+         #
+         dotest admin-27-3 "${testcvs} admin -ntagfour:1.1 file2"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+
+         dotest_fail admin-27-4 "${testcvs} admin -ntagfour:1.3 file2"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/file2,v: symbolic name tagfour 
already bound to 1\.1
+${SPROG} admin: RCS file for .file2. not modified\."
+         
+         # Succeed at reattaching existing tag, using -N
+         #
+         dotest admin-27-5 "${testcvs} admin -Ntagfour:1.3 file2"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+done"
+         
+         # Fail on some bogus operations
+         # Try to attach to nonexistant tag
+         #
+         dotest_fail admin-28-1 "${testcvs} admin -ntagsix:tagfive file2" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/file2,v: Symbolic name or 
revision tagfive is undefined\.
+${SPROG} admin: RCS file for .file2. not modified\."
+         
+         # Try a some nonexisting numeric target tags
+         #
+         dotest_fail admin-28-2 "${testcvs} admin -ntagseven:2.1 file2"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+${SPROG} \[admin aborted\]: revision .2\.1. does not exist"
+
+         dotest_fail admin-28-3 "${testcvs} admin -ntageight:2.1.2 file2"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+${SPROG} \[admin aborted\]: revision .2\.1\.2. does not exist"
+         
+         # Try some invalid targets
+         #
+         dotest_fail admin-28-4 "${testcvs} admin -ntagnine:1.a.2 file2"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+${SPROG} \[admin aborted\]: Tag .1\.a\.2. invalid. Cannot resolve extension: 
\`a'"
+
+         # Confirm that a missing tag is not a fatal error.
+         dotest admin-28-5.1 "${testcvs} -Q tag BO+GUS file1" ''
+         dotest_fail admin-28-5.2 "${testcvs} admin -ntagten:BO+GUS file2 
file1"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/file2,v: Symbolic name or 
revision BO${PLUS}GUS is undefined\.
+${SPROG} admin: RCS file for .file2. not modified\.
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+done"
+
+         dotest_fail admin-28-6 "${testcvs} admin -nq.werty:tagfour file2"  \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+${SPROG} \[admin aborted\]: tag .q\.werty. must not contain the characters ..*"
+
+         # Verify the archive
+         #
+         dotest admin-29 "cat ${CVSROOT_DIRNAME}/first-dir/file2,v" \
+"head  1\.4;
+access
+       auth3
+       auth2
+       foo;
+symbols
+       tagfour:1\.3
+       br4:1\.1\.0\.2
+       br2:1\.1\.0\.2
+       tagthree:1\.1
+       br1:1\.1\.0\.2
+       tagtwo:1\.1
+       tagone:1\.1
+       br:1\.1\.0\.2;
+locks; strict;
+comment        @# @;
+
+
+1\.4
+date   ${RCSDELTADATE};        author ${username};     state Exp;
+branches;
+next   1\.3;
+commitid       ${commitid};
+
+1\.3
+date   ${RCSDELTADATE};        author ${username};     state Exp;
+branches;
+next   1\.2;
+commitid       ${commitid};
+
+1\.2
+date   ${RCSDELTADATE};        author ${username};     state Exp;
+branches;
+next   ;
+commitid       ${commitid};
+
+
+desc
+@@
+
+
+1\.4
+log
address@hidden
+@
+text
address@hidden a line
+nuthr_line
+yet_another
+@
+
+
+1\.3
+log
address@hidden
+@
+text
address@hidden 1
+@
+
+
+1\.2
+log
address@hidden
+@
+text
address@hidden 1
+@"
+
+         dotest_fail admin-30 "${testcvs} admin -mbr:another-log-message \
+file2 aaa file3" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/file2,v: no such revision br: 1\.1
+${SPROG} admin: RCS file for .file2. not modified.
+RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/aaa,v: no such revision br
+${SPROG} admin: RCS file for .aaa. not modified.
+RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file3,v
+done"
+         dotest admin-31 "${testcvs} log" \
+"${SPROG} log: Logging \.
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/aaa,v
+Working file: aaa
+head: 1\.4
+branch:
+locks: strict
+access list:
+symbolic names:
+       br1: 1\.3\.0\.2
+keyword substitution: kv
+total revisions: 5;    selected revisions: 5
+description:
+----------------------------
+revision 1\.4
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+fourth
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+branches:  1\.3\.2;
+third
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+second
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+first
+----------------------------
+revision 1\.3\.2\.4
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}4 -0;  
commitid: ${commitid};
+branch-four
+=============================================================================
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+       foo
+       auth2
+symbolic names:
+       tagten: 1\.1
+       BO${PLUS}GUS: 1\.1
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+branches:  1\.1\.2;
+add
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: foo;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+modify-on-branch
+=============================================================================
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file2,v
+Working file: file2
+head: 1\.4
+branch:
+locks: strict
+access list:
+       auth3
+       auth2
+       foo
+symbolic names:
+       tagfour: 1\.3
+       br4: 1\.1\.0\.2
+       br2: 1\.1\.0\.2
+       tagthree: 1\.1
+       br1: 1\.1\.0\.2
+       tagtwo: 1\.1
+       tagone: 1\.1
+       br: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.4
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+yet_another
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+nuthr_line
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+modify
+=============================================================================
+
+RCS file: ${CVSROOT_DIRNAME}/first-dir/Attic/file3,v
+Working file: file3
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+       br: 1\.1\.0\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: dead;  commitid: 
${commitid};
+branches:  1\.1\.2;
+file file3 was initially added on branch br\.
+----------------------------
+revision 1\.1\.2\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+another-log-message
+============================================================================="
+
+         # Currently, this test outputs 36 identical lines, so I am just
+         # checking $DOTSTAR for brevity.
+         dotest admin-postadmin-examine-1 "cat $TESTDIR/2/admin-log" \
+"$CVSROOT_DIRNAME first-dir admin$DOTSTAR"
+
+         dokeep
+
+         # clean up our after ourselves
+         restore_adm
+         cd ../..
+         rm -r 1 2
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       reserved)
+         # Tests of reserved checkouts.  Eventually this will test
+         # rcslock.pl (or equivalent) and all kinds of stuff.  Right
+         # now it just does some very basic checks on cvs admin -u
+         # and cvs admin -l.
+         # Also should test locking on a branch (and making sure that
+         # locks from one branch don't get mixed up with those from
+         # another.  Both the case where one of the branches is the
+         # main branch, and in which neither one is).
+         # See also test keyword, which tests that keywords and -kkvl
+         # do the right thing in the presence of locks.
+
+         # The usual setup, directory first-dir containing file file1.
+         mkdir 1; cd 1
+         dotest reserved-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest reserved-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+         touch file1
+         dotest reserved-3 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest reserved-4 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1"
+
+         dotest reserved-5 "${testcvs} -q admin -l file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+1\.1 locked
+done"
+         dotest reserved-6 "${testcvs} log -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+       ${username}: 1\.1
+access list:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1  locked by: ${username};
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add
+============================================================================="
+
+         # Note that this just tests the owner of the lock giving
+         # it up.  It doesn't test breaking a lock.
+         dotest reserved-7 "${testcvs} -q admin -u file1" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+1\.1 unlocked
+done"
+
+         dotest reserved-8 "${testcvs} log -N file1" "
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch:
+locks: strict
+access list:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+add
+============================================================================="
+
+         # rcslock.pl tests.  Of course, the point isn't to test
+         # rcslock.pl from the distribution but equivalent
+         # functionality (for example, many sites may have an old
+         # rcslock.pl).  The functionality of this hook falls
+         # short of the real rcslock.pl though.
+         # Note that we can use rlog or look at the RCS file directly,
+         # but we can't use "cvs log" because "cvs commit" has a lock.
+
+         cat >${TESTDIR}/lockme <<EOF
+#!${TESTSHELL}
+line=\`grep <\$1/\$2,v 'locks $anyusername:1\.[0-9];'\`
+if test -z "\$line"; then
+  # It isn't locked
+  exit 0
+else
+  user=\`echo \$line | sed -e 's/locks \\($anyusername\\):[0-9.]*;.*/\\1/'\`
+  version=\`echo \$line | sed -e 's/locks $anyusername:\\([0-9.]*\\);.*/\\1/'\`
+  echo "\$user has file a-lock locked for version  \$version" >&2
+  exit 1
+fi
+EOF
+         # Cygwin.  Blaaarg.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod +x ${TESTDIR}/lockme"
+         else
+           chmod +x ${TESTDIR}/lockme
+         fi
+
+         echo stuff > a-lock
+         dotest reserved-9 "${testcvs} add a-lock" \
+"${SPROG} add: scheduling file .a-lock. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest reserved-10 "${testcvs} -q ci -m new a-lock" \
+"$CVSROOT_DIRNAME/first-dir/a-lock,v  <--  a-lock
+initial revision: 1\.1"
+         # FIXME: the contents of CVSROOT fluctuate a lot
+         # here. Maybe the expect pattern should just
+         # confirm that commitinfo is one of the files checked out,
+         # but for now we just check that CVS exited with success.
+         cd ..
+         if ${testcvs} -q co CVSROOT >>${LOGFILE} ; then
+           pass reserved-11
+         else
+           fail reserved-11
+         fi
+         cd CVSROOT
+         echo "DEFAULT ${TESTDIR}/lockme" >>commitinfo
+         dotest reserved-12 "${testcvs} -q ci -m rcslock commitinfo" \
+"$CVSROOT_DIRNAME/CVSROOT/commitinfo,v  <--  commitinfo
+new revision: 1\.2; previous revision: 1\.1
+$SPROG commit: Rebuilding administrative file database"
+         cd ..; cd first-dir
+
+         # Simulate (approximately) what a-lock would look like
+         # if someone else had locked revision 1.1.
+         sed -e 's/locks; strict;/locks fred:1.1; strict;/' 
${CVSROOT_DIRNAME}/first-dir/a-lock,v > a-lock,v
+         # Cygwin.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod 644 
${CVSROOT_DIRNAME}/first-dir/a-lock,v"
+         else
+           chmod 644 ${CVSROOT_DIRNAME}/first-dir/a-lock,v
+         fi
+         dotest reserved-13 "mv a-lock,v ${CVSROOT_DIRNAME}/first-dir/a-lock,v"
+         # Cygwin.  Blah.
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod 444 
${CVSROOT_DIRNAME}/first-dir/a-lock,v"
+         else
+           chmod 444 ${CVSROOT_DIRNAME}/first-dir/a-lock,v
+         fi
+         echo more stuff >> a-lock
+         dotest_fail_sort reserved-13b "$testcvs ci -m '' a-lock" \
+"    \"$TESTDIR/lockme\"
+Appending defaults (\" %r/%p %s\"), but please be aware that this usage is
+$SPROG \[commit aborted\]: correct above errors first!
+$SPROG commit: Pre-commit check failed
+$SPROG commit: warning: commitinfo line contains no format strings:
+deprecated\.
+fred has file a-lock locked for version  1\.1"
+         # OK, now test "cvs admin -l" in the case where someone
+         # else has the file locked.
+         dotest_fail reserved-13c "${testcvs} admin -l a-lock" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/a-lock,v
+${SPROG} \[admin aborted\]: Revision 1\.1 is already locked by fred"
+
+         dotest reserved-14 "${testcvs} admin -u1.1 a-lock" \
+"RCS file: ${CVSROOT_DIRNAME}/first-dir/a-lock,v
+${SPROG} admin: ${CVSROOT_DIRNAME}/first-dir/a-lock,v: revision 1\.1 locked by 
fred; breaking lock
+1\.1 unlocked
+done"
+         dotest reserved-15 "$testcvs -q ci -m success a-lock" \
+"$SPROG commit: warning: commitinfo line contains no format strings:
+    \"$TESTDIR/lockme\"
+Appending defaults (\" %r/%p %s\"), but please be aware that this usage is
+deprecated\.
+$CVSROOT_DIRNAME/first-dir/a-lock,v  <--  a-lock
+new revision: 1\.2; previous revision: 1\.1"
+
+         # Now test for a bug involving branches and locks
+         sed -e 's/locks; strict;/locks fred:1.2; strict;/' 
${CVSROOT_DIRNAME}/first-dir/a-lock,v > a-lock,v
+         chmod 644 ${CVSROOT_DIRNAME}/first-dir/a-lock,v
+         dotest reserved-16 \
+"mv a-lock,v ${CVSROOT_DIRNAME}/first-dir/a-lock,v" ""
+         chmod 444 ${CVSROOT_DIRNAME}/first-dir/a-lock,v
+         dotest reserved-17 "${testcvs} -q tag -b br a-lock" "T a-lock"
+         dotest reserved-18 "${testcvs} -q update -r br a-lock" ""
+         echo edit it >>a-lock
+         dotest reserved-19 "${testcvs} -q ci -m modify a-lock" \
+"$SPROG commit: warning: commitinfo line contains no format strings:
+    \"$TESTDIR/lockme\"
+Appending defaults (\" %r/%p %s\"), but please be aware that this usage is
+deprecated\.
+$CVSROOT_DIRNAME/first-dir/a-lock,v  <--  a-lock
+new revision: 1\.2\.2\.1; previous revision: 1\.2"
+
+         # undo commitinfo changes
+         cd ../CVSROOT
+         echo '# vanilla commitinfo' >commitinfo
+         dotest reserved-cleanup-1 "${testcvs} -q ci -m back commitinfo" \
+"$SPROG commit: warning: commitinfo line contains no format strings:
+    \"$TESTDIR/lockme\"
+Appending defaults (\" %r/%p %s\"), but please be aware that this usage is
+deprecated\.
+$CVSROOT_DIRNAME/CVSROOT/commitinfo,v  <--  commitinfo
+new revision: 1\.3; previous revision: 1\.2
+$SPROG commit: Rebuilding administrative file database"
+
+         dokeep
+         cd ..; rm -r CVSROOT
+         cd ..
+         rm -r 1
+         rm $TESTDIR/lockme
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+        diffmerge1)
+         # Make sure CVS can merge correctly in circumstances where it
+         # used to mess up (due to a bug which existed in diffutils 2.7
+         # and 2.6, but not 2.5, and which has been fixed in CVS's diff
+         # lib by Paul Eggert, bless his bitty heart).
+
+         # This first test involves two working copies, "mine" and
+         # "yours", checked out from the same repository at the same
+         # time.  In yours, you remove some text from the end of the
+         # file and check it in; meanwhile, "me" has commented out some
+         # lines earlier in the file, and I go to check it in right
+         # after you checked yours in.  CVS naturally tells me the file
+         # is not up-to-date, so I run cvs update, but it updates
+         # incorrectly, leaving in the lines of text you just deleted.
+         # Bad!  I'm in too much of a hurry to actually look at the
+         # file, so I check it in and go home, and so your changes have
+         # been lost.  Later you discover this, and you suspect me of
+         # deliberately sabotaging your work, so you let all the air
+         # out of my tires.  Only after a series of expensive lawsuits
+         # and countersuits do we discover that this was all CVS's
+         # fault.
+         #
+         # Luckily, this problem has been fixed now, as our test will
+         # handily confirm, no doubt:
+
+         # First make a repository containing the original text:
+
+         # We should be here anyway, but cd to it just in case:
+         cd ${TESTDIR}
+
+         mkdir diffmerge1
+         cd diffmerge1
+
+         # These are the files we both start out with:
+         mkdir import
+         cd import
+         diffmerge_create_older_files
+
+         dotest diffmerge1_import \
+           "${testcvs} import -m import diffmerge1 tag1 tag2" \
+           "${DOTSTAR}No conflicts created by this import"
+         cd ..
+
+         # Check out two working copies, one for "you" and one for
+         # "me".  If no branch is used and cvs detects that only one
+         # of the two people made changes, then cvs does not run the
+         # merge algorithm.  But if a branch is used, then cvs does run
+         # the merge algorithm (even in this case of only one of the two
+         # people having made changes).  CVS used to have a bug in this
+         # case.  Therefore, it is important to test this case by
+         # using a branch:
+         ${testcvs} rtag     -b tag diffmerge1 >/dev/null 2>&1
+         ${testcvs} checkout -r tag diffmerge1 >/dev/null 2>&1
+         mv diffmerge1 yours
+         ${testcvs} checkout diffmerge1 >/dev/null 2>&1
+         mv diffmerge1 mine
+
+         # In your working copy, you'll make changes, and
+         # then check in your changes before I check in mine:
+         cd yours
+         diffmerge_create_your_files
+          dotest diffmerge1_yours "${testcvs} -q ci -m yours" \
+"$CVSROOT_DIRNAME/diffmerge1/testcase01,v  <--  testcase01
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1
+$CVSROOT_DIRNAME/diffmerge1/testcase02,v  <--  testcase02
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1
+$CVSROOT_DIRNAME/diffmerge1/testcase03,v  <--  testcase03
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1
+$CVSROOT_DIRNAME/diffmerge1/testcase04,v  <--  testcase04
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1
+$CVSROOT_DIRNAME/diffmerge1/testcase05,v  <--  testcase05
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1
+$CVSROOT_DIRNAME/diffmerge1/testcase06,v  <--  testcase06
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1
+$CVSROOT_DIRNAME/diffmerge1/testcase07,v  <--  testcase07
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1
+$CVSROOT_DIRNAME/diffmerge1/testcase08,v  <--  testcase08
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1
+$CVSROOT_DIRNAME/diffmerge1/testcase09,v  <--  testcase09
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1
+$CVSROOT_DIRNAME/diffmerge1/testcase10,v  <--  testcase10
+new revision: 1\.1\.1\.1\.2\.1; previous revision: 1\.1\.1\.1"
+
+         # Change my copy.  Then I
+         # update, after both my modifications and your checkin:
+         cd ../mine
+         diffmerge_create_my_files
+         dotest diffmerge1_mine "${testcvs} -q update -j tag" \
+"M testcase01
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase01,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase01
+M testcase02
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase02,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase02
+M testcase03
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase03,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase03
+M testcase04
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase04,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase04
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase05,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase05
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase06,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase06
+M testcase07
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase07,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase07
+testcase07 already contains the differences between 1\.1\.1\.1 and 
1\.1\.1\.1\.2\.1
+M testcase08
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase08,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase08
+M testcase09
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase09,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase09
+M testcase10
+RCS file: ${CVSROOT_DIRNAME}/diffmerge1/testcase10,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.1\.1\.1\.2\.1
+Merging differences between 1\.1\.1\.1 and 1\.1\.1\.1\.2\.1 into testcase10"
+
+         # So if your changes didn't make it into my working copy, or
+         # in any case if the files do not look like the final text
+         # in the files in directory comp_me, then the test flunks:
+         cd ..
+         mkdir comp_me
+         cd comp_me
+         diffmerge_create_expected_files
+         cd ..
+         rm mine/.#*
+
+         dotest diffmerge1_cmp "directory_cmp comp_me mine"
+
+         # Clean up after ourselves:
+         dokeep
+         cd ..
+         rm -r diffmerge1
+         modify_repo rm -rf $CVSROOT_DIRNAME/diffmerge1
+         ;;
+
+
+
+        diffmerge2)
+
+         # FIXME: This test should be rewritten to be much more concise.
+         # It currently weighs in at something like 600 lines, but the
+         # same thing could probably be tested in more like 50-100 lines.
+         mkdir diffmerge2
+
+         # This tests for another diffmerge bug reported by Martin
+         # Tomes; actually, his bug was probably caused by an initial
+         # fix for the bug in test diffmerge1, and likely wasn't ever
+         # a problem in CVS as long as one was using a normal
+         # distribution of diff or a version of CVS that has the diff
+         # lib in it. 
+         #
+         # Nevertheless, once burned twice cautious, so we test for his
+         # bug here.
+         #
+         # Here is his report, more or less verbatim:
+         # ------------------------------------------
+         #
+         # Put the attached file (sgrid.h,v) into your repository
+         # somewhere, check out the module and do this:
+         #
+         # cvs update -j Review_Phase_2_Enhancements sgrid.h
+         # cvs diff -r Review_V1p3 sgrid.h
+         #
+         # As there have been no changes made on the trunk there
+         # should be no differences, however this is output:
+         #
+         # % cvs diff -r Review_V1p3 sgrid.h
+         # Index: sgrid.h
+         # ===================================================================
+         # RCS file: /usr/local/repository/play/fred/sgrid.h,v
+         # retrieving revision 1.1.2.1
+         # diff -r1.1.2.1 sgrid.h
+         # 178a179,184
+         # > /*--------------------------------------------------------------
+         # > INLINE FUNCTION    :    HORIZONTALLINES
+         # > NOTES              :    Description at the end of the file
+         # > ----------------------------------------------------------------*/
+         # >         uint16 horizontalLines( void );
+         # >
+         #
+         # I did a cvs diff -c -r 1.1 -r 1.1.2.1 sgrid.h and patched those
+         # differences to sgrid.h version 1.1 and got the correct result
+         # so it looks like the built in patch is faulty.
+         # -------------------------------------------------------------------
+         #
+         # This is the RCS file, sgrid.h,v, that he sent:
+
+         echo "head    1.1;
+access;
+symbols
+       Review_V1p3:1.1.2.1
+       Review_V1p3C:1.1.2.1
+       Review_1p3A:1.1.2.1
+       Review_V1p3A:1.1.2.1
+       Review_Phase_2_Enhancements:1.1.0.2
+       Review_V1p2:1.1
+       Review_V1p2B:1.1
+       Review_V1p2A:1.1
+       Review_V1p1:1.1
+       Review_1p1:1.1;
+locks; strict;
+comment        @ * @;
+
+
+1.1
+date   97.04.02.11.20.05;      author colinl;  state Exp;
+branches
+       1.1.2.1;
+next   ;
+
+1.1.2.1
+date   97.06.09.10.00.07;      author colinl;  state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.1
+log
address@hidden:     DEV1175
+DCN:
+Tested By:   Colin Law
+Reviewed By:
+Reason for Change: Initial Revision of all files
+
+Design Change Details:
+
+Implications:
+@
+text
+@/* \$""Header:   L:/gpanels/dis/sgrid.h_v   1.1.1.0   24 Jan 1996 14:59:20   
PAULT  \$ */
+/*
+ * \$""Log:   L:/gpanels/dis/sgrid.h_v  \$
+ * 
+ *    Rev 1.1.1.0   24 Jan 1996 14:59:20   PAULT
+ * Branched
+ * 
+ *    Rev 1.1   24 Jan 1996 12:09:52   PAULT
+ * Consolidated 4100 code merged to trunk
+ * 
+ *    Rev 1.0.2.0   01 Jun 1995 14:18:58   DAVEH
+ * Branched
+ * 
+ *    Rev 1.0   19 Apr 1995 16:32:48   COLINL
+ * Initial revision.
+*/
+/*****************************************************************************
+FILE        :   SGRID.H
+VERSION     :   2.1
+AUTHOR      :   Dave Hartley
+SYSTEM      :   Borland C++
+DESCRIPTION :   The declaration of the scrolling grid class
+                  
+*****************************************************************************/
+#if !defined(__SGRID_H)
+#define __SGRID_H
+
+#if !defined(__SCROLL_H)
+#include <scroll.h>
+#endif
+
+#if !defined(__GKI_H)
+#include \"gki.h\"
+#endif
+
+#if defined PRINTING_SUPPORT
+class Printer;
+#endif
+
+/*****************************************************************************
+CLASS      :    ScrollingGrid   
+DESCRIPTION:    This class inherits from a grid and a scrollable, and
+                can therefore use all the PUBLIC services provided by these
+                classes. A description of these can be found in
+                GRID.H and SCROLL.H.
+                A scrolling grid is a set of horizontal and vertical lines
+                that scroll and continually update to provide a complete grid
+
+*****************************************************************************/
+
+class ScrollingGrid : public Scrollable
+{
+    public:
+#if defined _WINDOWS
+/*---------------------------------------------------------------------------
+FUNCTION    :   CONSTRUCTOR
+DESCRIPTION :   sets up the details of the grid, ready for painting
+ARGUMENTS   :   name  : sgColour
+                        - the colour of the grid
+                        sgLineType
+                        - the syle of line
+                        sgHorizontalTotal
+                        - the total number of horizontal grid lines
+                        verticalSpacingMin
+                        - the min distance between the vertical grid lines
+                          on the scrolling axis
+                        currentTimestamp
+                        - timestamp value now
+                        ticksPerSecond
+                        - number of timestamp ticks per second
+                        ticksPerPixel
+                        - number of timestamp ticks per pixel required
+                      
+RETURN      :   None
+NOTES       :   
+---------------------------------------------------------------------------*/
+        ScrollingGrid( GkiColour sgColour, GkiLineType sgLineType, 
+            uint16 sgHorizontalTotal, 
+            uint16 verticalSpacingMin, uint32 currentTimestamp, 
+            uint16 ticksPerSecond, uint32 ticksPerPixel );
+#else
+/*---------------------------------------------------------------------------
+FUNCTION    :   CONSTRUCTOR
+DESCRIPTION :   sets up the details of the grid, ready for painting
+ARGUMENTS   :   name  : sgColour
+                        - the colour of the grid
+                        sgLineType
+                        - the syle of line
+                        sgHorizontalTotal ( THE MAX NUMBER OF LINES IS 100 )
+                        - the total number of horizontal grid lines
+                        sgVerticalSpacing
+                        - the distance between the vertical grid lines
+                        on the scrolling axis
+                      
+RETURN      :   None
+NOTES       :   If the caller does not get the total grid lines value, synced
+                with the overall size of the viewport, the spacing between
+                grid lines will not be consistent.
+
+---------------------------------------------------------------------------*/
+        ScrollingGrid( GkiColour sgColour, GkiLineType sgLineType
+                     , uint16 sgHorizontalTotal, uint16 sgVerticalSpacing );
+#endif
+/*---------------------------------------------------------------------------
+FUNCTION    :   DESTRUCTOR
+DESCRIPTION :   tidies it all up
+ARGUMENTS   :   name  :      
+                      
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        ~ScrollingGrid( void );
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   ATTACH
+DESCRIPTION :   This service overloads the base class service, as it does
+                additional work at the time of attachment.
+
+ARGUMENTS   :   name  : tDrawingArea
+                        - the scrolled viewport to attach this trend to
+                      
+RETURN      :   None
+NOTES       :
+---------------------------------------------------------------------------*/
+        void attach( SViewport *tDrawingArea );
+
+#if defined _WINDOWS
+/*---------------------------------------------------------------------------
+FUNCTION    :   calculateVerticalSpacing
+DESCRIPTION :   determines optimum spacing along time axis
+ARGUMENTS   :   
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void calculateVerticalSpacing();
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   gridSpacingTicks
+DESCRIPTION :   Provides the grid spacing in the time axis in ticks
+ARGUMENTS   :   
+RETURN      :   Number of ticks
+NOTES       : 
+---------------------------------------------------------------------------*/
+        uint32 gridSpacingTicks();
+
+#endif
+
+/*---------------------------------------------------------------------------
+INLINE FUNCTION    :    HORIZONTALLINES
+NOTES              :    Description at the end of the file
+---------------------------------------------------------------------------*/
+        uint16 horizontalLines( void );
+
+#if defined _WINDOWS
+// In Windows the OnDraw() function replaces paint()
+/*---------------------------------------------------------------------------
+FUNCTION    :   ScrollingGrid OnDraw   
+DESCRIPTION :   Paints the given area of the grid.
+                Pure virtual
+ARGUMENTS   :   pDC     pointer to the device context to use for display
+                        Note that the device context operates in the coords
+                        of the window owning the viewport
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        virtual void OnDraw( CDC *pDC );
+
+#else   // not Windows            
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   PAINT
+DESCRIPTION :   This extends the standard grid paint method to paint the
+                viewport relative to its current position. 
+                
+ARGUMENTS   :   name  :      
+                      
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void paint( void );
+#endif
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   P A I N T   T E X T   M A R K E R S 
+DESCRIPTION :   this service allow the text markers to be painted seperatley
+                from the grid lines
+
+ARGUMENTS   :   name : 
+                                                                          
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void paintTextMarkers();
+
+#if defined PRINTING_SUPPORT
+/*---------------------------------------------------------------------------
+FUNCTION    :   P R I N T 
+DESCRIPTION :   This print service prints a grid marker ( being either a
+                timestamp or a date, IF there is one at the plot position
+                given
+
+ARGUMENTS   :   name :
+                        displayPosition
+                        - Where in the log to look to see if there is an
+                          entry to print
+
+                        - printerPtr
+                          the printer to print to
+                                                                          
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void print( uint16 currentPrintPos, Printer *printerPtr );
+#endif
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   S E T  D R I V E  D I R E C T I O N
+DESCRIPTION :   Sets direction for update and scrolling forwards or backwards
+ARGUMENTS   :   direction  - required direction
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void setDriveDirection( ScrollDirection direction );
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   S E T U P 
+DESCRIPTION :   service that will setup the grid prior to a paint
+
+ARGUMENTS   :   name :
+                        - newTimestamp
+                            
+
+                        - newTimeBase
+                        the number of ticks that represent a plot point on
+                        the trendgraph. 
+                                                                          
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void setup( uint32 newTimestamp, uint32 newTimeBase );
+
+#if defined PRINTING_SUPPORT
+/*---------------------------------------------------------------------------
+FUNCTION    :   S E T U P   F O R   P R I N T   
+DESCRIPTION :   This service iis to be called prior to printing. It allows
+                the grid to prepare its markers ready for the print
+                commands
+
+ARGUMENTS   :   name : 
+                                                                          
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void setupForPrint();
+#endif
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   UPDATE
+DESCRIPTION :   When this service is called it will calculate what needs to
+                be painted and fill in the display again.
+
+ARGUMENTS   :   name  :     timeStamp
+                            - the reference time of this update.
+                      
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void update( uint32 timeStamp );
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   U P D A T E   B U F F E R
+DESCRIPTION :   When a display update is not required, use this method. It
+                updates the internal data ready for a call to paint that
+                will then show the grid in the right position
+
+ARGUMENTS   :   name  :      
+                      
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void updateBuffer( void );
+
+    private:
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   M A K E   G R I D   M A R K E R 
+DESCRIPTION :   service that perpares a string for display. The string will
+                either be a short date, or short time. this is determined
+                by the current setting of the dateMarker flag
+
+ARGUMENTS   :   name :  timestampVal
+                        - the value to convert
+                        
+                        storePtr
+                        - the place to put the string
+
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void makeGridMarker( uint32 timestampVal, char *storePtr );
+            
+/*---------------------------------------------------------------------------
+FUNCTION    :   P A I N T   G R I D   M A R K E R 
+DESCRIPTION :   given a position will put the string on the display
+
+ARGUMENTS   :   name :
+                        yPos
+                        - were it goes on the Y-axis
+
+                        gridMarkerPtr
+                        - what it is
+                                                                          
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void paintGridMarker( uint16 yPos, char *gridMarkerPtr );
+
+#if defined _WINDOWS
+/*---------------------------------------------------------------------------
+FUNCTION    :   PAINTHORIZONTALLINES
+DESCRIPTION :   responsible for painting the grids horizontal lines 
+ARGUMENTS   :   pRectToDraw     pointer to rectangle that needs refreshing.
+                                in viewport coords
+                pDC             pointer to device context to use
+                      
+RETURN      : None
+NOTES       :
+---------------------------------------------------------------------------*/
+        void paintHorizontalLines(RectCoords* pRectToDraw, CDC* pDC );
+#else
+/*---------------------------------------------------------------------------
+FUNCTION    :   PAINTHORIZONTALLINES
+DESCRIPTION :   responsible for painting the grids horizontal lines 
+ARGUMENTS   : name: xStart
+                    - the starting X co-ordinate for the horizontal line
+                    xEnd
+                    - the ending X co-ordinate for the horizontal line
+                      
+RETURN      : None
+NOTES       : Remember lines are drawn from origin. The origin in a
+              horizontal viewport will be the top.    
+---------------------------------------------------------------------------*/
+        void paintHorizontalLines( uint16 xStart, uint16 xEnd );
+#endif
+
+#if defined _WINDOWS
+/*---------------------------------------------------------------------------
+FUNCTION    :   PAINTVERTICALLINES
+DESCRIPTION :   responsible for painting the grids vertical lines 
+ARGUMENTS   :   pRectToDraw     pointer to rectangle that needs refreshing.
+                                in viewport coords
+                offset          offset from rhs that rightmost line would be 
+                                drawn if rectangle included whole viewport
+                pDC             pointer to device context to use
+RETURN      : None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void paintVerticalLines( RectCoords* pRectToDraw, uint16 offset,
+            CDC* pDC );
+#else
+/*---------------------------------------------------------------------------
+FUNCTION    :   PAINTVERTICALLINES
+DESCRIPTION :   responsible for painting the grids vertical lines 
+ARGUMENTS   : name  :   yStart
+                        - the starting Y co-ordinate for the vertical line
+                        yEnd
+                        - the ending Y co-ordinate for the vertical line
+                        offset
+                        - a starting point offset that determines at what X
+                        position the first line will be drawn
+
+                      
+RETURN      : None
+NOTES       : 
+---------------------------------------------------------------------------*/
+        void paintVerticalLines( uint16 yStart, uint16 yEnd, uint16 offset );
+#endif
+
+#if defined _WINDOWS
+/*---------------------------------------------------------------------------
+FUNCTION    :   PAINTVERTICALLINE
+DESCRIPTION :   paints one line at the position specified, and length
+ARGUMENTS   :   name  : yStart
+                        - the starting point on the y axis for the line
+                        yEnd
+                        - the end point on the y axis for the line
+                        xPosition
+                        - The horizontal offset from the start of the viewport
+                pDC             pointer to device context to use
+                      
+RETURN      :   None
+NOTES       :   There is not an equivalent horizontal method as yet. This
+                is a seperate method because the service is useful to a
+                derivation of this class
+---------------------------------------------------------------------------*/
+        void paintVerticalLine( uint16 yStart, uint16 yEnd
+                              , uint16 xPosition, CDC *pDC );
+#else
+/*---------------------------------------------------------------------------
+FUNCTION    :   PAINTVERTICALLINE
+DESCRIPTION :   paints one line at the position specified, and length
+ARGUMENTS   :   name  : yStart
+                        - the starting point on the y axis for the line
+                        yEnd
+                        - the end point on the y axis for the line
+                        xPosition
+                        - The horizontal offset from the start of the viewport
+                      
+RETURN      :   None
+NOTES       :   There is not an equivalent horizontal method as yet. This
+                is a seperate method because the service is useful to a
+                derivation of this class
+---------------------------------------------------------------------------*/
+        void paintVerticalLine( uint16 yStart, uint16 yEnd
+                              , uint16 xPosition );
+#endif
+
+/*---------------------------------------------------------------------------
+INLINE FUNCTION    :    VERTICALSPACING
+NOTES              :    Description at the end of the file
+---------------------------------------------------------------------------*/
+        uint16 verticalSpacing( void );
+
+
+        // Position in viewport that we are now writing to if going forwards
+        // Note that if this is greater than viewport length then we have
+        // just scrolled and value must be adjusted before use.
+        sint16 forwardsOutputPosition;
+        
+        // Position in viewport that we are now writing to if going backwards
+        // Note that if this is less than zero then we have
+        // just scrolled and value must be adjusted before use.
+        sint16 backwardsOutputPosition;
+
+        // position in grid cycle of forwards output position.
+        // if zero then it is time to output a grid line
+        sint16 forwardsIntervalCount;
+
+        // position in grid cycle of forwards output position.
+        // if zero then it is time to output a grid line
+        sint16 backwardsIntervalCount;
+        
+        uint32  lastUpdateTimestamp;
+        uint32  timeBase;       // ticks per pixel
+        uint16  currentOutputPosition;
+        uint16  gridTimestampSpacing;
+        uint16  intervalCount;
+        uint16  horizontalTotal;
+        uint16  vSpacing;
+#if defined PRINTING_SUPPORT
+        uint16  numberOfGridMarkersPrinted;
+#endif
+        bool    firstTime;       // indicates first time through
+        bool    dateMarker;
+
+        GkiLineType lineType;
+        GkiColour   gridColour;
+
+    #if defined _WINDOWS
+        uint16 ticksPerSec;     // number of time ticks per second
+        uint16 vSpacingMin;     // minimum pixels per division along time axis 
+        CPen *pPen;             // the pen to use for drawing in windows
+    #endif
+
+};
+
+
+/*****************************************************************************
+                        I N L I N E   F U N C T I O N S   
+*****************************************************************************/
+
+/*---------------------------------------------------------------------------
+FUNCTION    :   HORIZONTALLINES
+DESCRIPTION :   supplies the number of horizontal lines in the grid
+ARGUMENTS   :   name  :      
+                      
+RETURN      :   
+NOTES       : 
+---------------------------------------------------------------------------*/
+inline uint16 ScrollingGrid::horizontalLines( void )
+{
+    return( horizontalTotal );
+}
+/*---------------------------------------------------------------------------
+FUNCTION    :   VERTICALSPACING
+DESCRIPTION :   returns the distance between adjacent vertical lines
+ARGUMENTS   :   name  :      
+                      
+RETURN      :   None
+NOTES       : 
+---------------------------------------------------------------------------*/
+inline uint16 ScrollingGrid::verticalSpacing( void )
+{
+    return( vSpacing );
+}
+
+#endif
+@
+
+
+1.1.2.1
+log
address@hidden:DS4    Provision of major and minor grid lines
+@
+text
address@hidden 1
+a1 1
+/* \$""Header: /usr/local/repository/cmnsrc/review/src/sgrid.h,v 1.1 
1997/04/02 11:20:05 colinl Exp \$ */
+d3 1
+a3 12
+ * \$""Log: sgrid.h,v \$
+ * Revision 1.1  1997/04/02 11:20:05  colinl
+ * Project:     DEV1175
+ * DCN:
+ * Tested By:   Colin Law
+ * Reviewed By:
+ * Reason for Change: Initial Revision of all files
+ *
+ * Design Change Details:
+ *
+ * Implications:
+ *
+d58 6
+a63 5
+ARGUMENTS   :   name  : majorColour         colour for major grid lines
+                        minorColour         colour for minor grid lines
+                        sgLineType          line type for minor grid lines
+                        yMajorGridLines     number of major y lines on grid
+                        yMinorGridLines     number of major y lines on grid
+d77 2
+a78 3
+        ScrollingGrid( GkiColour majorColour, GkiColour minorColour, 
+            GkiLineType sgLineType, 
+            uint16 yMajorGridLines, uint16 yMinorGridLines,
+a137 17
+FUNCTION    :   DrawHorizontalGridLines
+
+DESCRIPTION :   Draws major or minor grid lines
+ARGUMENTS   :   pDC         device context
+                pPen        pen to use
+                numLines    total lines required
+                yLow, yHigh, xLow, xHigh   rectangle to draw in
+                yMax        max y value
+RETURN      :   None
+NOTES       :   
+---------------------------------------------------------------------------*/
+        void DrawHorizontalGridLines( CDC* pDC, CPen* pPen, 
+            uint16 numLines,
+            uint16 yLow, uint16 yHigh, uint16 xLow, uint16 xHigh, 
+            uint16 yMax );
+
+/*---------------------------------------------------------------------------
+d148 6
+d448 1
+a448 2
+        uint16  m_yMajorGridLines;
+        uint16  m_yMinorGridLines;
+d456 2
+a457 3
+        GkiLineType lineType;    // line type for minor grid lines
+        GkiColour   m_majorColour;
+        GkiColour   m_minorColour;
+d462 1
+a462 2
+        CPen *pMajorPen;        // pen to use for drawing major grid lines
+        CPen *pMinorPen;        // pen to use for drawing minor grid lines
+d472 12
+@" > diffmerge2/sgrid.h,v
+
+         # We have to put the RCS file in the repository by hand for
+         # this test:
+         modify_repo mkdir $CVSROOT_DIRNAME/diffmerge2
+         modify_repo cp diffmerge2/sgrid.h,v \
+                        $CVSROOT_DIRNAME/diffmerge2/sgrid.h,v
+         rm -rf diffmerge2
+         dotest diffmerge2_co \
+           "$testcvs co diffmerge2" "${DOTSTAR}U $DOTSTAR"
+         cd diffmerge2
+         dotest diffmerge2_update \
+           "${testcvs} update -j Review_Phase_2_Enhancements sgrid.h" \
+           "${DOTSTAR}erging ${DOTSTAR}"
+         # This is the one that counts -- there should be no output:
+         dotest diffmerge2_diff \
+           "${testcvs} diff -r Review_V1p3 sgrid.h" ''
+
+         dokeep
+         cd ..
+         rm -rf diffmerge2
+         modify_repo rm -rf $CVSROOT_DIRNAME/diffmerge2
+         ;;
+
+
+
+       release)
+         # Tests of "cvs release", particularly multiple arguments.
+         # Other CVS release tests:
+         #   info-cleanup-0 for "cvs -n release".
+         #   ignore-193 for the text of the question that cvs release asks.
+         #     Also for interactions with cvsignore.
+         #   basicc: "-d .", global -Q, no arguments (is a noop),
+         #     "cvs release" without -d, multiple arguments.
+         #   dirs-4: repository directory has been deleted.
+         #   modules2-6: multiple arguments.
+
+         # First the usual setup; create a directory first-dir.
+         mkdir 1; cd 1
+         dotest release-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest release-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+         mkdir dir1
+         dotest release-3 "${testcvs} add dir1" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir1 added to the repository"
+         mkdir dir2
+         dotest release-4 "${testcvs} add dir2" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir2 added to the repository"
+          cd dir2
+         mkdir dir3
+         dotest release-5 "${testcvs} add dir3" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir2/dir3 added to the repository"
+
+          cd ../..
+         dotest release-6 "${testcvs} release -d first-dir/dir2/dir3 
first-dir/dir1" \
+"You have .0. altered files in this repository.
+Are you sure you want to release (and delete) directory .first-dir/dir2/dir3.: 
\
+You have .0. altered files in this repository.
+Are you sure you want to release (and delete) directory .first-dir/dir1.: " 
<<EOF
+yes
+yes
+EOF
+         dotest_fail release-7 "test -d first-dir/dir1" ''
+         dotest_fail release-8 "test -d first-dir/dir2/dir3" ''
+         dotest release-9 "${testcvs} update" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating first-dir
+${SPROG} update: Updating first-dir/dir2"
+
+          cd first-dir
+         mkdir dir1
+         dotest release-10 "${testcvs} add dir1" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir1 added to the repository"
+          cd dir2
+         mkdir dir3
+         dotest release-11 "${testcvs} add dir3" \
+"Directory ${CVSROOT_DIRNAME}/first-dir/dir2/dir3 added to the repository"
+
+          cd ../..
+         dotest release-12 "${testcvs} release first-dir/dir2/dir3 
first-dir/dir1" \
+"You have .0. altered files in this repository.
+Are you sure you want to release directory .first-dir/dir2/dir3.: .. .release. 
aborted by user choice.
+You have .0. altered files in this repository.
+Are you sure you want to release directory .first-dir/dir1.: " <<EOF
+no
+yes
+EOF
+         dotest release-13 "${testcvs} release first-dir/dir2/dir3 
first-dir/dir2" \
+"You have .0. altered files in this repository.
+Are you sure you want to release directory .first-dir/dir2/dir3.: \
+You have .0. altered files in this repository.
+Are you sure you want to release directory .first-dir/dir2.: " <<EOF
+yes
+yes
+EOF
+         dotest release-14 "test -d first-dir/dir1" ''
+         dotest release-15 "test -d first-dir/dir2/dir3" ''
+
+         mkdir first-dir/dir1/dir4
+         # FIXCVS: There should be a path showing in front of dir below,
+         # I believe.
+         dotest release-unrecognized-dir-1 \
+"${testcvs} release -d first-dir/dir1" \
+"${QUESTION} dir4
+You have .0. altered files in this repository.
+Are you sure you want to release (and delete) directory \`first-dir/dir1': " 
<<EOF
+yes
+EOF
+
+         rm -rf first-dir/dir2
+
+         dotest release-16 "${testcvs} update" \
+"$SPROG update: Updating \.
+$SPROG update: Updating first-dir"
+
+         # Check to make sure release isn't overwriting a
+         # CVS/Entries file in the current directory (using data
+         # from the released directory).
+
+         # cvs 1.11 (remote) fails on release-21 (a message about
+          # chdir into the removed directory), although it seemingly
+         # unedits and removes the directory correctly.  If
+         # you manually continue, it then fails on release-22 do
+         # to the messed up CVS/Entries file from release-21.
+          cd first-dir
+         mkdir second-dir
+         dotest release-18 "$testcvs add second-dir" \
+"Directory $CVSROOT_DIRNAME/first-dir/second-dir added to the repository"
+
+         cd second-dir
+         touch file1
+         dotest release-19 "$testcvs -Q add file1"
+         dotest release-20 '$testcvs -q ci -m add' \
+"$CVSROOT_DIRNAME/first-dir/second-dir/file1,v  <--  file1
+initial revision: 1\.1"
+         dotest release-21 "$testcvs edit file1"
+         cd ..
+         dotest release-22 "echo yes | $testcvs release -d second-dir" \
+"You have \[0\] altered files in this repository.
+Are you sure you want to release (and delete) directory \`second-dir': "
+         dotest release-23 "$testcvs -q update -d" "U second-dir/file1"
+         dotest release-24 "$testcvs edit"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         modify_repo rm -rf 1 $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       recase)
+         #
+         # Some tests of behavior which broke at one time or another when run
+         # from case insensitive clients against case sensitive servers.
+         #
+         # These tests are namned according to the following convention:
+         #
+         #   ci        Client (sandbox filesystem) case Insensitive
+         #   cs        Client (sandbox filesystem) case Sensitive
+         #   si        Server (repository filesystem) case Insensitive
+         #   ss        Server (repository filesystem) case Sensitive
+         #
+
+         mkdir 1; cd 1
+
+         # First, we will expect different results for a few of these tests
+         # based on whether the repository is on a case sensitive filesystem
+         # or not and whether the sandbox is on a case sensitive filesystem or
+         # not, so determine which cases we are dealing with:
+         echo file >file
+         echo FiLe >FiLe
+         if cmp file FiLe >/dev/null; then
+           client_sensitive=false
+         else
+           client_sensitive=:
+         fi
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost 'echo file >file'
+           $CVS_RSH $remotehost 'echo FiLe >FiLe'
+           if $CVS_RSH $remotehost 'cmp file FiLe >/dev/null'; then
+             server_sensitive=false
+           else
+             server_sensitive=:
+           fi
+         else
+           server_sensitive=$client_sensitive
+         fi
+
+         # The first test (recase-1 & recase-2) is for a remove of a file then
+         # a readd in a different case.
+         modify_repo mkdir $CVSROOT_DIRNAME/first-dir
+         dotest recase-init-1 "$testcvs -Q co first-dir"       
+         cd first-dir
+
+         echo this file has no content >file
+         dotest recase-init-2 "$testcvs -Q add file"
+         dotest recase-init-3 "$testcvs -Q ci -madd"
+         dotest recase-init-4 "$testcvs -Q tag first"
+
+         # Now remove the file.
+         dotest recase-init-5 "$testcvs -Q rm -f file"
+         dotest recase-init-6 "$testcvs -Q ci -mrm"
+
+         # Now the test - readd in a different case.
+         echo this file needs some content >FiLe
+         if $server_sensitive; then
+           dotest recase-1ss "$testcvs add FiLe" \
+"$SPROG add: scheduling file \`FiLe' for addition
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+           dotest recase-2ss "$testcvs -q ci -mrecase" \
+"$CVSROOT_DIRNAME/first-dir/FiLe,v  <--  FiLe
+initial revision: 1\.1"
+         else # server insensitive
+           dotest recase-1si "$testcvs add FiLe" \
+"$SPROG add: Re-adding file \`FiLe' after dead revision 1\.2\.
+$SPROG add: use \`$SPROG commit' to add this file permanently"
+           dotest recase-2si "$testcvs -q ci -mrecase" \
+"$CVSROOT_DIRNAME/first-dir/FiLe,v  <--  FiLe
+new revision: 1\.3; previous revision: 1\.2"
+         fi
+
+         # Now verify that a checkout will still work
+         cd ../..
+         mkdir 2; cd 2
+         dotest recase-3 "$testcvs -q co first-dir" \
+"U first-dir/FiLe"
+
+         cd first-dir
+         # Prove that we can still get status and log information on
+         # conflicting case files (1 in Attic, one in parent).
+         if $remote; then
+           if $client_sensitive; then
+             file=file
+             fIlE=fIlE
+           else # client insensitive
+             # Because FiLe is present on a case insensitive client, it is the
+             # only one ever found and queried or altered.
+             file=FiLe
+             fIlE=FiLe
+           fi
+         else # ! $remote
+           file=file
+           fIlE=fIlE
+         fi
+         if $server_sensitive; then
+           if $client_sensitive; then
+             # Client finds Entry only for FiLe.  Others returned by server.
+             dotest recase-4sscs "$testcvs status file" \
+"===================================================================
+File: no file file             Status: Up-to-date
+
+   Working revision:   No entry for file
+   Repository revision:        1\.2    $CVSROOT_DIRNAME/first-dir/Attic/file,v
+   Commit Identifier:  ${commitid}"
+             dotest recase-5sscs "$testcvs log file" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/Attic/file,v
+Working file: file
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       first: 1\.1
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: $username;  state: dead;  lines: +0 -0;  
commitid: ${commitid};
+rm
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+add
+============================================================================="
+             dotest recase-6sscs "$testcvs status FiLe" \
+"===================================================================
+File: FiLe                     Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    $CVSROOT_DIRNAME/first-dir/FiLe,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+             dotest recase-7sscs "$testcvs log FiLe" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/FiLe,v
+Working file: FiLe
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+recase
+============================================================================="
+           else # server sensitive && client insensitive
+             # Client finds same Entry for file & FiLe.
+             dotest recase-4ssci "$testcvs status file" \
+"===================================================================
+File: FiLe                     Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    $CVSROOT_DIRNAME/first-dir/FiLe,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+             dotest recase-5ssci "$testcvs log file" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/FiLe,v
+Working file: FiLe
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+recase
+============================================================================="
+             dotest recase-6ss "$testcvs status FiLe" \
+"===================================================================
+File: FiLe                     Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    $CVSROOT_DIRNAME/first-dir/FiLe,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+             dotest recase-7ss "$testcvs log FiLe" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/FiLe,v
+Working file: FiLe
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+recase
+============================================================================="
+           fi
+         else # server insensitive
+           # There is only one archive when the server is insensitive, but the
+           # printed file/archive name can vary.
+           dotest recase-4si "$testcvs status file" \
+"===================================================================
+File: $file                    Status: Up-to-date
+
+   Working revision:   1\.3.*
+   Repository revision:        1\.3    $CVSROOT_DIRNAME/first-dir/$file,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+           dotest recase-5si "$testcvs log file" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/$file,v
+Working file: $file
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+       first: 1\.1
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: $username;  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+recase
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: $username;  state: dead;  lines: +0 -0;  
commitid: ${commitid};
+rm
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+add
+============================================================================="
+           dotest recase-6si "$testcvs status FiLe" \
+"===================================================================
+File: FiLe                     Status: Up-to-date
+
+   Working revision:   1\.3.*
+   Repository revision:        1\.3    $CVSROOT_DIRNAME/first-dir/FiLe,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+           dotest recase-7si "$testcvs log FiLe" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/FiLe,v
+Working file: FiLe
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+       first: 1\.1
+keyword substitution: kv
+total revisions: 3;    selected revisions: 3
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE};  author: $username;  state: Exp;  lines: +1 -1;  
commitid: ${commitid};
+recase
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: $username;  state: dead;  lines: +0 -0;  
commitid: ${commitid};
+rm
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+add
+============================================================================="
+         fi
+
+         # And when the file does not exist on the client, we go with the
+         # client Entries match.
+         if $client_sensitive && $server_sensitive; then
+           dotest recase-8sscs "$testcvs status fIlE" \
+"$SPROG status: nothing known about \`fIlE'
+===================================================================
+File: no file fIlE             Status: Unknown
+
+   Working revision:   No entry for fIlE
+   Repository revision:        No revision control file"
+         else # !$client_sensitive || !$server_sensitive
+           dotest recase-8anyi "$testcvs status fIlE" \
+"===================================================================
+File: $fIlE                    Status: Up-to-date
+
+   Working revision:   1\.[0-9]*.*
+   Repository revision:        1\.[0-9]*       
$CVSROOT_DIRNAME/first-dir/$fIlE,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         fi
+
+         # and an update
+         if $server_sensitive; then
+           dotest recase-9ss "$testcvs -q up -rfirst" \
+"$SPROG update: \`FiLe' is no longer in the repository
+U file"
+
+           if $client_sensitive; then
+             dotest recase-10sscs "$testcvs -q up -A" \
+"U FiLe
+$SPROG update: \`file' is no longer in the repository"
+           else # client insensitive
+             # FIXCVS: This should remove the offending file first.
+             dotest_fail recase-10ssci "$testcvs -q up -A" \
+"$SPROG update: move away \`\./FiLe'; it is in the way
+C FiLe
+$SPROG update: \`file' is no longer in the repository"
+
+             cd ..
+             rm -r first-dir
+             dotest recase-11ssci "$testcvs -q co first-dir" \
+"U first-dir/FiLe"
+             cd first-dir
+           fi
+
+           #
+           # See what happens when cased names clash.
+           #
+
+           # Copy the archive
+           if test -n "$remotehost"; then
+             modify_repo $CVS_RSH $remotehost \
+                         "cp $CVSROOT_DIRNAME/first-dir/FiLe,v \
+                         $CVSROOT_DIRNAME/first-dir/FILE,v"
+           else
+             modify_repo cp $CVSROOT_DIRNAME/first-dir/FiLe,v \
+                            $CVSROOT_DIRNAME/first-dir/FILE,v
+           fi
+
+           if $client_sensitive; then
+             dotest recase-12sscs "$testcvs -q up" "U FILE"
+           else # client insensitive
+             dotest_fail recase-12ssci "$testcvs -q up" \
+"$SPROG update: move away \`\./FILE'; it is in the way
+C FILE"
+           fi
+         else # server insensitive
+           dotest recase-9si "$testcvs -q up -rfirst" "U FiLe"
+           dotest recase-10si "$testcvs -q up -A" "U FiLe"
+         fi
+
+         # Prove that we can still get status and log information on
+         # conflicting case files (1 in Attic, two in parent).
+         if $server_sensitive; then
+           if $client_sensitive; then
+             # Client finds Entry only for FiLe.  Others returned by server.
+             dotest recase-13sscs "$testcvs status file" \
+"===================================================================
+File: no file file             Status: Up-to-date
+
+   Working revision:   No entry for file
+   Repository revision:        1\.2    $CVSROOT_DIRNAME/first-dir/Attic/file,v
+   Commit Identifier:  ${commitid}"
+           dotest recase-14sscs "$testcvs log file" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/Attic/file,v
+Working file: file
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       first: 1\.1
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: $username;  state: dead;  lines: +0 -0;  
commitid: ${commitid};
+rm
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+add
+============================================================================="
+           dotest recase-15sscs "$testcvs status FiLe" \
+"===================================================================
+File: FiLe                     Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    $CVSROOT_DIRNAME/first-dir/FiLe,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+             dotest recase-16sscs "$testcvs log FiLe" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/FiLe,v
+Working file: FiLe
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+recase
+============================================================================="
+             dotest recase-17sscs "$testcvs status FILE" \
+"===================================================================
+File: FILE                     Status: Up-to-date
+
+   Working revision:   1.1.*
+   Repository revision:        1.1     ${CVSROOT_DIRNAME}/first-dir/FILE,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+             dotest recase-18sscs "$testcvs log FILE" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/FILE,v
+Working file: FILE
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+recase
+============================================================================="
+           else # $server_sensitive && !$client_sensitive
+             # Client finds same Entry for file & FiLe.
+             dotest recase-13ssci "$testcvs status file" \
+"===================================================================
+File: FiLe                     Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    $CVSROOT_DIRNAME/first-dir/FiLe,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+             dotest recase-16ssci "$testcvs log FiLe" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/FiLe,v
+Working file: FiLe
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+recase
+============================================================================="
+             dotest recase-17ssci "$testcvs status FILE" \
+"===================================================================
+File: FiLe                     Status: Up-to-date
+
+   Working revision:   1\.1.*
+   Repository revision:        1\.1    $CVSROOT_DIRNAME/first-dir/FiLe,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+             dotest recase-18ssci "$testcvs log FILE" \
+"
+RCS file: $CVSROOT_DIRNAME/first-dir/FiLe,v
+Working file: FiLe
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: $username;  state: Exp;  commitid: ${commitid};
+recase
+============================================================================="
+           fi
+         else # !$server_sensitive
+           # Skip these when the server is case insensitive - nothing
+           # has changed since recase-[4-7]si
+           :
+         fi
+
+         if $client_sensitive && $server_sensitive; then
+           dotest recase-19sscs "$testcvs status fIlE" \
+"$SPROG status: nothing known about \`fIlE'
+===================================================================
+File: no file fIlE             Status: Unknown
+
+   Working revision:   No entry for fIlE
+   Repository revision:        No revision control file"
+         else # !$client_sensitive || !$server_sensitive
+           dotest recase-19anyi "$testcvs status fIlE" \
+"===================================================================
+File: $fIlE                    Status: Up-to-date
+
+   Working revision:   1\.[0-9]*.*
+   Repository revision:        1\.[0-9]*       
$CVSROOT_DIRNAME/first-dir/$fIlE,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)"
+         fi
+
+         # And last but not least, prove that a checkout is still possible.
+         cd ../..
+         mkdir 3; cd 3
+         if $server_sensitive; then
+           if $client_sensitive; then
+             dotest recase-20sscs "$testcvs -q co first-dir" \
+"U first-dir/FILE
+U first-dir/FiLe"
+           else # $server_senstive && !$client_sensitive
+             dotest_fail recase-20ssci "$testcvs -q co first-dir" \
+"U first-dir/FILE
+$SPROG checkout: move away \`first-dir/FiLe'; it is in the way
+C first-dir/FiLe"
+           fi
+         else # !$server_sensitive
+           # Skip these since nothing has changed.
+           :
+         fi
+
+         dokeep
+         cd ..
+         rm -r 1 2 3
+         if $server_sensitive && test -n "$remotehost"; then
+           # It is necessary to remove one of the case-conflicted files before
+           # recursively removing the rest under Cygwin on a Samba share or
+           # Samba returns a permission denied error due to its case
+           # confusion.
+           $CVS_RSH $remotehost "rm -f $CVSROOT_DIRNAME/first-dir/FILE,v"
+         fi
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       multiroot)
+         #
+         # set up two repositories
+         #
+
+         if $proxy; then
+           # don't even try
+           continue
+         fi
+
+         CVSROOT1_DIRNAME=${TESTDIR}/root.1
+         CVSROOT2_DIRNAME=${TESTDIR}/root.2
+         CVSROOT1=`newroot $CVSROOT1_DIRNAME`
+         CVSROOT2=`newroot $CVSROOT2_DIRNAME`
+         testcvs1="$testcvs -d '$CVSROOT1'"
+         testcvs2="$testcvs -d '$CVSROOT2'"
+
+         dotest multiroot-setup-1 "mkdir $CVSROOT1_DIRNAME $CVSROOT2_DIRNAME"
+         dotest multiroot-setup-2 "$testcvs1 init"
+         dotest multiroot-setup-3 "$testcvs2 init"
+
+         #
+         # create some directories in ${CVSROOT1_DIRNAME}
+         #
+         mkdir 1; cd 1
+         dotest multiroot-setup-4 "${testcvs1} co -l ." "${SPROG} checkout: 
Updating ."
+         mkdir mod1-1 mod1-2
+         dotest multiroot-setup-5 "${testcvs1} add mod1-1 mod1-2" \
+"Directory ${CVSROOT1_DIRNAME}/mod1-1 added to the repository
+Directory ${CVSROOT1_DIRNAME}/mod1-2 added to the repository"
+         echo file1-1 > mod1-1/file1-1
+         echo file1-2 > mod1-2/file1-2
+         dotest multiroot-setup-6 "${testcvs1} add mod1-1/file1-1 
mod1-2/file1-2" \
+"${SPROG} add: scheduling file .mod1-1/file1-1. for addition
+${SPROG} add: scheduling file .mod1-2/file1-2. for addition
+${SPROG} add: use \`${SPROG} commit' to add these files permanently"
+         dotest multiroot-setup-7 "${testcvs1} commit -m is" \
+"${CPROG} commit: Examining \.
+${CPROG} commit: Examining mod1-1
+${CPROG} commit: Examining mod1-2
+${CVSROOT1_DIRNAME}/mod1-1/file1-1,v  <--  mod1-1/file1-1
+initial revision: 1.1
+${CVSROOT1_DIRNAME}/mod1-2/file1-2,v  <--  mod1-2/file1-2
+initial revision: 1.1"
+         cd ..
+         rm -rf 1
+
+         #
+         # create some directories in ${CVSROOT2_DIRNAME}
+         #
+         mkdir 1; cd 1
+         dotest multiroot-setup-8 "${testcvs2} co -l ." "${SPROG} checkout: 
Updating ."
+         mkdir mod2-1 mod2-2
+         dotest multiroot-setup-9 "${testcvs2} add mod2-1 mod2-2" \
+"Directory ${CVSROOT2_DIRNAME}/mod2-1 added to the repository
+Directory ${CVSROOT2_DIRNAME}/mod2-2 added to the repository"
+         echo file2-1 > mod2-1/file2-1
+         echo file2-2 > mod2-2/file2-2
+         dotest multiroot-setup-6 "${testcvs2} add mod2-1/file2-1 
mod2-2/file2-2" \
+"${SPROG} add: scheduling file .mod2-1/file2-1. for addition
+${SPROG} add: scheduling file .mod2-2/file2-2. for addition
+${SPROG} add: use \`${SPROG} commit' to add these files permanently"
+         dotest multiroot-setup-10 "${testcvs2} commit -m anyone" \
+"${CPROG} commit: Examining \.
+${CPROG} commit: Examining mod2-1
+${CPROG} commit: Examining mod2-2
+${CVSROOT2_DIRNAME}/mod2-1/file2-1,v  <--  mod2-1/file2-1
+initial revision: 1.1
+${CVSROOT2_DIRNAME}/mod2-2/file2-2,v  <--  mod2-2/file2-2
+initial revision: 1.1"
+         cd ..
+         rm -rf 1
+
+         # check out a few directories, from simple/shallow to
+         # complex/deep
+         mkdir 1; cd 1
+
+         # OK, this case is kind of weird.  If we just run things from
+         # here, without CVS/Root, then CVS will contact the server
+         # mentioned in CVSROOT (which is irrelevant) which will print
+         # some messages.  Our workaround is to make sure we have a
+         # CVS/Root file at top level.  In the future, it is possible
+         # the best behavior will be to extend the existing behavior
+         # ("being called from a directory without CVS administration
+         # has always meant to process each of the sub-dirs") to also
+         # do that if there is no CVSROOT, CVS/Root, or -d at top level.
+         # 
+         # The local case could stumble through the tests without creating
+         # the top-level CVS/Root, but we create it for local and for
+         # remote to reduce special cases later in the test.
+         dotest multiroot-workaround "${testcvs1} -q co -l ." ""
+
+         dotest multiroot-setup-11 "${testcvs1} co mod1-1 mod1-2" \
+"${SPROG} checkout: Updating mod1-1
+U mod1-1/file1-1
+${SPROG} checkout: Updating mod1-2
+U mod1-2/file1-2"
+         dotest multiroot-setup-12 "${testcvs2} co mod2-1 mod2-2" \
+"${SPROG} checkout: Updating mod2-1
+U mod2-1/file2-1
+${SPROG} checkout: Updating mod2-2
+U mod2-2/file2-2"
+         cd mod1-2
+         dotest multiroot-setup-13 "${testcvs2} co mod2-2" \
+"${SPROG} checkout: Updating mod2-2
+U mod2-2/file2-2"
+         cd ..
+         cd mod2-2
+         dotest multiroot-setup-14 "${testcvs1} co mod1-2" \
+"${SPROG} checkout: Updating mod1-2
+U mod1-2/file1-2"
+         cd ..
+
+         #
+         # Make sure that the Root and Repository files contain the
+         # correct information.
+         #
+         dotest multiroot-cvsadm-1a "cat mod1-1/CVS/Root" "${CVSROOT1}"
+         dotest multiroot-cvsadm-1b "cat mod1-1/CVS/Repository" "mod1-1"
+         dotest multiroot-cvsadm-2a "cat mod2-1/CVS/Root" "${CVSROOT2}"
+         dotest multiroot-cvsadm-2b "cat mod2-1/CVS/Repository" "mod2-1"
+         dotest multiroot-cvsadm-3a "cat mod1-2/CVS/Root" "${CVSROOT1}"
+         dotest multiroot-cvsadm-3b "cat mod1-2/CVS/Repository" "mod1-2"
+         dotest multiroot-cvsadm-3c "cat mod1-2/mod2-2/CVS/Root" "${CVSROOT2}"
+         dotest multiroot-cvsadm-3d "cat mod1-2/mod2-2/CVS/Repository" "mod2-2"
+         dotest multiroot-cvsadm-4a "cat mod2-2/CVS/Root" "${CVSROOT2}"
+         dotest multiroot-cvsadm-4b "cat mod2-2/CVS/Repository" "mod2-2"
+         dotest multiroot-cvsadm-4c "cat mod2-2/mod1-2/CVS/Root" "${CVSROOT1}"
+         dotest multiroot-cvsadm-4d "cat mod2-2/mod1-2/CVS/Repository" "mod1-2"
+
+         #
+         # Start testing various cvs commands.  Begin with commands
+         # without extra arguments (e.g. "cvs update", "cvs diff",
+         # etc.
+         #
+
+         # Do at least one command with both CVSROOTs to make sure
+         # that there's not some kind of unexpected dependency on the
+         # choice of which CVSROOT is specified on the command line.
+
+         dotest multiroot-update-1a "${testcvs1} update" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating mod1-1
+${SPROG} update: Updating mod1-2
+${SPROG} update: Updating mod1-2/mod2-2
+${SPROG} update: cannot open directory ${CVSROOT1_DIRNAME}/mod2-2: No such 
file or directory
+${SPROG} update: skipping directory mod1-2/mod2-2
+${SPROG} update: Updating mod2-1
+${SPROG} update: cannot open directory ${CVSROOT1_DIRNAME}/mod2-1: No such 
file or directory
+${SPROG} update: skipping directory mod2-1
+${SPROG} update: Updating mod2-2
+${SPROG} update: cannot open directory ${CVSROOT1_DIRNAME}/mod2-2: No such 
file or directory
+${SPROG} update: skipping directory mod2-2"
+
+         # Same deal but with -d ${CVSROOT2}.
+         dotest multiroot-update-1b "${testcvs2} update" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating mod1-1
+${SPROG} update: cannot open directory ${CVSROOT2_DIRNAME}/mod1-1: No such 
file or directory
+${SPROG} update: skipping directory mod1-1
+${SPROG} update: Updating mod1-2
+${SPROG} update: cannot open directory ${CVSROOT2_DIRNAME}/mod1-2: No such 
file or directory
+${SPROG} update: skipping directory mod1-2
+${SPROG} update: Updating mod2-1
+${SPROG} update: Updating mod2-2
+${SPROG} update: Updating mod2-2/mod1-2
+${SPROG} update: cannot open directory ${CVSROOT2_DIRNAME}/mod1-2: No such 
file or directory
+${SPROG} update: skipping directory mod2-2/mod1-2"
+
+         # modify all files and do a diff
+
+         echo bobby >> mod1-1/file1-1
+         echo brown >> mod1-2/file1-2
+         echo goes >> mod2-1/file2-1
+         echo down >> mod2-2/file2-2
+
+         dotest_fail multiroot-diff-1 "${testcvs} diff" \
+"${SPROG} diff: Diffing \.
+${SPROG} diff: Diffing mod1-1
+Index: mod1-1/file1-1
+===================================================================
+RCS file: ${CVSROOT1_DIRNAME}/mod1-1/file1-1,v
+retrieving revision 1\.1
+diff -r1\.1 file1-1
+1a2
+> bobby
+${SPROG} diff: Diffing mod1-2
+Index: mod1-2/file1-2
+===================================================================
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+retrieving revision 1\.1
+diff -r1\.1 file1-2
+1a2
+> brown
+${SPROG} diff: Diffing mod2-2/mod1-2
+${SPROG} diff: Diffing mod1-2/mod2-2
+${SPROG} diff: Diffing mod2-1
+Index: mod2-1/file2-1
+===================================================================
+RCS file: ${CVSROOT2_DIRNAME}/mod2-1/file2-1,v
+retrieving revision 1\.1
+diff -r1\.1 file2-1
+1a2
+> goes
+${SPROG} diff: Diffing mod2-2
+Index: mod2-2/file2-2
+===================================================================
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+retrieving revision 1\.1
+diff -r1\.1 file2-2
+1a2
+> down" \
+"${SPROG} diff: Diffing \.
+${SPROG} diff: Diffing mod1-1
+Index: mod1-1/file1-1
+===================================================================
+RCS file: ${CVSROOT1_DIRNAME}/mod1-1/file1-1,v
+retrieving revision 1\.1
+diff -r1\.1 file1-1
+1a2
+> bobby
+${SPROG} diff: Diffing mod1-2
+Index: mod1-2/file1-2
+===================================================================
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+retrieving revision 1\.1
+diff -r1\.1 file1-2
+1a2
+> brown
+${SPROG} diff: Diffing mod2-2
+${SPROG} diff: Diffing mod2-2/mod1-2
+${SPROG} diff: Diffing mod1-2
+${SPROG} diff: Diffing mod1-2/mod2-2
+${SPROG} diff: Diffing mod2-1
+Index: mod2-1/file2-1
+===================================================================
+RCS file: ${CVSROOT2_DIRNAME}/mod2-1/file2-1,v
+retrieving revision 1\.1
+diff -r1\.1 file2-1
+1a2
+> goes
+${SPROG} diff: Diffing mod2-2
+Index: mod2-2/file2-2
+===================================================================
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+retrieving revision 1\.1
+diff -r1\.1 file2-2
+1a2
+> down"
+
+         dotest multiroot-commit-1 "${testcvs} commit -m actually" \
+"${CPROG} commit: Examining \.
+${CPROG} commit: Examining mod1-1
+${CPROG} commit: Examining mod1-2
+${CPROG} commit: Examining mod2-2/mod1-2
+${CVSROOT1_DIRNAME}/mod1-1/file1-1,v  <--  mod1-1/file1-1
+new revision: 1.2; previous revision: 1.1
+${CVSROOT1_DIRNAME}/mod1-2/file1-2,v  <--  mod1-2/file1-2
+new revision: 1.2; previous revision: 1.1
+${CPROG} commit: Examining mod1-2/mod2-2
+${CPROG} commit: Examining mod2-1
+${CPROG} commit: Examining mod2-2
+${CVSROOT2_DIRNAME}/mod2-1/file2-1,v  <--  mod2-1/file2-1
+new revision: 1.2; previous revision: 1.1
+${CVSROOT2_DIRNAME}/mod2-2/file2-2,v  <--  mod2-2/file2-2
+new revision: 1.2; previous revision: 1.1"
+
+         dotest multiroot-update-2 "${testcvs} update" \
+"${CPROG} update: Updating \.
+${CPROG} update: Updating mod1-1
+${CPROG} update: Updating mod1-2
+${CPROG} update: Updating mod2-2/mod1-2
+U mod2-2/mod1-2/file1-2
+${CPROG} update: Updating mod1-2/mod2-2
+U mod1-2/mod2-2/file2-2
+${CPROG} update: Updating mod2-1
+${CPROG} update: Updating mod2-2" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating mod1-1
+${SPROG} update: Updating mod1-2
+${SPROG} update: Updating mod2-2
+${SPROG} update: Updating mod2-2/mod1-2
+U mod2-2/mod1-2/file1-2
+${SPROG} update: Updating mod1-2
+${SPROG} update: Updating mod1-2/mod2-2
+U mod1-2/mod2-2/file2-2
+${SPROG} update: Updating mod2-1
+${SPROG} update: Updating mod2-2"
+
+         dotest multiroot-tag-1 "${testcvs} tag cattle" \
+"${SPROG} tag: Tagging \.
+${SPROG} tag: Tagging mod1-1
+T mod1-1/file1-1
+${SPROG} tag: Tagging mod1-2
+T mod1-2/file1-2
+${SPROG} tag: Tagging mod2-2/mod1-2
+${SPROG} tag: Tagging mod1-2/mod2-2
+T mod1-2/mod2-2/file2-2
+${SPROG} tag: Tagging mod2-1
+T mod2-1/file2-1
+${SPROG} tag: Tagging mod2-2" \
+"${SPROG} tag: Tagging \.
+${SPROG} tag: Tagging mod1-1
+T mod1-1/file1-1
+${SPROG} tag: Tagging mod1-2
+T mod1-2/file1-2
+${SPROG} tag: Tagging mod2-2
+${SPROG} tag: Tagging mod2-2/mod1-2
+${SPROG} tag: Tagging mod1-2
+${SPROG} tag: Tagging mod1-2/mod2-2
+T mod1-2/mod2-2/file2-2
+${SPROG} tag: Tagging mod2-1
+T mod2-1/file2-1
+${SPROG} tag: Tagging mod2-2"
+
+         echo anotherfile1-1 > mod1-1/anotherfile1-1
+         echo anotherfile2-1 > mod2-1/anotherfile2-1
+         echo anotherfile1-2 > mod2-2/mod1-2/anotherfile1-2
+         echo anotherfile2-2 > mod1-2/mod2-2/anotherfile2-2
+
+         if $remote; then
+           cd mod1-1
+           dotest multiroot-add-1ar "${testcvs} add anotherfile1-1" \
+"${SPROG} add: scheduling file .anotherfile1-1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+           cd ../mod2-1
+           dotest multiroot-add-1br "${testcvs} add anotherfile2-1" \
+"${SPROG} add: scheduling file .anotherfile2-1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+           cd ../mod2-2/mod1-2
+           dotest multiroot-add-1cr "${testcvs} add anotherfile1-2" \
+"${SPROG} add: scheduling file .anotherfile1-2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+           cd ../../mod1-2/mod2-2
+           dotest multiroot-add-1dr "${testcvs} add anotherfile2-2" \
+"${SPROG} add: scheduling file .anotherfile2-2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+           cd ../..
+          else
+           dotest multiroot-add-1 "${testcvs} add mod1-1/anotherfile1-1 
mod2-1/anotherfile2-1 mod2-2/mod1-2/anotherfile1-2 
mod1-2/mod2-2/anotherfile2-2" \
+"${SPROG} add: scheduling file .mod1-1/anotherfile1-1. for addition
+${SPROG} add: scheduling file .mod2-1/anotherfile2-1. for addition
+${SPROG} add: scheduling file .mod2-2/mod1-2/anotherfile1-2. for addition
+${SPROG} add: scheduling file .mod1-2/mod2-2/anotherfile2-2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+          fi
+
+         dotest multiroot-status-1 "${testcvs} status -v" \
+"${SPROG} status: Examining \.
+${SPROG} status: Examining mod1-1
+===================================================================
+File: anotherfile1-1           Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file1-1                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT1_DIRNAME}/mod1-1/file1-1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod1-2
+===================================================================
+File: file1-2                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod2-2/mod1-2
+===================================================================
+File: anotherfile1-2           Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file1-2                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod1-2/mod2-2
+===================================================================
+File: anotherfile2-2           Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file2-2                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod2-1
+===================================================================
+File: anotherfile2-1           Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file2-1                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT2_DIRNAME}/mod2-1/file2-1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod2-2
+===================================================================
+File: file2-2                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)" \
+"${SPROG} status: Examining \.
+${SPROG} status: Examining mod1-1
+===================================================================
+File: anotherfile1-1           Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file1-1                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT1_DIRNAME}/mod1-1/file1-1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod1-2
+===================================================================
+File: file1-2                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod2-2
+${SPROG} status: Examining mod2-2/mod1-2
+===================================================================
+File: anotherfile1-2           Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file1-2                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod1-2
+${SPROG} status: Examining mod1-2/mod2-2
+===================================================================
+File: anotherfile2-2           Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file2-2                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod2-1
+===================================================================
+File: anotherfile2-1           Status: Locally Added
+
+   Working revision:   New file!
+   Repository revision:        No revision control file
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+===================================================================
+File: file2-1                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT2_DIRNAME}/mod2-1/file2-1,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)
+
+${SPROG} status: Examining mod2-2
+===================================================================
+File: file2-2                  Status: Up-to-date
+
+   Working revision:   1\.2.*
+   Repository revision:        1\.2    ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+   Commit Identifier:  ${commitid}
+   Sticky Tag:         (none)
+   Sticky Date:                (none)
+   Sticky Options:     (none)
+
+   Existing Tags:
+       cattle                          (revision: 1\.2)"
+
+         dotest multiroot-commit-2 "${testcvs} commit -m reading" \
+"${CPROG} commit: Examining \.
+${CPROG} commit: Examining mod1-1
+${CPROG} commit: Examining mod1-2
+${CPROG} commit: Examining mod2-2/mod1-2
+${CVSROOT1_DIRNAME}/mod1-1/anotherfile1-1,v  <--  mod1-1/anotherfile1-1
+initial revision: 1\.1
+${CVSROOT1_DIRNAME}/mod1-2/anotherfile1-2,v  <--  mod2-2/mod1-2/anotherfile1-2
+initial revision: 1\.1
+${CPROG} commit: Examining mod1-2/mod2-2
+${CPROG} commit: Examining mod2-1
+${CPROG} commit: Examining mod2-2
+${CVSROOT2_DIRNAME}/mod2-2/anotherfile2-2,v  <--  mod1-2/mod2-2/anotherfile2-2
+initial revision: 1\.1
+${CVSROOT2_DIRNAME}/mod2-1/anotherfile2-1,v  <--  mod2-1/anotherfile2-1
+initial revision: 1\.1"
+
+         dotest multiroot-update-3 "${testcvs} update" \
+"${CPROG} update: Updating \.
+${CPROG} update: Updating mod1-1
+${CPROG} update: Updating mod1-2
+U mod1-2/anotherfile1-2
+${CPROG} update: Updating mod2-2/mod1-2
+${CPROG} update: Updating mod1-2/mod2-2
+${CPROG} update: Updating mod2-1
+${CPROG} update: Updating mod2-2
+U mod2-2/anotherfile2-2" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating mod1-1
+${SPROG} update: Updating mod1-2
+U mod1-2/anotherfile1-2
+${SPROG} update: Updating mod2-2
+${SPROG} update: Updating mod2-2/mod1-2
+${SPROG} update: Updating mod1-2
+${SPROG} update: Updating mod1-2/mod2-2
+${SPROG} update: Updating mod2-1
+${SPROG} update: Updating mod2-2
+U mod2-2/anotherfile2-2"
+
+         dotest multiroot-log-1 "${testcvs} log" \
+"${SPROG} log: Logging \.
+${SPROG} log: Logging mod1-1
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-1/anotherfile1-1,v
+Working file: mod1-1/anotherfile1-1
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-1/file1-1,v
+Working file: mod1-1/file1-1
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+is
+=============================================================================
+${SPROG} log: Logging mod1-2
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/anotherfile1-2,v
+Working file: mod1-2/anotherfile1-2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+Working file: mod1-2/file1-2
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+is
+=============================================================================
+${SPROG} log: Logging mod2-2/mod1-2
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/anotherfile1-2,v
+Working file: mod2-2/mod1-2/anotherfile1-2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+Working file: mod2-2/mod1-2/file1-2
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+is
+=============================================================================
+${SPROG} log: Logging mod1-2/mod2-2
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/anotherfile2-2,v
+Working file: mod1-2/mod2-2/anotherfile2-2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+Working file: mod1-2/mod2-2/file2-2
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+anyone
+=============================================================================
+${SPROG} log: Logging mod2-1
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-1/anotherfile2-1,v
+Working file: mod2-1/anotherfile2-1
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-1/file2-1,v
+Working file: mod2-1/file2-1
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+anyone
+=============================================================================
+${SPROG} log: Logging mod2-2
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/anotherfile2-2,v
+Working file: mod2-2/anotherfile2-2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+Working file: mod2-2/file2-2
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+anyone
+=============================================================================" 
\
+"${SPROG} log: Logging \.
+${SPROG} log: Logging mod1-1
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-1/anotherfile1-1,v
+Working file: mod1-1/anotherfile1-1
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-1/file1-1,v
+Working file: mod1-1/file1-1
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+is
+=============================================================================
+${SPROG} log: Logging mod1-2
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/anotherfile1-2,v
+Working file: mod1-2/anotherfile1-2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+Working file: mod1-2/file1-2
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+is
+=============================================================================
+${SPROG} log: Logging mod2-2
+${SPROG} log: Logging mod2-2/mod1-2
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/anotherfile1-2,v
+Working file: mod2-2/mod1-2/anotherfile1-2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT1_DIRNAME}/mod1-2/file1-2,v
+Working file: mod2-2/mod1-2/file1-2
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+is
+=============================================================================
+${SPROG} log: Logging mod1-2
+${SPROG} log: Logging mod1-2/mod2-2
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/anotherfile2-2,v
+Working file: mod1-2/mod2-2/anotherfile2-2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+Working file: mod1-2/mod2-2/file2-2
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+anyone
+=============================================================================
+${SPROG} log: Logging mod2-1
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-1/anotherfile2-1,v
+Working file: mod2-1/anotherfile2-1
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-1/file2-1,v
+Working file: mod2-1/file2-1
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+anyone
+=============================================================================
+${SPROG} log: Logging mod2-2
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/anotherfile2-2,v
+Working file: mod2-2/anotherfile2-2
+head: 1\.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+reading
+=============================================================================
+
+RCS file: ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+Working file: mod2-2/file2-2
+head: 1\.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       cattle: 1\.2
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}1 -0;  
commitid: ${commitid};
+actually
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+anyone
+============================================================================="
+
+
+         # After the simple cases, let's execute some commands which
+         # refer to parts of our checked-out tree (e.g. "cvs update
+         # mod1-1 mod2-2")
+
+         dokeep
+
+         # clean up after ourselves
+         cd ..
+         rm -r 1
+
+         # clean up our repositories
+         rm -rf ${CVSROOT1_DIRNAME} ${CVSROOT2_DIRNAME}
+         ;;
+
+
+
+       multiroot2)
+         # More multiroot tests.  In particular, nested directories.
+
+         if $proxy; then
+           # don't even try
+           continue
+         fi
+
+         CVSROOT1_DIRNAME=${TESTDIR}/root1
+         CVSROOT2_DIRNAME=${TESTDIR}/root2
+         CVSROOT1=`newroot $CVSROOT1_DIRNAME`
+         CVSROOT2=`newroot $CVSROOT2_DIRNAME`
+
+         dotest multiroot2-1 "${testcvs} -d ${CVSROOT1} init" ""
+         dotest multiroot2-2 "${testcvs} -d ${CVSROOT2} init" ""
+
+         mkdir imp-dir; cd imp-dir
+         echo file1 >file1
+         mkdir sdir
+         echo sfile >sdir/sfile
+         mkdir sdir/ssdir
+         echo ssfile >sdir/ssdir/ssfile
+         dotest_sort multiroot2-3 \
+"${testcvs} -d ${CVSROOT1} import -m import-to-root1 dir1 vend rel" "
+
+N dir1/file1
+N dir1/sdir/sfile
+N dir1/sdir/ssdir/ssfile
+No conflicts created by this import
+${SPROG} import: Importing ${TESTDIR}/root1/dir1/sdir
+${SPROG} import: Importing ${TESTDIR}/root1/dir1/sdir/ssdir"
+         cd sdir
+         dotest_sort multiroot2-4 \
+"${testcvs} -d ${CVSROOT2} import -m import-to-root2 sdir vend2 rel2" "
+
+N sdir/sfile
+N sdir/ssdir/ssfile
+No conflicts created by this import
+${SPROG} import: Importing ${TESTDIR}/root2/sdir/ssdir"
+         cd ../..
+
+         mkdir 1; cd 1
+         # Get TopLevelAdmin-like behavior.
+         dotest multiroot2-5 "${testcvs} -d ${CVSROOT1} -q co -l ."
+         dotest multiroot2-5 "${testcvs} -d ${CVSROOT1} -q co dir1" \
+"U dir1/file1
+U dir1/sdir/sfile
+U dir1/sdir/ssdir/ssfile"
+         cd dir1
+         dotest multiroot2-6 "${testcvs} -Q release -d sdir" ""
+         dotest multiroot2-7 "${testcvs} -d ${CVSROOT2} -q co sdir" \
+"U sdir/sfile
+U sdir/ssdir/ssfile"
+         cd ..
+         # This has one subtle effect - it deals with Entries.Log
+         # so that the next test doesn't get trace messages for
+         # Entries.Log
+         dotest multiroot2-8 "${testcvs} update" \
+"${CPROG} update: Updating \.
+${CPROG} update: Updating dir1
+${CPROG} update: Updating dir1/sdir
+${CPROG} update: Updating dir1/sdir/ssdir" \
+"${SPROG} update: Updating \.
+${SPROG} update: Updating dir1
+${SPROG} update: Updating dir1
+${SPROG} update: Updating dir1/sdir
+${SPROG} update: Updating dir1/sdir/ssdir"
+         # Two reasons we don't run this on the server: (1) the server
+         # also prints some trace messages, and (2) the server trace
+         # messages are subject to out-of-order bugs (this one is hard
+         # to work around).
+         if $remote; then :; else
+           dotest multiroot2-9a "${testcvs} -t update" \
+" *-> main: Session ID is ${commitid}
+ *-> main loop with CVSROOT=${TESTDIR}/root1
+ *-> parse_config ($TESTDIR/root1)
+ *-> do_update ((null), (null), (null), 1, 0, 0, 0, 0, 0, 3, (null), (null), 
(null), (null), (null), 1, (null))
+ *-> Write_Template (\., ${TESTDIR}/root1)
+${CPROG} update: Updating \.
+ *-> Reader_Lock(${TESTDIR}/root1)
+ *-> Simple_Lock_Cleanup()
+ *-> Write_Template (dir1, ${TESTDIR}/root1/dir1)
+${CPROG} update: Updating dir1
+ *-> Reader_Lock(${TESTDIR}/root1/dir1)
+ *-> Simple_Lock_Cleanup()
+ *-> main loop with CVSROOT=${TESTDIR}/root2
+ *-> parse_config ($TESTDIR/root2)
+ *-> do_update ((null), (null), (null), 1, 0, 0, 0, 0, 0, 3, (null), (null), 
(null), (null), (null), 1, (null))
+ *-> Write_Template (dir1/sdir, ${TESTDIR}/root2/dir1/sdir)
+${CPROG} update: Updating dir1/sdir
+ *-> Reader_Lock(${TESTDIR}/root2/sdir)
+ *-> Simple_Lock_Cleanup()
+ *-> Write_Template (dir1/sdir/ssdir, ${TESTDIR}/root2/sdir/ssdir)
+${CPROG} update: Updating dir1/sdir/ssdir
+ *-> Reader_Lock(${TESTDIR}/root2/sdir/ssdir)
+ *-> Simple_Lock_Cleanup()
+ *-> Lock_Cleanup()
+ *-> Simple_Lock_Cleanup()"
+         fi
+
+         dotest multiroot2-9 "${testcvs} -q tag tag1" \
+"T dir1/file1
+T dir1/sdir/sfile
+T dir1/sdir/ssdir/ssfile"
+         echo "change it" >>dir1/file1
+         echo "change him too" >>dir1/sdir/sfile
+         dotest multiroot2-10 "${testcvs} -q ci -m modify" \
+"$TESTDIR/root1/dir1/file1,v  <--  dir1/file1
+new revision: 1\.2; previous revision: 1\.1
+$TESTDIR/root2/sdir/sfile,v  <--  dir1/sdir/sfile
+new revision: 1\.2; previous revision: 1\.1"
+         dotest multiroot2-11 "${testcvs} -q tag tag2" \
+"T dir1/file1
+T dir1/sdir/sfile
+T dir1/sdir/ssdir/ssfile"
+         dotest_fail multiroot2-12 \
+"${testcvs} -q diff -u -r tag1 -r tag2" \
+"Index: dir1/file1
+===================================================================
+RCS file: ${TESTDIR}/root1/dir1/file1,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.2
+diff -u -r1\.1\.1\.1 -r1\.2
+--- dir1/file1 ${RFCDATE}      1\.1\.1\.1
+${PLUS}${PLUS}${PLUS} dir1/file1       ${RFCDATE}      1\.2
+@@ -1 ${PLUS}1,2 @@
+ file1
+${PLUS}change it
+Index: dir1/sdir/sfile
+===================================================================
+RCS file: ${TESTDIR}/root2/sdir/sfile,v
+retrieving revision 1\.1\.1\.1
+retrieving revision 1\.2
+diff -u -r1\.1\.1\.1 -r1\.2
+--- dir1/sdir/sfile    ${RFCDATE}      1\.1\.1\.1
+${PLUS}${PLUS}${PLUS} dir1/sdir/sfile  ${RFCDATE}      1\.2
+@@ -1 ${PLUS}1,2 @@
+ sfile
+${PLUS}change him too"
+
+         if $keep; then
+           echo Keeping ${TESTDIR} and exiting due to --keep
+           exit 0
+         fi
+
+         # clean up after ourselves
+         cd ..
+         rm -r imp-dir 1
+
+         # clean up our repositories
+         rm -rf root1 root2
+         ;;
+
+
+
+       multiroot3)
+         # More multiroot tests.  Directories are side-by-side, not nested.
+         # Not drastically different from multiroot but it covers somewhat
+         # different stuff.
+
+         if $proxy; then
+           # don't even try
+           continue
+         fi
+
+         CVSROOT1=`newroot ${TESTDIR}/root1`
+         CVSROOT2=`newroot ${TESTDIR}/root2`
+
+         mkdir 1; cd 1
+         dotest multiroot3-1 "${testcvs} -d ${CVSROOT1} init" ""
+         dotest multiroot3-2 "${testcvs} -d ${CVSROOT1} -q co -l ." ""
+         mkdir dir1
+         dotest multiroot3-3 "${testcvs} add dir1" \
+"Directory ${TESTDIR}/root1/dir1 added to the repository"
+         dotest multiroot3-4 "${testcvs} -d ${CVSROOT2} init" ""
+         rm -r CVS
+         dotest multiroot3-5 "${testcvs} -d ${CVSROOT2} -q co -l ." ""
+         mkdir dir2
+
+         # OK, the problem is that CVS/Entries doesn't look quite right,
+         # I suppose because of the "rm -r".  Then again, why *should* it
+         # look right?  CVS/Root can only point to a single location, but
+         # we expect CVS/Entries to hold entries for two repositories?  It
+         # just plain isn't part of the filespec yet.
+         #
+         # Use the quick and dirty fix.
+         echo "D/dir1////" >CVS/Entries
+         echo "D/dir2////" >>CVS/Entries
+
+         dotest multiroot3-7 "${testcvs} add dir2" \
+"Directory ${TESTDIR}/root2/dir2 added to the repository"
+
+         touch dir1/file1 dir2/file2
+         if $remote; then
+           # Trying to add them both in one command doesn't work,
+           # because add.c doesn't do multiroot (it doesn't use recurse.c).
+           # Furthermore, it can't deal with the parent directory
+           # having a different root from the child, hence the cd.
+           cd dir1
+           dotest multiroot3-8 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+           cd ..
+           dotest multiroot3-8a "${testcvs} add dir2/file2" \
+"${SPROG} add: scheduling file .dir2/file2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         else
+           dotest multiroot3-8 "${testcvs} add dir1/file1 dir2/file2" \
+"${SPROG} add: scheduling file .dir1/file1. for addition
+${SPROG} add: scheduling file .dir2/file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         fi
+
+         dotest multiroot3-9 "${testcvs} -q ci -m add-them" \
+"$TESTDIR/root2/dir2/file2,v  <--  dir2/file2
+initial revision: 1\.1
+$TESTDIR/root1/dir1/file1,v  <--  dir1/file1
+initial revision: 1\.1"
+
+         # That this is an error is good - we are asking CVS to do
+         # something which doesn't make sense.
+         dotest_fail multiroot3-10 \
+"${testcvs} -q -d ${CVSROOT1} diff dir1/file1 dir2/file2" \
+"${SPROG} diff: failed to create lock directory for .${TESTDIR}/root1/dir2' 
(${TESTDIR}/root1/dir2/#cvs.lock): No such file or directory
+${SPROG} diff: failed to obtain dir lock in repository .${TESTDIR}/root1/dir2'
+${SPROG} \[diff aborted\]: read lock failed - giving up"
+
+         # This one is supposed to work.
+         dotest multiroot3-11 "${testcvs} -q diff dir1/file1 dir2/file2" ""
+
+         # make sure we can't access across repositories
+         # FIXCVS: we probably shouldn't even create the local directories
+         # in this case, but we do, so deal with it.
+         mkdir 1a
+         cd 1a
+         dotest_fail multiroot3-12 \
+"$testcvs -d $CVSROOT1 -q co ../root2/dir2" \
+"$CPROG \[checkout aborted\]: up-level in module reference (\`..') invalid: 
\`\.\./root2/dir2'\." \
+"$SPROG \[server aborted\]: up-level in module reference (\`..') invalid: 
\`\.\./root2/dir2'\.
+$CPROG \[checkout aborted\]: end of file from server (consult above messages 
if any)"
+         dotest_fail multiroot3-13 \
+"$testcvs -d $CVSROOT2 -q co ../root1/dir1" \
+"$CPROG \[checkout aborted\]: up-level in module reference (\`..') invalid: 
\`\.\./root1/dir1'\." \
+"$SPROG \[server aborted\]: up-level in module reference (\`..') invalid: 
\`\.\./root1/dir1'\.
+$CPROG \[checkout aborted\]: end of file from server (consult above messages 
if any)"
+         dotest_fail multiroot3-14 \
+"$testcvs -d $CVSROOT1 -q co ./../root2/dir2" \
+"$CPROG \[checkout aborted\]: up-level in module reference (\`..') invalid: 
\`\./\.\./root2/dir2'\." \
+"$SPROG \[server aborted\]: up-level in module reference (\`..') invalid: 
\`\./\.\./root2/dir2'\.
+$CPROG \[checkout aborted\]: end of file from server (consult above messages 
if any)"
+         dotest_fail multiroot3-15 \
+"$testcvs -d $CVSROOT2 -q co ./../root1/dir1" \
+"$CPROG \[checkout aborted\]: up-level in module reference (\`..') invalid: 
\`\./\.\./root1/dir1'\." \
+"$SPROG \[server aborted\]: up-level in module reference (\`..') invalid: 
\`\./\.\./root1/dir1'\.
+$CPROG \[checkout aborted\]: end of file from server (consult above messages 
if any)"
+         dotest_fail multiroot3-16 \
+"$testcvs -d $CVSROOT1 -q co -p ../root2/dir2" \
+"$CPROG \[checkout aborted\]: up-level in module reference (\`..') invalid: 
\`\.\./root2/dir2'\." \
+"$SPROG \[server aborted\]: up-level in module reference (\`..') invalid: 
\`\.\./root2/dir2'\.
+$CPROG \[checkout aborted\]: end of file from server (consult above messages 
if any)"
+         dotest_fail multiroot3-17 \
+"$testcvs -d $CVSROOT1 -q co -p ./../root1/dir1" \
+"$CPROG \[checkout aborted\]: up-level in module reference (\`..') invalid: 
\`\./\.\./root1/dir1'\." \
+"$SPROG \[server aborted\]: up-level in module reference (\`..') invalid: 
\`\./\.\./root1/dir1'\.
+$CPROG \[checkout aborted\]: end of file from server (consult above messages 
if any)"
+
+         cd ../..
+
+         if $keep; then
+           echo Keeping ${TESTDIR} and exiting due to --keep
+           exit 0
+         fi
+
+         rm -r 1
+         rm -rf ${TESTDIR}/root1 ${TESTDIR}/root2
+         unset CVSROOT1
+         unset CVSROOT2
+         ;;
+
+
+
+       multiroot4)
+         # More multiroot tests, in particular we have two roots with
+         # similarly-named directories and we try to see that CVS can
+         # keep them separate.
+
+         if $proxy; then
+           # don't even try
+           continue
+         fi
+
+         CVSROOT1=`newroot ${TESTDIR}/root1`
+         CVSROOT2=`newroot ${TESTDIR}/root2`
+
+         mkdir 1; cd 1
+         dotest multiroot4-1 "${testcvs} -d ${CVSROOT1} init" ""
+         dotest multiroot4-2 "${testcvs} -d ${CVSROOT1} -q co -l ." ""
+         mkdir dircom
+         dotest multiroot4-3 "${testcvs} add dircom" \
+"Directory ${TESTDIR}/root1/dircom added to the repository"
+         cd dircom
+         touch file1
+         dotest multiroot4-4 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest multiroot4-5 "${testcvs} -q ci -m add" \
+"$TESTDIR/root1/dircom/file1,v  <--  file1
+initial revision: 1\.1"
+         cd ../..
+         mkdir 2; cd 2
+         dotest multiroot4-6 "${testcvs} -d ${CVSROOT2} init" ""
+         dotest multiroot4-7 "${testcvs} -d ${CVSROOT2} -q co -l ." ""
+         mkdir dircom
+         dotest multiroot4-8 "${testcvs} add dircom" \
+"Directory ${TESTDIR}/root2/dircom added to the repository"
+         cd dircom
+         touch file2
+         dotest multiroot4-9 "${testcvs} add file2" \
+"${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+         dotest multiroot4-10 "${testcvs} -q ci -m add" \
+"$TESTDIR/root2/dircom/file2,v  <--  file2
+initial revision: 1\.1"
+
+         cd ../..
+         cd 1/dircom
+         # This may look contrived; the real world example which inspired
+         # it was that a user was changing from local to remote.  Cases
+         # like switching servers (among those mounting the same
+         # repository) and so on would also look the same.
+         mkdir sdir2
+         dotest multiroot4-11 "${testcvs} -d ${CVSROOT2} add sdir2" \
+"Directory ${TESTDIR}/root2/dircom/sdir2 added to the repository"
+
+         dotest multiroot4-12 "${testcvs} -q update" ""
+         cd ..
+         dotest multiroot4-13 "${testcvs} -q update dircom" ""
+         cd ..
+
+         rm -r 1 2
+         rm -rf ${TESTDIR}/root1 ${TESTDIR}/root2
+         unset CVSROOT1
+         unset CVSROOT2
+         ;;
+
+
+
+       rmroot)
+         # When the Entries/Root file is removed from an existing
+         # workspace, CVS should assume $CVSROOT instead
+         #
+         # Right now only checking that CVS exits normally on an
+         # update once CVS/Root is deleted
+         #
+         # There was a time when this would core dump when run in
+         # client/server mode
+
+         mkdir 1; cd 1
+         dotest rmroot-setup-1 "${testcvs} -q co -l ." ''
+         mkdir first-dir
+         dotest rmroot-setup-2 "${testcvs} add first-dir" \
+"Directory ${CVSROOT_DIRNAME}/first-dir added to the repository"
+          cd first-dir
+         touch file1 file2
+         dotest rmroot-setup-3 "${testcvs} add file1 file2" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: scheduling file .file2. for addition
+${SPROG} add: use .${SPROG} commit. to add these files permanently"
+         dotest rmroot-setup-4 "${testcvs} -q commit -minit" \
+"$CVSROOT_DIRNAME/first-dir/file1,v  <--  file1
+initial revision: 1\.1
+$CVSROOT_DIRNAME/first-dir/file2,v  <--  file2
+initial revision: 1\.1"
+         rm CVS/Root
+         dotest rmroot-1 "${testcvs} -q update" ''
+
+         dokeep
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+         ;;
+
+
+
+       reposmv)
+         # More tests of repositories and specifying them.
+         # Similar to crerepos but that test is probably getting big
+         # enough.
+
+         if $proxy; then
+           # don't even try
+           continue
+         fi
+
+         CVSROOT1=`newroot ${TESTDIR}/root1`
+         CVSROOT_MOVED=`newroot ${TESTDIR}/root-moved`
+
+         dotest reposmv-setup-1 "${testcvs} -d ${CVSROOT1} init" ""
+         mkdir imp-dir; cd imp-dir
+         echo file1 >file1
+         dotest reposmv-setup-2 \
+"${testcvs} -d ${CVSROOT1} import -m add dir1 vendor release" \
+"N dir1/file1
+
+No conflicts created by this import"
+         cd ..
+
+         mkdir 1; cd 1
+         dotest reposmv-1 "${testcvs} -d ${CVSROOT1} -Q co dir1" ""
+         mv ${TESTDIR}/root1 ${TESTDIR}/root-moved
+         cd dir1
+
+         # If we didn't have a relative repository, get one now.
+         dotest reposmv-1a "cat CVS/Repository" \
+"${TESTDIR}/root1/dir1" "dir1"
+         echo dir1 >CVS/Repository
+
+         # There were some duplicated warnings and such; only test
+         # for the part of the error message which makes sense.
+         #
+         # FIXCVS then FIXME
+         # Now the duplicated error messages only occur on some platforms,
+         # including, apparently, NetBSD 1.6.1, RedHat Linux 7.3, whatever
+         # kernel that is using, and Solaris 9.  These platforms somehow
+         # decide to call Name_Root() up to four times, via do_recursion, but
+         # I'm not sure of the rest of the details.  Other platforms,
+         # including Fedora Core 1 (Linux 2.4.22-1.2199.nptl), RH Linux 9
+         # (Linux 2.4.20-37.9.legacy), and probably AIX 3.4, Solaris 8, 
+         # BSD/OS 4.2, & IRIX 6.5 only call Name_Root() once as a result of
+         # this test.
+         #
+         # Bug: "skipping directory " without filename.
+         if $remote; then
+           dotest_fail reposmv-2r "${testcvs} update" \
+"Cannot access ${TESTDIR}/root1/CVSROOT
+No such file or directory"
+         else
+           dotest reposmv-2 "$testcvs update" \
+"$DOTSTAR$CPROG update: in directory \.:
+$CPROG update: ignoring CVS/Root because it specifies a non-existent 
repository $TESTDIR/root1
+$CPROG update: Updating \.
+$DOTSTAR$CPROG update: cannot open directory $CVSROOT_DIRNAME/dir1: No such 
file or directory
+$CPROG update: skipping directory "
+         fi
+
+         # CVS/Root overrides $CVSROOT
+         if $remote; then
+           CVSROOT_save=${CVSROOT}
+           CVSROOT=:fork:${TESTDIR}/root-moved; export CVSROOT
+           dotest_fail reposmv-3r "${testcvs} update" \
+"Cannot access ${TESTDIR}/root1/CVSROOT
+No such file or directory"
+           CVSROOT=${CVSROOT_save}; export CVSROOT
+         else
+           CVSROOT_save=$CVSROOT
+           CVSROOT=$TESTDIR/root-moved; export CVSROOT
+           dotest reposmv-3 "$testcvs update" \
+"$DOTSTAR$CPROG update: in directory \.:
+$CPROG update: ignoring CVS/Root because it specifies a non-existent 
repository $TESTDIR/root1
+$CPROG update: Updating \.$DOTSTAR"
+           CVSROOT=$CVSROOT_save; export CVSROOT
+         fi
+
+         if $remote; then
+           CVSROOT_save=${CVSROOT}
+           CVSROOT=:fork:${TESTDIR}/root-none; export CVSROOT
+           dotest_fail reposmv-4r "${testcvs} update" \
+"Cannot access ${TESTDIR}/root1/CVSROOT
+No such file or directory"
+           CVSROOT=${CVSROOT_save}; export CVSROOT
+         else
+           # CVS/Root doesn't seem to quite completely override $CVSROOT
+           # Bug?  Not necessarily a big deal if it only affects error
+           # messages.
+           CVSROOT_save=${CVSROOT}
+           CVSROOT=${TESTDIR}/root-none; export CVSROOT
+           dotest_fail reposmv-4 "${testcvs} update" \
+"${CPROG} update: in directory \.:
+${CPROG} update: ignoring CVS/Root because it specifies a non-existent 
repository ${TESTDIR}/root1
+${CPROG} \[update aborted\]: ${TESTDIR}/root-none/CVSROOT: No such file or 
directory"
+           CVSROOT=${CVSROOT_save}; export CVSROOT
+         fi
+
+         # -d overrides CVS/Root
+         # 
+         # Oddly enough, with CVS 1.10 I think this didn't work for
+         # local (that is, it would appear that CVS/Root would not
+         # get used, but would produce an error if it didn't exist).
+         dotest reposmv-5 "${testcvs} -d ${CVSROOT_MOVED} update" \
+"${SPROG} update: Updating \."
+
+         # TODO: could also test various other things, like what if the
+         # user removes CVS/Root (which is legit).  Or another set of
+         # tests would be if both repositories exist but we want to make
+         # sure that CVS is using the correct one.
+
+         cd ../..
+         rm -r imp-dir 1
+         rm -rf root1 root2
+         unset CVSROOT1
+         ;;
+
+
+
+       pserver)
+         # Test basic pserver functionality.
+         if $remote; then
+           if test -n "$remotehost"; then
+             # Don't even try.  (The issue is getting servercvs & testcvs
+             # set correctly for the following tests.  Some expect one access
+             # method and some another, which in $remotehost mode, means that
+             # sometimes the executables must run on one platform and
+             # sometimes another.)
+             continue
+           fi
+           save_servercvs=$servercvs
+           servercvs=$testcvs
+           # First set SystemAuth=no.  Not really necessary, I don't
+           # think, but somehow it seems like the clean thing for
+           # the testsuite.
+           mkdir 1; cd 1
+           dotest pserver-1 "$testcvs -Q co CVSROOT" ""
+           cd CVSROOT
+           echo "SystemAuth=no" >>config
+           dotest pserver-2 "$testcvs -q ci -m config-it" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+           cat >$CVSROOT_DIRNAME/CVSROOT/passwd <<EOF
+testme:q6WV9d2t848B2:$username
+dontroot:q6WV9d2t848B2:root
+anonymous::$username
+$username:
+willfail:   :whocares
+EOF
+           dotest_fail pserver-3 "$servercvs pserver" \
+"error 0 Server configuration missing --allow-root in inetd.conf" <<EOF
+BEGIN AUTH REQUEST
+$CVSROOT_DIRNAME
+testme
+Ay::'d
+END AUTH REQUEST
+EOF
+
+           # Confirm that not sending a newline during auth cannot constitute
+           # a denial-of-service attack.  This assumes that PATH_MAX is less
+           # than 65536 bytes.  If PATH_MAX is larger than 65535 bytes, this
+           # test could hang indefinitely.
+           ${AWK} 'BEGIN { printf "0123456789abcdef" }' </dev/null >garbageseg
+           echo "BEGIN AUTH REQUEST" >garbageinput
+           i=0
+           while test $i -lt 64; do
+             cat <garbageseg >>garbageseg2
+             i=`expr $i + 1`
+           done
+           i=0
+           while test $i -lt 64; do
+             cat <garbageseg2 >>garbageinput
+             i=`expr $i + 1`
+           done
+           dotest_fail pserver-auth-no-dos \
+"${servercvs} --allow-root=${CVSROOT_DIRNAME} pserver" \
+"$CPROG \\[pserver aborted\\]: error reading from net while validating 
pserver: Not enough space" \
+"$CPROG \\[pserver aborted\\]: error reading from net while validating 
pserver: Cannot allocate memory" <garbageinput
+           unset i
+           rm garbageseg garbageseg2 garbageinput
+
+           # Sending the Root and noop before waiting for the
+           # "I LOVE YOU" is bogus, but hopefully we can get
+           # away with it.
+           dotest pserver-4 "$servercvs --allow-root=$CVSROOT_DIRNAME pserver" 
\
+"$DOTSTAR LOVE YOU
+ok" <<EOF
+BEGIN AUTH REQUEST
+$CVSROOT_DIRNAME
+testme
+Ay::'d
+END AUTH REQUEST
+Root $CVSROOT_DIRNAME
+noop
+EOF
+
+           # The "no such system user" error is occurring on at least one of
+           # our BSD 2.0.2 nightly test platforms.
+           dotest_fail pserver-4.2 \
+"$servercvs --allow-root=$CVSROOT_DIRNAME pserver" \
+"error 0: root not allowed" \
+"E Fatal error, aborting\.
+error 0 root: no such system user" <<EOF
+BEGIN AUTH REQUEST
+$CVSROOT_DIRNAME
+dontroot
+Ay::'d
+END AUTH REQUEST
+EOF
+
+           dotest pserver-5 "$servercvs --allow-root=$CVSROOT_DIRNAME pserver" 
\
+"$DOTSTAR LOVE YOU
+E Protocol error: Root says \"$TESTDIR/1\" but pserver says 
\"$CVSROOT_DIRNAME\"
+error  " <<EOF
+BEGIN AUTH REQUEST
+$CVSROOT_DIRNAME
+testme
+Ay::'d
+END AUTH REQUEST
+Root $TESTDIR/1
+noop
+EOF
+
+           dotest pserver-5a "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+E Protocol error: init says \"${TESTDIR}/2\" but pserver says 
\"${CVSROOT_DIRNAME}\"
+error  " <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d
+END AUTH REQUEST
+init ${TESTDIR}/2
+EOF
+           dotest_fail pserver-5b "test -d ${TESTDIR}/2" ''
+
+           dotest pserver-5c "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+E init xxx must be an absolute pathname
+error  " <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d
+END AUTH REQUEST
+init xxx
+EOF
+           dotest_fail pserver-5d "test -d xxx" ''
+
+           dotest_fail pserver-6 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"I HATE YOU" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d^b?hd
+END AUTH REQUEST
+EOF
+
+           dotest_fail pserver-7 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"I HATE YOU" <<EOF
+BEGIN VERIFICATION REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d^b?hd
+END VERIFICATION REQUEST
+EOF
+
+           dotest pserver-8 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU" <<EOF
+BEGIN VERIFICATION REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d
+END VERIFICATION REQUEST
+EOF
+
+# Tests pserver-9 through pserver-13 are about empty passwords
+
+            # Test empty password (both sides) for aliased user
+           dotest pserver-9 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+anonymous
+A
+END AUTH REQUEST
+EOF
+
+            # Test empty password (server side only) for aliased user
+           dotest pserver-10 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+anonymous
+Aanythingwouldworkhereittrulydoesnotmatter
+END AUTH REQUEST
+EOF
+
+            # Test empty (both sides) password for non-aliased user
+           dotest pserver-11 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+${username}
+A
+END AUTH REQUEST
+EOF
+
+            # Test empty (server side only) password for non-aliased user
+           dotest pserver-12 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+${username}
+Anypasswordwouldworkwhynotthisonethen
+END AUTH REQUEST
+EOF
+
+            # Test failure of whitespace password
+           dotest_fail pserver-13 "${servercvs} 
--allow-root=${CVSROOT_DIRNAME} pserver" \
+"${DOTSTAR} HATE YOU" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+willfail
+Amquiteunabletocomeupwithinterestingpasswordsanymore
+END AUTH REQUEST
+EOF
+
+           # The following tests are for read-only access
+
+           # Check that readers can only read, everyone else can write
+
+           echo anonymous >$CVSROOT_DIRNAME/CVSROOT/readers
+
+           dotest pserver-14 "$servercvs --allow-root=$CVSROOT_DIRNAME 
pserver" \
+"$DOTSTAR LOVE YOU
+M Concurrent Versions System (CVS) .*
+ok" <<EOF
+BEGIN AUTH REQUEST
+$CVSROOT_DIRNAME
+anonymous
+Ay::'d
+END AUTH REQUEST
+Root $CVSROOT_DIRNAME
+version
+EOF
+
+           dotest pserver-15 "$servercvs --allow-root=$CVSROOT_DIRNAME 
pserver" \
+"$DOTSTAR LOVE YOU
+E $CPROG \\[server aborted\\]: .init. requires write access to the repository
+error  " <<EOF
+BEGIN AUTH REQUEST
+$CVSROOT_DIRNAME
+anonymous
+Ay::'d
+END AUTH REQUEST
+init $CVSROOT_DIRNAME
+EOF
+
+           dotest pserver-16 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+M Concurrent Versions System (CVS) .*
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d
+END AUTH REQUEST
+Root ${CVSROOT_DIRNAME}
+version
+EOF
+
+           dotest pserver-17 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d
+END AUTH REQUEST
+init ${CVSROOT_DIRNAME}
+EOF
+
+           dotest pserver-18 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+M Concurrent Versions System (CVS) .*
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+${username}
+Ay::'d
+END AUTH REQUEST
+Root ${CVSROOT_DIRNAME}
+version
+EOF
+
+           dotest pserver-19 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+${username}
+Anything
+END AUTH REQUEST
+init ${CVSROOT_DIRNAME}
+EOF
+
+           # Check that writers can write, everyone else can only read
+           # even if not listed in readers
+
+           cat >${CVSROOT_DIRNAME}/CVSROOT/writers <<EOF
+testme
+EOF
+
+           dotest pserver-20 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+M Concurrent Versions System (CVS) .*
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+anonymous
+Ay::'d
+END AUTH REQUEST
+Root ${CVSROOT_DIRNAME}
+version
+EOF
+
+           dotest pserver-21 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+E $CPROG \\[server aborted\\]: .init. requires write access to the repository
+error  " <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+anonymous
+Ay::'d
+END AUTH REQUEST
+init ${CVSROOT_DIRNAME}
+EOF
+
+           dotest pserver-22 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+M Concurrent Versions System (CVS) .*
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d
+END AUTH REQUEST
+Root ${CVSROOT_DIRNAME}
+version
+EOF
+
+           dotest pserver-23 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d
+END AUTH REQUEST
+init ${CVSROOT_DIRNAME}
+EOF
+
+           dotest pserver-24 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+M Concurrent Versions System (CVS) .*
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+${username}
+Ay::'d
+END AUTH REQUEST
+Root ${CVSROOT_DIRNAME}
+version
+EOF
+
+           dotest pserver-25 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+E $CPROG \\[server aborted\\]: .init. requires write access to the repository
+error  " <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+${username}
+Anything
+END AUTH REQUEST
+init ${CVSROOT_DIRNAME}
+EOF
+
+           # Should work the same without readers
+
+           rm ${CVSROOT_DIRNAME}/CVSROOT/readers
+
+           dotest pserver-26 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+M Concurrent Versions System (CVS) .*
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+anonymous
+Ay::'d
+END AUTH REQUEST
+Root ${CVSROOT_DIRNAME}
+version
+EOF
+
+           dotest pserver-27 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+E $CPROG \\[server aborted\\]: .init. requires write access to the repository
+error  " <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+anonymous
+Ay::'d
+END AUTH REQUEST
+init ${CVSROOT_DIRNAME}
+EOF
+
+           dotest pserver-28 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+M Concurrent Versions System (CVS) .*
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d
+END AUTH REQUEST
+Root ${CVSROOT_DIRNAME}
+version
+EOF
+
+           dotest pserver-29 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+testme
+Ay::'d
+END AUTH REQUEST
+init ${CVSROOT_DIRNAME}
+EOF
+
+           dotest pserver-30 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+M Concurrent Versions System (CVS) .*
+ok" <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+${username}
+Ay::'d
+END AUTH REQUEST
+Root ${CVSROOT_DIRNAME}
+version
+EOF
+
+           dotest pserver-31 "${servercvs} --allow-root=${CVSROOT_DIRNAME} 
pserver" \
+"${DOTSTAR} LOVE YOU
+E $CPROG \\[server aborted\\]: .init. requires write access to the repository
+error  " <<EOF
+BEGIN AUTH REQUEST
+${CVSROOT_DIRNAME}
+${username}
+Anything
+END AUTH REQUEST
+init ${CVSROOT_DIRNAME}
+EOF
+
+           # pserver used to try and print from the NULL pointer 
+           # in this error message in this case
+           dotest_fail pserver-bufinit "${servercvs} pserver" \
+"$CPROG \[pserver aborted\]: unexpected EOF encountered during authentication" 
</dev/null
+
+           # Clean up.
+           dotest pserver-cleanup-1 "${testcvs} -q up -pr1.1 config >config" ""
+           dotest pserver-cleanup-2 "${testcvs} -q ci -m config-it" \
+"$CVSROOT_DIRNAME/CVSROOT/config,v  <--  config
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+$SPROG commit: Rebuilding administrative file database"
+
+           dokeep
+           cd ../..
+           rm -r 1
+           restore_adm
+           servercvs=$save_servercvs
+         fi # skip the whole thing for local
+         ;;
+
+
+
+       server)
+         # Some tests of the server (independent of the client).
+         if $remote; then
+           save_servercvs=$servercvs
+           servercvs=$testcvs
+           dotest server-1 "${servercvs} server" \
+"E Protocol error: Root request missing
+error  " <<EOF
+Directory bogus
+mumble/bar
+update
+EOF
+
+           # Could also test for relative pathnames here (so that crerepos-6a
+           # and crerepos-6b can use :fork:).
+           dotest server-2 "${servercvs} server" "ok" <<EOF
+Set OTHER=variable
+Set MYENV=env-value
+init ${TESTDIR}/crerepos
+EOF
+           dotest server-3 "test -d ${TESTDIR}/crerepos/CVSROOT" ""
+
+           # Now some tests of gzip-file-contents (used by jCVS).
+           ${AWK} 'BEGIN { \
+printf "%c%c%c%c%c%c.6%c%c+I-.%c%c%c%c5%c;%c%c%c%c", \
+31, 139, 8, 64, 5, 7, 64, 3, 225, 2, 64, 198, 185, 5, 64, 64, 64}' \
+             </dev/null | ${TR} '\100' '\000' >gzipped.dat
+           # Note that the CVS client sends "-b 1.1.1", and this
+           # test doesn't.  But the server also defaults to that.
+           cat <<EOF >session.dat
+Root ${TESTDIR}/crerepos
+UseUnchanged
+gzip-file-contents 3
+Argument -m
+Argument msg
+Argumentx 
+Argument dir1
+Argument tag1
+Argument tag2
+Directory .
+${TESTDIR}/crerepos
+Modified file1
+u=rw,g=r,o=r
+z25
+EOF
+           cat gzipped.dat >>session.dat
+           echo import >>session.dat
+           dotest server-4 "${servercvs} server" \
+"M N dir1/file1
+M 
+M No conflicts created by this import
+M 
+ok" <session.dat
+           dotest server-5 \
+"${testcvs} -q -d ${TESTDIR}/crerepos co -p dir1/file1" "test"
+
+           # OK, here are some notify tests.
+           dotest server-6 "${servercvs} server" \
+"Notified \./
+${TESTDIR}/crerepos/dir1/file1
+ok" <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+Notify file1
+E      Fri May  7 13:21:09 1999 -0000  myhost  some-work-dir   EUC
+noop
+EOF
+           # Sending the second "noop" before waiting for the output
+           # from the first is bogus but hopefully we can get away
+           # with it.
+           dotest server-7 "${servercvs} server" \
+"M file1       $username       Fri May  7 13:21:09 1999 -0000  myhost  
some-work-dir
+Notified \./
+${TESTDIR}/crerepos/dir1/file1
+ok
+M file1        $username       Fri May  7 13:21:09 1999 -0000  myhost  
some-work-dir
+Notified \./
+${TESTDIR}/crerepos/dir1/file1
+ok" <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+Notify file1
+E      Fri May  7 13:21:09 1999 -0000  myhost  some-work-dir   EUC
+noop
+Notify file1
+E      The 57th day of Discord in the YOLD 3165        myhost  some-work-dir   
EUC
+noop
+EOF
+
+           # OK, now test a few error conditions.
+           # FIXCVS: should give "error" and no "Notified", like server-9
+           dotest server-8 "${servercvs} server" \
+"M file1       $username       The 57th day of Discord in the YOLD 3165        
myhost  some-work-dir
+E $CPROG server: invalid character in editor value
+Notified \./
+${TESTDIR}/crerepos/dir1/file1
+ok" <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+Notify file1
+E      Setting Orange, the 52th day of Discord in the YOLD 3165        myhost  
some-work-dir   EUC
+noop
+EOF
+
+           dotest server-9 "${servercvs} server" \
+"E Protocol error; misformed Notify request
+error  " <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+Notify file1
+E      Setting Orange+57th day of Discord      myhost  some-work-dir   EUC
+noop
+EOF
+
+           # First demonstrate an interesting quirk in the protocol.
+           # The "watchers" request selects the files to operate based
+           # on files which exist in the working directory.  So if we
+           # don't send "Entry" or the like, it won't do anything.
+           # Wants to be documented in cvsclient.texi...
+           dotest server-10 "${servercvs} server" "ok" <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+watchers
+EOF
+           # See if "watchers" and "editors" display the right thing.
+           dotest server-11 "${servercvs} server" \
+"M file1       ${username}     tedit   tunedit tcommit
+ok" <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+Entry /file1/1.1////
+watchers
+EOF
+           dotest server-12 "${servercvs} server" \
+"M file1       ${username}     The 57th day of Discord in the YOLD 3165        
myhost  some-work-dir
+ok" <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+Entry /file1/1.1////
+editors
+EOF
+
+           # Now do an unedit.
+           dotest server-13 "${servercvs} server" \
+"Notified \./
+${TESTDIR}/crerepos/dir1/file1
+ok" <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+Notify file1
+U      7 May 1999 15:00 -0000  myhost  some-work-dir   EUC
+noop
+EOF
+
+           # Now try "watchers" and "editors" again.
+           dotest server-14 "${servercvs} server" "ok" <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+watchers
+EOF
+           dotest server-15 "${servercvs} server" "ok" <<EOF
+Root ${TESTDIR}/crerepos
+Directory .
+${TESTDIR}/crerepos/dir1
+editors
+EOF
+
+           # Test that the global `-l' option is ignored nonfatally.
+           dotest server-16 "${testcvs} server" \
+"E $CPROG server: WARNING: global \`-l' option ignored\.
+ok" <<EOF
+Global_option -l
+noop
+EOF
+
+           # There used to be some exploits based on malformed Entry requests
+           dotest server-17 "$testcvs server" \
+"E protocol error: Malformed Entry
+error  " <<EOF
+Root $TESTDIR/crerepos
+Directory .
+$TESTDIR/crerepos/dir1
+Entry X/file1/1.1////
+noop
+EOF
+
+           dotest server-18 "$testcvs server" \
+"E protocol error: Malformed Entry
+error  " <<EOF
+Root $TESTDIR/crerepos
+Directory .
+$TESTDIR/crerepos/dir1
+Entry /CC/CC/CC
+noop
+EOF
+
+           # Check that the config file may be set from the command line.
+           # But first verify the default config produces no error messages.
+           dotest server-19 "$testcvs server" \
+"ok" <<EOF
+Root $TESTDIR/crerepos
+Directory .
+$TESTDIR/crerepos
+noop
+EOF
+           echo THIS-CONFIG-OPTION-IS-BAD=XXX >$TESTDIR/newconfig
+           dotest_fail server-20 "$testcvs server -c $TESTDIR/newconfig" \
+"E $SPROG \[server aborted\]: Invalid path to config file specified: 
\`$TESTDIR/newconfig'" <<EOF
+Root $TESTDIR/crerepos
+Directory .
+$TESTDIR/crerepos
+noop
+EOF
+           dotest_fail server-21 \
+"$testcvs server -c /etc/cvs/this-shouldnt-exist" \
+"E $SPROG \[server aborted\]: Failed to resolve path: 
\`/etc/cvs/this-shouldnt-exist': No such file or directory" <<EOF
+Root $TESTDIR/crerepos
+Directory .
+$TESTDIR/crerepos
+noop
+EOF
+
+           # Now make sure that the config file can't be set via the user's
+           # .cvsrc.
+           echo server -c $TESTDIR/newconfig >$HOME/.cvsrc
+           dotest server-22 "$testcvs server" \
+"ok" <<EOF
+Root $TESTDIR/crerepos
+Directory .
+$TESTDIR/crerepos
+noop
+EOF
+
+           dokeep
+           rm -rf $TESTDIR/crerepos
+           rm gzipped.dat session.dat
+           rm $TESTDIR/newconfig $HOME/.cvsrc
+           servercvs=$save_servercvs
+         fi # skip the whole thing for local
+         ;;
+
+
+
+       server2)
+         # More server tests, in particular testing that various
+         # possible security holes are plugged.
+         if $remote; then
+           if test -n "$remotehost"; then
+             # Don't even try.  (The issue is getting servercvs & testcvs
+             # set correctly for the following tests.  Some expect one access
+             # method and some another, which in $remotehost mode, means that
+             # sometimes the executables must run on one platform and
+             # sometimes another.)
+             continue
+           fi
+           save_servercvs=$servercvs
+           servercvs=$testcvs
+           dotest server2-1 "${servercvs} server" \
+"E protocol error: directory '${CVSROOT_DIRNAME}/\.\./dir1' not within root 
'${CVSROOT_DIRNAME}'
+error  " <<EOF
+Root ${CVSROOT_DIRNAME}
+Directory .
+${CVSROOT_DIRNAME}/../dir1
+noop
+EOF
+
+           dotest server2-2 "${servercvs} server" \
+"E protocol error: directory '${CVSROOT_DIRNAME}dir1' not within root 
'${CVSROOT_DIRNAME}'
+error  " <<EOF
+Root ${CVSROOT_DIRNAME}
+Directory .
+${CVSROOT_DIRNAME}dir1
+noop
+EOF
+
+           dotest server2-3 "${servercvs} server" \
+"E protocol error: directory '${TESTDIR}' not within root '${CVSROOT_DIRNAME}'
+error  " <<EOF
+Root ${CVSROOT_DIRNAME}
+Directory .
+${TESTDIR}
+noop
+EOF
+
+           # OK, now a few tests for the rule that one cannot pass a
+           # filename containing a slash to Modified, Is-modified,
+           # Notify, Questionable, or Unchanged.  For completeness
+           # we'd try them all.  For lazyness/conciseness we don't.
+           dotest server2-4 "${servercvs} server" \
+"E protocol error: directory 'foo/bar' not within current directory
+error  " <<EOF
+Root ${CVSROOT_DIRNAME}
+Directory .
+${CVSROOT_DIRNAME}
+Unchanged foo/bar
+noop
+EOF
+
+           dotest server2-5 \
+"${servercvs} --allow-root=${CVSROOT_DIRNAME}.bad server" \
+"E Bad root ${CVSROOT_DIRNAME}
+error  " <<EOF
+Root ${CVSROOT_DIRNAME}
+noop
+EOF
+           dotest server2-6 \
+"${servercvs} --allow-root=${CVSROOT_DIRNAME} server" \
+"ok" <<EOF
+Root ${CVSROOT_DIRNAME}
+noop
+EOF
+           servercvs=$save_servercvs
+         fi
+         ;;
+
+
+
+       client)
+         # Some tests of the client (independent of the server).
+         if $remote; then :; else
+           remoteonly client
+           continue
+         fi
+
+         if $proxy; then
+           # Skip these tests in proxy mode since they assume we are not
+           # writing through a proxy server.  There is no writeproxy-client
+           # test currently.  The writeproxy & writeproxy-noredirect tests
+           # test the writeproxy server.
+           notproxy client
+           continue
+         fi
+
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+# This is admittedly a bit cheezy, in the sense that we make lots
+# of assumptions about what the client is going to send us.
+# We don't mention Repository, because current clients don't require it.
+# Sending these at our own pace, rather than waiting for the client to
+# make the requests, is bogus, but hopefully we can get away with it.
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "M special message"
+echo "Created first-dir/"
+echo "$CVSROOT_DIRNAME/first-dir/file1"
+echo "/file1/1.1///"
+echo "u=rw,g=rw,o=rw"
+echo "4"
+echo "xyz"
+echo "ok"
+cat >/dev/null
+EOF
+         # Cygwin.  Pthffffffffft!
+         if test -n "$remotehost"; then
+           $CVS_RSH $remotehost "chmod +x $TESTDIR/serveme"
+         else
+           chmod +x $TESTDIR/serveme
+         fi
+         save_CVS_SERVER=$CVS_SERVER
+         CVS_SERVER=$TESTDIR/serveme; export CVS_SERVER
+         mkdir 1; cd 1
+         dotest_fail client-1 "$testcvs -q co first-dir" \
+"$CPROG \[checkout aborted\]: This server does not support the global -q 
option$DOTSTAR"
+         dotest client-2 "$testcvs co first-dir" "special message"
+
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "M merge-it"
+echo "Copy-file ./"
+echo "$CVSROOT_DIRNAME/first-dir/file1"
+echo "$TESTDIR/bogus/.#file1.1.1"
+echo "Merged ./"
+echo "$CVSROOT_DIRNAME/first-dir/file1"
+echo "/file1/1.2///"
+echo "u=rw,g=rw,o=rw"
+echo "4"
+echo "abd"
+echo "ok"
+cat >/dev/null
+EOF
+         cd first-dir
+         mkdir $TESTDIR/bogus
+         # The ${DOTSTAR} is to match a potential "broken pipe" if the
+         # client exits before the server script sends everything
+         dotest_fail client-3 "$testcvs update" \
+"merge-it
+$CPROG \[update aborted\]: protocol error: Copy-file tried to specify 
director$DOTSTAR"
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "M merge-it"
+echo "Copy-file ./"
+echo "$CVSROOT_DIRNAME/first-dir/file1"
+echo ".#file1.1.1"
+echo "Merged ./"
+echo "$CVSROOT_DIRNAME/first-dir/file1"
+echo "/file1/1.2///"
+echo "u=rw,g=rw,o=rw"
+echo "4"
+echo "abc"
+echo "ok"
+cat >/dev/null
+EOF
+         dotest client-4 "$testcvs update" "merge-it"
+         dotest client-5 "cat .#file1.1.1" "xyz"
+         dotest client-6 "cat CVS/Entries" "/file1/1.2/[A-Za-z0-9 :]*//
+D"
+         dotest client-7 "cat file1" "abc"
+
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "M OK, whatever"
+echo "ok"
+cat >$TESTDIR/client.tmp
+EOF
+         chmod u=rw,go= file1
+         # By specifying the time zone in local time, we don't
+         # know exactly how that will translate to GMT.
+         dotest client-8 "$testcvs update -D 99-10-04" "OK, whatever"
+         # String 2 below is Cygwin again - ptoooey.
+         dotest client-9 "cat $TESTDIR/client.tmp" \
+"Root $CVSROOT_DIRNAME
+Valid-responses [-a-zA-Z ]*
+valid-requests
+Argument -D
+Argument [34] Oct 1999 [0-9][0-9]:00:00 -0000
+Argument --
+Directory \.
+$CVSROOT_DIRNAME/first-dir
+Entry /file1/1\.2///
+Modified file1
+u=rw,g=,o=
+4
+abc
+update" \
+"Root $CVSROOT_DIRNAME
+Valid-responses [-a-zA-Z ]*
+valid-requests
+Argument -D
+Argument [34] Oct 1999 [0-9][0-9]:00:00 -0000
+Argument --
+Directory \.
+$CVSROOT_DIRNAME/first-dir
+Entry /file1/1\.2///
+Modified file1
+u=rw,g=r,o=r
+4
+abc
+update"
+
+         # The following test tests what was a potential client exploit in
+         # CVS versions 1.11.14 and CVS versions 1.12.6 and earlier.  This
+         # exploit would allow a trojan server to create arbitrary files,
+         # anywhere the user had write permissions, even outside of the
+         # user's sandbox.
+         cat >$HOME/.bashrc <<EOF
+#!$TESTSHELL
+# This is where login scripts would usually be
+# stored.
+EOF
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "Rcs-diff $HOME/"
+echo "$HOME/.bashrc"
+echo "/.bashrc/73.50///"
+echo "u=rw,g=rw,o=rw"
+echo "20"
+echo "a1 1"
+echo "echo 'gotcha!'"
+echo "ok"
+cat >/dev/null
+EOF
+         
+         # If I don't run the following sleep between the above cat and
+         # the following calls to dotest, sometimes the serveme file isn't
+         # completely written yet by the time CVS tries to execute it,
+         # causing the shell to intermittantly report syntax errors (usually
+         # early EOF).  There's probably a new race condition here, but this
+         # works.
+         #
+         # Incidentally, I can reproduce this behavior with Linux 2.4.20 and
+         # Bash 2.05 or Bash 2.05b.
+         sleep 1
+         dotest_fail client-10 "$testcvs update" \
+"$CPROG update: Server attempted to update a file via an invalid pathname:
+$CPROG \[update aborted\]: \`$HOME/.bashrc'\."
+
+         # A second try at a client exploit.  This one never actually
+         # failed in the past, but I thought it wouldn't hurt to add a test.
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "Rcs-diff ./"
+echo "$HOME/.bashrc"
+echo "/.bashrc/73.50///"
+echo "u=rw,g=rw,o=rw"
+echo "20"
+echo "a1 1"
+echo "echo 'gotcha!'"
+echo "ok"
+cat >/dev/null
+EOF
+         sleep 1
+         dotest_fail client-11 "$testcvs update" \
+"$CPROG \[update aborted\]: patch original file \./\.bashrc does not exist"
+
+         # A third try at a client exploit.  This one did used to fail like
+         # client-10.
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "Rcs-diff ../../home/"
+echo "../../.bashrc"
+echo "/.bashrc/73.50///"
+echo "u=rw,g=rw,o=rw"
+echo "20"
+echo "a1 1"
+echo "echo 'gotcha!'"
+echo "ok"
+cat >/dev/null
+EOF
+         sleep 1
+         dotest_fail client-12 "$testcvs update" \
+"$CPROG update: Server attempted to update a file via an invalid pathname:
+$CPROG \[update aborted\]: \`\.\./\.\./home/.bashrc'\."
+
+         # Try the same exploit using the Created response.
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "Created $HOME/"
+echo "$HOME/.bashrc"
+echo "/.bashrc/73.50///"
+echo "u=rw,g=rw,o=rw"
+echo "26"
+echo "#! $TESTSHELL"
+echo "echo 'gotcha!'"
+echo "ok"
+cat >/dev/null
+EOF
+         sleep 1
+         dotest_fail client-13 "$testcvs update" \
+"$CPROG update: Server attempted to update a file via an invalid pathname:
+$CPROG \[update aborted\]: \`$HOME/.bashrc'\."
+
+         # Now try using the Update-existing response
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "Update-existing ../../home/"
+echo "../../home/.bashrc"
+echo "/.bashrc/73.50///"
+echo "u=rw,g=rw,o=rw"
+echo "26"
+echo "#! $TESTSHELL"
+echo "echo 'gotcha!'"
+echo "ok"
+cat >/dev/null
+EOF
+         sleep 1
+         dotest_fail client-14 "$testcvs update" \
+"$CPROG update: Server attempted to update a file via an invalid pathname:
+$CPROG \[update aborted\]: \`\.\./\.\./home/.bashrc'\."
+
+         # Try the same exploit using the Merged response.
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "Merged $HOME/"
+echo "$HOME/.bashrc"
+echo "/.bashrc/73.50///"
+echo "u=rw,g=rw,o=rw"
+echo "26"
+echo "#! $TESTSHELL"
+echo "echo 'gotcha!'"
+echo "ok"
+cat >/dev/null
+EOF
+         sleep 1
+         dotest_fail client-15 "$testcvs update" \
+"$CPROG update: Server attempted to update a file via an invalid pathname:
+$CPROG \[update aborted\]: \`$HOME/.bashrc'\."
+
+         # Now try using the Updated response
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "Updated ../../home/"
+echo "../../home/.bashrc"
+echo "/.bashrc/73.50///"
+echo "u=rw,g=rw,o=rw"
+echo "26"
+echo "#! $TESTSHELL"
+echo "echo 'gotcha!'"
+echo "ok"
+cat >/dev/null
+EOF
+         sleep 1
+         dotest_fail client-16 "$testcvs update" \
+"$CPROG update: Server attempted to update a file via an invalid pathname:
+$CPROG \[update aborted\]: \`\.\./\.\./home/.bashrc'\."
+
+         # Try the same exploit using the Copy-file response.
+         # As far as I know, Copy-file was never exploitable either.
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Directory Entry 
Modified Unchanged Argument Argumentx ci co update"
+echo "ok"
+echo "Created ."
+echo "./innocuous"
+echo "/innocuous/73.50///"
+echo "u=rw,g=rw,o=rw"
+echo "26"
+echo "#! $TESTSHELL"
+echo "echo 'gotcha!'"
+echo "Copy-file ."
+echo "./innocuous"
+echo "$HOME/innocuous"
+echo "ok"
+cat >/dev/null
+EOF
+         sleep 1
+         dotest_fail client-18 "$testcvs update" \
+"$CPROG \[update aborted\]: protocol error: Copy-file tried to specify 
directory"
+
+         # And verify that none of the exploits was successful.
+         dotest client-19 "cat $HOME/.bashrc" \
+"#!$TESTSHELL
+# This is where login scripts would usually be
+# stored\."
+
+         # Check that the client detects redirect loops.
+         cat >$TESTDIR/serveme <<EOF
+#!$TESTSHELL
+echo "Valid-requests Root Valid-responses valid-requests Command-prep Referrer 
Repository Directory Relative-directory Max-dotdot Static-directory Sticky 
Entry Kopt Checkin-time Modified Is-modified UseUnchanged Unchanged Notify 
Hostname LocalDir Questionable Argument Argumentx Global_option Gzip-stream 
wrapper-sendme-rcsOptions Set Gssapi-authenticate expand-modules ci co update 
diff log rlog list rlist global-list-quiet ls add remove update-patches 
gzip-file-contents status rdiff tag rtag import admin export history release 
watch-on watch-off watch-add watch-remove watchers editors edit init annotate 
rannotate noop version"
+echo "ok"
+echo "Redirect $CVSROOT"
+
+# Eat up data from the client to avoid broken pipe errors.
+cat >/dev/null
+EOF
+         echo newstuff >file1
+         sleep 1
+         dotest_fail client-20 "$testcvs ci" \
+"$CPROG commit: Examining \.
+$CPROG \[commit aborted\]: \`Redirect' loop detected\.  Server 
misconfiguration$QUESTION"
+
+         dokeep
+         cd ../..
+         rm -r 1
+         rmdir $TESTDIR/bogus
+         rm $TESTDIR/serveme $HOME/.bashrc
+         CVS_SERVER=$save_CVS_SERVER; export CVS_SERVER
+         ;;
+
+
+
+       dottedroot)
+         # Check that a CVSROOT with a "." in the name will work.
+
+         if $proxy; then
+           # don't even try
+           continue
+         fi
+
+         CVSROOT_save=${CVSROOT}
+         CVSROOT_DIRNAME_save=${CVSROOT_DIRNAME}
+         CVSROOT_DIRNAME=${TESTDIR}/cvs.root
+         CVSROOT=`newroot ${CVSROOT_DIRNAME}`
+
+         dotest dottedroot-init-1 "${testcvs} init" ""
+         mkdir dir1
+         mkdir dir1/dir2
+         echo version1 >dir1/dir2/file1
+         cd dir1
+         dotest dottedroot-1 "${testcvs} import -m '' module1 AUTHOR INITIAL" \
+"${SPROG} import: Importing ${CVSROOT_DIRNAME}/module1/dir2
+N module1/dir2/file1
+
+No conflicts created by this import"
+         cd ..
+
+         # This is the test that used to cause an assertion failure
+         # in recurse.c:do_recursion().
+         dotest dottedroot-2 "${testcvs} co -rINITIAL module1" \
+"${SPROG} checkout: Updating module1
+${SPROG} checkout: Updating module1/dir2
+U module1/dir2/file1"
+
+         dokeep
+
+         rm -rf ${CVSROOT_DIRNAME}
+         rm -r dir1 module1
+         CVSROOT_DIRNAME=${CVSROOT_DIRNAME_save}
+         CVSROOT=${CVSROOT_save}
+         ;;
+
+
+
+       fork)
+         # Test that the server defaults to the correct executable in :fork:
+         # mode.  See the note in the TODO at the end of this file about this.
+         #
+         # This test and client should be left after all other references to
+         # CVS_SERVER are removed from this script.
+         #
+         # The client series of tests already tests that CVS_SERVER is
+         # working, but that test might be better here.
+         if $remote; then
+           if test -n "$remotehost"; then
+             # Don't even try.  If our caller specified a remotehost, our
+             # access method has been determined anyhow.
+             continue
+           fi
+           mkdir fork; cd fork
+           save_CVS_SERVER=$CVS_SERVER
+           unset CVS_SERVER
+           # So looking through $PATH for cvs won't work...
+           echo "echo junk" >cvs
+           chmod a+x cvs
+           save_PATH=$PATH; PATH=.:$PATH
+           # The second error message below is for testing clients without
+           # server support.
+           if ${testcvs_server_support}; then
+               dotest fork-1 "$testcvs -d:fork:$CVSROOT_DIRNAME version" \
+'Client: \(.*\)
+Server: \1'
+           else
+               dotest_fail fork-1-noss \
+"$testcvs -d:fork:$CVSROOT_DIRNAME version" \
+"Client: .*
+Server: ${CPROG} version: You must set the CVS_SERVER environment variable when
+${CPROG} version: using the :fork: access method\.
+${CPROG} \[version aborted\]: This CVS was not compiled with server support\."
+           fi
+
+           CVS_SERVER=${save_CVS_SERVER}; export CVS_SERVER
+           unset save_CVS_SERVER
+           PATH=$save_PATH; unset save_PATH
+
+           dokeep
+           cd ..
+           rm -r fork
+         fi
+         ;;
+
+
+
+       commit-add-missing)
+         # Make sure that a commit fails when a `cvs add'ed file has
+         # been removed from the working directory.
+
+         mkdir 1; cd 1
+         module=c-a-m
+         echo > unused-file
+         dotest commit-add-missing-1 \
+           "$testcvs -Q import -m. $module X Y" ''
+
+         file=F
+         # Check it out and tag it.
+         dotest commit-add-missing-2 "$testcvs -Q co $module" ''
+         cd $module
+         dotest commit-add-missing-3 "$testcvs -Q tag -b B" ''
+         echo v1 > $file
+         dotest commit-add-missing-4 "$testcvs -Q add $file" ''
+         rm -f $file
+         dotest_fail commit-add-missing-5 "$testcvs -Q ci -m. $file" \
+"${SPROG} commit: Up-to-date check failed for .$file'
+${SPROG} \[commit aborted\]: correct above errors first!"
+
+         dotest
+         cd ../..
+         rm -rf 1
+         modify_repo rm -rf $CVSROOT_DIRNAME/$module
+         ;;
+
+
+
+       commit-d)
+         # Check that top-level commits work when CVS/Root
+         # is overridden by cvs -d.
+
+         mkdir -p 1/subdir; cd 1
+         touch file1 subdir/file2
+         dotest commit-d-1 "$testcvs -Q import -m. c-d-c X Y" ""
+         dotest commit-d-2 "$testcvs -Q co c-d-c" ""
+         cd c-d-c
+         echo change >>file1; echo another change >>subdir/file2
+         # Changing working root, then override with -d
+         echo nosuchhost:/cvs > CVS/Root
+         dotest commit-d-3 "$testcvs -q -d '$CVSROOT' commit -m." \
+"$CVSROOT_DIRNAME/c-d-c/file1,v  <--  file1
+new revision: 1.2; previous revision: 1.1
+$CVSROOT_DIRNAME/c-d-c/subdir/file2,v  <--  subdir/file2
+new revision: 1.2; previous revision: 1.1"
+
+         dokeep
+         cd ../..
+         rm -rf 1 cvsroot/c-d-c
+         ;;
+
+
+
+       template)
+         # Check that the CVS/Template directory is being
+         # properly created.
+         modify_repo mkdir -p $CVSROOT_DIRNAME/first/subdir
+         modify_repo mkdir $CVSROOT_DIRNAME/second
+         mkdir template; cd template
+
+         # check that no CVS/Template is created for an empty rcsinfo
+         # Note: For cvs clients with no Clear-template response, the
+         # CVS/Template file will exist and be zero bytes in length.
+         dotest template-empty-1 "${testcvs} -Q co first" ''
+         dotest template-empty-2 \
+"test ! -s first/CVS/Template" ''
+         dotest template-empty-3 \
+"test ! -s first/subdir/CVS/Template" ''
+         rm -fr first
+
+         # create some template files
+         echo 'CVS: the default template' > ${TESTDIR}/template/temp.def
+         echo 'CVS: the first template' > ${TESTDIR}/template/temp.first
+         echo 'CVS: the subdir template' > ${TESTDIR}/template/temp.subdir
+         
+         dotest template-rcsinfo-1 "${testcvs} -Q co CVSROOT" ''
+         cd CVSROOT
+         echo DEFAULT ${TESTDIR}/template/temp.def >>rcsinfo
+         dotest template-rcsinfo-2 "$testcvs -Q ci -m."
+         # Make sure we get the update without a commit.
+         dotest template-rcsinfo-3 "${testcvs} -Q ci -m." ''
+         # Did the CVSROOT/CVS/Template file get the updated version?
+         if $remote; then
+           dotest template-rcsinfo-4r \
+"$diff_u CVS/Template $TESTDIR/template/temp.def"
+         else
+           dotest template-rcsinfo-4 \
+"test ! -f CVS/Template" ''
+         fi
+         echo "^first/subdir ${TESTDIR}/template/temp.subdir" >>rcsinfo
+         echo "^first ${TESTDIR}/template/temp.first" >>rcsinfo
+         dotest template-rcsinfo-4.1 "${testcvs} -Q ci -m. rcsinfo"
+         # Did the CVSROOT/CVS/Template file get the updated version?
+         if $remote; then
+           dotest template-rcsinfo-5r \
+"$diff_u CVS/Template $TESTDIR/template/temp.def"
+         else
+           dotest template-rcsinfo-5 \
+"test ! -f CVS/Template" ''
+         fi
+         cd ..
+
+         # Now checkout the first and second modules and see
+         # if the proper template has been provided for each
+         dotest template-first "${testcvs} co first second" \
+"${SPROG} checkout: Updating first
+${SPROG} checkout: Updating first/subdir
+${SPROG} checkout: Updating second"
+
+         if $remote; then
+           # When in client/server CVS/Template must exist
+           dotest template-first-r-1 "test -f first/CVS/Template" ''
+           dotest template-first-r-2 "test -f first/subdir/CVS/Template" ''
+           dotest template-first-r-3 "test -f second/CVS/Template" ''
+           # The value of the CVS/Template should be equal to the
+           # file called out in the rcsinfo file.
+           dotest template-first-r-4 \
+"$diff_u first/CVS/Template $TESTDIR/template/temp.first"
+           dotest template-first-r-5 \
+"$diff_u first/subdir/CVS/Template $TESTDIR/template/temp.subdir"
+           dotest template-first-r-6 \
+"$diff_u second/CVS/Template $TESTDIR/template/temp.def"
+          else
+           # When in local mode CVS/Template must NOT exist
+           dotest_fail template-first-1 "test -f first/CVS/Template" ''
+           dotest_fail template-first-2 "test -f first/subdir/CVS/Template" ''
+           dotest_fail template-first-3 "test -f second/CVS/Template" ''
+         fi
+
+         # Next, create a new subdirectory and see if it gets the
+         # correct template or not
+         cd second
+         mkdir otherdir
+         dotest template-add-1 "${testcvs} add otherdir" \
+"Directory ${CVSROOT_DIRNAME}/second/otherdir added to the repository"
+         if $remote; then
+           dotest template-add-2r \
+"$diff_u otherdir/CVS/Template $TESTDIR/template/temp.def"
+         else
+           dotest_fail template-add-2 "test -f otherdir/CVS/Template" ''
+         fi
+         cd ..
+
+         # Update the remote template. Then see if doing an
+         # update of a checked out tree will properly update
+         # the CVS/Template files.
+         echo 'CVS: Line two' >> ${TESTDIR}/template/temp.def
+         echo 'CVS: Line two' >> ${TESTDIR}/template/temp.first
+         echo 'CVS: Line two' >> ${TESTDIR}/template/temp.subdir
+         dotest template-second "${testcvs} update first second" \
+"${SPROG} update: Updating first
+${SPROG} update: Updating first/subdir
+${SPROG} update: Updating second
+${SPROG} update: Updating second/otherdir"
+
+         if $remote; then
+           dotest template-second-r-1 \
+"$diff_u first/CVS/Template $TESTDIR/template/temp.first"
+           dotest template-second-r-2 \
+"$diff_u first/subdir/CVS/Template $TESTDIR/template/temp.subdir"
+           dotest template-second-r-3 \
+"$diff_u second/CVS/Template $TESTDIR/template/temp.def"
+           dotest template-second-r-4 \
+"$diff_u second/otherdir/CVS/Template $TESTDIR/template/temp.def"
+          else
+           # When in local mode CVS/Template must NOT exist
+           dotest_fail template-second-1 "test -f CVS/Template" ''
+           dotest_fail template-second-2 "test -f subdir/CVS/Template" ''
+           dotest_fail template-second-3 "test -f second/CVS/Template" ''
+           dotest_fail template-second-4 \
+"test -f second/otherdir/CVS/Template" ''
+         fi
+         # Update the remote template with a zero-length template
+         : > ${TESTDIR}/template/temp.def
+         dotest template-third-1 "${testcvs} update second" \
+"${SPROG} update: Updating second
+${SPROG} update: Updating second/otherdir"
+
+         if $remote; then
+           dotest_fail template-third-r-2 "test -s second/CVS/Template" ''
+           dotest_fail template-third-r-3 "test -s 
second/otherdir/CVS/Template" ''
+          else
+           dotest_fail template-third-2 "test -f second/CVS/Template" ''
+           dotest_fail template-third-3 \
+"test -f second/otherdir/CVS/Template" ''
+          fi
+
+         # fun with remote protocols and tags
+         if $remote; then
+           cd second
+           echo hello > file1
+           dotest template-tag-r-1 "${testcvs} -Q add file1" ''
+           dotest template-tag-r-2 "${testcvs} -Q commit -madd file1"
+            dotest template-tag-r-3 "${testcvs} -q tag tag" 'T file1'
+           rm ${CVSROOT_DIRNAME}/CVSROOT/val-tags
+           cd ..
+           rm -fr second
+           dotest template-tag-r-4 "${testcvs} -Q co -rtag second" ''
+         fi
+
+         cd CVSROOT
+         dotest template-norcsinfo-1 "${testcvs} up" \
+"${SPROG} update: Updating \."
+         # Did the CVSROOT/CVS/Template file get the updated version?
+         if $remote; then
+           dotest template-norcsinfo-r-2 \
+"$diff_u CVS/Template $TESTDIR/template/temp.def"
+          else
+           dotest_fail template-norcsinfo-2 "test -f CVS/Template" ''
+         fi
+
+         : > rcsinfo
+         dotest template-norcsinfo-3 "${testcvs} -Q ci -m. rcsinfo"
+         # Did the CVSROOT/CVS/Template file get the updated version?
+         # The file should be gone or of zero length.
+         dotest template-norcsinfo-4 \
+"test ! -s CVS/Template" ''
+         cd ..
+
+         dotest template-norcsinfo-5 "${testcvs} update first" \
+"${SPROG} update: Updating first
+${SPROG} update: Updating first/subdir"
+
+         # Note: For cvs clients with no Clear-template response, the
+         # CVS/Template file will exist and be zero bytes in length.
+         dotest template-norcsinfo-6 \
+"test ! -s first/CVS/Template" ''
+         dotest template-norcsinfo-7 \
+"test ! -s first/subdir/CVS/Template" ''
+
+         dokeep
+
+         # cleanup
+          modify_repo rm -rf $CVSROOT_DIRNAME/first $CVSROOT_DIRNAME/second
+         restore_adm
+         cd ..
+         rm -rf template
+         ;;
+
+
+
+       writeproxy)
+         # Various tests for a read-only CVS mirror set up as a write-proxy
+         # for a central server.
+         #
+         # These tests are only meaningful in client/server mode.
+         if $remote; then :; else
+           remoteonly writeproxy
+           continue
+         fi
+
+         if $noredirect; then
+           notnoredirect writeproxy
+           continue
+         fi
+
+         require_rsync
+         if test $? -eq 77; then
+           skip writeproxy "$skipreason"
+           continue
+         fi
+
+         PRIMARY_CVSROOT_DIRNAME_save=$PRIMARY_CVSROOT_DIRNAME
+         PRIMARY_CVSROOT_save=$PRIMARY_CVSROOT
+         PRIMARY_CVSROOT_DIRNAME=$TESTDIR/primary_cvsroot
+         PRIMARY_CVSROOT=`newroot $PRIMARY_CVSROOT_DIRNAME`
+         SECONDARY_CVSROOT_DIRNAME_save=$SECONDARY_CVSROOT_DIRNAME
+         SECONDARY_CVSROOT_save=$SECONDARY_CVSROOT
+         SECONDARY_CVSROOT_DIRNAME=$TESTDIR/writeproxy_cvsroot
+         SECONDARY_CVSROOT=`newroot $SECONDARY_CVSROOT_DIRNAME`
+
+         # Initialize the primary repository
+         dotest writeproxy-init-1 "$testcvs -d$PRIMARY_CVSROOT init"
+         mkdir writeproxy; cd writeproxy
+         mkdir primary; cd primary
+         dotest writeproxy-init-2 "$testcvs -Qd$PRIMARY_CVSROOT co CVSROOT"
+         cd CVSROOT
+         cat >>loginfo <<EOF
+ALL (cat >/dev/null; echo %R) >$TESTDIR/referrer
+ALL $RSYNC -gopr --delete $PRIMARY_CVSROOT_DIRNAME/ $SECONDARY_CVSROOT_DIRNAME
+EOF
+         cat >>config <<EOF
+PrimaryServer=$PRIMARY_CVSROOT
+EOF
+         dotest writeproxy-init-3 \
+"$testcvs -Q ci -mconfigure-writeproxy"
+
+         # Quickly verify that the server can resolve symlinks when
+         # determining whether it is the primary.
+         # This shouldn't actually change the repository.
+         save_CVS_SERVER=$CVS_SERVER
+         ln -s $PRIMARY_CVSROOT_DIRNAME $TESTDIR/primary_link
+         dotest writeproxy-0 "$CVS_SERVER server" \
+"Valid-requests Root Valid-responses valid-requests Command-prep Referrer 
Repository Directory Relative-directory Max-dotdot Static-directory Sticky 
Entry Kopt Checkin-time Modified Is-modified UseUnchanged Unchanged Notify 
Hostname LocalDir Questionable Argument Argumentx Global_option Gzip-stream 
wrapper-sendme-rcsOptions Set ${DOTSTAR}expand-modules ci co update diff log 
rlog list rlist global-list-quiet ls add remove update-patches 
gzip-file-contents status rdiff tag rtag import admin export history release 
watch-on watch-off watch-add watch-remove watchers editors edit init annotate 
rannotate noop version
+ok
+ok
+ok" \
+<< EOF
+Root $TESTDIR/primary_link
+Valid-responses ok error Valid-requests Redirect Checked-in New-entry Checksum 
Copy-file Updated Created Update-existing Merged Patched Rcs-diff Mode Mod-time 
Removed Remove-entry Set-static-directory Clear-static-directory Set-sticky 
Clear-sticky Edit-file Template Clear-template Notified Module-expansion 
Wrapper-rcsOption M Mbinary E F MT
+valid-requests
+UseUnchanged
+Command-prep commit
+Global_option -q
+Global_option -Q
+Argument -m
+Argument configure-writeproxy
+Argument --
+Directory .
+CVSROOT
+Entry /checkoutlist/1.1///
+Modified checkoutlist
+u=rw,g=rw,o=r
+495
+# The "checkoutlist" file is used to support additional version controlled
+# administrative files in \$CVSROOT/CVSROOT, such as template files.
+#
+# The first entry on a line is a filename which will be checked out from
+# the corresponding RCS file in the \$CVSROOT/CVSROOT directory.
+# The remainder of the line is an error message to use if the file cannot
+# be checked out.
+#
+# File format:
+#
+#      [<whitespace>]<filename>[<whitespace><error message>]<end-of-line>
+#
+# comment lines begin with '#'
+ci
+EOF
+         rm $TESTDIR/primary_link
+
+         # And now the secondary.
+         $RSYNC -gopr $PRIMARY_CVSROOT_DIRNAME/ $SECONDARY_CVSROOT_DIRNAME
+
+         # Checkout from secondary
+         #
+         # For now, move the primary root out of the way to satisfy
+         # ourselves that the data is coming from the secondary.
+         mv $PRIMARY_CVSROOT_DIRNAME $TESTDIR/save-root
+         cd ../..
+         mkdir secondary; cd secondary
+         dotest writeproxy-1 "$testcvs -qd$SECONDARY_CVSROOT co CVSROOT" \
+"U CVSROOT/checkoutlist
+U CVSROOT/commitinfo
+U CVSROOT/config
+U CVSROOT/cvswrappers
+U CVSROOT/loginfo
+U CVSROOT/modules
+U CVSROOT/notify
+U CVSROOT/postadmin
+U CVSROOT/postproxy
+U CVSROOT/posttag
+U CVSROOT/postwatch
+U CVSROOT/preproxy
+U CVSROOT/rcsinfo
+U CVSROOT/taginfo
+U CVSROOT/verifymsg"
+
+         # Confirm data present
+         cd CVSROOT
+         dotest writeproxy-2 "grep rsync loginfo" \
+"ALL $RSYNC -gopr --delete $PRIMARY_CVSROOT_DIRNAME/ 
$SECONDARY_CVSROOT_DIRNAME"
+         dotest writeproxy-3 "grep PrimaryServer config" \
+"${DOTSTAR}
+PrimaryServer=$PRIMARY_CVSROOT"
+
+         # Checkin to secondary
+         cd ..
+         dotest writeproxy-4 "$testcvs -Qd$SECONDARY_CVSROOT co -ldtop ."
+         cd top
+         mkdir firstdir
+
+         # Have to move the primary root back before we can perform write
+         # operations.
+         mv $TESTDIR/save-root $PRIMARY_CVSROOT_DIRNAME
+
+         dotest writeproxy-5 "$testcvs -Q add firstdir"
+         cd firstdir
+         echo now you see me >file1
+         dotest writeproxy-6 "$testcvs -Q add file1"
+         dotest writeproxy-6a "grep file1 CVS/Entries >/dev/null"
+         dotest writeproxy-7 "$testcvs -Q ci -mfirst-file file1"
+
+         # Verify that the server got the correct referrer.
+         #
+         # This happens even when using a :fork:ed server because CVS is
+         # hardcoded to support only :ext: servers.
+         #
+         # This test meaningfully detects that a referrer was passed in fork
+         # mode because the only time the referrer string can be altered from
+         # its original state is when the server sends a Referrer response.
+         # If the client were not parsing and resending the referrer, this
+         # string would still match $SECONDARY_CVSROOT_DIRNAME.
+         dotest writeproxy-7a "cat $TESTDIR/referrer" \
+":ext:address@hidden"
+
+         # Make sure the sync took place
+         dotest writeproxy-7b "$testcvs -Q up"
+
+         # Checkout from primary
+         cd ../../../primary
+         dotest writeproxy-8 "$testcvs -qd$PRIMARY_CVSROOT co firstdir" \
+"U firstdir/file1"
+
+         # Confirm data present
+         #  - This test indirectly confirms that the commit did not take
+         #    place on the secondary.
+         cd firstdir
+         dotest writeproxy-9 "cat file1" "now you see me"
+
+         # Commit to primary
+         echo now you see me again >file1
+         dotest writeproxy-10 "$testcvs -Q ci -medit file1"
+
+         # Update from secondary
+         cd ../../secondary/top/firstdir
+         dotest writeproxy-11 "$testcvs -q up" \
+"U file1"
+
+         # Confirm data present
+         dotest writeproxy-12 "cat file1" "now you see me again"
+
+         # Test a failing rsync
+         cd ../../CVSROOT
+         sed \$d <loginfo >tmp
+         mv tmp loginfo
+         echo >>loginfo \
+"ALL echo >&2 'Im rsync and I encountered an error!'; cat >/dev/null; exit 1"
+         dotest writeproxy-init-13 "$testcvs -Q ci -mbreak-rsync" \
+"Im rsync and I encountered an error!"
+         echo "# a comment" >>loginfo
+         dotest writeproxy-13 "$testcvs -Q ci -mtest-broken-rsync" \
+"Im rsync and I encountered an error!"
+         touch loginfo
+         dotest_fail writeproxy-14 "$testcvs up" \
+"$SPROG update: Updating \.
+$SPROG \[update aborted\]: could not find desired version 1\.4 in 
$PRIMARY_CVSROOT_DIRNAME/CVSROOT/loginfo,v"
+
+         dokeep
+         cd ../../..
+         rm -r writeproxy $TESTDIR/referrer
+         rm -rf $PRIMARY_CVSROOT_DIRNAME $SECONDARY_CVSROOT_DIRNAME
+         PRIMARY_CVSROOT_DIRNAME=$PRIMARY_CVSROOT_DIRNAME_save
+         PRIMARY_CVSROOT=$PRIMARY_CVSROOT_save
+         SECONDARY_CVSROOT_DIRNAME=$SECONDARY_CVSROOT_DIRNAME_save
+         SECONDARY_CVSROOT=$SECONDARY_CVSROOT_save
+         ;;
+
+
+
+       writeproxy-noredirect)
+         # Various tests for a read-only CVS mirror set up as a write-proxy
+         # for a central server.
+         #
+         # These tests are only meaningful in client/server mode.
+         #
+         # These tests are a few simple tests for a writeproxy setup with a
+         # client that can't handle the `Redirect' response.  Mostly they
+         # parallel the "writeproxy" tests but, in the style of the "server",
+         # "server2", "pserver", and related tests, they bypass the CVS client
+         # for write commands by piping data into a server on STDIN to mimic
+         # a client that cannot handle the `Redirect' response.
+         if $remote; then :; else
+           remoteonly writeproxy-noredirect
+           continue
+         fi
+
+         require_rsync
+         if test $? -eq 77; then
+           skip writeproxy-noredirect "$skipreason"
+           continue
+         fi
+
+         PRIMARY_CVSROOT_DIRNAME_save=$PRIMARY_CVSROOT_DIRNAME
+         PRIMARY_CVSROOT_save=$PRIMARY_CVSROOT
+         PRIMARY_CVSROOT_DIRNAME=$TESTDIR/primary_cvsroot
+         PRIMARY_CVSROOT=`newroot $PRIMARY_CVSROOT_DIRNAME`
+         SECONDARY_CVSROOT_DIRNAME_save=$SECONDARY_CVSROOT_DIRNAME
+         SECONDARY_CVSROOT_DIRNAME=$TESTDIR/writeproxy_cvsroot
+
+         # Initialize the primary repository
+         dotest writeproxy-noredirect-init-1 \
+"$testcvs -d'$PRIMARY_CVSROOT' init"
+         mkdir writeproxy-noredirect; cd writeproxy-noredirect
+         mkdir primary; cd primary
+         dotest writeproxy-noredirect-init-2 \
+"$testcvs -Qd'$PRIMARY_CVSROOT' co CVSROOT"
+         cd CVSROOT
+         cat >>loginfo <<EOF
+ALL $RSYNC -gopr --delete $PRIMARY_CVSROOT_DIRNAME/ $SECONDARY_CVSROOT_DIRNAME
+EOF
+         cat >>config <<EOF
+PrimaryServer=$PRIMARY_CVSROOT
+EOF
+         dotest writeproxy-noredirect-init-3 \
+"$testcvs -Q ci -mconfigure-writeproxy"
+
+         # And now the secondary.
+         $RSYNC -gopr $PRIMARY_CVSROOT_DIRNAME/ $SECONDARY_CVSROOT_DIRNAME
+
+         CVS_SERVER_save=$CVS_SERVER
+         CVS_SERVER_secondary=$TESTDIR/writeproxy-secondary-wrapper
+         CVS_SERVER=$CVS_SERVER_secondary
+
+         # Wrap the CVS server to allow --primary-root to be set by the
+         # secondary.
+         cat <<EOF >$TESTDIR/writeproxy-secondary-wrapper
+#! $TESTSHELL
+CVS_SERVER=$TESTDIR/writeproxy-primary-wrapper
+export CVS_SERVER
+
+# No need to check the PID of the last client since we are testing with
+# Redirect disabled.
+proot_arg="--allow-root $SECONDARY_CVSROOT_DIRNAME --allow-root 
$PRIMARY_CVSROOT_DIRNAME"
+exec $servercvs \$proot_arg "\$@"
+EOF
+         cat <<EOF >$TESTDIR/writeproxy-primary-wrapper
+#! $TESTSHELL
+#CVS_SERVER_LOG=/tmp/cvsprimarylog
+exec $servercvs "\$@"
+EOF
+
+         chmod a+x $TESTDIR/writeproxy-secondary-wrapper \
+                   $TESTDIR/writeproxy-primary-wrapper
+
+         # Checkout from secondary
+         #
+         # It may look like we are checking out from the primary here, but
+         # in fork mode, the deciding factor is the PrimaryServer translation
+         # above.
+         #
+         # When the primary and secondary hostname were different, the server
+         # the client is talking directly to is more obvious.
+         #
+         # For now, move the primary root out of the way to satisfy
+         # ourselves that the data is coming from the secondary.
+         mv $PRIMARY_CVSROOT_DIRNAME $TESTDIR/save-root
+         cd ../..
+         mkdir secondary; cd secondary
+         dotest writeproxy-noredirect-1 \
+"$testcvs -qd'$PRIMARY_CVSROOT' co CVSROOT" \
+"U CVSROOT/checkoutlist
+U CVSROOT/commitinfo
+U CVSROOT/config
+U CVSROOT/cvswrappers
+U CVSROOT/loginfo
+U CVSROOT/modules
+U CVSROOT/notify
+U CVSROOT/postadmin
+U CVSROOT/postproxy
+U CVSROOT/posttag
+U CVSROOT/postwatch
+U CVSROOT/preproxy
+U CVSROOT/rcsinfo
+U CVSROOT/taginfo
+U CVSROOT/verifymsg"
+
+         # Confirm data present
+         cd CVSROOT
+         dotest writeproxy-noredirect-2 "grep rsync loginfo" \
+"ALL $RSYNC -gopr --delete $PRIMARY_CVSROOT_DIRNAME/ 
$SECONDARY_CVSROOT_DIRNAME"
+         dotest writeproxy-noredirect-3 "grep PrimaryServer config" \
+"${DOTSTAR}
+PrimaryServer=$PRIMARY_CVSROOT"
+
+         # Checkin to secondary
+         cd ..
+         dotest writeproxy-noredirect-4 \
+"$testcvs -Qd'$PRIMARY_CVSROOT' co -ldtop ."
+         cd top
+         mkdir firstdir
+
+         # Have to move the primary root back before we can perform write
+         # operations.
+         mv $TESTDIR/save-root $PRIMARY_CVSROOT_DIRNAME
+
+         dotest writeproxy-noredirect-5 "$CVS_SERVER server" \
+"Valid-requests Root Valid-responses valid-requests Command-prep Referrer 
Repository Directory Relative-directory Max-dotdot Static-directory Sticky 
Entry Kopt Checkin-time Modified Is-modified UseUnchanged Unchanged Notify 
Hostname LocalDir Questionable Argument Argumentx Global_option Gzip-stream 
wrapper-sendme-rcsOptions Set ${DOTSTAR}expand-modules ci co update diff log 
rlog list rlist global-list-quiet ls add remove update-patches 
gzip-file-contents status rdiff tag rtag import admin export history release 
watch-on watch-off watch-add watch-remove watchers editors edit init annotate 
rannotate noop version
+ok
+ok
+ok
+Clear-template firstdir/
+firstdir/
+ok" \
+<< EOF
+Root $PRIMARY_CVSROOT_DIRNAME
+Valid-responses ok error Valid-requests Checked-in New-entry Checksum 
Copy-file Updated Created Update-existing Merged Patched Rcs-diff Mode Mod-time 
Removed Remove-entry Set-static-directory Clear-static-directory Set-sticky 
Clear-sticky Template Clear-template Notified Module-expansion 
Wrapper-rcsOption M Mbinary E F MT
+valid-requests
+UseUnchanged
+Command-prep add
+Global_option -q
+Global_option -Q
+wrapper-sendme-rcsOptions
+Argument --
+Directory firstdir
+firstdir
+Directory .
+
+Argument firstdir
+add
+EOF
+
+         # Gotta update the workspace ourselves since we bypassed the client.
+         cp -R CVS firstdir/CVS
+         echo "firstdir" >firstdir/CVS/Repository
+
+         cd firstdir
+         echo now you see me >file1
+         dotest writeproxy-noredirect-6 "$CVS_SERVER server" \
+"Valid-requests Root Valid-responses valid-requests Command-prep Referrer 
Repository Directory Relative-directory Max-dotdot Static-directory Sticky 
Entry Kopt Checkin-time Modified Is-modified UseUnchanged Unchanged Notify 
Hostname LocalDir Questionable Argument Argumentx Global_option Gzip-stream 
wrapper-sendme-rcsOptions Set ${DOTSTAR}expand-modules ci co update diff log 
rlog list rlist global-list-quiet ls add remove update-patches 
gzip-file-contents status rdiff tag rtag import admin export history release 
watch-on watch-off watch-add watch-remove watchers editors edit init annotate 
rannotate noop version
+ok
+ok
+ok
+Checked-in \./
+firstdir/file1
+/file1/0///
+ok" \
+<< EOF
+Root $PRIMARY_CVSROOT_DIRNAME
+Valid-responses ok error Valid-requests Checked-in New-entry Checksum 
Copy-file Updated Created Update-existing Merged Patched Rcs-diff Mode Mod-time 
Removed Remove-entry Set-static-directory Clear-static-directory Set-sticky 
Clear-sticky Template Clear-template Notified Module-expansion 
Wrapper-rcsOption M Mbinary E F MT
+valid-requests
+UseUnchanged
+Command-prep add
+Global_option -q
+Global_option -Q
+wrapper-sendme-rcsOptions
+Argument --
+Directory .
+firstdir
+Is-modified file1
+Argument file1
+add
+EOF
+
+         # Have to add it to the workspace ourselves again since we are
+         # bypassing the client.
+         echo /file1/0/dummy+timestamp// >>CVS/Entries
+
+         dotest writeproxy-noredirect-7 "$CVS_SERVER server" \
+"Valid-requests Root Valid-responses valid-requests Command-prep Referrer 
Repository Directory Relative-directory Max-dotdot Static-directory Sticky 
Entry Kopt Checkin-time Modified Is-modified UseUnchanged Unchanged Notify 
Hostname LocalDir Questionable Argument Argumentx Global_option Gzip-stream 
wrapper-sendme-rcsOptions Set ${DOTSTAR}expand-modules ci co update diff log 
rlog list rlist global-list-quiet ls add remove update-patches 
gzip-file-contents status rdiff tag rtag import admin export history release 
watch-on watch-off watch-add watch-remove watchers editors edit init annotate 
rannotate noop version
+ok
+ok
+Mode u=rw,g=rw,o=r
+Checked-in \./
+firstdir/file1
+/file1/1\.1///
+ok" \
+<< EOF
+Root $PRIMARY_CVSROOT_DIRNAME
+Valid-responses ok error Valid-requests Checked-in New-entry Checksum 
Copy-file Updated Created Update-existing Merged Patched Rcs-diff Mode Mod-time 
Removed Remove-entry Set-static-directory Clear-static-directory Set-sticky 
Clear-sticky Template Clear-template Notified Module-expansion 
Wrapper-rcsOption M Mbinary E F MT
+valid-requests
+UseUnchanged
+Command-prep commit
+Global_option -q
+Global_option -Q
+Argument -m
+Argument first-file
+Argument --
+Directory .
+firstdir
+Entry /file1/0/+modified//
+Modified file1
+u=rw,g=rw,o=r
+15
+now you see me
+Argument file1
+ci
+EOF
+
+         # Have to add it to the workspace ourselves again since we are
+         # bypassing the client.
+         echo D >CVS/Entries
+         echo /file1/1.1/dummy+timestamp// >>CVS/Entries
+
+         # Make sure the sync took place
+         dotest writeproxy-noredirect-7a "$testcvs -Q up"
+
+         CVS_SERVER=$servercvs
+         # Checkout from primary
+         cd ../../../primary
+         dotest writeproxy-noredirect-8 \
+"$testcvs -qd'$PRIMARY_CVSROOT' co firstdir" \
+"U firstdir/file1"
+
+         # Confirm data present
+         #  - This test indirectly confirms that the commit did not take
+         #    place on the secondary.
+         cd firstdir
+         dotest writeproxy-noredirect-9 "cat file1" "now you see me"
+
+         # Commit to primary
+         echo now you see me again >file1
+         dotest writeproxy-noredirect-10 "$testcvs -Q ci -medit file1"
+
+         CVS_SERVER=$CVS_SERVER_secondary
+         # Update from secondary
+         cd ../../secondary/top/firstdir
+         dotest writeproxy-noredirect-11 "$testcvs -q up" "U file1"
+
+         # Confirm data present
+         dotest writeproxy-noredirect-12 "cat file1" "now you see me again"
+
+         dokeep
+         cd ../../../..
+         rm -r writeproxy-noredirect
+         rm -rf $PRIMARY_CVSROOT_DIRNAME $SECONDARY_CVSROOT_DIRNAME
+         rm $TESTDIR/writeproxy-secondary-wrapper \
+            $TESTDIR/writeproxy-primary-wrapper
+         CVS_SERVER=$CVS_SERVER_save
+         PRIMARY_CVSROOT_DIRNAME=$PRIMARY_CVSROOT_DIRNAME_save
+         PRIMARY_CVSROOT=$PRIMARY_CVSROOT_save
+         SECONDARY_CVSROOT_DIRNAME=$SECONDARY_CVSROOT_DIRNAME_save
+         ;;
+
+
+
+       writeproxy-ssh)
+         # Various tests for a read-only CVS mirror set up as a write-proxy
+         # for a central server accessed via the :ext: method.
+         #
+         # Mostly these tests are intended to set up for the final test which
+         # verifies that the server registers the referrer.
+         if $remote; then :; else
+           remoteonly writeproxy-ssh
+           continue
+         fi
+
+         if $noredirect; then
+           notnoredirect writeproxy-ssh
+           continue
+         fi
+
+         require_rsh "$CVS_RSH"
+         if test $? -eq 77; then
+           skip writeproxy-ssh "$skipreason"
+           continue
+         fi
+
+         require_rsync
+         if test $? -eq 77; then
+           skip writeproxy-ssh "$skipreason"
+           continue
+         fi
+
+         # Save old roots.
+         PRIMARY_CVSROOT_DIRNAME_save=$PRIMARY_CVSROOT_DIRNAME
+         PRIMARY_CVSROOT_save=$PRIMARY_CVSROOT
+         SECONDARY_CVSROOT_DIRNAME_save=$SECONDARY_CVSROOT_DIRNAME
+         SECONDARY_CVSROOT_save=$SECONDARY_CVSROOT
+
+         # Set new roots.
+         PRIMARY_CVSROOT_DIRNAME=$TESTDIR/primary_cvsroot
+         PRIMARY_CVSROOT=:ext:$host$PRIMARY_CVSROOT_DIRNAME
+         SECONDARY_CVSROOT_DIRNAME=$TESTDIR/writeproxy_cvsroot
+         SECONDARY_CVSROOT=":ext;Redirect=yes:$host$SECONDARY_CVSROOT_DIRNAME"
+
+         # Initialize the primary repository
+         dotest writeproxy-ssh-init-1 "$testcvs -d$PRIMARY_CVSROOT init"
+         mkdir writeproxy-ssh; cd writeproxy-ssh
+         mkdir primary; cd primary
+         dotest writeproxy-ssh-init-2 "$testcvs -Qd$PRIMARY_CVSROOT co CVSROOT"
+         cd CVSROOT
+         cat >>loginfo <<EOF
+ALL $RSYNC -gopr --delete $PRIMARY_CVSROOT_DIRNAME/ $SECONDARY_CVSROOT_DIRNAME
+EOF
+         cat >>loginfo <<EOF
+ALL echo Referrer=%R; cat >/dev/null
+EOF
+         cat >>config <<EOF
+PrimaryServer=$PRIMARY_CVSROOT
+EOF
+         dotest writeproxy-ssh-init-3 \
+"$testcvs -Q ci -mconfigure-writeproxy-ssh" \
+"Referrer=NONE"
+
+         # And now the secondary.
+         $RSYNC -gopr $PRIMARY_CVSROOT_DIRNAME/ $SECONDARY_CVSROOT_DIRNAME
+
+         # Checkout from secondary
+         #
+         # For now, move the primary root out of the way to satisfy
+         # ourselves that the data is coming from the secondary.
+         mv $PRIMARY_CVSROOT_DIRNAME $TESTDIR/save-root
+
+         # Checkin to secondary
+         cd ../..
+         save_CVSROOT=$CVSROOT
+         CVSROOT=$SECONDARY_CVSROOT
+         export CVSROOT
+         dotest writeproxy-ssh-1 "$testcvs -Q co -ldtop ."
+         CVSROOT=$save_CVSROOT
+         export CVSROOT
+         cd top
+         mkdir firstdir
+
+         # Have to move the primary root back before we can perform write
+         # operations.
+         mv $TESTDIR/save-root $PRIMARY_CVSROOT_DIRNAME
+
+         dotest writeproxy-ssh-2 "$testcvs -Q add firstdir" \
+"Referrer=:ext:address@hidden"
+
+         cd firstdir
+         echo now you see me >file1
+         dotest writeproxy-ssh-3 "$testcvs -Q add file1"
+         dotest writeproxy-ssh-4 "$testcvs -Q ci -mfirst-file file1" \
+"Referrer=:ext:address@hidden"
+
+         dokeep
+         cd ../../..
+         rm -r writeproxy-ssh
+         rm -rf $PRIMARY_CVSROOT_DIRNAME $SECONDARY_CVSROOT_DIRNAME
+         PRIMARY_CVSROOT_DIRNAME=$PRIMARY_CVSROOT_DIRNAME_save
+         PRIMARY_CVSROOT=$PRIMARY_CVSROOT_save
+         SECONDARY_CVSROOT_DIRNAME=$SECONDARY_CVSROOT_DIRNAME_save
+         SECONDARY_CVSROOT=$SECONDARY_CVSROOT_save
+         ;;
+
+
+
+       writeproxy-ssh-noredirect)
+         # Various tests for a read-only CVS mirror set up as a write-proxy
+         # for a central server accessed via the :ext: method.
+         #
+         # Mostly these tests are intended to set up for the final test which
+         # verifies that the server registers the referrer.
+         if $remote; then :; else
+           remoteonly writeproxy-ssh-noredirect
+           continue
+         fi
+
+         require_rsh "$CVS_RSH"
+         if test $? -eq 77; then
+           skip writeproxy-ssh-noredirect "$skipreason"
+           continue
+         fi
+
+         require_rsync
+         if test $? -eq 77; then
+           skip writeproxy-ssh-noredirect "$skipreason"
+           continue
+         fi
+
+         # Save old roots.
+         PRIMARY_CVSROOT_DIRNAME_save=$PRIMARY_CVSROOT_DIRNAME
+         PRIMARY_CVSROOT_save=$PRIMARY_CVSROOT
+         SECONDARY_CVSROOT_DIRNAME_save=$SECONDARY_CVSROOT_DIRNAME
+         SECONDARY_CVSROOT_save=$SECONDARY_CVSROOT
+
+         # Set new roots.
+         PRIMARY_CVSROOT_DIRNAME=$TESTDIR/primary_cvsroot
+         PRIMARY_CVSROOT=:ext:$host$PRIMARY_CVSROOT_DIRNAME
+         SECONDARY_CVSROOT_DIRNAME=$TESTDIR/writeproxy_cvsroot
+         SECONDARY_CVSROOT=":ext;Redirect=no:$host$PRIMARY_CVSROOT_DIRNAME"
+
+         # Initialize the primary repository
+         dotest writeproxy-ssh-noredirect-init-1 \
+"$testcvs -d$PRIMARY_CVSROOT init"
+         mkdir writeproxy-ssh-noredirect; cd writeproxy-ssh-noredirect
+         mkdir primary; cd primary
+         dotest writeproxy-ssh-noredirect-init-2 \
+"$testcvs -Qd$PRIMARY_CVSROOT co CVSROOT"
+         cd CVSROOT
+         cat >>loginfo <<EOF
+ALL $RSYNC -gopr --delete $PRIMARY_CVSROOT_DIRNAME/ $SECONDARY_CVSROOT_DIRNAME
+EOF
+         cat >>loginfo <<EOF
+ALL echo Referrer=%R; cat >/dev/null
+EOF
+         cat >>config <<EOF
+PrimaryServer=$PRIMARY_CVSROOT
+EOF
+         dotest writeproxy-ssh-noredirect-init-3 \
+"$testcvs -Q ci -mconfigure-writeproxy-ssh-noredirect" \
+"Referrer=NONE"
+
+         # And now the secondary.
+         $RSYNC -gopr $PRIMARY_CVSROOT_DIRNAME/ $SECONDARY_CVSROOT_DIRNAME
+
+         # Wrap the CVS server to allow --primary-root to be set by the
+         # secondary.
+         cat <<EOF >$TESTDIR/writeproxy-secondary-wrapper
+#! $TESTSHELL
+CVS_SERVER=$TESTDIR/writeproxy-primary-wrapper
+export CVS_SERVER
+
+# No need to check the PID of the last client since we are testing with
+# Redirect disabled.
+proot_arg="--allow-root=$SECONDARY_CVSROOT_DIRNAME 
--allow-root=$PRIMARY_CVSROOT_DIRNAME"
+exec $CVS_SERVER \$proot_arg "\$@"
+EOF
+         cat <<EOF >$TESTDIR/writeproxy-primary-wrapper
+#! $TESTSHELL
+if test -n "\$CVS_SERVER_LOG"; then
+       CVS_SERVER_LOG=$TMPDIR/cvsprimarylog; export CVS_SERVER_LOG
+fi
+exec $CVS_SERVER "\$@"
+EOF
+
+         CVS_SERVER_save=$CVS_SERVER
+         CVS_SERVER_secondary=$TESTDIR/writeproxy-secondary-wrapper
+         CVS_SERVER=$CVS_SERVER_secondary
+
+         chmod a+x $TESTDIR/writeproxy-secondary-wrapper \
+                   $TESTDIR/writeproxy-primary-wrapper
+
+         # Checkout from secondary
+         #
+         # For now, move the primary root out of the way to satisfy
+         # ourselves that the data is coming from the secondary.
+         mv $PRIMARY_CVSROOT_DIRNAME $TESTDIR/save-root
+
+         # Checkin to secondary
+         cd ../..
+         dotest writeproxy-ssh-noredirect-1 \
+"$testcvs -qd '$SECONDARY_CVSROOT' co -ldtop ."
+
+         cd top
+         mkdir firstdir
+
+         # Have to move the primary root back before we can perform write
+         # operations.
+         mv $TESTDIR/save-root $PRIMARY_CVSROOT_DIRNAME
+
+         dotest writeproxy-ssh-noredirect-2 "$testcvs -Q add firstdir" \
+"Referrer=NONE"
+
+         cd firstdir
+         echo now you see me >file1
+         dotest writeproxy-ssh-noredirect-3 "$testcvs -Q add file1"
+         dotest writeproxy-ssh-noredirect-4 \
+"$testcvs -Q ci -mfirst-file file1" \
+"Referrer=NONE"
+
+         dokeep
+         cd ../../..
+         rm -r writeproxy-ssh-noredirect
+         rm -rf $PRIMARY_CVSROOT_DIRNAME $SECONDARY_CVSROOT_DIRNAME
+         PRIMARY_CVSROOT_DIRNAME=$PRIMARY_CVSROOT_DIRNAME_save
+         PRIMARY_CVSROOT=$PRIMARY_CVSROOT_save
+         SECONDARY_CVSROOT_DIRNAME=$SECONDARY_CVSROOT_DIRNAME_save
+         SECONDARY_CVSROOT=$SECONDARY_CVSROOT_save
+         rm $TESTDIR/writeproxy-secondary-wrapper \
+            $TESTDIR/writeproxy-primary-wrapper
+         CVS_SERVER=$CVS_SERVER_save
+         ;;
+
+
+
+       trace)
+         # Check that there are no core dumps lurking in the trace
+         # options. 
+
+         # Perform some cleanup for normalized testing...
+         rm ${CVSROOT_DIRNAME}/CVSROOT/history
+         rm -f ${CVSROOT_DIRNAME}/CVSROOT/cvsignore
+         rm -f ${CVSROOT_DIRNAME}/CVSROOT/cvsignore,v
+
+         # checkout the trace option
+
+         mkdir trace && cd trace
+         mkdir imp && cd imp
+         touch file1
+
+         dotest_sort trace-1 "${testcvs} -t -t -t init" \
+"  *-> Lock_Cleanup()
+  *-> RCS_checkout (checkoutlist,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (commitinfo,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (config,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (cvswrappers,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (loginfo,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (modules,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (notify,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (postadmin,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (postproxy,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (posttag,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (postwatch,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (preproxy,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (rcsinfo,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (taginfo,v, , , , \.#[0-9][0-9]*)
+  *-> RCS_checkout (verifymsg,v, , , , \.#[0-9][0-9]*)
+  *-> Simple_Lock_Cleanup()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#[0-9][0-9]*)
+  *-> unlink_file(\.#checkoutlist)
+  *-> unlink_file(\.#commitinfo)
+  *-> unlink_file(\.#config)
+  *-> unlink_file(\.#cvswrappers)
+  *-> unlink_file(\.#loginfo)
+  *-> unlink_file(\.#modules)
+  *-> unlink_file(\.#notify)
+  *-> unlink_file(\.#postadmin)
+  *-> unlink_file(\.#postproxy)
+  *-> unlink_file(\.#posttag)
+  *-> unlink_file(\.#postwatch)
+  *-> unlink_file(\.#preproxy)
+  *-> unlink_file(\.#rcsinfo)
+  *-> unlink_file(\.#taginfo)
+  *-> unlink_file(\.#verifymsg)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )" \
+"
+  *-> Forking server: ${CVS_SERVER} server
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> CVS_SERVER_SLEEP not set\.
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> RCS_checkout (checkoutlist,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (commitinfo,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (config,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (cvswrappers,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (loginfo,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (modules,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (notify,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (postadmin,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (postproxy,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (posttag,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (postwatch,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (preproxy,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (rcsinfo,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (taginfo,v, , , , \.#[0-9][0-9]*)
+S -> RCS_checkout (verifymsg,v, , , , \.#[0-9][0-9]*)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> do_cvs_command (init)
+S -> remove_locks()
+S -> remove_locks()
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#[0-9][0-9]*)
+S -> unlink_file(\.#checkoutlist)
+S -> unlink_file(\.#commitinfo)
+S -> unlink_file(\.#config)
+S -> unlink_file(\.#cvswrappers)
+S -> unlink_file(\.#loginfo)
+S -> unlink_file(\.#modules)
+S -> unlink_file(\.#notify)
+S -> unlink_file(\.#postadmin)
+S -> unlink_file(\.#postproxy)
+S -> unlink_file(\.#posttag)
+S -> unlink_file(\.#postwatch)
+S -> unlink_file(\.#preproxy)
+S -> unlink_file(\.#rcsinfo)
+S -> unlink_file(\.#taginfo)
+S -> unlink_file(\.#verifymsg)" \
+
+         dotest_sort trace-2 \
+"${testcvs} -t -t -t import -mimport trace MYVENDOR version-1" \
+"
+
+  *-> Lock_Cleanup()
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace, ALL)
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/verifymsg, trace, not ALL)
+  *-> Simple_Lock_Cleanup()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> safe_location( where=(null) )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+N trace/file1
+No conflicts created by this import" \
+"
+
+
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Sending file \`file1' to server
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+N trace/file1
+No conflicts created by this import
+S -> CVS_SERVER_SLEEP not set\.
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace, ALL)
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/verifymsg, trace, not ALL)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (import)
+S -> remove_locks()
+S -> remove_locks()
+S -> safe_location( where=(null) )
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()"
+
+         cd ..
+         rm -fr imp
+
+         dotest_sort trace-3 "${testcvs} -t -t -t co trace" \
+"  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *local=0, which=3, aflag=0,
+  *locktype=1, update_preload=trace
+  *-> Create_Admin
+  *-> Create_Admin (\., trace, ${CVSROOT_DIRNAME}/trace, , , 0, 0, 1)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, , , file1)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Register(file1, 1\.1\.1\.1, ${DATE}, ,  )
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Write_Template (trace, ${CVSROOT_DIRNAME}/trace)
+  *-> chmod(file1,[0-7][0-7]*)
+  *-> do_module (trace, Updating, NULL, NULL)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> safe_location( where=(null) )
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(\./CVS/Entries\.Static)
+  *-> unlink_file(\./CVS/Tag)
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> unlink_file_dir(CVS/,,file1)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+U trace/file1
+${SPROG} checkout: Updating trace" \
+"
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *local=0, which=3, aflag=0,
+  *locktype=1, update_preload=trace
+  *-> Create_Admin
+  *-> Create_Admin (trace, trace, ${CVSROOT_DIRNAME}/trace, , , 0, 0, 1)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Register(file1, 1\.1\.1\.1, ${DATE}, ,  )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> rename(\.new\.file1,file1)
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> safe_location( where=(null) )
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> unlink_file(CVS/Entries\.Static)
+  *-> unlink_file(CVS/Tag)
+  *-> unlink_file(CVS/Template)
+  *-> unlink_file(trace/CVS/Tag)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> CVS_SERVER_SLEEP not set\.
+S -> Create_Admin
+S -> Create_Admin (\., trace, ${CVSROOT_DIRNAME}/trace, , , 0, 0, 1)
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/rcsinfo, trace, ALL)
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, , , 
(function))
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Register(file1, 1\.1\.1\.1, , ,  )
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Write_Template (trace, ${CVSROOT_DIRNAME}/trace)
+S -> dirswitch (\., ${CVSROOT_DIRNAME})
+S -> dirswitch (\., ${CVSROOT_DIRNAME})
+S -> do_cvs_command (checkout)
+S -> do_module (trace, Updating, NULL, NULL)
+S -> do_module (trace, Updating, NULL, NULL)
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_simple_remove()
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> safe_location( where=(null) )
+S -> serve_directory (\.)
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_register(file1, 1\.1\.1\.1, , , , , )
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> unlink_file(\./CVS/Entries\.Static)
+S -> unlink_file(\./CVS/Tag)
+S -> unlink_file(CVS/Entries\.Log)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+U trace/file1
+${SPROG} checkout: Updating trace"
+
+         cd trace
+         mkdir subdir
+         dotest_sort trace-4 "${testcvs} -t -t -t add subdir" \
+"  *-> Create_Admin
+  *-> Create_Admin (\., subdir, ${CVSROOT_DIRNAME}/trace/subdir, , , 0, 0, 1)
+  *-> Lock_Cleanup()
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace/subdir, ALL)
+  *-> Simple_Lock_Cleanup()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> unlink_file(\./CVS/Tag)
+  *-> unlink_file(${CVSROOT_DIRNAME}/trace/subdir/CVS/fileattr)
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+Directory ${CVSROOT_DIRNAME}/trace/subdir added to the repository" \
+"
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *-> Create_Admin
+  *-> Create_Admin (subdir, subdir, ${CVSROOT_DIRNAME}/trace/subdir, , , 0, 0, 
1)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+${DOTSTAR}  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Entries\.Log)
+${DOTSTAR}  *-> unlink_file(CVS/Template)
+  *-> unlink_file(subdir/CVS/Tag)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${DOTSTAR}Directory ${CVSROOT_DIRNAME}/trace/subdir added to the repository
+S -> CVS_SERVER_SLEEP not set\.
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace/subdir, ALL)
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/rcsinfo, trace/subdir, ALL)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Write_Template (subdir, ${CVSROOT_DIRNAME}/trace/subdir)
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> dirswitch (subdir, ${CVSROOT_DIRNAME}/trace/subdir)
+S -> do_cvs_command (add)
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> serve_directory (\.)
+S -> serve_directory (subdir)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> unlink_file(${CVSROOT_DIRNAME}/trace/subdir/CVS/fileattr)
+S -> unlink_file(CVS/Entries\.Log)
+S -> unlink_file(CVS/Entries\.Log)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )"
+         touch file2
+         dotest_sort trace-5 "${testcvs} -t -t -t add file2" \
+"  *-> Lock_Cleanup()
+  *-> Register(file2, 0, Initial file2, ,  )
+  *-> Simple_Lock_Cleanup()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${SPROG} add: scheduling file \`file2' for addition
+${SPROG} add: use \`${SPROG} commit' to add this file permanently" \
+"
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Register(file2, 0, dummy timestamp, ,  )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+${DOTSTAR}  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Entries\.Log)
+${DOTSTAR}  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${DOTSTAR}S -> CVS_SERVER_SLEEP not set\.
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Register(file2, 0, Initial file2, ,  )
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (add)
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_register(file2, 0, Initial file2, , , , )
+S -> unlink_file(CVS/Entries\.Log)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${SPROG} add: scheduling file \`file2' for addition
+${SPROG} add: use \`${SPROG} commit' to add this file permanently"
+         dotest_sort trace-6 "${testcvs} -t -t -t ci -mnew-file file2" \
+"  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Lock_Cleanup()
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/commitinfo, trace, ALL)
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace, ALL)
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/verifymsg, trace, not ALL)
+  *-> Promotable_Lock ()
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file2,v, 1, , , (function))
+  *-> RCS_cmp_file( ${CVSROOT_DIRNAME}/trace/file2,v, 1, (null), , file2 )
+  *-> Register(file2, 1\.1, ${DATE}, ,  )
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_tree_promotably (1, argv, 0, 1, 0)
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_lock(${CVSROOT_DIRNAME}/trace)
+  *-> rcs_cleanup()
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace)
+  *-> remove_locks()
+  *-> remove_locks()
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 0)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Base/file2)
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> unlink_file(CVS/file2,t)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${CVSROOT_DIRNAME}/trace/file2,v  <--  file2
+initial revision: 1\.1" \
+"
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Register(file2, 1\.1, ${DATE}, ,  )
+  *-> Sending file \`file2' to server
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Base/file2)
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${CVSROOT_DIRNAME}/trace/file2,v  <--  file2
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/commitinfo, trace, ALL)
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace, ALL)
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/verifymsg, trace, not ALL)
+S -> Promotable_Lock ()
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file2,v, 1, , , (function))
+S -> RCS_cmp_file( ${CVSROOT_DIRNAME}/trace/file2,v, 1, (null), , file2 )
+S -> Register(file2, 1\.1, ${DATE}, ,  )
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (commit)
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+S -> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_tree_promotably (1, argv, 0, 1, 0)
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace)
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace)
+S -> promotable_lock(${CVSROOT_DIRNAME}/trace)
+S -> rcs_cleanup()
+S -> readers_exist (${CVSROOT_DIRNAME}/trace)
+S -> remove_locks()
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+S -> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_pathname_check (file2)
+S -> server_pathname_check (file2)
+S -> server_pathname_check (file2)
+S -> server_register(file2, 1\.1, ${DATE}, , , , )
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 0)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> unlink_file(CVS/Entries\.Log)
+S -> unlink_file(CVS/file2,t)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+initial revision: 1\.1"
+         dotest_sort trace-7 "${testcvs} -t -t -t tag bp" \
+"  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in= )
+  *dosrcs=1, repository_in= )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=1, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *local_specified=0, mname=(null), msg=(null) )
+  *mwhere=(null), mfile=(null), shorten=0,
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/taginfo, trace, ALL)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, (null))
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, (null))
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.pfl\.\*, (null))
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.rfl\.\*, (null))
+  *-> check_fileproc ( ${CVSROOT_DIRNAME}/trace, file1, 
${CVSROOT_DIRNAME}/trace/file1,v )
+  *-> check_fileproc ( ${CVSROOT_DIRNAME}/trace, file2, 
${CVSROOT_DIRNAME}/trace/file2,v )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> rcs_cleanup()
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace)
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> remove_locks()
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> rtag_proc ( argc=1, argv=${PFMT}, xwhere=(null),
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+T file1
+T file2
+${SPROG} tag: Tagging \.
+${SPROG} tag: Tagging subdir" \
+"
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in= )
+  *dosrcs=1, repository_in= )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=1, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *local_specified=0, mname=(null), msg=(null) )
+  *mwhere=(null), mfile=(null), shorten=0,
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/taginfo, trace, ALL)
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, (null))
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, (null))
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.pfl\.\*, (null))
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.rfl\.\*, (null))
+S -> check_fileproc ( ${CVSROOT_DIRNAME}/trace, file1, 
${CVSROOT_DIRNAME}/trace/file1,v )
+S -> check_fileproc ( ${CVSROOT_DIRNAME}/trace, file2, 
${CVSROOT_DIRNAME}/trace/file2,v )
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> dirswitch (subdir, ${CVSROOT_DIRNAME}/trace/subdir)
+S -> do_cvs_command (tag)
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+S -> lock_dir_for_write (${CVSROOT_DIRNAME}/trace/subdir)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace)
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace/subdir)
+S -> rcs_cleanup()
+S -> readers_exist (${CVSROOT_DIRNAME}/trace)
+S -> readers_exist (${CVSROOT_DIRNAME}/trace/subdir)
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+S -> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> rtag_proc ( argc=1, argv=${PFMT}, xwhere=(null),
+S -> serve_directory (\.)
+S -> serve_directory (\.)
+S -> serve_directory (subdir)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> unlink_file(CVS/Entries\.Log)
+S -> unlink_file(CVS/Entries\.Log)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+T file1
+T file2
+${SPROG} tag: Tagging \.
+${SPROG} tag: Tagging subdir"
+
+         dotest_sort trace-8 "${testcvs} -t -t -t tag -b branch1" \
+"  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in= )
+  *dosrcs=1, repository_in= )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=1, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *local_specified=0, mname=(null), msg=(null) )
+  *mwhere=(null), mfile=(null), shorten=0,
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/taginfo, trace, ALL)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, (null))
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, (null))
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.pfl\.\*, (null))
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.rfl\.\*, (null))
+  *-> check_fileproc ( ${CVSROOT_DIRNAME}/trace, file1, 
${CVSROOT_DIRNAME}/trace/file1,v )
+  *-> check_fileproc ( ${CVSROOT_DIRNAME}/trace, file2, 
${CVSROOT_DIRNAME}/trace/file2,v )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> rcs_cleanup()
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace)
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> remove_locks()
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+  *-> rtag_proc ( argc=1, argv=${PFMT}, xwhere=(null),
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+T file1
+T file2
+${SPROG} tag: Tagging \.
+${SPROG} tag: Tagging subdir" \
+"
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in= )
+  *dosrcs=1, repository_in= )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=1, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *local_specified=0, mname=(null), msg=(null) )
+  *mwhere=(null), mfile=(null), shorten=0,
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/taginfo, trace, ALL)
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, (null))
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, (null))
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.pfl\.\*, (null))
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.rfl\.\*, (null))
+S -> check_fileproc ( ${CVSROOT_DIRNAME}/trace, file1, 
${CVSROOT_DIRNAME}/trace/file1,v )
+S -> check_fileproc ( ${CVSROOT_DIRNAME}/trace, file2, 
${CVSROOT_DIRNAME}/trace/file2,v )
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> dirswitch (subdir, ${CVSROOT_DIRNAME}/trace/subdir)
+S -> do_cvs_command (tag)
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+S -> lock_dir_for_write (${CVSROOT_DIRNAME}/trace/subdir)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace)
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace/subdir)
+S -> rcs_cleanup()
+S -> readers_exist (${CVSROOT_DIRNAME}/trace)
+S -> readers_exist (${CVSROOT_DIRNAME}/trace/subdir)
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+S -> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> rtag_proc ( argc=1, argv=${PFMT}, xwhere=(null),
+S -> serve_directory (\.)
+S -> serve_directory (\.)
+S -> serve_directory (subdir)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> unlink_file(CVS/Entries\.Log)
+S -> unlink_file(CVS/Entries\.Log)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+T file1
+T file2
+${SPROG} tag: Tagging \.
+${SPROG} tag: Tagging subdir"
+         dotest_sort trace-9 "${testcvs} -t -t -t log" \
+"
+
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=7, aflag=0,
+  *locktype=1, update_preload=(null)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+ MYVENDOR: 1\.1\.1
+ bp: 1\.1
+ bp: 1\.1\.1\.1
+ branch1: 1\.1\.0\.2
+ branch1: 1\.1\.1\.1\.0\.2
+ version-1: 1\.1\.1\.1
+----------------------------
+----------------------------
+----------------------------
+=============================================================================
+=============================================================================
+Initial revision
+RCS file: ${CVSROOT_DIRNAME}/trace/file1,v
+RCS file: ${CVSROOT_DIRNAME}/trace/file2,v
+Working file: file1
+Working file: file2
+access list:
+access list:
+branch:
+branch: 1\.1\.1
+branches:  1\.1\.1;
+${SPROG} log: Logging \.
+${SPROG} log: Logging subdir
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+description:
+description:
+head: 1\.1
+head: 1\.1
+import
+keyword substitution: kv
+keyword substitution: kv
+locks: strict
+locks: strict
+new-file
+revision 1\.1
+revision 1\.1
+revision 1\.1\.1\.1
+symbolic names:
+symbolic names:
+total revisions: 1; selected revisions: 1
+total revisions: 2; selected revisions: 2" \
+"
+
+
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=7, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=1, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+ MYVENDOR: 1\.1\.1
+ bp: 1\.1
+ bp: 1\.1\.1\.1
+ branch1: 1\.1\.0\.2
+ branch1: 1\.1\.1\.1\.0\.2
+ version-1: 1\.1\.1\.1
+----------------------------
+----------------------------
+----------------------------
+=============================================================================
+=============================================================================
+Initial revision
+RCS file: ${CVSROOT_DIRNAME}/trace/file1,v
+RCS file: ${CVSROOT_DIRNAME}/trace/file2,v
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> dirswitch (subdir, ${CVSROOT_DIRNAME}/trace/subdir)
+S -> do_cvs_command (log)
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> serve_directory (\.)
+S -> serve_directory (\.)
+S -> serve_directory (subdir)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> unlink_file(CVS/Entries\.Log)
+S -> unlink_file(CVS/Entries\.Log)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+Working file: file1
+Working file: file2
+access list:
+access list:
+branch:
+branch: 1\.1\.1
+branches:  1\.1\.1;
+${SPROG} log: Logging \.
+${SPROG} log: Logging subdir
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  lines: ${PLUS}0 -0;  
commitid: ${commitid};
+date: ${ISO8601DATE};  author: ${username};  state: Exp;  commitid: 
${commitid};
+description:
+description:
+head: 1\.1
+head: 1\.1
+import
+keyword substitution: kv
+keyword substitution: kv
+locks: strict
+locks: strict
+new-file
+revision 1\.1
+revision 1\.1
+revision 1\.1\.1\.1
+symbolic names:
+symbolic names:
+total revisions: 1; selected revisions: 1
+total revisions: 2; selected revisions: 2"
+
+         dotest_sort trace-10 "${testcvs} -t -t -t annotate file1" \
+"
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in= )
+  *local=0, which=1, aflag=0,
+  *locktype=1, update_preload=(null)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> do_recursion ( frame=${PFMT} )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+Annotations for file1" \
+"
+
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in= )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=1, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+Annotations for file1
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (annotate)
+S -> do_recursion ( frame=${PFMT} )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_simple_remove()
+S -> remove_locks()
+S -> remove_locks()
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_pathname_check (file1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )"
+
+         dotest_sort \
+trace-11 "${testcvs} -t -t -t rtag -r bp -b branch2 trace" \
+"  *aflag=0, repository=${CVSROOT_DIRNAME}/trace )
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *local=0, which=6, aflag=0,
+  *local=0, which=6, aflag=0,
+  *local=0, which=6, aflag=0,
+  *locktype=1, update_preload=(null)
+  *locktype=1, update_preload=trace
+  *locktype=2, update_preload=trace
+  *local_specified=0, mname=trace, msg=Tagging )
+  *mwhere=(null), mfile=(null), shorten=0,
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/taginfo, trace, ALL)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, (null))
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, (null))
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.pfl\.\*, (null))
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.rfl\.\*, (null))
+  *-> check_fileproc ( ${CVSROOT_DIRNAME}/trace, trace/file1, 
${CVSROOT_DIRNAME}/trace/file1,v )
+  *-> check_fileproc ( ${CVSROOT_DIRNAME}/trace, trace/file2, 
${CVSROOT_DIRNAME}/trace/file2,v )
+  *-> do_module (trace, Tagging, NULL, branch2)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> rcs_cleanup()
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace)
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> remove_locks()
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+  *-> rtag_proc ( argc=1, argv=${PFMT}, xwhere=(null),
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> tag_check_valid ( name=bp, argc=0, argv=${PFMT}, local=0,
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${SPROG} rtag: Tagging trace
+${SPROG} rtag: Tagging trace/subdir" \
+"
+  *aflag=0, repository=${CVSROOT_DIRNAME}/trace )
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *local=0, which=6, aflag=0,
+  *local=0, which=6, aflag=0,
+  *local=0, which=6, aflag=0,
+  *locktype=1, update_preload=(null)
+  *locktype=1, update_preload=trace
+  *locktype=2, update_preload=trace
+  *local_specified=0, mname=trace, msg=Tagging )
+  *mwhere=(null), mfile=(null), shorten=0,
+  *-> Forking server: ${CVS_SERVER} server
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/taginfo, trace, ALL)
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace/subdir)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, (null))
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, (null))
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.pfl\.\*, (null))
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.rfl\.\*, (null))
+S -> check_fileproc ( ${CVSROOT_DIRNAME}/trace, trace/file1, 
${CVSROOT_DIRNAME}/trace/file1,v )
+S -> check_fileproc ( ${CVSROOT_DIRNAME}/trace, trace/file2, 
${CVSROOT_DIRNAME}/trace/file2,v )
+S -> do_cvs_command (rtag)
+S -> do_module (trace, Tagging, NULL, branch2)
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+S -> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+S -> lock_dir_for_write (${CVSROOT_DIRNAME}/trace/subdir)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace)
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace/subdir)
+S -> rcs_cleanup()
+S -> readers_exist (${CVSROOT_DIRNAME}/trace)
+S -> readers_exist (${CVSROOT_DIRNAME}/trace/subdir)
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+S -> rename(${CVSROOT_DIRNAME}/trace/,file2,,${CVSROOT_DIRNAME}/trace/file2,v)
+S -> rtag_proc ( argc=1, argv=${PFMT}, xwhere=(null),
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> tag_check_valid ( name=bp, argc=0, argv=${PFMT}, local=0,
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${SPROG} rtag: Tagging trace
+${SPROG} rtag: Tagging trace/subdir"
+
+         dotest_sort trace-12 "${testcvs} -t -t -t status file1" \
+"
+
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *locktype=1, update_preload=(null)
+   Repository revision: 1\.1\.1\.1 ${CVSROOT_DIRNAME}/trace/file1,v
+   Sticky Date:  (none)
+   Sticky Options: (none)
+   Sticky Tag:  (none)
+   Working revision: 1\.1\.1\.1 ${DATE}
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> do_recursion ( frame=${PFMT} )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+===================================================================
+File: file1  *Status: Up-to-date" \
+"
+
+
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=1, update_preload=(null)
+   Repository revision: 1\.1\.1\.1 ${CVSROOT_DIRNAME}/trace/file1,v
+   Sticky Date:  (none)
+   Sticky Options: (none)
+   Sticky Tag:  (none)
+   Working revision: 1\.1\.1\.1
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+===================================================================
+File: file1  *Status: Up-to-date
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (status)
+S -> do_recursion ( frame=${PFMT} )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_simple_remove()
+S -> remove_locks()
+S -> remove_locks()
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_pathname_check (file1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )"
+
+         echo foo >> file1
+         dotest_sort trace-13 "${testcvs} -t -t -t up -C file1" \
+"  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=3, aflag=0,
+  *locktype=1, update_preload=(null)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, , , 
(function))
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, , , file1)
+  *-> RCS_cmp_file( ${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, (null), , 
file1 )
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Register(file1, 1\.1\.1\.1, ${DATE}, ,  )
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> chmod(file1,[0-7][0-7]*)
+  *-> copy(file1,\.#file1\.1\.1\.1\.1)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> rename(file1,CVS/,,file1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> unlink_file_dir(CVS/,,file1)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+(Locally modified file1 moved to \.#file1\.1\.1\.1\.1)
+U file1" \
+"
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=3, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=1, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Register(file1, 1\.1\.1\.1, ${DATE}, ,  )
+  *-> copy(file1,\.#file1\.1\.1\.1\.1)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> rename(\.new\.file1,file1)
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+(Locally modified file1 moved to \.#file1\.1\.1\.1\.1)
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, , , 
(function))
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Register(file1, 1\.1\.1\.1, M, ,  )
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (update)
+S -> do_recursion ( frame=${PFMT} )
+S -> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_simple_remove()
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_pathname_check (file1)
+S -> server_register(file1, 1\.1\.1\.1, M, , , , )
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> unlink_file(CVS/Entries\.Log)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+U file1"
+         echo foo >> file1
+         dotest_sort trace-14 "${testcvs} -t -t -t ci -madd-data file1" \
+"  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Lock_Cleanup()
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/commitinfo, trace, ALL)
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace, ALL)
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/verifymsg, trace, not ALL)
+  *-> Promotable_Lock ()
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, , , , (function))
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1, , -ko, ${tempname})
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, , , 
(function))
+  *-> RCS_cmp_file( ${CVSROOT_DIRNAME}/trace/file1,v, (null), (null), , file1 )
+  *-> RCS_cmp_file( ${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, (null), , 
file1 )
+  *-> Register(file1, 1\.2, ${DATE}, ,  )
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_tree_promotably (1, argv, 0, 1, 0)
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_lock(${CVSROOT_DIRNAME}/trace)
+  *-> rcs_cleanup()
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace)
+  *-> remove_locks()
+  *-> remove_locks()
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 0)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(${tempname})
+  *-> unlink_file(${tempname})
+  *-> unlink_file(CVS/Base/file1)
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${CVSROOT_DIRNAME}/trace/file1,v  <--  file1
+new revision: 1\.2; previous revision: 1\.1" \
+"
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Register(file1, 1\.2, ${DATE}, ,  )
+  *-> Sending file \`file1' to server
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Base/file1)
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${CVSROOT_DIRNAME}/trace/file1,v  <--  file1
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/commitinfo, trace, ALL)
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace, ALL)
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/verifymsg, trace, not ALL)
+S -> Promotable_Lock ()
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, , , , (function))
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1, , -ko, ${tempname})
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, , , 
(function))
+S -> RCS_cmp_file( ${CVSROOT_DIRNAME}/trace/file1,v, (null), (null), , file1 )
+S -> RCS_cmp_file( ${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, (null), , 
file1 )
+S -> Register(file1, 1\.2, ${DATE}, ,  )
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (commit)
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+S -> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_tree_promotably (1, argv, 0, 1, 0)
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace)
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace)
+S -> promotable_lock(${CVSROOT_DIRNAME}/trace)
+S -> rcs_cleanup()
+S -> readers_exist (${CVSROOT_DIRNAME}/trace)
+S -> remove_locks()
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_pathname_check (file1)
+S -> server_pathname_check (file1)
+S -> server_pathname_check (file1)
+S -> server_register(file1, 1\.2, ${DATE}, , , , )
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 0)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> unlink_file(${tempname})
+S -> unlink_file(${tempname})
+S -> unlink_file(CVS/Entries\.Log)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+new revision: 1\.2; previous revision: 1\.1"
+
+         dotest_fail_sort trace-15 "${testcvs} -t -t -t diff -r1.1 file1" \
+"  *aflag=0, repository= )
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=7, aflag=0,
+  *locktype=1, update_preload=(null)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1, , , ${tempname})
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.2, , , (function))
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.2, , , ${tempname})
+  *-> RCS_cmp_file( ${CVSROOT_DIRNAME}/trace/file1,v, 1\.1, 1\.2, , file1 )
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> diff_file_nodiff (file1, 3)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> tag_check_valid ( name=1\.1, argc=1, argv=${PFMT}, local=0,
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+0a1
+===================================================================
+> foo
+Index: file1
+RCS file: ${CVSROOT_DIRNAME}/trace/file1,v
+diff -r1\.1 -r1\.2
+retrieving revision 1\.1
+retrieving revision 1\.2" \
+"
+  *aflag=0, repository= )
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=7, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=1, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+0a1
+===================================================================
+> foo
+Index: file1
+RCS file: ${CVSROOT_DIRNAME}/trace/file1,v
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1, , , ${tempname})
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.2, , , (function))
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.2, , , ${tempname})
+S -> RCS_cmp_file( ${CVSROOT_DIRNAME}/trace/file1,v, 1\.1, 1\.2, , file1 )
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> diff_file_nodiff (file1, 3)
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (diff)
+S -> do_recursion ( frame=${PFMT} )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_simple_remove()
+S -> remove_locks()
+S -> remove_locks()
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_pathname_check (file1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> tag_check_valid ( name=1\.1, argc=1, argv=${PFMT}, local=0,
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+diff -r1\.1 -r1\.2
+retrieving revision 1\.1
+retrieving revision 1\.2"
+
+         dotest_sort trace-16 "${testcvs} -t -t -t rdiff -rbp trace/file1" \
+"  *aflag=0, repository=${CVSROOT_DIRNAME}/trace )
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *local=0, which=6, aflag=0,
+  *locktype=1, update_preload=trace
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, bp, , 
${tempname})
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.2, , , ${tempname})
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> do_module (trace/file1, Patching, NULL, NULL)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> patch_proc ( (null), (null), (null), 0, 0, trace/file1, Patching )
+  *-> remove_locks()
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> tag_check_valid ( name=bp, argc=1, argv=${PFMT}, local=0,
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+\*\*\* 0 \*\*\*\*
+\*\*\* trace/file1:1\.1\.1\.1 ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+${PLUS} foo
+--- 1 ----
+--- trace/file1 ${DATE}
+Index: trace/file1
+diff -c trace/file1:1\.1\.1\.1 trace/file1:1\.2" \
+"
+  *aflag=0, repository=${CVSROOT_DIRNAME}/trace )
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=${CVSROOT_DIRNAME}/trace )
+  *local=0, which=6, aflag=0,
+  *locktype=1, update_preload=trace
+  *-> Forking server: ${CVS_SERVER} server
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+\*\*\* 0 \*\*\*\*
+\*\*\* trace/file1:1\.1\.1\.1 ${DATE}
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+${PLUS} foo
+--- 1 ----
+--- trace/file1 ${DATE}
+Index: trace/file1
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.1\.1\.1, bp, , 
${tempname})
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.2, , , ${tempname})
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> do_cvs_command (rdiff)
+S -> do_module (trace/file1, Patching, NULL, NULL)
+S -> do_recursion ( frame=${PFMT} )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_simple_remove()
+S -> patch_proc ( (null), (null), (null), 0, 0, trace/file1, Patching )
+S -> remove_locks()
+S -> remove_locks()
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_pathname_check (file1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> tag_check_valid ( name=bp, argc=1, argv=${PFMT}, local=0,
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+diff -c trace/file1:1\.1\.1\.1 trace/file1:1\.2"
+
+         dotest_sort trace-17 "${testcvs} -t -t -t rm -f file1" \
+"  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *locktype=1, update_preload=(null)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+  *-> Register(file1, -1\.2, ${DATE}, ,  )
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> do_recursion ( frame=${PFMT} )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_simple_remove()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${SPROG} remove: scheduling \`file1' for removal
+${SPROG} remove: use \`${SPROG} commit' to remove this file permanently" \
+"
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=1, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Register(file1, -1\.2, dummy timestamp, ,  )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Reader_Lock(${CVSROOT_DIRNAME}/trace)
+S -> Register(file1, -1\.2, , ,  )
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (remove)
+S -> do_recursion ( frame=${PFMT} )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_simple_remove()
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_pathname_check (file1)
+S -> server_register(file1, -1\.2, , , , , )
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> unlink_file(CVS/Entries\.Log)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${SPROG} remove: scheduling \`file1' for removal
+${SPROG} remove: use \`${SPROG} commit' to remove this file permanently"
+
+         dotest_sort trace-18 "${testcvs} -t -t -t ci -mremove file1" \
+"  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Lock_Cleanup()
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/commitinfo, trace, ALL)
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace, ALL)
+  *-> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/verifymsg, trace, not ALL)
+  *-> Promotable_Lock ()
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, , , , file1)
+  *-> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.2, , -ko, ${tempname})
+  *-> Scratch_Entry(file1)
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, 
#cvs.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_tree_promotably (1, argv, 0, 1, 0)
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_lock(${CVSROOT_DIRNAME}/trace)
+  *-> rcs_cleanup()
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace)
+  *-> remove_locks()
+  *-> remove_locks()
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+  *-> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 0)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(${tempname})
+  *-> unlink_file(${tempname})
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> unlink_file(file1)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${CVSROOT_DIRNAME}/trace/file1,v  <--  file1
+new revision: delete; previous revision: 1\.2" \
+"
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *callerdat=${PFMT}, argc=1, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *dosrcs=1, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Scratch_Entry(file1)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> rename(CVS/Entries\.Backup,CVS/Entries)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file(CVS/Entries\.Log)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+${CVSROOT_DIRNAME}/trace/file1,v  <--  file1
+S -> CVS_SERVER_SLEEP not set\.
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Leaving do_recursion ( frame=${PFMT} )
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/commitinfo, trace, ALL)
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/loginfo, trace, ALL)
+S -> Parse_Info (${CVSROOT_DIRNAME}/CVSROOT/verifymsg, trace, not ALL)
+S -> Promotable_Lock ()
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, , , , file1)
+S -> RCS_checkout (${CVSROOT_DIRNAME}/trace/file1,v, 1\.2, , -ko, ${tempname})
+S -> Scratch_Entry(file1)
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs.pfl\.${hostname}\.[0-9][0-9]*)
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs.pfl\.${hostname}\.[0-9][0-9]*)
+S -> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, 
#cvs.rfl\.${hostname}\.[0-9][0-9]*)
+S -> dirswitch (\., ${CVSROOT_DIRNAME}/trace)
+S -> do_cvs_command (commit)
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> do_recursion ( frame=${PFMT} )
+S -> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+S -> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_name (${CVSROOT_DIRNAME}/trace, )
+S -> lock_simple_remove()
+S -> lock_simple_remove()
+S -> lock_tree_promotably (1, argv, 0, 1, 0)
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace)
+S -> promotable_exists (${CVSROOT_DIRNAME}/trace)
+S -> promotable_lock(${CVSROOT_DIRNAME}/trace)
+S -> rcs_cleanup()
+S -> readers_exist (${CVSROOT_DIRNAME}/trace)
+S -> remove_locks()
+S -> remove_locks()
+S -> remove_locks()
+S -> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+S -> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+S -> rename(${CVSROOT_DIRNAME}/trace/,file1,,${CVSROOT_DIRNAME}/trace/file1,v)
+S -> rename(CVS/Entries\.Backup,CVS/Entries)
+S -> serve_directory (\.)
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_pathname_check (file1)
+S -> server_pathname_check (file1)
+S -> server_pathname_check (file1)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 0)
+S -> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> unlink_file(${tempname})
+S -> unlink_file(${tempname})
+S -> unlink_file(CVS/Entries\.Log)
+S -> unlink_file(file1)
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+done
+new revision: delete; previous revision: 1\.2"
+
+         # SGI IRIX seems to have problems with the stdout and stderr
+         # mix for this test, so separate them.
+         dotest_sort trace-19 "${testcvs} -t -t -t history file1 2>stderr19" \
+"O ${ISODATE} ${username} trace =trace= ${TESTDIR}/trace/\*" \
+"O ${ISODATE} ${username} trace =trace= <remote>/\*"
+         dotest_sort trace-19stderr "sort < stderr19" \
+"  *-> Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> remove_locks()
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )" \
+"
+  *-> Forking server: ${CVS_SERVER} server
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+S -> CVS_SERVER_SLEEP not set\.
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> do_cvs_command (history)
+S -> remove_locks()
+S -> remove_locks()
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()"
+         rm stderr19
+
+         cd ..
+         dotest_sort \
+trace-20 "echo yes | ${testcvs} -t -t -t release -d trace" \
+"  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *locktype=2, update_preload=(null)
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Lock_Cleanup()
+  *-> Lock_Cleanup()
+  *-> Promotable_Lock ()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> Simple_Lock_Cleanup()
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.\*, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.pfl\.\*, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> _lock_exists (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.rfl\.\*, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace)
+  *-> lock_dir_for_write (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, #cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, #cvs\.lock)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.pfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.rfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, 
#cvs\.wfl\.${hostname}\.[0-9][0-9]*)
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+  *-> lock_name (${CVSROOT_DIRNAME}/trace/subdir, )
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_simple_remove()
+  *-> lock_tree_promotably (0, argv, 0, 1, 0)
+  *-> main loop with CVSROOT=${CVSROOT_DIRNAME}
+  *-> parse_cvsroot ( ${CVSROOT_DIRNAME} )
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace)
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> promotable_exists (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> promotable_lock(${CVSROOT_DIRNAME}/trace)
+  *-> promotable_lock(${CVSROOT_DIRNAME}/trace/subdir)
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace)
+  *-> readers_exist (${CVSROOT_DIRNAME}/trace/subdir)
+  *-> remove_locks()
+  *-> remove_locks()
+  *-> run_popen(${testcvs} -n -q -d ${CVSROOT_DIRNAME} update,r)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 0)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace, 1)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 0)
+  *-> set_lock (${CVSROOT_DIRNAME}/trace/subdir, 1)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file_dir(trace)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+Are you sure you want to release (and delete) directory \`trace':   *-> 
start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+You have \[0\] altered files in this repository\." \
+"
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *callerdat=${PFMT}, argc=0, argv=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *direntproc=${PFMT}, dirleavproc=${PFMT},
+  *dosrcs=0, repository_in=(null) )
+  *dosrcs=0, repository_in=(null) )
+  *local=0, which=1, aflag=0,
+  *local=0, which=1, aflag=0,
+  *locktype=0, update_preload=(null)
+  *locktype=0, update_preload=(null)
+  *-> Forking server: ${CVS_SERVER} server
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> Leaving do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> do_recursion ( frame=${PFMT} )
+  *-> main loop with CVSROOT=${CVSROOT}
+  *-> parse_cvsroot ( ${CVSROOT} )
+  *-> run_popen(${testcvs} -n -q -d ${CVSROOT} update,r)
+  *-> start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+  *-> unlink_file_dir(trace)
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+  *-> walklist ( list=${PFMT}, proc=${PFMT}, closure=${PFMT} )
+Are you sure you want to release (and delete) directory \`trace':   *-> 
start_recursion ( fileproc=${PFMT}, filesdoneproc=${PFMT},
+S -> CVS_SERVER_SLEEP not set\.
+S -> Lock_Cleanup()
+S -> Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> Simple_Lock_Cleanup()
+S -> do_cvs_command (release)
+S -> fopen(${CVSROOT_DIRNAME}/CVSROOT/history,a)
+S -> remove_locks()
+S -> remove_locks()
+S -> server_cleanup()
+S -> server_cleanup()
+S -> server_notify()
+S -> server_notify()
+S -> server_notify()
+You have \[0\] altered files in this repository\."
+
+         dokeep
+         cd ..
+         rm -fr trace
+         modify_repo rm -fr $CVSROOT_DIRNAME/trace 
+         ;;
+
+
+
+       *)
+          echo $what is not the name of a test -- ignored
+          ;;
+       esac
+
+    # Sanity check sanity.sh.  :)
+    #
+    # Test our exit directory so that tests that exit in an incorrect directory
+    # are noticed during single test runs.
+    #
+    # FIXME?
+    # Sparc Solaris 9 is dereferencing paths here as if /bin/pwd were
+    # called when /tmp is a symlink.  This might be a new problem with this
+    # test, but since this was recently tested I think it more likely to be
+    # A Solaris issue.
+    if test "x$TESTDIR" != "x`pwd`"; then
+           fail "cleanup: PWD != TESTDIR (\``pwd`' != \`$TESTDIR')"
+    fi
+
+    # Test that the last test didn't overwrite any write proxy configuration
+    # which may be in place.
+    if $proxy; then
+       problem=false
+       for file in \
+                   $SECONDARY_CVSROOT_DIRNAME/CVSROOT/config \
+                   $CVSROOT_DIRNAME/CVSROOT/config \
+                   $SECONDARY_CVSROOT_DIRNAME/CVSROOT/loginfo \
+                   $CVSROOT_DIRNAME/CVSROOT/loginfo \
+                   $SECONDARY_CVSROOT_DIRNAME/CVSROOT/postadmin \
+                   $CVSROOT_DIRNAME/CVSROOT/postadmin \
+                   $SECONDARY_CVSROOT_DIRNAME/CVSROOT/posttag \
+                   $CVSROOT_DIRNAME/CVSROOT/posttag \
+                   $SECONDARY_CVSROOT_DIRNAME/CVSROOT/postwatch \
+                   $CVSROOT_DIRNAME/CVSROOT/postwatch; do
+           if $diff_u $file $TESTDIR/`basename $file`-clean >>$LOGFILE 2>&1; 
then
+               :;
+           else
+               echo "\`$file' and \`$TESTDIR/`basename $file`-clean' differ." \
+                    >>$LOGFILE
+               problem=:
+           fi
+       done
+       if $problem; then
+           fail "cleanup: write proxy configuration not preserved"
+       fi
+    fi
+
+    if $remote && test "$servercvs_orig" != "$servercvs" >/dev/null 2>&1; then
+       fail "test slagged \$servercvs"
+    fi
+
+    # Reset val-tags to a pristine state.
+    if test -s $CVSROOT_DIRNAME/CVSROOT/val-tags; then
+       modify_repo ":" > $CVSROOT_DIRNAME/CVSROOT/val-tags
+    fi
+    verify_tmp_empty "post $what"
+
+done # The big loop
+
+# Set up summary data for output.
+skippedoutput=
+warningsoutput=
+extendedinfo=
+if test $skipped -ne 0; then
+  skippedoutput="$skipped test group"
+  if test $skipped -ne 1; then
+    skippedoutput="${skippedoutput}s"
+  fi
+  skippedoutput="$skippedoutput skipped"
+fi
+if test $warnings -ne 0; then
+  warningsoutput="$warnings test"
+  if test $warnings -ne 1; then
+    warningsoutput="${warningsoutput}s"
+  fi
+  warningsoutput="$warningsoutput passed with warnings"
+fi
+if test -n "$skippedoutput" || test -n "$warningsoutput"; then
+  extendedinfo=" ("
+  if test -n "$skippedoutput"; then
+    extendedinfo="$extendedinfo$skippedoutput"
+  fi
+  if test -n "$skippedoutput" && test -n "$warningsoutput"; then
+    extendedinfo="$extendedinfo and "
+  fi
+  if test -n "$warningsoutput"; then
+    extendedinfo="$extendedinfo$warningsoutput"
+  fi
+  extendedinfo="$extendedinfo)"
+fi
+
+echo "OK, all $passed tests passed$extendedinfo."
+
+# TODO:
+# * Test `cvs update -d foo' (where foo does not exist).
+# * Test `cvs update foo bar' (where foo and bar are both from the
+#   same directory in the 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 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`:${TESTDIR}/non/existent co foo
+#   gives an appropriate error (e.g.
+#     Cannot access ${TESTDIR}/non-existent/CVSROOT
+#     No such file or directory).
+#   (like basica-9, but for remote).
+# * 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 the contents of adm files other than Root and Repository.
+#   Entries seems the next most important thing.
+# * Test the following compatibility issues:
+#   - The filler fields in "D" entries in CVS/Entries get preserved
+#     (per cvs.texinfo).
+#   - Unrecognized entry types in CVS/Entries get ignored (looks like
+#     this needs to be documented in cvs.texinfo, but is not)
+#   - Test that unrecognized files in CVS directories (e.g. CVS/Foobar)
+#     are ignored (per cvs.texinfo).
+#   - Test 'cvs history' with symlinks in the path to the working directory.
+#   - Remove most of the CVS_SERVER stuff after a reasonable amount of time.
+#     The "fork" & "client" series of tests should be left.  4/2/00, CVS
+#     1.11.0.1 was altered so that it would default to program_name (set from
+#     argv[0]) rather than "cvs", but I'd like this script to work on legacy
+#     versions of CVS for awhile.
+#   - Testsuite doesn't work with usernames over eight characters in length.
+#     Fix it.
+# End of TODO list.
+
+# Exit if keep set
+dokeep
+
+# Remove the test directory, but first change out of it.
+if $TIMING; then
+    echo "exiting without removing test dir in order to preserve timing 
information."
+else
+    cd `dirname $TESTDIR`
+    rm -rf $TESTDIR
+fi
+
+# end of sanity.sh
Index: ccvs/src/status.c
diff -u /dev/null ccvs/src/status.c:1.68.8.1
--- /dev/null   Tue Jan 17 15:41:25 2006
+++ ccvs/src/status.c   Tue Jan 17 15:41:24 2006
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * Status Information
+ */
+
+#include "cvs.h"
+
+static Dtype status_dirproc (void *callerdat, const char *dir,
+                             const char *repos, const char *update_dir,
+                             List *entries);
+static int status_fileproc (void *callerdat, struct file_info *finfo);
+static int tag_list_proc (Node * p, void *closure);
+
+static int local = 0;
+static int long_format = 0;
+static RCSNode *xrcsnode;
+
+static const char *const status_usage[] =
+{
+    "Usage: %s %s [-vlR] [files...]\n",
+    "\t-v\tVerbose format; includes tag information for the file\n",
+    "\t-l\tProcess this directory only (not recursive).\n",
+    "\t-R\tProcess directories recursively.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+int
+cvsstatus (int argc, char **argv)
+{
+    int c;
+    int err = 0;
+
+    if (argc == -1)
+       usage (status_usage);
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+vlR")) != -1)
+    {
+       switch (c)
+       {
+           case 'v':
+               long_format = 1;
+               break;
+           case 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case '?':
+           default:
+               usage (status_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       start_server ();
+
+       ign_setup ();
+
+       if (long_format)
+           send_arg("-v");
+       if (local)
+           send_arg("-l");
+       send_arg ("--");
+
+       /* For a while, we tried setting SEND_NO_CONTENTS here so this
+          could be a fast operation.  That prevents the
+          server from updating our timestamp if the timestamp is
+          changed but the file is unmodified.  Worse, it is user-visible
+          (shows "locally modified" instead of "up to date" if
+          timestamp is changed but file is not).  And there is no good
+          workaround (you might not want to run "cvs update"; "cvs -n
+          update" doesn't update CVS/Entries; "cvs diff --brief" or
+          something perhaps could be made to work but somehow that
+          seems nonintuitive to me even if so).  Given that timestamps
+          seem to have the potential to get munged for any number of
+          reasons, it seems better to not rely too much on them.  */
+
+       send_files (argc, argv, local, 0, 0);
+
+       send_file_names (argc, argv, SEND_EXPAND_WILD);
+
+       send_to_server ("status\012", 0);
+       err = get_responses_and_close ();
+
+       return err;
+    }
+#endif
+
+    /* start the recursion processor */
+    err = start_recursion (status_fileproc, NULL, status_dirproc,
+                          NULL, NULL, argc, argv, local, W_LOCAL,
+                          0, CVS_LOCK_READ, NULL, 1, NULL);
+
+    return (err);
+}
+
+/*
+ * display the status of a file
+ */
+/* ARGSUSED */
+static int
+status_fileproc (void *callerdat, struct file_info *finfo)
+{
+    Ctype status;
+    char *sstat;
+    Vers_TS *vers;
+    Node *node;
+
+    status = Classify_File (finfo, NULL, NULL, NULL, 1, 0, &vers, 0);
+    sstat = "Classify Error";
+    switch (status)
+    {
+       case T_UNKNOWN:
+           sstat = "Unknown";
+           break;
+       case T_CHECKOUT:
+           sstat = "Needs Checkout";
+           break;
+       case T_PATCH:
+           sstat = "Needs Patch";
+           break;
+       case T_CONFLICT:
+           /* FIXME - This message could be clearer.  It comes up
+            * when a file exists or has been added in the local sandbox
+            * and a file of the same name has been committed indepenently to
+            * the repository from a different sandbox, as well as when a
+            * timestamp hasn't changed since a merge resulted in conflicts.
+            * It also comes up whether an update has been attempted or not, so
+            * technically, I think the double-add case is not actually a
+            * conflict yet.
+            */
+           sstat = "Unresolved Conflict";
+           break;
+       case T_ADDED:
+           sstat = "Locally Added";
+           break;
+       case T_REMOVED:
+           sstat = "Locally Removed";
+           break;
+       case T_MODIFIED:
+           if (file_has_markers (finfo))
+               sstat = "File had conflicts on merge";
+           else
+               /* Note that we do not re Register() the file when we spot
+                * a resolved conflict like update_fileproc() does on the
+                * premise that status should not alter the sandbox.
+                */
+               sstat = "Locally Modified";
+           break;
+       case T_REMOVE_ENTRY:
+           sstat = "Entry Invalid";
+           break;
+       case T_UPTODATE:
+           sstat = "Up-to-date";
+           break;
+       case T_NEEDS_MERGE:
+           sstat = "Needs Merge";
+           break;
+       case T_TITLE:
+           /* I don't think this case can occur here.  Just print
+              "Classify Error".  */
+           break;
+    }
+
+    cvs_output ("\
+===================================================================\n", 0);
+    if (vers->ts_user == NULL)
+    {
+       cvs_output ("File: no file ", 0);
+       cvs_output (finfo->file, 0);
+       cvs_output ("\t\tStatus: ", 0);
+       cvs_output (sstat, 0);
+       cvs_output ("\n\n", 0);
+    }
+    else
+    {
+       char *buf;
+       buf = Xasprintf ("File: %-17s\tStatus: %s\n\n", finfo->file, sstat);
+       cvs_output (buf, 0);
+       free (buf);
+    }
+
+    if (vers->vn_user == NULL)
+    {
+       cvs_output ("   Working revision:\tNo entry for ", 0);
+       cvs_output (finfo->file, 0);
+       cvs_output ("\n", 0);
+    }
+    else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+       cvs_output ("   Working revision:\tNew file!\n", 0);
+    else
+    {
+       cvs_output ("   Working revision:\t", 0);
+       cvs_output (vers->vn_user, 0);
+
+       /* Only add the UTC timezone if there is a time to use. */
+       if (!server_active && strlen (vers->ts_rcs) > 0)
+       {
+           /* Convert from the asctime() format to ISO 8601 */
+           char *buf;
+
+           cvs_output ("\t", 0);
+
+           /* Allow conversion from CVS/Entries asctime() to ISO 8601 */
+           buf = Xasprintf ("%s UTC", vers->ts_rcs);
+           cvs_output_tagged ("date", buf);
+           free (buf);
+       }
+       cvs_output ("\n", 0);
+    }
+
+    if (vers->vn_rcs == NULL)
+       cvs_output ("   Repository revision:\tNo revision control file\n", 0);
+    else
+    {
+       cvs_output ("   Repository revision:\t", 0);
+       cvs_output (vers->vn_rcs, 0);
+       cvs_output ("\t", 0);
+       cvs_output (vers->srcfile->print_path, 0);
+       cvs_output ("\n", 0);
+
+       node = findnode(vers->srcfile->versions,vers->vn_rcs);
+       if (node)
+       {
+           RCSVers *v;
+           v=(RCSVers*)node->data;
+           node = findnode(v->other_delta,"commitid");
+           cvs_output("   Commit Identifier:\t", 0);
+           if(node && node->data)
+               cvs_output(node->data, 0);
+           else
+               cvs_output("(none)",0);
+           cvs_output("\n",0);
+       }
+    }
+
+    if (vers->entdata)
+    {
+       Entnode *edata;
+
+       edata = vers->entdata;
+       if (edata->tag)
+       {
+           if (vers->vn_rcs == NULL)
+           {
+               cvs_output ("   Sticky Tag:\t\t", 0);
+               cvs_output (edata->tag, 0);
+               cvs_output (" - MISSING from RCS file!\n", 0);
+           }
+           else
+           {
+               if (isdigit ((unsigned char) edata->tag[0]))
+               {
+                   cvs_output ("   Sticky Tag:\t\t", 0);
+                   cvs_output (edata->tag, 0);
+                   cvs_output ("\n", 0);
+               }
+               else
+               {
+                   char *branch = NULL;
+
+                   if (RCS_nodeisbranch (finfo->rcs, edata->tag))
+                       branch = RCS_whatbranch (finfo->rcs, edata->tag);
+
+                   cvs_output ("   Sticky Tag:\t\t", 0);
+                   cvs_output (edata->tag, 0);
+                   cvs_output (" (", 0);
+                   cvs_output (branch ? "branch" : "revision", 0);
+                   cvs_output (": ", 0);
+                   cvs_output (branch ? branch : vers->vn_rcs, 0);
+                   cvs_output (")\n", 0);
+
+                   if (branch)
+                       free (branch);
+               }
+           }
+       }
+       else if (!really_quiet)
+           cvs_output ("   Sticky Tag:\t\t(none)\n", 0);
+
+       if (edata->date)
+       {
+           cvs_output ("   Sticky Date:\t\t", 0);
+           cvs_output (edata->date, 0);
+           cvs_output ("\n", 0);
+       }
+       else if (!really_quiet)
+           cvs_output ("   Sticky Date:\t\t(none)\n", 0);
+
+       if (edata->options && edata->options[0])
+       {
+           cvs_output ("   Sticky Options:\t", 0);
+           cvs_output (edata->options, 0);
+           cvs_output ("\n", 0);
+       }
+       else if (!really_quiet)
+           cvs_output ("   Sticky Options:\t(none)\n", 0);
+    }
+
+    if (long_format && vers->srcfile)
+    {
+       List *symbols = RCS_symbols(vers->srcfile);
+
+       cvs_output ("\n   Existing Tags:\n", 0);
+       if (symbols)
+       {
+           xrcsnode = finfo->rcs;
+           (void) walklist (symbols, tag_list_proc, NULL);
+       }
+       else
+           cvs_output ("\tNo Tags Exist\n", 0);
+    }
+
+    cvs_output ("\n", 0);
+    freevers_ts (&vers);
+    return (0);
+}
+
+
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+status_dirproc (void *callerdat, const char *dir, const char *repos,
+                const char *update_dir, List *entries)
+{
+    if (!quiet)
+       error (0, 0, "Examining %s", update_dir);
+    return (R_PROCESS);
+}
+
+
+
+/*
+ * Print out a tag and its type
+ */
+static int
+tag_list_proc (Node *p, void *closure)
+{
+    char *branch = NULL;
+    char *buf;
+
+    if (RCS_nodeisbranch (xrcsnode, p->key))
+       branch = RCS_whatbranch (xrcsnode, p->key);
+
+    buf = Xasprintf ("\t%-25s\t(%s: %s)\n", p->key,
+                    branch ? "branch" : "revision",
+                    branch ? branch : (char *)p->data);
+    cvs_output (buf, 0);
+    free (buf);
+
+    if (branch)
+       free (branch);
+
+    return (0);
+}
Index: ccvs/src/tag.c
diff -u /dev/null ccvs/src/tag.c:1.142.8.1
--- /dev/null   Tue Jan 17 15:41:25 2006
+++ ccvs/src/tag.c      Tue Jan 17 15:41:24 2006
@@ -0,0 +1,1717 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Tag and Rtag
+ *
+ * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
+ * Tag uses the checked out revision in the current directory, rtag uses
+ * the modules database, if necessary.
+ */
+
+#include "cvs.h"
+#include "save-cwd.h"
+
+static int rtag_proc (int argc, char **argv, char *xwhere,
+                     char *mwhere, char *mfile, int shorten,
+                     int local_specified, char *mname, char *msg);
+static int check_fileproc (void *callerdat, struct file_info *finfo);
+static int check_filesdoneproc (void *callerdat, int err,
+                               const char *repos, const char *update_dir,
+                               List *entries);
+static int pretag_proc (const char *_repository, const char *_filter,
+                        void *_closure);
+static void masterlist_delproc (Node *_p);
+static void tag_delproc (Node *_p);
+static int pretag_list_to_args_proc (Node *_p, void *_closure);
+
+static Dtype tag_dirproc (void *callerdat, const char *dir,
+                          const char *repos, const char *update_dir,
+                          List *entries);
+static int rtag_fileproc (void *callerdat, struct file_info *finfo);
+static int rtag_delete (RCSNode *rcsfile);
+static int tag_fileproc (void *callerdat, struct file_info *finfo);
+
+static char *numtag;                   /* specific revision to tag */
+static bool numtag_validated = false;
+static char *date = NULL;
+static char *symtag;                   /* tag to add or delete */
+static bool delete_flag;               /* adding a tag by default */
+static bool branch_mode;               /* make an automagic "branch" tag */
+static bool disturb_branch_tags = false;/* allow -F,-d to disturb branch tags 
*/
+static bool force_tag_match = true;    /* force tag to match by default */
+static bool force_tag_move;            /* don't force tag to move by default */
+static bool check_uptodate;            /* no uptodate-check by default */
+static bool attic_too;                 /* remove tag from Attic files */
+static bool is_rtag;
+
+struct tag_info
+{
+    Ctype status;
+    char *oldrev;
+    char *rev;
+    char *tag;
+    char *options;
+};
+
+struct master_lists
+{
+    List *tlist;
+};
+
+static List *mtlist;
+
+static const char rtag_opts[] = "+aBbdFflnQqRr:D:";
+static const char *const rtag_usage[] =
+{
+    "Usage: %s %s [-abdFflnR] [-r rev|-D date] tag modules...\n",
+    "\t-a\tClear tag from removed files that would not otherwise be tagged.\n",
+    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
+    "\t-B\tAllows -F and -d to disturb branch tags.  Use with extreme care.\n",
+    "\t-d\tDelete the given tag.\n",
+    "\t-F\tMove tag if it already exists.\n",
+    "\t-f\tForce a head revision match if tag/date not found.\n",
+    "\t-l\tLocal directory only, not recursive.\n",
+    "\t-n\tNo execution of 'tag program'.\n",
+    "\t-R\tProcess directories recursively.\n",
+    "\t-r rev\tExisting revision/tag.\n",
+    "\t-D\tExisting date.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+static const char tag_opts[] = "+BbcdFflQqRr:D:";
+static const char *const tag_usage[] =
+{
+    "Usage: %s %s [-bcdFflR] [-r rev|-D date] tag [files...]\n",
+    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
+    "\t-B\tAllows -F and -d to disturb branch tags.  Use with extreme care.\n",
+    "\t-c\tCheck that working files are unmodified.\n",
+    "\t-d\tDelete the given tag.\n",
+    "\t-F\tMove tag if it already exists.\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-r rev\tExisting revision/tag.\n",
+    "\t-D\tExisting date.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+
+
+int
+cvstag (int argc, char **argv)
+{
+    bool local = false;                        /* recursive by default */
+    int c;
+    int err = 0;
+    bool run_module_prog = true;
+
+    is_rtag = (strcmp (cvs_cmd_name, "rtag") == 0);
+
+    if (argc == -1)
+       usage (is_rtag ? rtag_usage : tag_usage);
+
+    optind = 0;
+    while ((c = getopt (argc, argv, is_rtag ? rtag_opts : tag_opts)) != -1)
+    {
+       switch (c)
+       {
+           case 'a':
+               attic_too = true;
+               break;
+           case 'b':
+               branch_mode = true;
+               break;
+           case 'B':
+               disturb_branch_tags = true;
+               break;
+           case 'c':
+               check_uptodate = true;
+               break;
+           case 'd':
+               delete_flag = true;
+               break;
+            case 'F':
+               force_tag_move = true;
+               break;
+           case 'f':
+               force_tag_match = false;
+               break;
+           case 'l':
+               local = true;
+               break;
+           case 'n':
+               run_module_prog = false;
+               break;
+           case 'Q':
+           case 'q':
+               /* The CVS 1.5 client sends these options (in addition to
+                  Global_option requests), so we must ignore them.  */
+               if (!server_active)
+                   error (1, 0,
+                          "-q or -Q must be specified before \"%s\"",
+                          cvs_cmd_name);
+               break;
+           case 'R':
+               local = false;
+               break;
+            case 'r':
+               parse_tagdate (&numtag, &date, optarg);
+                break;
+            case 'D':
+                if (date) free (date);
+                date = Make_Date (optarg);
+                break;
+           case '?':
+           default:
+               usage (is_rtag ? rtag_usage : tag_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc < (is_rtag ? 2 : 1))
+       usage (is_rtag ? rtag_usage : tag_usage);
+    symtag = argv[0];
+    argc--;
+    argv++;
+
+    if (date && delete_flag)
+       error (1, 0, "-d makes no sense with a date specification.");
+    if (delete_flag && branch_mode)
+       error (0, 0, "warning: -b ignored with -d options");
+    RCS_check_tag (symtag);
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       /* We're the client side.  Fire up the remote server.  */
+       start_server ();
+       
+       ign_setup ();
+
+       if (attic_too)
+           send_arg ("-a");
+       if (branch_mode)
+           send_arg ("-b");
+       if (disturb_branch_tags)
+           send_arg ("-B");
+       if (check_uptodate)
+           send_arg ("-c");
+       if (delete_flag)
+           send_arg ("-d");
+       if (force_tag_move)
+           send_arg ("-F");
+       if (!force_tag_match)
+           send_arg ("-f");
+       if (local)
+           send_arg ("-l");
+       if (!run_module_prog)
+           send_arg ("-n");
+
+       if (numtag)
+           option_with_arg ("-r", numtag);
+       if (date)
+           client_senddate (date);
+
+       send_arg ("--");
+
+       send_arg (symtag);
+
+       if (is_rtag)
+       {
+           int i;
+           for (i = 0; i < argc; ++i)
+               send_arg (argv[i]);
+           send_to_server ("rtag\012", 0);
+       }
+       else
+       {
+           send_files (argc, argv, local, 0,
+
+                   /* I think the -c case is like "cvs status", in
+                      which we really better be correct rather than
+                      being fast; it is just too confusing otherwise.  */
+                       check_uptodate ? 0 : SEND_NO_CONTENTS);
+           send_file_names (argc, argv, SEND_EXPAND_WILD);
+           send_to_server ("tag\012", 0);
+       }
+
+        return get_responses_and_close ();
+    }
+#endif
+
+    if (is_rtag)
+    {
+       DBM *db;
+       int i;
+       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, NULL, 0, local, run_module_prog,
+                             0, symtag);
+       }
+       close_module (db);
+    }
+    else
+    {
+       err = rtag_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, local, NULL,
+                        NULL);
+    }
+
+    return err;
+}
+
+
+
+struct pretag_proc_data {
+     List *tlist;
+     bool delete_flag;
+     bool force_tag_move;
+     char *symtag;
+};
+
+/*
+ * called from Parse_Info, this routine processes a line that came out
+ * of the posttag file and turns it into a command and executes it.
+ *
+ * RETURNS
+ *    the absolute value of the return value of run_exec, which may or
+ *    may not be the return value of the child process.  this is
+ *    contrained to return positive values because Parse_Info is summing
+ *    return values and testing for non-zeroness to signify one or more
+ *    of its callbacks having returned an error.
+ */
+static int
+posttag_proc (const char *repository, const char *filter, void *closure)
+{
+    char *cmdline;
+    const char *srepos = Short_Repository (repository);
+    struct pretag_proc_data *ppd = closure;
+
+    /* %t = tag being added/moved/removed
+     * %o = operation = "add" | "mov" | "del"
+     * %b = branch mode = "?" (delete ops - unknown) | "T" (branch)
+     *                    | "N" (not branch)
+     * %c = cvs_cmd_name
+     * %p = path from $CVSROOT
+     * %r = path from root
+     * %{sVv} = attribute list = file name, old version tag will be deleted
+     *                           from, new version tag will be added to (or
+     *                           deleted from until
+     *                           SUPPORT_OLD_INFO_FMT_STRINGS is undefined).
+     */
+    /*
+     * Cast any NULL arguments as appropriate pointers as this is an
+     * stdarg function and we need to be certain the caller gets what
+     * is expected.
+     */
+    cmdline = format_cmdline (
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                             false, srepos,
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                             filter,
+                             "t", "s", ppd->symtag,
+                             "o", "s", ppd->delete_flag
+                             ? "del" : ppd->force_tag_move ? "mov" : "add",
+                             "b", "c", delete_flag
+                             ? '?' : branch_mode ? 'T' : 'N',
+                             "c", "s", cvs_cmd_name,
+#ifdef SERVER_SUPPORT
+                             "R", "s", referrer ? referrer->original : "NONE",
+#endif /* SERVER_SUPPORT */
+                             "p", "s", srepos,
+                             "r", "s", current_parsed_root->directory,
+                             "sVv", ",", ppd->tlist,
+                             pretag_list_to_args_proc, (void *) NULL,
+                             (char *) NULL);
+
+    if (!cmdline || !strlen (cmdline))
+    {
+       if (cmdline) free (cmdline);
+       error (0, 0, "pretag proc resolved to the empty string!");
+       return 1;
+    }
+
+    run_setup (cmdline);
+
+    free (cmdline);
+    return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
+}
+
+
+
+/*
+ * Call any postadmin procs.
+ */
+static int
+tag_filesdoneproc (void *callerdat, int err, const char *repository,
+                   const char *update_dir, List *entries)
+{
+    Node *p;
+    List *mtlist, *tlist;
+    struct pretag_proc_data ppd;
+
+    TRACE (TRACE_FUNCTION, "tag_filesdoneproc (%d, %s, %s)", err, repository,
+           update_dir);
+
+    mtlist = callerdat;
+    p = findnode (mtlist, update_dir);
+    if (p != NULL)
+        tlist = ((struct master_lists *) p->data)->tlist;
+    else
+        tlist = NULL;
+    if (tlist == NULL || tlist->list->next == tlist->list)
+        return err;
+
+    ppd.tlist = tlist;
+    ppd.delete_flag = delete_flag;
+    ppd.force_tag_move = force_tag_move;
+    ppd.symtag = symtag;
+    Parse_Info (CVSROOTADM_POSTTAG, repository, posttag_proc,
+                PIOPT_ALL, &ppd);
+
+    return err;
+}
+
+
+
+/*
+ * callback proc for doing the real work of tagging
+ */
+/* ARGSUSED */
+static int
+rtag_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
+           int shorten, int local_specified, char *mname, char *msg)
+{
+    /* Begin section which is identical to patch_proc--should this
+       be abstracted out somehow?  */
+    char *myargv[2];
+    int err = 0;
+    int which;
+    char *repository;
+    char *where;
+
+#ifdef HAVE_PRINTF_PTR
+    TRACE (TRACE_FUNCTION,
+          "rtag_proc (argc=%d, argv=%p, xwhere=%s,\n"
+      "                mwhere=%s, mfile=%s, shorten=%d,\n"
+      "                local_specified=%d, mname=%s, msg=%s)",
+           argc, (void *)argv, xwhere ? xwhere : "(null)",
+           mwhere ? mwhere : "(null)", mfile ? mfile : "(null)",
+           shorten, local_specified,
+           mname ? mname : "(null)", msg ? msg : "(null)" );
+#else
+    TRACE (TRACE_FUNCTION,
+          "rtag_proc (argc=%d, argv=%lx, xwhere=%s,\n"
+      "                mwhere=%s, mfile=%s, shorten=%d,\n"
+      "                local_specified=%d, mname=%s, msg=%s )",
+           argc, (unsigned long)argv, xwhere ? xwhere : "(null)",
+           mwhere ? mwhere : "(null)", mfile ? mfile : "(null)",
+           shorten, local_specified,
+           mname ? mname : "(null)", msg ? msg : "(null)" );
+#endif
+
+    if (is_rtag)
+    {
+       repository = xmalloc (strlen (current_parsed_root->directory)
+                              + strlen (argv[0])
+                             + (mfile == NULL ? 0 : strlen (mfile) + 1)
+                              + 2);
+       (void) sprintf (repository, "%s/%s", current_parsed_root->directory,
+                        argv[0]);
+       where = xmalloc (strlen (argv[0])
+                         + (mfile == NULL ? 0 : strlen (mfile) + 1)
+                        + 1);
+       (void) strcpy (where, argv[0]);
+
+       /* If MFILE isn't null, we need to set up to do only part of the
+         * module.
+         */
+       if (mfile != NULL)
+       {
+           char *cp;
+           char *path;
+
+           /* If the portion of the module is a path, put the dir part on
+             * REPOS.
+             */
+           if ((cp = strrchr (mfile, '/')) != NULL)
+           {
+               *cp = '\0';
+               (void) strcat (repository, "/");
+               (void) strcat (repository, mfile);
+               (void) strcat (where, "/");
+               (void) strcat (where, mfile);
+               mfile = cp + 1;
+           }
+
+           /* take care of the rest */
+           path = xmalloc (strlen (repository) + strlen (mfile) + 5);
+           (void) sprintf (path, "%s/%s", repository, mfile);
+           if (isdir (path))
+           {
+               /* directory means repository gets the dir tacked on */
+               (void) strcpy (repository, path);
+               (void) strcat (where, "/");
+               (void) strcat (where, mfile);
+           }
+           else
+           {
+               myargv[0] = argv[0];
+               myargv[1] = mfile;
+               argc = 2;
+               argv = myargv;
+           }
+           free (path);
+       }
+
+       /* cd to the starting repository */
+       if (CVS_CHDIR (repository) < 0)
+       {
+           error (0, errno, "cannot chdir to %s", repository);
+           free (repository);
+           free (where);
+           return 1;
+       }
+       /* End section which is identical to patch_proc.  */
+
+       if (delete_flag || attic_too || (force_tag_match && numtag))
+           which = W_REPOS | W_ATTIC;
+       else
+           which = W_REPOS;
+    }
+    else
+    {
+        where = NULL;
+        which = W_LOCAL;
+        repository = "";
+    }
+
+    if (numtag != NULL && !numtag_validated)
+    {
+       tag_check_valid (numtag, argc - 1, argv + 1, local_specified, 0,
+                        repository, false);
+       numtag_validated = true;
+    }
+
+    /* 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,
+                           NULL, NULL, NULL,
+                          argc - 1, argv + 1, local_specified, which, 0,
+                          CVS_LOCK_READ, where, 1, repository);
+
+    if (err)
+    {
+       error (1, 0, "correct the above errors first!");
+    }
+
+    /* It would be nice to provide consistency with respect to
+       commits; however CVS lacks the infrastructure to do that (see
+       Concurrency in cvs.texinfo and comment in do_recursion).  */
+
+    /* start the recursion processor */
+    err = start_recursion
+       (is_rtag ? rtag_fileproc : tag_fileproc,
+        tag_filesdoneproc, tag_dirproc, NULL, mtlist, argc - 1, argv + 1,
+        local_specified, which, 0, CVS_LOCK_WRITE, where, 1,
+        repository);
+    dellist (&mtlist);
+    if (which & W_REPOS) free (repository);
+    if (where != NULL)
+       free (where);
+    return err;
+}
+
+
+
+/* check file that is to be tagged */
+/* All we do here is add it to our list */
+static int
+check_fileproc (void *callerdat, struct file_info *finfo)
+{
+    const char *xdir;
+    Node *p;
+    Vers_TS *vers;
+    List *tlist;
+    struct tag_info *ti;
+    int addit = 1;
+
+    TRACE (TRACE_FUNCTION, "check_fileproc (%s, %s, %s)",
+          finfo->repository ? finfo->repository : "(null)",
+          finfo->fullname ? finfo->fullname : "(null)",
+          finfo->rcs ? (finfo->rcs->path ? finfo->rcs->path : "(null)")
+          : "NULL");
+
+    if (check_uptodate)
+    {
+       switch (Classify_File (finfo, NULL, NULL, NULL, 1, 0, &vers, 0))
+       {
+       case T_UPTODATE:
+       case T_CHECKOUT:
+       case T_PATCH:
+       case T_REMOVE_ENTRY:
+           break;
+       case T_UNKNOWN:
+       case T_CONFLICT:
+       case T_NEEDS_MERGE:
+       case T_MODIFIED:
+       case T_ADDED:
+       case T_REMOVED:
+       default:
+           error (0, 0, "%s is locally modified", finfo->fullname);
+           freevers_ts (&vers);
+           return 1;
+       }
+    }
+    else
+       vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
+
+    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 = xmalloc (sizeof (struct master_lists));
+       ml->tlist = tlist;
+       p->data = 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;
+    if (vers->srcfile == NULL)
+    {
+        if (!really_quiet)
+           error (0, 0, "nothing known about %s", finfo->file);
+       freevers_ts (&vers);
+       freenode (p);
+       return 1;
+    }
+
+    /* Here we duplicate the calculation in tag_fileproc about which
+       version we are going to tag.  There probably are some subtle races
+       (e.g. numtag is "foo" which gets moved between here and
+       tag_fileproc).  */
+    p->data = ti = xmalloc (sizeof (struct tag_info));
+    ti->tag = xstrdup (numtag ? numtag : vers->tag);
+    if (!is_rtag && numtag == NULL && date == NULL)
+       ti->rev = xstrdup (vers->vn_user);
+    else
+    {
+        char *tmp;
+        if (RCS_is_relative (numtag))
+        {
+            tmp = Version_resolve_relTag (finfo, numtag, !is_rtag);
+            if (!tmp)
+            {
+                if (!really_quiet)
+                    error (0, 0, "Cannot resolve relative tag: `%s'.", numtag);
+                return 1;
+            }
+        }
+        else
+        {
+           tmp = xstrdup (numtag);
+        }
+       ti->rev = RCS_getversion (vers->srcfile, tmp, date,
+                                 force_tag_match, NULL);
+        free (tmp);
+    }
+    if (ti->rev != NULL)
+    {
+        ti->oldrev = RCS_getversion (vers->srcfile, symtag, NULL, 1, NULL);
+
+       if (ti->oldrev == NULL)
+        {
+            if (delete_flag)
+            {
+               /* Deleting a tag which did not exist is a noop and
+                  should not be logged.  */
+                addit = 0;
+            }
+        }
+       else if (delete_flag)
+       {
+           free (ti->rev);
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+           /* a hack since %v used to mean old or new rev */
+           ti->rev = xstrdup (ti->oldrev);
+#else /* SUPPORT_OLD_INFO_FMT_STRINGS */
+           ti->rev = NULL;
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+       }
+        else if (strcmp(ti->oldrev, p->data) == 0)
+            addit = 0;
+        else if (!force_tag_move)
+            addit = 0;
+    }
+    else
+       addit = 0;
+    if (!addit)
+    {
+       free(p->data);
+       p->data = NULL;
+    }
+    freevers_ts (&vers);
+    (void)addnode (tlist, p);
+    return 0;
+}
+
+
+
+static int
+check_filesdoneproc (void *callerdat, int err, const char *repos,
+                     const char *update_dir, List *entries)
+{
+    int n;
+    Node *p;
+    List *tlist;
+    struct pretag_proc_data ppd;
+
+    p = findnode (mtlist, update_dir);
+    if (p != NULL)
+        tlist = ((struct master_lists *) p->data)->tlist;
+    else
+        tlist = NULL;
+    if (tlist == NULL || tlist->list->next == tlist->list)
+        return err;
+
+    ppd.tlist = tlist;
+    ppd.delete_flag = delete_flag;
+    ppd.force_tag_move = force_tag_move;
+    ppd.symtag = symtag;
+    if ((n = Parse_Info (CVSROOTADM_TAGINFO, repos, pretag_proc, PIOPT_ALL,
+                        &ppd)) > 0)
+    {
+        error (0, 0, "Pre-tag check failed");
+        err += n;
+    }
+    return err;
+}
+
+
+
+/*
+ * called from Parse_Info, this routine processes a line that came out
+ * of a taginfo file and turns it into a command and executes it.
+ *
+ * RETURNS
+ *    the absolute value of the return value of run_exec, which may or
+ *    may not be the return value of the child process.  this is
+ *    contrained to return positive values because Parse_Info is adding up
+ *    return values and testing for non-zeroness to signify one or more
+ *    of its callbacks having returned an error.
+ */
+static int
+pretag_proc (const char *repository, const char *filter, void *closure)
+{
+    char *newfilter = NULL;
+    char *cmdline;
+    const char *srepos = Short_Repository (repository);
+    struct pretag_proc_data *ppd = closure;
+
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+    if (!strchr (filter, '%'))
+    {
+       error (0,0,
+               "warning: taginfo line contains no format strings:\n"
+               "    \"%s\"\n"
+               "Filling in old defaults ('%%t %%o %%p %%{sv}'), but please be 
aware that this\n"
+               "usage is deprecated.", filter);
+       newfilter = xmalloc (strlen (filter) + 16);
+       strcpy (newfilter, filter);
+       strcat (newfilter, " %t %o %p %{sv}");
+       filter = newfilter;
+    }
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+
+    /* %t = tag being added/moved/removed
+     * %o = operation = "add" | "mov" | "del"
+     * %b = branch mode = "?" (delete ops - unknown) | "T" (branch)
+     *                    | "N" (not branch)
+     * %c = cvs_cmd_name
+     * %p = path from $CVSROOT
+     * %r = path from root
+     * %{sVv} = attribute list = file name, old version tag will be deleted
+     *                           from, new version tag will be added to (or
+     *                           deleted from until
+     *                           SUPPORT_OLD_INFO_FMT_STRINGS is undefined)
+     */
+    /*
+     * Cast any NULL arguments as appropriate pointers as this is an
+     * stdarg function and we need to be certain the caller gets what
+     * is expected.
+     */
+    cmdline = format_cmdline (
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                             false, srepos,
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                             filter,
+                             "t", "s", ppd->symtag,
+                             "o", "s", ppd->delete_flag ? "del" :
+                             ppd->force_tag_move ? "mov" : "add",
+                             "b", "c", delete_flag
+                             ? '?' : branch_mode ? 'T' : 'N',
+                             "c", "s", cvs_cmd_name,
+#ifdef SERVER_SUPPORT
+                             "R", "s", referrer ? referrer->original : "NONE",
+#endif /* SERVER_SUPPORT */
+                             "p", "s", srepos,
+                             "r", "s", current_parsed_root->directory,
+                             "sVv", ",", ppd->tlist,
+                             pretag_list_to_args_proc, (void *) NULL,
+                             (char *) NULL);
+
+    if (newfilter) free (newfilter);
+
+    if (!cmdline || !strlen (cmdline))
+    {
+       if (cmdline) free (cmdline);
+       error (0, 0, "pretag proc resolved to the empty string!");
+       return 1;
+    }
+
+    run_setup (cmdline);
+
+    /* FIXME - the old code used to run the following here:
+     *
+     * if (!isfile(s))
+     * {
+     *     error (0, errno, "cannot find pre-tag filter '%s'", s);
+     *     free(s);
+     *     return (1);
+     * }
+     *
+     * not sure this is really necessary.  it might give a little finer grained
+     * error than letting the execution attempt fail but i'm not sure.  in any
+     * case it should be easy enough to add a function in run.c to test its
+     * first arg for fileness & executability.
+     */
+
+    free (cmdline);
+    return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
+}
+
+
+
+static void
+masterlist_delproc (Node *p)
+{
+    struct master_lists *ml = p->data;
+
+    dellist (&ml->tlist);
+    free (ml);
+    return;
+}
+
+
+
+static void
+tag_delproc (Node *p)
+{
+    struct tag_info *ti;
+    if (p->data)
+    {
+       ti = (struct tag_info *) p->data;
+       if (ti->oldrev) free (ti->oldrev);
+       if (ti->rev) free (ti->rev);
+       free (ti->tag);
+        free (p->data);
+        p->data = NULL;
+    }
+    return;
+}
+
+
+
+/* to be passed into walklist with a list of tags
+ * p->key = tagname
+ * p->data = struct tag_info *
+ * p->data->oldrev = rev tag will be deleted from
+ * p->data->rev = rev tag will be added to
+ * p->data->tag = tag oldrev is attached to, if any
+ *
+ * closure will be a struct format_cmdline_walklist_closure
+ * where closure is undefined
+ */
+static int
+pretag_list_to_args_proc (Node *p, void *closure)
+{
+    struct tag_info *taginfo = (struct tag_info *)p->data;
+    struct format_cmdline_walklist_closure *c =
+            (struct format_cmdline_walklist_closure *)closure;
+    char *arg = NULL;
+    const char *f;
+    char *d;
+    size_t doff;
+
+    if (!p->data) return 1;
+
+    f = c->format;
+    d = *c->d;
+    /* foreach requested attribute */
+    while (*f)
+    {
+       switch (*f++)
+       {
+           case 's':
+               arg = p->key;
+               break;
+           case 'T':
+               arg = taginfo->tag ? taginfo->tag : "";
+               break;
+           case 'v':
+               arg = taginfo->rev ? taginfo->rev : "NONE";
+               break;
+           case 'V':
+               arg = taginfo->oldrev ? taginfo->oldrev : "NONE";
+               break;
+           default:
+               error(1,0,
+                      "Unknown format character or not a list attribute: %c",
+                     f[-1]);
+               break;
+       }
+       /* copy the attribute into an argument */
+       if (c->quotes)
+       {
+           arg = cmdlineescape (c->quotes, arg);
+       }
+       else
+       {
+           arg = cmdlinequote ('"', arg);
+       }
+
+       doff = d - *c->buf;
+       expand_string (c->buf, c->length, doff + strlen (arg));
+       d = *c->buf + doff;
+       strncpy (d, arg, strlen (arg));
+       d += strlen (arg);
+
+       free (arg);
+
+       /* and always put the extra space on.  we'll have to back up a char 
when we're
+        * done, but that seems most efficient
+        */
+       doff = d - *c->buf;
+       expand_string (c->buf, c->length, doff + 1);
+       d = *c->buf + doff;
+       *d++ = ' ';
+    }
+    /* correct our original pointer into the buff */
+    *c->d = d;
+    return 0;
+}
+
+
+/*
+ * Called to rtag a particular file, as appropriate with the options that were
+ * set above.
+ */
+/* ARGSUSED */
+static int
+rtag_fileproc (void *callerdat, struct file_info *finfo)
+{
+    RCSNode *rcsfile;
+    char *version = NULL, *rev = NULL;
+    int retcode = 0;
+    int retval = 0;
+    static bool valtagged = false;
+
+    /* find the parsed RCS data */
+    if ((rcsfile = finfo->rcs) == NULL)
+    {
+       retval = 1;
+       goto free_vars_and_return;
+    }
+
+    /*
+     * 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)
+    {
+       retval = rtag_delete (rcsfile);
+       goto free_vars_and_return;
+    }
+
+    /*
+     * 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))
+       {
+           retval = rtag_delete (rcsfile);
+           goto free_vars_and_return;
+       }
+    }
+    version = RCS_getversion (rcsfile, numtag, date, force_tag_match, NULL);
+    if (version == NULL)
+    {
+       /* If -a specified, clean up any old tags */
+       if (attic_too)
+           (void)rtag_delete (rcsfile);
+
+       if (!quiet && !force_tag_match)
+       {
+           error (0, 0, "cannot find tag `%s' in `%s'",
+                  numtag ? numtag : "head", rcsfile->path);
+           retval = 1;
+       }
+       goto free_vars_and_return;
+    }
+    if (numtag
+       && isdigit ((unsigned char)*numtag)
+       && strcmp (numtag, version) != 0)
+    {
+
+       /*
+        * We didn't find a match for the numeric tag that was specified, but
+        * that's OK.  just pass the numeric tag on to rcs, to be tagged as
+        * specified.  Could get here if one tried to tag "1.1.1" and there
+        * was a 1.1.1 branch with some head revision.  In this case, we want
+        * the tag to reference "1.1.1" and not the revision at the head of
+        * the branch.  Use a symbolic tag for that.
+        */
+       rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag;
+       retcode = RCS_settag(rcsfile, symtag, numtag);
+       if (retcode == 0)
+           RCS_rewrite (rcsfile, NULL, NULL);
+    }
+    else
+    {
+       char *oversion;
+
+       /*
+        * As an enhancement for the case where a tag is being re-applied to
+        * a large body of a module, make one extra call to RCS_getversion to
+        * see if the tag is already set in the RCS file.  If so, check to
+        * see if it needs to be moved.  If not, do nothing.  This will
+        * likely save a lot of time when simply moving the tag to the
+        * "current" head revisions of a module -- which I have found to be a
+        * typical tagging operation.
+        */
+       rev = branch_mode ? RCS_magicrev (rcsfile, version) : version;
+       oversion = RCS_getversion (rcsfile, symtag, NULL, 1, NULL);
+       if (oversion != NULL)
+       {
+           int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
+
+           /*
+            * if versions the same and neither old or new are branches don't
+            * have to do anything
+            */
+           if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
+           {
+               free (oversion);
+               goto free_vars_and_return;
+           }
+
+           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);
+               goto free_vars_and_return;
+           }
+           else /* force_tag_move is set and... */
+               if ((isbranch && !disturb_branch_tags) ||
+                   (!isbranch && disturb_branch_tags))
+           {
+               error(0,0, "%s: Not moving %s tag `%s' from %s to %s%s.",
+                       finfo->fullname,
+                       isbranch ? "branch" : "non-branch",
+                       symtag, oversion, rev,
+                       isbranch ? "" : " due to `-B' option");
+               free (oversion);
+               goto free_vars_and_return;
+           }
+           free (oversion);
+       }
+       retcode = RCS_settag (rcsfile, symtag, rev);
+       if (retcode == 0)
+           RCS_rewrite (rcsfile, NULL, NULL);
+    }
+
+    if (retcode != 0)
+    {
+       error (1, retcode == -1 ? errno : 0,
+              "failed to set tag `%s' to revision `%s' in `%s'",
+              symtag, rev, rcsfile->path);
+        retval = 1;
+       goto free_vars_and_return;
+    }
+
+free_vars_and_return:
+    if (branch_mode && rev) free (rev);
+    if (version) free (version);
+    if (!delete_flag && !retval && !valtagged)
+    {
+       tag_check_valid (symtag, 0, NULL, 0, 0, NULL, true);
+       valtagged = true;
+    }
+    return retval;
+}
+
+
+
+/*
+ * 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 (RCSNode *rcsfile)
+{
+    char *version;
+    int retcode, isbranch;
+
+    if (numtag)
+    {
+       version = RCS_getversion (rcsfile, numtag, NULL, 1, NULL);
+       if (version == NULL)
+           return (0);
+       free (version);
+    }
+
+    version = RCS_getversion (rcsfile, symtag, NULL, 1, NULL);
+    if (version == NULL)
+       return 0;
+    free (version);
+
+
+    isbranch = RCS_nodeisbranch (rcsfile, symtag);
+    if ((isbranch && !disturb_branch_tags) ||
+       (!isbranch && disturb_branch_tags))
+    {
+       if (!quiet)
+           error (0, 0,
+                   "Not removing %s tag `%s' from `%s'%s.",
+                   isbranch ? "branch" : "non-branch",
+                   symtag, rcsfile->path,
+                   isbranch ? "" : " due to `-B' option");
+       return 1;
+    }
+
+    if ((retcode = RCS_deltag(rcsfile, symtag)) != 0)
+    {
+       if (!quiet)
+           error (0, retcode == -1 ? errno : 0,
+                  "failed to remove tag `%s' from `%s'", symtag,
+                  rcsfile->path);
+       return 1;
+    }
+    RCS_rewrite (rcsfile, NULL, NULL);
+    return 0;
+}
+
+
+
+/*
+ * 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 (void *callerdat, struct file_info *finfo)
+{
+    char *version, *oversion;
+    char *nversion = NULL;
+    char *rev;
+    Vers_TS *vers;
+    int retcode = 0;
+    int retval = 0;
+    static bool valtagged = false;
+
+    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
+
+    if (numtag || date)
+    {
+        char *tmp;
+        if (RCS_is_relative (numtag))
+        {
+            tmp = Version_resolve_relTag (finfo, numtag, true);
+            if (!tmp)
+            {
+                if (!really_quiet)
+                    error (0, 0, "Cannot resolve relative tag: `%s'.", numtag);
+                return 1;
+            }
+        }
+        else
+        {
+           tmp = xstrdup (numtag);
+        }
+        nversion = RCS_getversion (vers->srcfile, tmp, date,
+                                   force_tag_match, NULL);
+        free (tmp);
+        if (!nversion)
+           goto free_vars_and_return;
+    }
+    if (delete_flag)
+    {
+
+       int isbranch;
+       /*
+        * 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, NULL, 1, NULL);
+       if (version == NULL || vers->srcfile == NULL)
+           goto free_vars_and_return;
+
+       free (version);
+
+       isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
+       if ((isbranch && !disturb_branch_tags) ||
+           (!isbranch && disturb_branch_tags))
+       {
+           if (!quiet)
+               error(0, 0,
+                      "Not removing %s tag `%s' from `%s'%s.",
+                       isbranch ? "branch" : "non-branch",
+                       symtag, vers->srcfile->path,
+                       isbranch ? "" : " due to `-B' option");
+           retval = 1;
+           goto free_vars_and_return;
+       }
+
+       if ((retcode = RCS_deltag (vers->srcfile, symtag)) != 0)
+       {
+           if (!quiet)
+               error (0, retcode == -1 ? errno : 0,
+                      "failed to remove tag %s from %s", symtag,
+                      vers->srcfile->path);
+           retval = 1;
+           goto free_vars_and_return;
+       }
+       RCS_rewrite (vers->srcfile, NULL, NULL);
+
+       /* warm fuzzies */
+       if (!really_quiet)
+       {
+           cvs_output ("D ", 2);
+           cvs_output (finfo->fullname, 0);
+           cvs_output ("\n", 1);
+       }
+
+       goto free_vars_and_return;
+    }
+
+    /*
+     * If we are adding a tag, we need to know which version we have checked
+     * out and we'll tag that version.
+     */
+    if (!nversion)
+        version = vers->vn_user;
+    else
+        version = nversion;
+    if (!version)
+       goto free_vars_and_return;
+    else if (strcmp (version, "0") == 0)
+    {
+       if (!quiet)
+           error (0, 0, "couldn't tag added but un-commited file `%s'",
+                  finfo->file);
+       goto free_vars_and_return;
+    }
+    else if (version[0] == '-')
+    {
+       if (!quiet)
+           error (0, 0, "skipping removed but un-commited file `%s'",
+                  finfo->file);
+       goto free_vars_and_return;
+    }
+    else if (vers->srcfile == NULL)
+    {
+       if (!quiet)
+           error (0, 0, "cannot find revision control file for `%s'",
+                  finfo->file);
+       goto free_vars_and_return;
+    }
+
+    /*
+     * 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, NULL, 1, NULL);
+    if (oversion != NULL)
+    {
+       int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
+
+       /*
+        * if versions the same and neither old or new are branches don't have
+        * to do anything
+        */
+       if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
+       {
+           free (oversion);
+           if (branch_mode)
+               free (rev);
+           goto free_vars_and_return;
+       }
+
+       if (!force_tag_move)
+       {
+           /* we're NOT going to move the tag */
+           cvs_output ("W ", 2);
+           cvs_output (finfo->fullname, 0);
+           cvs_output (" : ", 0);
+           cvs_output (symtag, 0);
+           cvs_output (" already exists on ", 0);
+           cvs_output (isbranch ? "branch" : "version", 0);
+           cvs_output (" ", 0);
+           cvs_output (oversion, 0);
+           cvs_output (" : NOT MOVING tag to ", 0);
+           cvs_output (branch_mode ? "branch" : "version", 0);
+           cvs_output (" ", 0);
+           cvs_output (rev, 0);
+           cvs_output ("\n", 1);
+           free (oversion);
+           if (branch_mode)
+               free (rev);
+           goto free_vars_and_return;
+       }
+       else    /* force_tag_move == 1 and... */
+               if ((isbranch && !disturb_branch_tags) ||
+                   (!isbranch && disturb_branch_tags))
+       {
+           error (0,0, "%s: Not moving %s tag `%s' from %s to %s%s.",
+                  finfo->fullname,
+                  isbranch ? "branch" : "non-branch",
+                  symtag, oversion, rev,
+                  isbranch ? "" : " due to `-B' option");
+           free (oversion);
+           if (branch_mode)
+               free (rev);
+           goto free_vars_and_return;
+       }
+       free (oversion);
+    }
+
+    if ((retcode = RCS_settag(vers->srcfile, symtag, rev)) != 0)
+    {
+       error (1, retcode == -1 ? errno : 0,
+              "failed to set tag %s to revision %s in %s",
+              symtag, rev, vers->srcfile->path);
+       if (branch_mode)
+           free (rev);
+       retval = 1;
+       goto free_vars_and_return;
+    }
+    if (branch_mode)
+       free (rev);
+    RCS_rewrite (vers->srcfile, NULL, NULL);
+
+    /* more warm fuzzies */
+    if (!really_quiet)
+    {
+       cvs_output ("T ", 2);
+       cvs_output (finfo->fullname, 0);
+       cvs_output ("\n", 1);
+    }
+
+ free_vars_and_return:
+    if (nversion != NULL)
+        free (nversion);
+    freevers_ts (&vers);
+    if (!delete_flag && !retval && !valtagged)
+    {
+       tag_check_valid (symtag, 0, NULL, 0, 0, NULL, true);
+       valtagged = true;
+    }
+    return retval;
+}
+
+
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+tag_dirproc (void *callerdat, const char *dir, const char *repos,
+             const char *update_dir, List *entries)
+{
+
+    if (ignore_directory (update_dir))
+    {
+       /* print the warm fuzzy message */
+       if (!quiet)
+         error (0, 0, "Ignoring %s", update_dir);
+        return R_SKIP_ALL;
+    }
+
+    if (!quiet)
+       error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging",
+               update_dir);
+    return R_PROCESS;
+}
+
+
+
+/* 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 {
+    const char *name;
+    int found;
+};
+
+static int
+val_fileproc (void *callerdat, struct file_info *finfo)
+{
+    RCSNode *rcsdata;
+    struct val_args *args = callerdat;
+    char *tag;
+
+    if ((rcsdata = finfo->rcs) == NULL)
+       /* Not sure this can happen, after all we passed only
+          W_REPOS | W_ATTIC.  */
+       return 0;
+
+    tag = RCS_gettag (rcsdata, args->name, 1, NULL);
+    if (tag != NULL)
+    {
+       /* FIXME: should find out a way to stop the search at this point.  */
+       args->found = 1;
+       free (tag);
+    }
+    return 0;
+}
+
+
+
+/* This routine determines whether a tag appears in CVSROOT/val-tags.
+ *
+ * The val-tags file will be open read-only when IDB is NULL.  Since writes to
+ * val-tags always append to it, the lack of locking is okay.  The worst case
+ * race condition might misinterpret a partially written "foobar" matched, for
+ * instance,  a request for "f", "foo", of "foob".  Such a mismatch would be
+ * caught harmlessly later.
+ *
+ * Before CVS adds a tag to val-tags, it will lock val-tags for write and
+ * verify that the tag is still not present to avoid adding it twice.
+ *
+ * NOTES
+ *   This function expects its parent to handle any necessary locking of the
+ *   val-tags file.
+ *
+ * INPUTS
+ *   idb       When this value is NULL, the val-tags file is opened in
+ *             in read-only mode.  When present, the val-tags file is opened
+ *             in read-write mode and the DBM handle is stored in *IDB.
+ *   name      The tag to search for.
+ *
+ * OUTPUTS
+ *   *idb      The val-tags file opened for read/write, or NULL if it couldn't
+ *             be opened.
+ *
+ * ERRORS
+ *   Exits with an error message if the val-tags file cannot be opened for
+ *   read (failure to open val-tags read/write is harmless - see below).
+ *
+ * RETURNS
+ *   true      1. If NAME exists in val-tags.
+ *             2. If IDB is non-NULL and val-tags cannot be opened for write.
+ *                This allows callers to ignore the harmless inability to
+ *                update the val-tags cache.
+ *   false     If the file could be opened and the tag is not present.
+ */
+static int is_in_val_tags (DBM **idb, const char *name)
+{
+    DBM *db = NULL;
+    char *valtags_filename;
+    datum mytag;
+    int status;
+
+    /* Casting out const should be safe here - input datums are not
+     * written to by the myndbm functions.
+     */
+    mytag.dptr = (char *)name;
+    mytag.dsize = strlen (name);
+
+    valtags_filename = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
+                                 CVSROOTADM, CVSROOTADM_VALTAGS);
+
+    if (idb)
+    {
+       mode_t omask;
+
+       omask = umask (cvsumask);
+       db = dbm_open (valtags_filename, O_RDWR | O_CREAT, 0666);
+       umask (omask);
+
+       if (!db)
+       {
+
+           error (0, errno, "warning: cannot open `%s' read/write",
+                  valtags_filename);
+           *idb = NULL;
+           return 1;
+       }
+
+       *idb = db;
+    }
+    else
+    {
+       db = dbm_open (valtags_filename, O_RDONLY, 0444);
+       if (!db && !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.  */
+
+    status = 0;
+    if (db)
+    {
+       datum val;
+
+       val = dbm_fetch (db, mytag);
+       if (val.dptr != NULL)
+           /* Found.  The tag is valid.  */
+           status = 1;
+
+       /* FIXME: should check errors somehow (add dbm_error to myndbm.c?).  */
+
+       if (!idb) dbm_close (db);
+    }
+
+    free (valtags_filename);
+    return status;
+}
+
+
+
+/* Add a tag to the CVSROOT/val-tags cache.  Establishes a write lock and
+ * reverifies that the tag does not exist before adding it.
+ */
+static void add_to_val_tags (const char *name)
+{
+    DBM *db;
+    datum mytag;
+    datum value;
+
+    if (noexec) return;
+
+    val_tags_lock (current_parsed_root->directory);
+
+    /* Check for presence again since we have a lock now.  */
+    if (is_in_val_tags (&db, name)) return;
+
+    /* Casting out const should be safe here - input datums are not
+     * written to by the myndbm functions.
+     */
+    mytag.dptr = (char *)name;
+    mytag.dsize = strlen (name);
+    value.dptr = "y";
+    value.dsize = 1;
+
+    if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
+       error (0, errno, "failed to store %s into val-tags", name);
+    dbm_close (db);
+
+    clear_val_tags_lock ();
+}
+
+
+
+static Dtype
+val_direntproc (void *callerdat, const char *dir, const char *repository,
+                const char *update_dir, List *entries)
+{
+    /* This is not quite right--it doesn't get right the case of "cvs
+       update -d -r foobar" where foobar is a tag which exists only in
+       files in a directory which does not exist yet, but which is
+       about to be created.  */
+    if (isdir (dir))
+       return R_PROCESS;
+    return R_SKIP_ALL;
+}
+
+
+
+/* With VALID set, insert NAME into val-tags if it is not already present
+ * there.
+ *
+ * Without VALID set, check to see whether NAME is a valid tag.  If so, return.
+ * If not print an error message and exit.
+ *
+ * INPUTS
+ *
+ *   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.
+ *
+ * ERRORS
+ *   Errors may be encountered opening and accessing the DBM file.  Write
+ *   errors generate warnings and read errors are fatal.  When !VALID and NAME
+ *   is not in val-tags, errors may also be generated as per start_recursion.
+ *   When !VALID, non-existance of tags both in val-tags and in the archive
+ *   files also causes a fatal error.
+ *
+ * RETURNS
+ *   Nothing.
+ */
+void
+tag_check_valid (const char *oriname, int argc, char **argv, int local, int 
aflag,
+                 char *repository, bool valid)
+{
+    char *name;
+    struct val_args the_val_args;
+    struct saved_cwd cwd;
+    int which;
+
+#ifdef HAVE_PRINTF_PTR
+    TRACE (TRACE_FUNCTION,
+          "tag_check_valid (name=%s, argc=%d, argv=%p, local=%d,\n"
+      "                      aflag=%d, repository=%s, valid=%s)",
+          oriname ? oriname : "(name)", argc, (void *)argv, local, aflag,
+          repository ? repository : "(null)",
+          valid ? "true" : "false");
+#else
+    TRACE (TRACE_FUNCTION,
+          "tag_check_valid (name=%s, argc=%d, argv=%lx, local=%d,\n"
+      "                      aflag=%d, repository=%s, valid=%s)",
+          oriname ? oriname : "(name)", argc, (unsigned long)argv, local, 
aflag,
+          repository ? repository : "(null)",
+          valid ? "true" : "false");
+#endif
+
+    /* validate tag and return symbolic tag if any */
+    bool files = (argc > 0);
+    int i;
+    for (i=0; i<argc; ++i)
+       if (isdir (argv[i]))
+       {
+          files = false;
+          break;
+       }
+    if (!(name = RCS_extract_tag (oriname, files)))
+    {
+       assert (!valid);
+       return;
+    }
+
+    if (is_in_val_tags (NULL, name))
+    {
+        free (name);
+        return;
+    }
+
+    if (!valid)
+    {
+       /* 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;
+       which = W_REPOS | W_ATTIC;
+
+       if (repository == NULL || repository[0] == '\0')
+           which |= W_LOCAL;
+       else
+       {
+           if (save_cwd (&cwd))
+               error (1, errno, "Failed to save current directory.");
+           if (CVS_CHDIR (repository) < 0)
+               error (1, errno, "cannot change to %s directory", repository);
+       }
+
+       start_recursion
+           (val_fileproc, NULL, val_direntproc, NULL,
+            &the_val_args, argc, argv, local, which, aflag,
+            CVS_LOCK_READ, NULL, 1, repository);
+       if (repository != NULL && repository[0] != '\0')
+       {
+           if (restore_cwd (&cwd))
+               error (1, errno, "Failed to restore current directory, `%s'.",
+                      cwd.name);
+           free_cwd (&cwd);
+       }
+
+       if (!the_val_args.found)
+           error (1, 0, "no such tag `%s'", name);
+    }
+
+    /* The tags is valid but not mentioned in val-tags.  Add it.  */
+    add_to_val_tags (name);
+    free (name);
+}
Index: ccvs/src/update.c
diff -u /dev/null ccvs/src/update.c:1.260.2.1
--- /dev/null   Tue Jan 17 15:41:25 2006
+++ ccvs/src/update.c   Tue Jan 17 15:41:24 2006
@@ -0,0 +1,2939 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * "update" updates the version in the present directory with respect to the 
RCS
+ * repository.  The present version must have been created by "checkout". The
+ * user can keep up-to-date by calling "update" whenever he feels like it.
+ *
+ * The present version can be committed by "commit", but this keeps the version
+ * in tact.
+ *
+ * Arguments following the options are taken to be file names to be updated,
+ * rather than updating the entire directory.
+ *
+ * Modified or non-existent RCS files are checked out and reported as U
+ * <user_file>
+ *
+ * Modified user files are reported as M <user_file>.  If both the RCS file and
+ * the user file have been modified, the user file is replaced by the result
+ * of rcsmerge, and a backup file is written for the user in .#file.version.
+ * If this throws up irreconcilable differences, the file is reported as C
+ * <user_file>, and as M <user_file> otherwise.
+ *
+ * Files added but not yet committed are reported as A <user_file>. Files
+ * removed but not yet committed are reported as R <user_file>.
+ *
+ * If the current directory contains subdirectories that hold concurrent
+ * versions, these are updated too.  If the -d option was specified, new
+ * directories added to the repository are automatically created and updated
+ * as well.
+ */
+
+#include "cvs.h"
+#include <assert.h>
+#include "save-cwd.h"
+#ifdef SERVER_SUPPORT
+# include "md5.h"
+#endif
+#include "watch.h"
+#include "fileattr.h"
+#include "edit.h"
+#include "getline.h"
+#include "buffer.h"
+#include "hardlink.h"
+
+static int checkout_file (struct file_info *finfo, Vers_TS *vers_ts,
+                                int adding, int merging, int update_server);
+#ifdef SERVER_SUPPORT
+static void checkout_to_buffer (void *, const char *, size_t);
+static int patch_file (struct file_info *finfo,
+                       Vers_TS *vers_ts, 
+                       int *docheckout, struct stat *file_info,
+                       unsigned char *checksum);
+static void patch_file_write (void *, const char *, size_t);
+#endif
+static int merge_file (struct file_info *finfo, Vers_TS *vers);
+static int scratch_file (struct file_info *finfo, Vers_TS *vers);
+static Dtype update_dirent_proc (void *callerdat, const char *dir,
+                                 const char *repository,
+                                 const char *update_dir,
+                                 List *entries);
+static int update_dirleave_proc (void *callerdat, const char *dir,
+                                 int err, const char *update_dir,
+                                 List *entries);
+static int update_fileproc (void *callerdat, struct file_info *);
+static int update_filesdone_proc (void *callerdat, int err,
+                                  const char *repository,
+                                  const char *update_dir, List *entries);
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+static int get_linkinfo_proc( void *_callerdat, struct _finfo * );
+#endif
+static void join_file (struct file_info *finfo, Vers_TS *vers_ts,
+                       const char *j1, const char *j2);
+
+static char *options = NULL;
+static char *tag = NULL;
+static char *date = NULL;
+/* This is a bit of a kludge.  We call WriteTag at the beginning
+   before we know whether nonbranch is set or not.  And then at the
+   end, once we have the right value for nonbranch, we call WriteTag
+   again.  I don't know whether the first call is necessary or not.
+   rewrite_tag is nonzero if we are going to have to make that second
+   call.  warned is nonzero if we've already warned the user that the
+   tag occurs as both a revision tag and a branch tag.  */
+static int rewrite_tag;
+static int nonbranch;
+static int warned;
+
+/* If we set the tag or date for a subdirectory, we use this to undo
+   the setting.  See update_dirent_proc.  */
+static char *tag_update_dir;
+
+static char *join_rev1, *join_date1;
+static char *join_rev2, *join_date2;
+static int aflag = 0;
+static int toss_local_changes = 0;
+static int force_tag_match = 1;
+static int update_build_dirs = 0;
+static int update_prune_dirs = 0;
+static int pipeout = 0;
+static int dotemplate = 0;
+#ifdef SERVER_SUPPORT
+static int patches = 0;
+static int rcs_diff_patches = 0;
+#endif
+static List *ignlist = NULL;
+static time_t last_register_time;
+static const char *const update_usage[] =
+{
+    "Usage: %s %s [-APCdflRp] [-k kopt] [-r rev] [-D date] [-j rev]\n",
+    "    [-I ign] [-W spec] [files...]\n",
+    "\t-A\tReset any sticky tags/date/kopts.\n",
+    "\t-P\tPrune empty directories.\n",
+    "\t-C\tOverwrite locally modified files with clean repository copies.\n",
+    "\t-d\tBuild directories, like checkout does.\n",
+    "\t-f\tForce a head revision match if tag/date not found.\n",
+    "\t-l\tLocal directory only, no recursion.\n",
+    "\t-R\tProcess directories recursively.\n",
+    "\t-p\tSend updates to standard output (avoids stickiness).\n",
+    "\t-k kopt\tUse RCS kopt -k option on checkout. (is sticky)\n",
+    "\t-r rev\tUpdate using specified revision/tag (is sticky).\n",
+    "\t-D date\tSet date to update from (is sticky).\n",
+    "\t-j rev\tMerge in changes made between current revision and rev.\n",
+    "\t-I ign\tMore files to ignore (! to reset).\n",
+    "\t-W spec\tWrappers specification line.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+
+
+/*
+ * update is the argv,argc based front end for arg parsing
+ */
+int
+update (int argc, char **argv)
+{
+    int c, err;
+    int local = 0;                     /* recursive by default */
+    int which;                         /* where to look for files and dirs */
+    char *xjoin_rev1, *xjoin_date1,
+        *xjoin_rev2, *xjoin_date2,
+        *join_orig1, *join_orig2;
+
+    if (argc == -1)
+       usage (update_usage);
+
+    xjoin_rev1 = xjoin_date1 = xjoin_rev2 = xjoin_date2 = join_orig1 =
+                join_orig2 = NULL;
+
+    ign_setup ();
+    wrap_setup ();
+
+    /* parse the args */
+    optind = 0;
+    while ((c = getopt (argc, argv, "+ApCPflRQqduk:r:D:j:I:W:")) != -1)
+    {
+       switch (c)
+       {
+           case 'A':
+               aflag = 1;
+               break;
+           case 'C':
+               toss_local_changes = 1;
+               break;
+           case 'I':
+               ign_add (optarg, 0);
+               break;
+           case 'W':
+               wrap_add (optarg, 0);
+               break;
+           case 'k':
+               if (options)
+                   free (options);
+               options = RCS_check_kflag (optarg);
+               break;
+           case 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case 'Q':
+           case 'q':
+               /* The CVS 1.5 client sends these options (in addition to
+                  Global_option requests), so we must ignore them.  */
+               if (!server_active)
+                   error (1, 0,
+                          "-q or -Q must be specified before \"%s\"",
+                          cvs_cmd_name);
+               break;
+           case 'd':
+               update_build_dirs = 1;
+               break;
+           case 'f':
+               force_tag_match = 0;
+               break;
+           case 'r':
+               parse_tagdate (&tag, &date, optarg);
+               break;
+           case 'D':
+               if (date) free (date);
+               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_orig2)
+                   error (1, 0, "only two -j options can be specified");
+               if (join_orig1)
+               {
+                   join_orig2 = xstrdup (optarg);
+                   parse_tagdate (&xjoin_rev2, &xjoin_date2, optarg);
+               }
+               else
+               {
+                   join_orig1 = xstrdup (optarg);
+                   parse_tagdate (&xjoin_rev1, &xjoin_date1, optarg);
+               }
+               break;
+           case 'u':
+#ifdef SERVER_SUPPORT
+               if (server_active)
+               {
+                   patches = 1;
+                   rcs_diff_patches = server_use_rcs_diff ();
+               }
+               else
+#endif
+                   usage (update_usage);
+               break;
+           case '?':
+           default:
+               usage (update_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote) 
+    {
+       int pass;
+       unsigned int flags = 0;
+
+       /* The first pass does the regular update.  If we receive at least
+          one patch which failed, we do a second pass and just fetch
+          those files whose patches failed.  */
+       pass = 1;
+       do
+       {
+           int status;
+
+           start_server ();
+
+           if (local)
+               send_arg("-l");
+           if (update_build_dirs)
+               send_arg("-d");
+           if (pipeout)
+               send_arg("-p");
+           if (!force_tag_match)
+               send_arg("-f");
+           if (aflag)
+               send_arg("-A");
+           if (toss_local_changes)
+               send_arg("-C");
+           if (update_prune_dirs)
+               send_arg("-P");
+           client_prune_dirs = update_prune_dirs;
+           option_with_arg ("-r", tag);
+           if (options && options[0] != '\0')
+               send_arg (options);
+           if (date)
+               client_senddate (date);
+           if (join_orig1)
+               option_with_arg ("-j", join_orig1);
+           if (join_orig2)
+               option_with_arg ("-j", join_orig2);
+           wrap_send ();
+
+           if (failed_patches_count == 0)
+           {
+               /* If the server supports the command "update-patches", that 
+                  means that it knows how to handle the -u argument to update,
+                  which means to send patches instead of complete files.
+
+                  We don't send -u if failed_patches != NULL, so that the
+                  server doesn't try to send patches which will just fail
+                  again.  At least currently, the client also clobbers the
+                  file and tells the server it is lost, which also will get
+                  a full file instead of a patch, but it seems clean to omit
+                  -u.  */
+               if (supported_request ("update-patches"))
+                   send_arg ("-u");
+
+               send_arg ("--");
+
+                if (update_build_dirs)
+                    flags |= SEND_BUILD_DIRS;
+
+                if (toss_local_changes) {
+                    flags |= SEND_NO_CONTENTS;
+                    flags |= BACKUP_MODIFIED_FILES;
+                }
+
+               /* If noexec, probably could be setting SEND_NO_CONTENTS.
+                  Same caveats as for "cvs status" apply.  */
+
+               send_files (argc, argv, local, aflag, flags);
+               send_file_names (argc, argv, SEND_EXPAND_WILD);
+           }
+           else
+           {
+               fprintf (stderr, "%s client: refetching unpatchable files\n",
+                        program_name);
+
+               if (toplevel_wd != NULL
+                   && CVS_CHDIR (toplevel_wd) < 0)
+               {
+                   error (1, errno, "could not chdir to %s", toplevel_wd);
+               }
+
+               send_arg ("--");
+
+               /* Failed patches should only occur with files that were not
+                * locally modified.
+                */
+               flags |= SEND_NO_CONTENTS;
+
+               send_files (failed_patches_count, failed_patches, local,
+                           aflag, flags);
+               send_file_names (failed_patches_count, failed_patches, 0);
+               free_names (&failed_patches_count, failed_patches);
+           }
+
+           send_to_server ("update\012", 0);
+
+           status = get_responses_and_close ();
+
+           /* If there are any conflicts, the server will return a
+               non-zero exit status.  If any patches failed, we still
+               want to run the update again.  We use a pass count to
+               avoid an endless loop.  */
+
+           /* Notes: (1) assuming that status != 0 implies a
+              potential conflict is the best we can cleanly do given
+              the current protocol.  I suppose that trying to
+              re-fetch in cases where there was a more serious error
+              is probably more or less harmless, but it isn't really
+              ideal.  (2) it would be nice to have a testsuite case for the
+              conflict-and-patch-failed case.  */
+
+           if (status != 0
+               && (failed_patches_count == 0 || pass > 1))
+           {
+               if (failed_patches_count > 0)
+                   free_names (&failed_patches_count, failed_patches);
+               return status;
+           }
+
+           ++pass;
+       } while (failed_patches_count > 0);
+
+       return 0;
+    }
+#endif
+
+    if (tag != NULL)
+       tag_check_valid (tag, argc, argv, local, aflag, "", false);
+    if (xjoin_rev1 != NULL)
+       tag_check_valid (xjoin_rev1, argc, argv, local, aflag, "", false);
+    if (xjoin_rev2 != NULL)
+       tag_check_valid (xjoin_rev2, argc, argv, local, aflag, "", false);
+
+    /*
+     * 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)
+           {
+               char *repos = Name_Repository (NULL, NULL);
+               server_clear_entstat (".", repos);
+               free (repos);
+           }
+#endif
+       }
+
+       /* keep the CVS/Tag file current with the specified arguments */
+       if (aflag || tag || date)
+       {
+           char *repos = Name_Repository (NULL, NULL);
+           WriteTag (NULL, tag, date, 0, ".", repos);
+           free (repos);
+           rewrite_tag = 1;
+           nonbranch = -1;
+           warned = 0;
+       }
+    }
+
+    /* look for files/dirs locally and in the repository */
+    which = W_LOCAL | W_REPOS;
+
+    /* look in the attic too if a tag or date is specified */
+    if (tag || date || join_orig1)
+    {
+       TRACE (TRACE_DATA, "update: searching attic");
+       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, xjoin_rev1, xjoin_date1, xjoin_rev2,
+                    xjoin_date2, NULL, 1, NULL);
+
+    /* Free the space allocated for tags and dates, if necessary.  */
+    if (tag) free (tag);
+    if (date) free (date);
+
+    return err;
+}
+
+
+
+/*
+ * Command line interface to update (used by checkout)
+ *
+ * repository = cvsroot->repository + update_dir.  This is necessary for
+ * checkout so that start_recursion can determine our repository.  In the
+ * update case, start_recursion can use the CVS/Root & CVS/Repository file
+ * to determine this value.
+ */
+int
+do_update (int argc, char **argv, char *xoptions, char *xtag, char *xdate,
+           int xforce, int local, int xbuild, int xaflag, int xprune,
+           int xpipeout, int which, char *xjoin_rev1, char *xjoin_date1,
+          char *xjoin_rev2, char *xjoin_date2,
+           char *preload_update_dir, int xdotemplate, char *repository)
+{
+    int err = 0;
+
+    TRACE (TRACE_FUNCTION,
+"do_update (%s, %s, %s, %d, %d, %d, %d, %d, %d, %d, %s, %s, %s, %s, %s, %d, 
%s)",
+           xoptions ? xoptions : "(null)", xtag ? xtag : "(null)",
+          xdate ? xdate : "(null)", xforce, local, xbuild, xaflag, xprune,
+          xpipeout, which, xjoin_rev1 ? xjoin_rev1 : "(null)",
+          xjoin_date1 ? xjoin_date1 : "(null)",
+          xjoin_rev2 ? xjoin_rev2 : "(null)",
+          xjoin_date2 ? xjoin_date2 : "(null)",
+          preload_update_dir ? preload_update_dir : "(null)", xdotemplate,
+          repository ? repository : "(null)");
+
+    /* 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;
+    dotemplate = xdotemplate;
+
+    /* setup the join support */
+    join_rev1 = xjoin_rev1;
+    join_date1 = xjoin_date1;
+    join_rev2 = xjoin_rev2;
+    join_date2 = xjoin_date2;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    if (preserve_perms)
+    {
+       /* We need to do an extra recursion, bleah.  It's to make sure
+          that we know as much as possible about file linkage. */
+       hardlist = getlist();
+       working_dir = xgetcwd ();               /* save top-level working dir */
+
+       /* FIXME-twp: the arguments to start_recursion make me dizzy.  This
+          function call was copied from the update_fileproc call that
+          follows it; someone should make sure that I did it right. */
+       err = start_recursion
+           (get_linkinfo_proc, NULL, NULL, NULL, NULL,
+            argc, argv, local, which, aflag, CVS_LOCK_READ,
+            preload_update_dir, 1, NULL);
+       if (err)
+           return err;
+
+       /* FIXME-twp: at this point we should walk the hardlist
+          and update the `links' field of each hardlink_info struct
+          to list the files that are linked on dist.  That would make
+          it easier & more efficient to compare the disk linkage with
+          the repository linkage (a simple strcmp). */
+    }
+#endif
+
+    /* call the recursion processor */
+    err = start_recursion (update_fileproc, update_filesdone_proc,
+                          update_dirent_proc, update_dirleave_proc, NULL,
+                          argc, argv, local, which, aflag, CVS_LOCK_READ,
+                          preload_update_dir, 1, repository);
+
+    /* see if we need to sleep before returning to avoid time-stamp races */
+    if (!server_active && last_register_time)
+    {
+       sleep_past (last_register_time);
+    }
+
+    return err;
+}
+
+
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+/*
+ * The get_linkinfo_proc callback adds each file to the hardlist
+ * (see hardlink.c).
+ */
+
+static int
+get_linkinfo_proc (void *callerdat, struct file_info *finfo)
+{
+    char *fullpath;
+    Node *linkp;
+    struct hardlink_info *hlinfo;
+
+    /* Get the full pathname of the current file. */
+    fullpath = Xasprintf ("%s/%s", working_dir, finfo->fullname);
+
+    /* To permit recursing into subdirectories, files
+       are keyed on the full pathname and not on the basename. */
+    linkp = lookup_file_by_inode (fullpath);
+    if (linkp == NULL)
+    {
+       /* The file isn't on disk; we are probably restoring
+          a file that was removed. */
+       return 0;
+    }
+    
+    /* Create a new, empty hardlink_info node. */
+    hlinfo = xmalloc (sizeof (struct hardlink_info));
+
+    hlinfo->status = (Ctype) 0;        /* is this dumb? */
+    hlinfo->checked_out = 0;
+
+    linkp->data = hlinfo;
+
+    return 0;
+}
+#endif
+
+
+
+/*
+ * This is the callback proc for update.  It is called for each file in each
+ * directory by the recursion code.  The current directory is the local
+ * instantiation.  file is the file name we are to operate on. update_dir is
+ * set to the path relative to where we started (for pretty printing).
+ * repository is the repository. entries and srcfiles are the pre-parsed
+ * entries and source control files.
+ * 
+ * This routine decides what needs to be done for each file and does the
+ * appropriate magic for checkout
+ */
+static int
+update_fileproc (void *callerdat, struct file_info *finfo)
+{
+    int retval, nb;
+    Ctype status;
+    Vers_TS *vers;
+
+    status = Classify_File (finfo, tag, date, options, force_tag_match,
+                           aflag, &vers, pipeout);
+
+    /* Keep track of whether TAG is a branch tag.
+       Note that if it is a branch tag in some files and a nonbranch tag
+       in others, treat it as a nonbranch tag.  */
+    if (rewrite_tag
+       && tag != NULL
+       && finfo->rcs != NULL)
+    {
+       char *rev = RCS_getversion (finfo->rcs, tag, NULL, 1, NULL);
+       if (rev != NULL
+           && nonbranch != (nb = !RCS_nodeisbranch (finfo->rcs, tag)))
+       {
+           if (nonbranch >= 0 && !warned && !quiet)
+           {
+               error (0, 0,
+"warning: %s is a branch tag in some files and a revision tag in others.",
+                       tag);
+               warned = 1;
+           }
+           if (nonbranch < nb) nonbranch = nb;
+       }
+       if (rev != NULL)
+           free (rev);
+    }
+
+    if (pipeout)
+    {
+       /*
+        * We just return success without doing anything if any of the really
+        * funky cases occur
+        * 
+        * If there is still a valid RCS file, do a regular checkout type
+        * operation
+        */
+       switch (status)
+       {
+           case T_UNKNOWN:             /* unknown file was explicitly asked
+                                        * about */
+           case T_REMOVE_ENTRY:        /* needs to be un-registered */
+           case T_ADDED:               /* added but not committed */
+               retval = 0;
+               break;
+           case T_CONFLICT:            /* old punt-type errors */
+               retval = 1;
+               break;
+           case T_UPTODATE:            /* file was already up-to-date */
+           case T_NEEDS_MERGE:         /* needs merging */
+           case T_MODIFIED:            /* locally modified */
+           case T_REMOVED:             /* removed but not committed */
+           case T_CHECKOUT:            /* needs checkout */
+           case T_PATCH:               /* needs patch */
+               retval = checkout_file (finfo, vers, 0, 0, 0);
+               break;
+
+           default:                    /* can't ever happen :-) */
+               error (0, 0,
+                      "unknown file status %d for file %s", status, 
finfo->file);
+               retval = 0;
+               break;
+       }
+    }
+    else
+    {
+       switch (status)
+       {
+           case T_UNKNOWN:             /* unknown file was explicitly asked
+                                        * about */
+           case T_UPTODATE:            /* file was already up-to-date */
+               retval = 0;
+               break;
+           case T_CONFLICT:            /* old punt-type errors */
+               retval = 1;
+               write_letter (finfo, 'C');
+               break;
+           case T_NEEDS_MERGE:         /* needs merging */
+               if (! toss_local_changes)
+               {
+                   retval = merge_file (finfo, vers);
+                   break;
+               }
+               /* else FALL THROUGH */
+           case T_MODIFIED:            /* locally modified */
+               retval = 0;
+                if (toss_local_changes)
+                {
+                    char *bakname;
+                    bakname = backup_file (finfo->file, vers->vn_user);
+                    /* This behavior is sufficiently unexpected to
+                       justify overinformativeness, I think. */
+                    if (!really_quiet && !server_active)
+                        (void) printf ("(Locally modified %s moved to %s)\n",
+                                       finfo->file, bakname);
+                    free (bakname);
+
+                    /* The locally modified file is still present, but
+                       it will be overwritten by the repository copy
+                       after this. */
+                    status = T_CHECKOUT;
+                    retval = checkout_file (finfo, vers, 0, 0, 1);
+                }
+                else 
+                {
+                    if (vers->ts_conflict)
+                    {
+                       if (file_has_markers (finfo))
+                        {
+                            write_letter (finfo, 'C');
+                            retval = 1;
+                        }
+                        else
+                        {
+                            /* Reregister to clear conflict flag. */
+                            Register (finfo->entries, finfo->file, 
+                                      vers->vn_rcs, vers->ts_rcs,
+                                      vers->options, vers->tag,
+                                      vers->date, NULL);
+                        }
+                    }
+                    if (!retval)
+                        write_letter (finfo, 'M');
+                }
+               break;
+           case T_PATCH:               /* needs patch */
+#ifdef SERVER_SUPPORT
+               if (patches)
+               {
+                   int docheckout;
+                   struct stat file_info;
+                   unsigned char checksum[16];
+
+                   retval = patch_file (finfo,
+                                        vers, &docheckout,
+                                        &file_info, checksum);
+                   if (! docheckout)
+                   {
+                       if (server_active && retval == 0)
+                           server_updated (finfo, vers,
+                                           (rcs_diff_patches
+                                            ? SERVER_RCS_DIFF
+                                            : SERVER_PATCHED),
+                                           file_info.st_mode, checksum,
+                                           NULL);
+                       break;
+                   }
+               }
+#endif
+               /* If we're not running as a server, just check the
+                  file out.  It's simpler and faster than producing
+                  and applying patches.  */
+               /* Fall through.  */
+           case T_CHECKOUT:            /* needs checkout */
+               retval = checkout_file (finfo, vers, 0, 0, 1);
+               break;
+           case T_ADDED:               /* added but not committed */
+               write_letter (finfo, 'A');
+               retval = 0;
+               break;
+           case T_REMOVED:             /* removed but not committed */
+               write_letter (finfo, 'R');
+               retval = 0;
+               break;
+           case T_REMOVE_ENTRY:        /* needs to be un-registered */
+               retval = scratch_file (finfo, vers);
+               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)
+    {
+        char *j1;
+        char *j2;
+        bool invalid_tag = false;
+        if (RCS_is_relative (join_rev1))
+        {
+            j1 = Version_resolve_relTag (finfo, join_rev1, false);
+            if (!j1)
+                invalid_tag = true;
+        }
+        else
+            j1 = xstrdup (join_rev1);
+        if (join_rev2 && RCS_is_relative (join_rev2))
+        {
+            j2 = Version_resolve_relTag (finfo, join_rev2, false);
+            if (!j2)
+                invalid_tag = true;
+        }
+        else
+            j2 = xstrdup (join_rev2);
+        if (invalid_tag)
+        {
+            if (!really_quiet)
+                error (0, 0, "Cannot resolve relative tag: `%s'.", j1);
+        }
+        else
+            join_file (finfo, vers, j1, j2);
+        free (j1);
+        free (j2);
+    }
+
+    /* if this directory has an ignore list, add this file to it */
+    if (ignlist && (status != T_UNKNOWN || vers->ts_user == NULL))
+    {
+       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 (const char *file, const char *dir)
+{
+    struct file_info finfo;
+    char *tmp;
+
+    memset (&finfo, 0, sizeof (finfo));
+    finfo.file = file;
+    finfo.update_dir = dir;
+
+    finfo.fullname = tmp = Xasprintf ("%s%s%s",
+                                     dir[0] == '\0' ? "" : dir,
+                                     dir[0] == '\0' ? "" : "/",
+                                     file);
+    write_letter (&finfo, '?');
+    free (tmp);
+}
+
+
+
+/* ARGSUSED */
+static int
+update_filesdone_proc (void *callerdat, int err, const char *repository,
+                       const char *update_dir, List *entries)
+{
+    if (nonbranch < 0) nonbranch = 0;
+    if (rewrite_tag)
+    {
+       WriteTag (NULL, tag, date, nonbranch, update_dir, repository);
+       rewrite_tag = 0;
+    }
+
+    /* if this directory has an ignore list, process it then free it */
+    if (ignlist)
+    {
+       ignore_files (ignlist, entries, update_dir, update_ignproc);
+       dellist (&ignlist);
+    }
+
+    /* Clean up CVS admin dirs if we are export */
+    if (strcmp (cvs_cmd_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);
+    }
+    else if (!server_active && !pipeout)
+    {
+        /* If there is no CVS/Root file, add one */
+        if (!isfile (CVSADM_ROOT))
+           Create_Root (NULL, original_parsed_root->original);
+    }
+
+    return err;
+}
+
+
+
+/*
+ * update_dirent_proc () is called back by the recursion processor before a
+ * sub-directory is processed for update.  In this case, update_dirent proc
+ * will probably create the directory unless -d isn't specified and this is a
+ * new directory.  A return code of 0 indicates the directory should be
+ * processed by the recursion code.  A return of non-zero indicates the
+ * recursion code should skip this directory.
+ */
+static Dtype
+update_dirent_proc (void *callerdat, const char *dir, const char *repository,
+                    const char *update_dir, List *entries)
+{
+    if (ignore_directory (update_dir))
+    {
+       /* print the warm fuzzy message */
+       if (!quiet)
+         error (0, 0, "Ignoring %s", update_dir);
+        return R_SKIP_ALL;
+    }
+
+    if (!isdir (dir))
+    {
+       /* if we aren't building dirs, blow it off */
+       if (!update_build_dirs)
+           return R_SKIP_ALL;
+
+       /* Various CVS administrators are in the habit of removing
+          the repository directory for things they don't want any
+          more.  I've even been known to do it myself (on rare
+          occasions).  Not the usual recommended practice, but we
+          want to try to come up with some kind of
+          reasonable/documented/sensible behavior.  Generally
+          the behavior is to just skip over that directory (see
+          dirs test in sanity.sh; the case which reaches here
+          is when update -d is specified, and the working directory
+          is gone but the subdirectory is still mentioned in
+          CVS/Entries).  */
+       /* In the remote case, the client should refrain from
+          sending us the directory in the first place.  So we
+          want to continue to give an error, so clients make
+          sure to do this.  */
+       if (!server_active && !isdir (repository))
+           return R_SKIP_ALL;
+
+       if (noexec)
+       {
+           /* ignore the missing dir if -n is specified */
+           error (0, 0, "New directory `%s' -- ignored", update_dir);
+           return R_SKIP_ALL;
+       }
+       else
+       {
+           /* otherwise, create the dir and appropriate adm files */
+
+           /* If no tag or date were specified on the command line,
+               and we're not using -A, we want the subdirectory to use
+               the tag and date, if any, of the current directory.
+               That way, update -d will work correctly when working on
+               a branch.
+
+              We use TAG_UPDATE_DIR to undo the tag setting in
+              update_dirleave_proc.  If we did not do this, we would
+              not correctly handle a working directory with multiple
+              tags (and maybe we should prohibit such working
+              directories, but they work now and we shouldn't make
+              them stop working without more thought).  */
+           if ((tag == NULL && date == NULL) && ! aflag)
+           {
+               ParseTag (&tag, &date, &nonbranch);
+               if (tag != NULL || date != NULL)
+                   tag_update_dir = xstrdup (update_dir);
+           }
+
+           make_directory (dir);
+           Create_Admin (dir, update_dir, repository, tag, date,
+                         /* This is a guess.  We will rewrite it later
+                            via WriteTag.  */
+                         0,
+                         0,
+                         dotemplate);
+           rewrite_tag = 1;
+           nonbranch = -1;
+           warned = 0;
+           Subdir_Register (entries, NULL, dir);
+       }
+    }
+    /* Do we need to check noexec here? */
+    else if (!pipeout)
+    {
+       char *cvsadmdir;
+
+       /* The directory exists.  Check to see if it has a CVS
+          subdirectory.  */
+
+       cvsadmdir = Xasprintf ("%s/%s", dir, 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 = Xasprintf ("%s/%s", dir, CVSADM_ENTSTAT);
+
+           if (unlink_file (tmp) < 0 && ! existence_error (errno))
+               error (1, errno, "cannot remove file %s", tmp);
+#ifdef SERVER_SUPPORT
+           if (server_active)
+               server_clear_entstat (update_dir, repository);
+#endif
+           free (tmp);
+       }
+
+       /* keep the CVS/Tag file current with the specified arguments */
+       if (aflag || tag || date)
+       {
+           WriteTag (dir, tag, date, 0, update_dir, repository);
+           rewrite_tag = 1;
+           nonbranch = -1;
+           warned = 0;
+       }
+
+       WriteTemplate (update_dir, dotemplate, repository);
+
+       /* 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 (void *callerdat, const char *dir, int err,
+                      const char *update_dir, List *entries)
+{
+    /* Delete the ignore list if it hasn't already been done.  */
+    if (ignlist)
+       dellist (&ignlist);
+
+    /* If we set the tag or date for a new subdirectory in
+       update_dirent_proc, and we're now done with that subdirectory,
+       undo the tag/date setting.  Note that we know that the tag and
+       date were both originally NULL in this case.  */
+    if (tag_update_dir != NULL && strcmp (update_dir, tag_update_dir) == 0)
+    {
+       if (tag != NULL)
+       {
+           free (tag);
+           tag = NULL;
+       }
+       if (date != NULL)
+       {
+           free (date);
+           date = NULL;
+       }
+       nonbranch = -1;
+       warned = 0;
+       free (tag_update_dir);
+       tag_update_dir = NULL;
+    }
+
+    if (strchr (dir, '/') == NULL)
+    {
+       /* FIXME: chdir ("..") loses with symlinks.  */
+       /* Prune empty dirs on the way out - if necessary */
+       (void) CVS_CHDIR ("..");
+       if (update_prune_dirs && isemptydir (dir, 0))
+       {
+           /* I'm not sure the existence_error is actually possible (except
+              in cases where we really should print a message), but since
+              this code used to ignore all errors, I'll play it safe.  */
+           if (unlink_file_dir (dir) < 0 && !existence_error (errno))
+               error (0, errno, "cannot remove %s directory", dir);
+           Subdir_Deregister (entries, NULL, dir);
+       }
+    }
+
+    return err;
+}
+
+
+
+/* Returns 1 if the file indicated by node has been removed.  */
+static int
+isremoved (Node *node, void *closure)
+{
+    Entnode *entdata = node->data;
+
+    /* If the first character of the version is a '-', the file has been
+       removed. */
+    return (entdata->version && entdata->version[0] == '-') ? 1 : 0;
+}
+
+
+
+/* Returns 1 if the argument directory is completely empty, other than the
+   existence of the CVS directory entry.  Zero otherwise.  If MIGHT_NOT_EXIST
+   and the directory doesn't exist, then just return 0.  */
+int
+isemptydir (const char *dir, int might_not_exist)
+{
+    DIR *dirp;
+    struct dirent *dp;
+
+    if ((dirp = CVS_OPENDIR (dir)) == NULL)
+    {
+       if (might_not_exist && existence_error (errno))
+           return 0;
+       error (0, errno, "cannot open directory %s for empty check", dir);
+       return 0;
+    }
+    errno = 0;
+    while ((dp = CVS_READDIR (dirp)) != NULL)
+    {
+       if (strcmp (dp->d_name, ".") != 0
+           && strcmp (dp->d_name, "..") != 0)
+       {
+           if (strcmp (dp->d_name, CVSADM) != 0)
+           {
+               /* An entry other than the CVS directory.  The directory
+                  is certainly not empty. */
+               (void) CVS_CLOSEDIR (dirp);
+               return 0;
+           }
+           else
+           {
+               /* The CVS directory entry.  We don't have to worry about
+                  this unless the Entries file indicates that files have
+                  been removed, but not committed, in this directory.
+                  (Removing the directory would prevent people from
+                  comitting the fact that they removed the files!) */
+               List *l;
+               int files_removed;
+               struct saved_cwd cwd;
+
+               if (save_cwd (&cwd))
+                   error (1, errno, "Failed to save current directory.");
+
+               if (CVS_CHDIR (dir) < 0)
+                   error (1, errno, "cannot change directory to %s", dir);
+               l = Entries_Open (0, NULL);
+               files_removed = walklist (l, isremoved, 0);
+               Entries_Close (l);
+
+               if (restore_cwd (&cwd))
+                   error (1, errno,
+                          "Failed to restore current directory, `%s'.",
+                          cwd.name);
+               free_cwd (&cwd);
+
+               if (files_removed != 0)
+               {
+                   /* There are files that have been removed, but not
+                      committed!  Do not consider the directory empty. */
+                   (void) CVS_CLOSEDIR (dirp);
+                   return 0;
+               }
+           }
+       }
+       errno = 0;
+    }
+    if (errno != 0)
+    {
+       error (0, errno, "cannot read directory %s", dir);
+       (void) CVS_CLOSEDIR (dirp);
+       return 0;
+    }
+    (void) CVS_CLOSEDIR (dirp);
+    return 1;
+}
+
+
+
+/*
+ * scratch the Entries file entry associated with a file
+ */
+static int
+scratch_file (struct file_info *finfo, Vers_TS *vers)
+{
+    history_write ('W', finfo->update_dir, "", finfo->file, finfo->repository);
+    Scratch_Entry (finfo->entries, finfo->file);
+#ifdef SERVER_SUPPORT
+    if (server_active)
+    {
+       if (vers->ts_user == NULL)
+           server_scratch_entry_only ();
+       server_updated (finfo, vers, SERVER_UPDATED, (mode_t) -1, NULL, NULL);
+    }
+#endif
+    if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
+       error (0, errno, "unable to remove %s", finfo->fullname);
+    else if (!server_active)
+    {
+       /* skip this step when the server is running since
+        * server_updated should have handled it */
+       /* keep the vers structure up to date in case we do a join
+        * - if there isn't a file, it can't very well have a version number, 
can it?
+        */
+       if (vers->vn_user != NULL)
+       {
+           free (vers->vn_user);
+           vers->vn_user = NULL;
+       }
+       if (vers->ts_user != NULL)
+       {
+           free (vers->ts_user);
+           vers->ts_user = NULL;
+       }
+    }
+    return 0;
+}
+
+
+
+/*
+ * Check out a file.
+ */
+static int
+checkout_file (struct file_info *finfo, Vers_TS *vers_ts, int adding,
+               int merging, int update_server)
+{
+    char *backup;
+    int set_time, retval = 0;
+    int status;
+    int file_is_dead;
+    struct buffer *revbuf;
+
+    backup = NULL;
+    revbuf = NULL;
+
+    /* Don't screw with backup files if we're going to stdout, or if
+       we are the server.  */
+    if (!pipeout && !server_active)
+    {
+       backup = Xasprintf ("%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
+       if (isfile (finfo->file))
+           rename_file (finfo->file, backup);
+       else /* FIXME: -f/-t has been disabled for so long it should probably
+             * just be stripped out to reduce clutter.
+             */
+       {
+           /* If -f/-t wrappers are being used to wrap up a directory,
+              then backup might be a directory instead of just a file.  */
+           if (unlink_file_dir (backup) < 0)
+           {
+               /* Not sure if the existence_error check is needed here.  */
+               if (!existence_error (errno))
+                   /* FIXME: should include update_dir in message.  */
+                   error (0, errno, "error removing %s", backup);
+           }
+           free (backup);
+           backup = NULL;
+       }
+    }
+
+    file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs);
+
+    if (!file_is_dead)
+    {
+       /*
+        * if we are checking out to stdout, print a nice message to
+        * stderr, and add the -p flag to the command */
+       if (pipeout)
+       {
+           if (!quiet)
+           {
+               cvs_outerr ("\
+===================================================================\n\
+Checking out ", 0);
+               cvs_outerr (finfo->fullname, 0);
+               cvs_outerr ("\n\
+RCS:  ", 0);
+               cvs_outerr (vers_ts->srcfile->print_path, 0);
+               cvs_outerr ("\n\
+VERS: ", 0);
+               cvs_outerr (vers_ts->vn_rcs, 0);
+               cvs_outerr ("\n***************\n", 0);
+           }
+       }
+
+#ifdef SERVER_SUPPORT
+       if (update_server
+           && server_active
+           && ! pipeout
+           && ! file_gzip_level
+           && ! joining ()
+           && ! wrap_name_has (finfo->file, WRAP_FROMCVS))
+       {
+           revbuf = buf_nonio_initialize (NULL);
+           status = RCS_checkout (vers_ts->srcfile, NULL,
+                                  vers_ts->vn_rcs, vers_ts->tag,
+                                  vers_ts->options, RUN_TTY,
+                                  checkout_to_buffer, revbuf);
+       }
+       else
+#endif
+           status = RCS_checkout (vers_ts->srcfile,
+                                  pipeout ? NULL : finfo->file,
+                                  vers_ts->vn_rcs, vers_ts->tag,
+                                  vers_ts->options, RUN_TTY, NULL, NULL);
+    }
+    if (file_is_dead || status == 0)
+    {
+       mode_t mode;
+
+       mode = (mode_t) -1;
+
+       if (!pipeout)
+       {
+           Vers_TS *xvers_ts;
+
+           if (revbuf != NULL && !noexec)
+           {
+               struct stat sb;
+
+               /* FIXME: We should have RCS_checkout return the mode.
+                  That would also fix the kludge with noexec, above, which
+                  is here only because noexec doesn't write srcfile->path
+                  for us to stat.  */
+               if (stat (vers_ts->srcfile->path, &sb) < 0)
+               {
+#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
+                   buf_free (revbuf);
+#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */
+                   error (1, errno, "cannot stat %s",
+                          vers_ts->srcfile->path);
+               }
+               mode = sb.st_mode &~ (S_IWRITE | S_IWGRP | S_IWOTH);
+           }
+
+           if (cvswrite
+               && !file_is_dead
+               && !fileattr_get (finfo->file, "_watched"))
+           {
+               if (revbuf == NULL)
+                   xchmod (finfo->file, 1);
+               else
+               {
+                   /* We know that we are the server here, so
+                       although xchmod checks umask, we don't bother.  */
+                   mode |= (((mode & S_IRUSR) ? S_IWUSR : 0)
+                            | ((mode & S_IRGRP) ? S_IWGRP : 0)
+                            | ((mode & S_IROTH) ? S_IWOTH : 0));
+               }
+           }
+
+           {
+               /* A newly checked out file is never under the spell
+                  of "cvs edit".  If we think we were editing it
+                  from a previous life, clean up.  Would be better to
+                  check for same the working directory instead of
+                  same user, but that is hairy.  */
+
+               struct addremove_args args;
+
+               editor_set (finfo->file, getcaller (), NULL);
+
+               memset (&args, 0, sizeof args);
+               args.remove_temp = 1;
+               watch_modify_watchers (finfo->file, &args);
+           }
+
+           /* set the time from the RCS file iff it was unknown before */
+           set_time =
+               (!noexec
+                && (vers_ts->vn_user == NULL ||
+                    strncmp (vers_ts->ts_rcs, "Initial", 7) == 0)
+                && !file_is_dead);
+
+           wrap_fromcvs_process_file (finfo->file);
+
+           xvers_ts = Version_TS (finfo, options, tag, date, 
+                                  force_tag_match, set_time);
+           if (strcmp (xvers_ts->options, "-V4") == 0)
+               xvers_ts->options[0] = '\0';
+
+           if (revbuf != NULL)
+           {
+               /* If we stored the file data into a buffer, then we
+                   didn't create a file at all, so xvers_ts->ts_user
+                   is wrong.  The correct value is to have it be the
+                   same as xvers_ts->ts_rcs, meaning that the working
+                   file is unchanged from the RCS file.
+
+                  FIXME: We should tell Version_TS not to waste time
+                  statting the nonexistent file.
+
+                  FIXME: Actually, I don't think the ts_user value
+                  matters at all here.  The only use I know of is
+                  that it is printed in a trace message by
+                  Server_Register.  */
+
+               if (xvers_ts->ts_user != NULL)
+                   free (xvers_ts->ts_user);
+               xvers_ts->ts_user = xstrdup (xvers_ts->ts_rcs);
+           }
+
+           (void) time (&last_register_time);
+
+           if (file_is_dead)
+           {
+               if (xvers_ts->vn_user != NULL)
+               {
+                   error (0, 0,
+                          "warning: %s is not (any longer) pertinent",
+                          finfo->fullname);
+               }
+               Scratch_Entry (finfo->entries, finfo->file);
+#ifdef SERVER_SUPPORT
+               if (server_active && xvers_ts->ts_user == NULL)
+                   server_scratch_entry_only ();
+#endif
+               /* FIXME: Rather than always unlink'ing, and ignoring the
+                  existence_error, we should do the unlink only if
+                  vers_ts->ts_user is non-NULL.  Then there would be no
+                  need to ignore an existence_error (for example, if the
+                  user removes the file while we are running).  */
+               if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
+               {
+                   error (0, errno, "cannot remove %s", finfo->fullname);
+               }
+           }
+           else
+               Register (finfo->entries, finfo->file,
+                         adding ? "0" : xvers_ts->vn_rcs,
+                         xvers_ts->ts_user, xvers_ts->options,
+                         xvers_ts->tag, xvers_ts->date,
+                         NULL); /* Clear conflict flag on fresh checkout */
+
+           /* fix up the vers structure, in case it is used by join */
+           if (join_rev1)
+           {
+               /* FIXME: Throwing away the original revision info is almost
+                  certainly wrong -- what if join_rev1 is "BASE"?  */
+               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 (cvs_cmd_name, "update") == 0)
+               history_write ('U', finfo->update_dir, xvers_ts->vn_rcs, 
finfo->file,
+                              finfo->repository);
+
+           freevers_ts (&xvers_ts);
+
+           if (!really_quiet && !file_is_dead)
+           {
+               write_letter (finfo, 'U');
+           }
+       }
+
+#ifdef SERVER_SUPPORT
+       if (update_server && server_active)
+           server_updated (finfo, vers_ts,
+                           merging ? SERVER_MERGED : SERVER_UPDATED,
+                           mode, NULL, revbuf);
+#endif
+    }
+    else
+    {
+       if (backup != NULL)
+       {
+           rename_file (backup, finfo->file);
+           free (backup);
+           backup = NULL;
+       }
+
+       error (0, 0, "could not check out %s", finfo->fullname);
+
+       retval = status;
+    }
+
+    if (backup != NULL)
+    {
+       /* If -f/-t wrappers are being used to wrap up a directory,
+          then backup might be a directory instead of just a file.  */
+       if (unlink_file_dir (backup) < 0)
+       {
+           /* Not sure if the existence_error check is needed here.  */
+           if (!existence_error (errno))
+               /* FIXME: should include update_dir in message.  */
+               error (0, errno, "error removing %s", backup);
+       }
+       free (backup);
+    }
+
+#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
+    if (revbuf != NULL)
+       buf_free (revbuf);
+#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */
+    return retval;
+}
+
+
+
+#ifdef SERVER_SUPPORT
+
+/* This function is used to write data from a file being checked out
+   into a buffer.  */
+
+static void
+checkout_to_buffer (void *callerdat, const char *data, size_t len)
+{
+    struct buffer *buf = (struct buffer *) callerdat;
+
+    buf_output (buf, data, len);
+}
+
+#endif /* SERVER_SUPPORT */
+
+#ifdef SERVER_SUPPORT
+
+/* This structure is used to pass information between patch_file and
+   patch_file_write.  */
+
+struct patch_file_data
+{
+    /* File name, for error messages.  */
+    const char *filename;
+    /* File to which to write.  */
+    FILE *fp;
+    /* Whether to compute the MD5 checksum.  */
+    int compute_checksum;
+    /* Data structure for computing the MD5 checksum.  */
+    struct md5_ctx context;
+    /* Set if the file has a final newline.  */
+    int final_nl;
+};
+
+/* Patch a file.  Runs diff.  This is only done when running as the
+ * server.  The hope is that the diff will be smaller than the file
+ * itself.
+ */
+static int
+patch_file (struct file_info *finfo, Vers_TS *vers_ts, int *docheckout,
+           struct stat *file_info, unsigned char *checksum)
+{
+    char *backup;
+    char *file1;
+    char *file2;
+    int retval = 0;
+    int retcode = 0;
+    int fail;
+    FILE *e;
+    struct patch_file_data data;
+
+    *docheckout = 0;
+
+    if (noexec || pipeout || joining ())
+    {
+       *docheckout = 1;
+       return 0;
+    }
+
+    /* If this file has been marked as being binary, then never send a
+       patch.  */
+    if (strcmp (vers_ts->options, "-kb") == 0)
+    {
+       *docheckout = 1;
+       return 0;
+    }
+
+    /* First check that the first revision exists.  If it has been nuked
+       by cvs admin -o, then just fall back to checking out entire
+       revisions.  In some sense maybe we don't have to do this; after
+       all cvs.texinfo says "Make sure that no-one has checked out a
+       copy of the revision you outdate" but then again, that advice
+       doesn't really make complete sense, because "cvs admin" operates
+       on a working directory and so _someone_ will almost always have
+       _some_ revision checked out.  */
+    {
+       char *rev;
+
+       rev = RCS_gettag (finfo->rcs, vers_ts->vn_user, 1, NULL);
+       if (rev == NULL)
+       {
+           *docheckout = 1;
+           return 0;
+       }
+       else
+           free (rev);
+    }
+
+    /* If the revision is dead, let checkout_file handle it rather
+       than duplicating the processing here.  */
+    if (RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs))
+    {
+       *docheckout = 1;
+       return 0;
+    }
+
+    backup = Xasprintf ("%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
+    if (isfile (finfo->file))
+        rename_file (finfo->file, backup);
+    else
+    {
+       if (unlink_file (backup) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", backup);
+    }
+
+    file1 = Xasprintf ("%s/%s%s-1", CVSADM, CVSPREFIX, finfo->file);
+    file2 = Xasprintf ("%s/%s%s-2", CVSADM, CVSPREFIX, finfo->file);
+
+    fail = 0;
+
+    /* We need to check out both revisions first, to see if either one
+       has a trailing newline.  Because of this, we don't use rcsdiff,
+       but just use diff.  */
+
+    e = CVS_FOPEN (file1, "w");
+    if (e == NULL)
+       error (1, errno, "cannot open %s", file1);
+
+    data.filename = file1;
+    data.fp = e;
+    data.final_nl = 0;
+    data.compute_checksum = 0;
+
+    /* FIXME - Passing vers_ts->tag here is wrong in the least number
+     * of cases.  Since we don't know whether vn_user was checked out
+     * using a tag, we pass vers_ts->tag, which, assuming the user did
+     * not specify a new TAG to -r, will be the branch we are on.
+     *
+     * The only thing it is used for is to substitute in for the Name
+     * RCS keyword, so in the error case, the patch fails to apply on
+     * the client end and we end up resending the whole file.
+     *
+     * At least, if we are keeping track of the tag vn_user came from,
+     * I don't know where yet. -DRP
+     */
+    retcode = RCS_checkout (vers_ts->srcfile, NULL,
+                           vers_ts->vn_user, vers_ts->tag,
+                           vers_ts->options, RUN_TTY,
+                           patch_file_write, (void *) &data);
+
+    if (fclose (e) < 0)
+       error (1, errno, "cannot close %s", file1);
+
+    if (retcode != 0 || ! data.final_nl)
+       fail = 1;
+
+    if (! fail)
+    {
+       e = CVS_FOPEN (file2, "w");
+       if (e == NULL)
+           error (1, errno, "cannot open %s", file2);
+
+       data.filename = file2;
+       data.fp = e;
+       data.final_nl = 0;
+       data.compute_checksum = 1;
+       md5_init_ctx (&data.context);
+
+       retcode = RCS_checkout (vers_ts->srcfile, NULL,
+                               vers_ts->vn_rcs, vers_ts->tag,
+                               vers_ts->options, RUN_TTY,
+                               patch_file_write, (void *) &data);
+
+       if (fclose (e) < 0)
+           error (1, errno, "cannot close %s", file2);
+
+       if (retcode != 0 || ! data.final_nl)
+           fail = 1;
+       else
+           md5_finish_ctx (&data.context, checksum);
+    }    
+
+    retcode = 0;
+    if (! fail)
+    {
+       int dargc = 0;
+       size_t darg_allocated = 0;
+       char **dargv = NULL;
+
+       /* If the client does not support the Rcs-diff command, we
+           send a context diff, and the client must invoke patch.
+           That approach was problematical for various reasons.  The
+           new approach only requires running diff in the server; the
+           client can handle everything without invoking an external
+           program.  */
+       if (!rcs_diff_patches)
+           /* We use -c, not -u, because that is what CVS has
+              traditionally used.  Kind of a moot point, now that
+              Rcs-diff is preferred, so there is no point in making
+              the compatibility issues worse.  */
+           run_add_arg_p (&dargc, &darg_allocated, &dargv, "-c");
+       else
+           /* Now that diff is librarified, we could be passing -a if
+              we wanted to.  However, it is unclear to me whether we
+              would want to.  Does diff -a, in any significant
+              percentage of cases, produce patches which are smaller
+              than the files it is patching?  I guess maybe text
+              files with character sets which diff regards as
+              'binary'.  Conversely, do they tend to be much larger
+              in the bad cases?  This needs some more
+              thought/investigation, I suspect.  */
+           run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n");
+       retcode = diff_exec (file1, file2, NULL, NULL, dargc, dargv,
+                            finfo->file);
+       run_arg_free_p (dargc, dargv);
+       free (dargv);
+
+       /* A retcode of 0 means no differences.  1 means some differences.  */
+       if (retcode != 0 && retcode != 1)
+           fail = 1;
+    }
+
+    if (!fail)
+    {
+       struct stat file2_info;
+
+       /* Check to make sure the patch is really shorter */
+       if (stat (file2, &file2_info) < 0)
+           error (1, errno, "could not stat %s", file2);
+       if (stat (finfo->file, file_info) < 0)
+           error (1, errno, "could not stat %s", finfo->file);
+       if (file2_info.st_size <= file_info->st_size)
+           fail = 1;
+    }
+
+    if (! fail)
+    {
+# define BINARY "Binary"
+       char buf[sizeof BINARY];
+       unsigned int c;
+
+       /* Check the diff output to make sure patch will be handle it.  */
+       e = CVS_FOPEN (finfo->file, "r");
+       if (e == NULL)
+           error (1, errno, "could not open diff output file %s",
+                  finfo->fullname);
+       c = fread (buf, 1, sizeof BINARY - 1, e);
+       buf[c] = '\0';
+       if (strcmp (buf, BINARY) == 0)
+       {
+           /* These are binary files.  We could use diff -a, but
+              patch can't handle that.  */
+           fail = 1;
+       }
+       fclose (e);
+    }
+
+    if (! fail)
+    {
+        Vers_TS *xvers_ts;
+
+       /* Stat the original RCS file, and then adjust it the way
+          that RCS_checkout would.  FIXME: This is an abstraction
+          violation.  */
+       if (stat (vers_ts->srcfile->path, file_info) < 0)
+           error (1, errno, "could not stat %s", vers_ts->srcfile->path);
+       if (chmod (finfo->file,
+                  file_info->st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH))
+           < 0)
+           error (0, errno, "cannot change mode of file %s", finfo->file);
+       if (cvswrite
+           && !fileattr_get (finfo->file, "_watched"))
+           xchmod (finfo->file, 1);
+
+        /* This stuff is just copied blindly from checkout_file.  I
+          don't really know what it does.  */
+        xvers_ts = Version_TS (finfo, options, tag, date,
+                              force_tag_match, 0);
+       if (strcmp (xvers_ts->options, "-V4") == 0)
+           xvers_ts->options[0] = '\0';
+
+       Register (finfo->entries, finfo->file, xvers_ts->vn_rcs,
+                 xvers_ts->ts_user, xvers_ts->options,
+                 xvers_ts->tag, xvers_ts->date, NULL);
+
+       if (stat (finfo->file, file_info) < 0)
+           error (1, errno, "could not stat %s", finfo->file);
+
+       /* If this is really Update and not Checkout, record history.  */
+       if (strcmp (cvs_cmd_name, "update") == 0)
+           history_write ('P', finfo->update_dir, xvers_ts->vn_rcs,
+                          finfo->file, finfo->repository);
+
+       freevers_ts (&xvers_ts);
+
+       if (!really_quiet)
+       {
+           if (trace)
+               write_letter (finfo, 'P');
+           else
+               write_letter (finfo, 'U');
+       }
+    }
+    else
+    {
+       int old_errno = errno;          /* save errno value over the rename */
+
+       if (isfile (backup))
+           rename_file (backup, finfo->file);
+
+       if (retcode != 0 && retcode != 1)
+           error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
+                  "could not diff %s", finfo->fullname);
+
+       *docheckout = 1;
+       retval = retcode;
+    }
+
+    if (unlink_file (backup) < 0
+       && !existence_error (errno))
+       error (0, errno, "cannot remove %s", backup);
+    if (unlink_file (file1) < 0
+       && !existence_error (errno))
+       error (0, errno, "cannot remove %s", file1);
+    if (unlink_file (file2) < 0
+       && !existence_error (errno))
+       error (0, errno, "cannot remove %s", file2);
+
+    free (backup);
+    free (file1);
+    free (file2);
+    return retval;
+}
+
+
+
+/* Write data to a file.  Record whether the last byte written was a
+   newline.  Optionally compute a checksum.  This is called by
+   patch_file via RCS_checkout.  */
+
+static void
+patch_file_write (void *callerdat, const char *buffer, size_t len)
+{
+    struct patch_file_data *data = (struct patch_file_data *) callerdat;
+
+    if (fwrite (buffer, 1, len, data->fp) != len)
+       error (1, errno, "cannot write %s", data->filename);
+
+    data->final_nl = (buffer[len - 1] == '\n');
+
+    if (data->compute_checksum)
+       md5_process_bytes (buffer, len, &data->context);
+}
+
+#endif /* SERVER_SUPPORT */
+
+/*
+ * Several of the types we process only print a bit of information consisting
+ * of a single letter and the name.
+ */
+void
+write_letter (struct file_info *finfo, int letter)
+{
+    if (!really_quiet)
+    {
+       char *tag = NULL;
+       /* Big enough for "+updated" or any of its ilk.  */
+       char buf[80];
+
+       switch (letter)
+       {
+           case 'U':
+               tag = "updated";
+               break;
+           default:
+               /* We don't yet support tagged output except for "U".  */
+               break;
+       }
+
+       if (tag != NULL)
+       {
+           sprintf (buf, "+%s", tag);
+           cvs_output_tagged (buf, NULL);
+       }
+       buf[0] = letter;
+       buf[1] = ' ';
+       buf[2] = '\0';
+       cvs_output_tagged ("text", buf);
+       cvs_output_tagged ("fname", finfo->fullname);
+       cvs_output_tagged ("newline", NULL);
+       if (tag != NULL)
+       {
+           sprintf (buf, "-%s", tag);
+           cvs_output_tagged (buf, NULL);
+       }
+    }
+    return;
+}
+
+
+
+/* Reregister a file after a merge.  */
+static void
+RegisterMerge (struct file_info *finfo, Vers_TS *vers,
+              const char *backup, int has_conflicts)
+{
+    /* This file is the result of a merge, which means that it has
+       been modified.  We use a special timestamp string which will
+       not compare equal to any actual timestamp.  */
+    char *cp = NULL;
+
+    if (has_conflicts)
+    {
+       time (&last_register_time);
+       cp = time_stamp (finfo->file);
+    }
+    Register (finfo->entries, finfo->file, vers->vn_rcs ? vers->vn_rcs : "0",
+             "Result of merge", vers->options, vers->tag, vers->date, cp);
+    if (cp)
+       free (cp);
+
+#ifdef SERVER_SUPPORT
+    /* Send the new contents of the file before the message.  If we
+       wanted to be totally correct, we would have the client write
+       the message only after the file has safely been written.  */
+    if (server_active)
+    {
+        server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
+                         backup);
+       server_updated (finfo, vers, SERVER_MERGED, (mode_t) -1, NULL, NULL);
+    }
+#endif
+}
+
+
+
+/*
+ * Do all the magic associated with a file which needs to be merged
+ */
+static int
+merge_file (struct file_info *finfo, Vers_TS *vers)
+{
+    char *backup;
+    int status;
+    int retval;
+
+    assert (vers->vn_user);
+
+    /*
+     * The users currently modified file is moved to a backup file name
+     * ".#filename.version", so that it will stay around for a few days
+     * before being automatically removed by some cron daemon.  The "version"
+     * is the version of the file that the user was most up-to-date with
+     * before the merge.
+     */
+    backup = Xasprintf ("%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
+
+    if (unlink_file (backup) && !existence_error (errno))
+       error (0, errno, "unable to remove %s", backup);
+    copy_file (finfo->file, backup);
+    xchmod (finfo->file, 1);
+
+    if (strcmp (vers->options, "-kb") == 0
+       || wrap_merge_is_copy (finfo->file)
+       || special_file_mismatch (finfo, NULL, vers->vn_rcs))
+    {
+       /* For binary files, a merge is always a conflict.  Same for
+          files whose permissions or linkage do not match.  We give the
+          user the two files, and let them resolve it.  It is possible
+          that we should require a "touch foo" or similar step before
+          we allow a checkin.  */
+
+       /* TODO: it may not always be necessary to regard a permission
+          mismatch as a conflict.  The working file and the RCS file
+          have a common ancestor `A'; if the working file's permissions
+          match A's, then it's probably safe to overwrite them with the
+          RCS permissions.  Only if the working file, the RCS file, and
+          A all disagree should this be considered a conflict.  But more
+          thought needs to go into this, and in the meantime it is safe
+          to treat any such mismatch as an automatic conflict. -twp */
+
+       status = RCS_checkout (finfo->rcs, finfo->file, vers->vn_rcs,
+                              vers->tag, vers->options, NULL, NULL, NULL);
+       if (status)
+       {
+           error (0, 0, "failed to check out `%s' file", finfo->fullname);
+           error (0, 0, "restoring `%s' from backup file `%s'",
+                  finfo->fullname, backup);
+           rename_file (backup, finfo->file);
+           retval = 1;
+           goto out;
+       }
+
+       xchmod (finfo->file, 1);
+
+       RegisterMerge (finfo, vers, backup, 1);
+
+       /* Is there a better term than "nonmergeable file"?  What we
+          really mean is, not something that CVS cannot or does not
+          want to merge (there might be an external manual or
+          automatic merge process).  */
+       error (0, 0, "nonmergeable file needs merge");
+       error (0, 0, "revision %s from repository is now in %s",
+              vers->vn_rcs, finfo->fullname);
+       error (0, 0, "file from working directory is now in %s", backup);
+       write_letter (finfo, 'C');
+
+       history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file,
+                      finfo->repository);
+       retval = 0;
+       goto out;
+    }
+
+    status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file,
+                       vers->options, vers->vn_user, vers->vn_rcs);
+    if (status != 0 && status != 1)
+    {
+       error (0, status == -1 ? errno : 0,
+              "could not merge revision %s of %s", vers->vn_user, 
finfo->fullname);
+       error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
+              finfo->fullname, backup);
+       rename_file (backup, finfo->file);
+       retval = 1;
+       goto out;
+    }
+
+    if (strcmp (vers->options, "-V4") == 0)
+       vers->options[0] = '\0';
+
+    /* fix up the vers structure, in case it is used by join */
+    if (join_rev1)
+    {
+       /* FIXME: Throwing away the original revision info is almost
+          certainly wrong -- what if join_rev1 is "BASE"?  */
+       if (vers->vn_user != NULL)
+           free (vers->vn_user);
+       vers->vn_user = xstrdup (vers->vn_rcs);
+    }
+
+    RegisterMerge (finfo, vers, backup, status);
+
+    if (status == 1)
+    {
+       error (0, 0, "conflicts found in %s", finfo->fullname);
+
+       write_letter (finfo, 'C');
+
+       history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file,
+                      finfo->repository);
+
+    }
+    else /* status == 0 */
+    {
+       history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file,
+                      finfo->repository);
+
+       /* FIXME: the noexec case is broken.  RCS_merge could be doing the
+          xcmp on the temporary files without much hassle, I think.  */
+       if (!noexec && !xcmp (backup, finfo->file))
+       {
+           cvs_output (finfo->fullname, 0);
+           cvs_output (" already contains the differences between ", 0);
+           cvs_output (vers->vn_user, 0);
+           cvs_output (" and ", 0);
+           cvs_output (vers->vn_rcs, 0);
+           cvs_output ("\n", 1);
+
+           retval = 0;
+           goto out;
+       }
+
+       write_letter (finfo, 'M');
+    }
+    retval = 0;
+ out:
+    free (backup);
+    return retval;
+}
+
+
+
+/*
+ * Do all the magic associated with a file which needs to be joined
+ * (reached via the -j option to checkout or update).
+ *
+ * INPUTS
+ *   finfo             File information about the destination file.
+ *   vers              The Vers_TS structure for finfo.
+ *
+ * GLOBALS
+ *   join_rev1         From the command line.
+ *   join_rev2         From the command line.
+ *   server_active     Natch.
+ *
+ * ASSUMPTIONS
+ *   1.  Is not called in client mode.
+ */
+static void
+join_file (struct file_info *finfo, Vers_TS *vers, const char *j1, const char 
*j2)
+{
+    char *backup;
+    char *t_options;
+    int status;
+
+    char *rev1;
+    char *rev2;
+    char *jrev1;
+    char *jrev2;
+    char *jdate1;
+    char *jdate2;
+
+    TRACE (TRACE_FUNCTION, "join_file(%s, %s%s%s%s, %s, %s)",
+          finfo->file,
+          vers->tag ? vers->tag : "",
+          vers->tag ? " (" : "",
+          vers->vn_rcs ? vers->vn_rcs : "",
+          vers->tag ? ")" : "",
+          j1 ? j1 : "",
+          j2 ? j2 : "");
+
+    jrev1 = j1;
+    jrev2 = j2;
+    jdate1 = join_date1;
+    jdate2 = join_date2;
+
+    /* Determine if we need to do anything at all.  */
+    if (vers->srcfile == NULL ||
+       vers->srcfile->path == NULL)
+    {
+       return;
+    }
+
+    /* If only one join revision is specified, it becomes the second
+       revision.  */
+    if (jrev2 == NULL)
+    {
+       jrev2 = jrev1;
+       jrev1 = NULL;
+       jdate2 = jdate1;
+       jdate1 = NULL;
+    }
+
+    /* FIXME: Need to handle "BASE" for jrev1 and/or jrev2.  Note caveat
+       below about vn_user.  */
+
+    /* Convert the second revision, walking branches and dates.  */
+    rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1, NULL);
+
+    /* If this is a merge of two revisions, get the first revision.
+       If only one join tag was specified, then the first revision is
+       the greatest common ancestor of the second revision and the
+       working file.  */
+    if (jrev1 != NULL)
+       rev1 = RCS_getversion (vers->srcfile, jrev1, jdate1, 1, NULL);
+    else
+    {
+       /* Note that we use vn_rcs here, since vn_user may contain a
+           special string such as "-nn".  */
+       if (vers->vn_rcs == NULL)
+           rev1 = NULL;
+       else if (rev2 == NULL)
+       {
+           /* This means that the file never existed on the branch.
+               It does not mean that the file was removed on the
+               branch: that case is represented by a dead rev2.  If
+               the file never existed on the branch, then we have
+               nothing to merge, so we just return.  */
+           return;
+       }
+       else
+           rev1 = gca (vers->vn_rcs, rev2);
+    }
+
+    /* Handle a nonexistent or dead merge target.  */
+    if (rev2 == NULL || RCS_isdead (vers->srcfile, rev2))
+    {
+       char *mrev;
+
+       if (rev2 != NULL)
+           free (rev2);
+
+       /* If the first revision doesn't exist either, then there is
+           no change between the two revisions, so we don't do
+           anything.  */
+       if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1))
+       {
+           if (rev1 != NULL)
+               free (rev1);
+           return;
+       }
+
+       /* If we are merging two revisions, then the file was removed
+          between the first revision and the second one.  In this
+          case we want to mark the file for removal.
+
+          If we are merging one revision, then the file has been
+          removed between the greatest common ancestor and the merge
+          revision.  From the perspective of the branch on to which
+          we ar emerging, which may be the trunk, either 1) the file
+          does not currently exist on the target, or 2) the file has
+          not been modified on the target branch since the greatest
+          common ancestor, or 3) the file has been modified on the
+          target branch since the greatest common ancestor.  In case
+          1 there is nothing to do.  In case 2 we mark the file for
+          removal.  In case 3 we have a conflict.
+
+          Note that the handling is slightly different depending upon
+          whether one or two join targets were specified.  If two
+          join targets were specified, we don't check whether the
+          file was modified since a given point.  My reasoning is
+          that if you ask for an explicit merge between two tags,
+          then you want to merge in whatever was changed between
+          those two tags.  If a file was removed between the two
+          tags, then you want it to be removed.  However, if you ask
+          for a merge of a branch, then you want to merge in all
+          changes which were made on the branch.  If a file was
+          removed on the branch, that is a change to the file.  If
+          the file was also changed on the main line, then that is
+          also a change.  These two changes--the file removal and the
+          modification--must be merged.  This is a conflict.  */
+
+       /* If the user file is dead, or does not exist, or has been
+           marked for removal, then there is nothing to do.  */
+       if (vers->vn_user == NULL
+           || vers->vn_user[0] == '-'
+           || RCS_isdead (vers->srcfile, vers->vn_user))
+       {
+           if (rev1 != NULL)
+               free (rev1);
+           return;
+       }
+
+       /* If the user file has been marked for addition, or has been
+          locally modified, then we have a conflict which we can not
+          resolve.  No_Difference will already have been called in
+          this case, so comparing the timestamps is sufficient to
+          determine whether the file is locally modified.  */
+       if (strcmp (vers->vn_user, "0") == 0
+           || (vers->ts_user != NULL
+               && strcmp (vers->ts_user, vers->ts_rcs) != 0))
+       {
+           if (jdate2 != NULL)
+               error (0, 0,
+                      "file %s is locally modified, but has been removed in 
revision %s as of %s",
+                      finfo->fullname, jrev2, jdate2);
+           else
+               error (0, 0,
+                      "file %s is locally modified, but has been removed in 
revision %s",
+                      finfo->fullname, jrev2);
+
+           /* FIXME: Should we arrange to return a non-zero exit
+               status?  */
+
+           if (rev1 != NULL)
+               free (rev1);
+
+           return;
+       }
+
+       /* If only one join tag was specified, and the user file has
+           been changed since the greatest common ancestor (rev1),
+           then there is a conflict we can not resolve.  See above for
+           the rationale.  */
+       if (j2 == NULL
+           && strcmp (rev1, vers->vn_user) != 0)
+       {
+           if (jdate2 != NULL)
+               error (0, 0,
+                      "file %s has been modified, but has been removed in 
revision %s as of %s",
+                      finfo->fullname, jrev2, jdate2);
+           else
+               error (0, 0,
+                      "file %s has been modified, but has been removed in 
revision %s",
+                      finfo->fullname, jrev2);
+
+           /* FIXME: Should we arrange to return a non-zero exit
+               status?  */
+
+           if (rev1 != NULL)
+               free (rev1);
+
+           return;
+       }
+
+       if (rev1 != NULL)
+           free (rev1);
+
+       /* The user file exists and has not been modified.  Mark it
+           for removal.  FIXME: If we are doing a checkout, this has
+           the effect of first checking out the file, and then
+           removing it.  It would be better to just register the
+           removal. 
+       
+          The same goes for a removal then an add.  e.g.
+          cvs up -rbr -jbr2 could remove and readd the same file
+        */
+       /* save the rev since server_updated might invalidate it */
+       mrev = Xasprintf ("-%s", vers->vn_user);
+#ifdef SERVER_SUPPORT
+       if (server_active)
+       {
+           server_scratch (finfo->file);
+           server_updated (finfo, vers, SERVER_UPDATED, (mode_t) -1,
+                           NULL, NULL);
+       }
+#endif
+       Register (finfo->entries, finfo->file, mrev, vers->ts_rcs,
+                 vers->options, vers->tag, vers->date, vers->ts_conflict);
+       free (mrev);
+       /* We need to check existence_error here because if we are
+           running as the server, and the file is up to date in the
+           working directory, the client will not have sent us a copy.  */
+       if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
+           error (0, errno, "cannot remove file %s", finfo->fullname);
+#ifdef SERVER_SUPPORT
+       if (server_active)
+           server_checked_in (finfo->file, finfo->update_dir,
+                              finfo->repository);
+#endif
+       if (! really_quiet)
+           error (0, 0, "scheduling `%s' for removal", finfo->fullname);
+
+       return;
+    }
+
+    /* If the two merge revisions are the same, then there is nothing
+     * to do.  This needs to be checked before the rev2 == up-to-date base
+     * revision check tha comes next.  Otherwise, rev1 can == rev2 and get an
+     * "already contains the changes between <rev1> and <rev1>" message.
+     */
+    if (rev1 && strcmp (rev1, rev2) == 0)
+    {
+       free (rev1);
+       free (rev2);
+       return;
+    }
+
+    /* If we know that the user file is up-to-date, then it becomes an
+     * optimization to skip the merge when rev2 is the same as the base
+     * revision.  i.e. we know that diff3(file2,file1,file2) will produce
+     * file2.
+     */
+    if (vers->vn_user != NULL && vers->ts_user != NULL
+        && strcmp (vers->ts_user, vers->ts_rcs) == 0
+        && strcmp (rev2, vers->vn_user) == 0)
+    {
+       if (!really_quiet)
+       {
+           cvs_output (finfo->fullname, 0);
+           cvs_output (" already contains the differences between ", 0);
+           cvs_output (rev1 ? rev1 : "creation", 0);
+           cvs_output (" and ", 0);
+           cvs_output (rev2, 0);
+           cvs_output ("\n", 1);
+       }
+
+       if (rev1 != NULL)
+           free (rev1);
+       free (rev2);
+
+       return;
+    }
+
+    /* If rev1 is dead or does not exist, then the file was added
+       between rev1 and rev2.  */
+    if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1))
+    {
+       if (rev1 != NULL)
+           free (rev1);
+       free (rev2);
+
+       /* If the file does not exist in the working directory, then
+           we can just check out the new revision and mark it for
+           addition.  */
+       if (vers->vn_user == NULL)
+       {
+           char *saved_options = options;
+           Vers_TS *xvers;
+
+           xvers = Version_TS (finfo, vers->options, jrev2, jdate2, 1, 0);
+
+           /* Reset any keyword expansion option.  Otherwise, when a
+              command like `cvs update -kk -jT1 -jT2' creates a new file
+              (because a file had the T2 tag, but not T1), the subsequent
+              commit of that just-added file effectively would set the
+              admin `-kk' option for that file in the repository.  */
+           options = NULL;
+
+           /* FIXME: If checkout_file fails, we should arrange to
+               return a non-zero exit status.  */
+           status = checkout_file (finfo, xvers, 1, 0, 1);
+           options = saved_options;
+
+           freevers_ts (&xvers);
+
+           return;
+       }
+
+       /* The file currently exists in the working directory, so we
+           have a conflict which we can not resolve.  Note that this
+           is true even if the file is marked for addition or removal.  */
+
+       if (jdate2 != NULL)
+           error (0, 0,
+                  "file %s exists, but has been added in revision %s as of %s",
+                  finfo->fullname, jrev2, jdate2);
+       else
+           error (0, 0,
+                  "file %s exists, but has been added in revision %s",
+                  finfo->fullname, jrev2);
+
+       return;
+    }
+
+    /* If there is no working file, then we can't do the merge.  */
+    if (vers->vn_user == NULL || vers->vn_user[0] == '-')
+    {
+       free (rev1);
+       free (rev2);
+
+       if (jdate2 != NULL)
+           error (0, 0,
+                  "file %s does not exist, but is present in revision %s as of 
%s",
+                  finfo->fullname, jrev2, jdate2);
+       else
+           error (0, 0,
+                  "file %s does not exist, but is present in revision %s",
+                  finfo->fullname, jrev2);
+
+       /* FIXME: Should we arrange to return a non-zero exit status?  */
+
+       return;
+    }
+
+#ifdef SERVER_SUPPORT
+    if (server_active && !isreadable (finfo->file))
+    {
+       int retcode;
+       /* The file is up to date.  Need to check out the current contents.  */
+       /* FIXME - see the FIXME comment above the call to RCS_checkout in the
+        * patch_file function.
+        */
+       retcode = RCS_checkout (vers->srcfile, finfo->file,
+                               vers->vn_user, vers->tag,
+                               NULL, RUN_TTY, NULL, NULL);
+       if (retcode != 0)
+           error (1, 0,
+                  "failed to check out %s file", finfo->fullname);
+    }
+#endif
+
+    /*
+     * The users currently modified file is moved to a backup file name
+     * ".#filename.version", so that it will stay around for a few days
+     * before being automatically removed by some cron daemon.  The "version"
+     * is the version of the file that the user was most up-to-date with
+     * before the merge.
+     */
+    backup = Xasprintf ("%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
+
+    if (unlink_file (backup) < 0
+       && !existence_error (errno))
+       error (0, errno, "cannot remove %s", backup);
+    copy_file (finfo->file, backup);
+    xchmod (finfo->file, 1);
+
+    t_options = vers->options;
+#if 0
+    if (*t_options == '\0')
+       t_options = "-kk";              /* to ignore keyword expansions */
+#endif
+
+    /* If the source of the merge is the same as the working file
+       revision, then we can just RCS_checkout the target (no merging
+       as such).  In the text file case, this is probably quite
+       similar to the RCS_merge, but in the binary file case,
+       RCS_merge gives all kinds of trouble.  */
+    if (vers->vn_user != NULL
+       && strcmp (rev1, vers->vn_user) == 0
+       /* See comments above about how No_Difference has already been
+          called.  */
+       && vers->ts_user != NULL
+       && strcmp (vers->ts_user, vers->ts_rcs) == 0
+
+       /* Avoid this in the text file case.  See below for why.
+        */
+       && (strcmp (t_options, "-kb") == 0
+           || wrap_merge_is_copy (finfo->file)))
+    {
+       /* FIXME: Verify my comment below:
+        *
+        * RCS_merge does nothing with keywords.  It merges the changes between
+        * two revisions without expanding the keywords (it might expand in
+        * -kk mode before computing the diff between rev1 and rev2 - I'm not
+        * sure).  In other words, the keyword lines in the current work file
+        * get left alone.
+        *
+        * Therfore, checking out the destination revision (rev2) is probably
+        * incorrect in the text case since we should see the keywords that were
+        * substituted into the original file at the time it was checked out
+        * and not the keywords from rev2.
+        *
+        * Also, it is safe to pass in NULL for nametag since we know no
+        * substitution is happening during the binary mode checkout.
+        */
+       if (RCS_checkout (finfo->rcs, finfo->file, rev2, NULL, t_options,
+                         RUN_TTY, NULL, NULL) != 0)
+           status = 2;
+       else
+           status = 0;
+
+       /* OK, this is really stupid.  RCS_checkout carefully removes
+          write permissions, and we carefully put them back.  But
+          until someone gets around to fixing it, that seems like the
+          easiest way to get what would seem to be the right mode.
+          I don't check CVSWRITE or _watched; I haven't thought about
+          that in great detail, but it seems like a watched file should
+          be checked out (writable) after a merge.  */
+       xchmod (finfo->file, 1);
+
+       /* Traditionally, the text file case prints a whole bunch of
+          scary looking and verbose output which fails to tell the user
+          what is really going on (it gives them rev1 and rev2 but doesn't
+          indicate in any way that rev1 == vn_user).  I think just a
+          simple "U foo" is good here; it seems analogous to the case in
+          which the file was added on the branch in terms of what to
+          print.  */
+       write_letter (finfo, 'U');
+    }
+    else if (strcmp (t_options, "-kb") == 0
+            || wrap_merge_is_copy (finfo->file)
+            || special_file_mismatch (finfo, rev1, rev2))
+    {
+       /* We are dealing with binary files, or files with a
+          permission/linkage mismatch (this second case only occurs when
+          PRESERVE_PERMISSIONS_SUPPORT is enabled), and real merging would
+          need to take place.  This is a conflict.  We give the user
+          the two files, and let them resolve it.  It is possible
+          that we should require a "touch foo" or similar step before
+          we allow a checkin.  */
+       if (RCS_checkout (finfo->rcs, finfo->file, rev2, NULL,
+                         t_options, RUN_TTY, NULL, NULL) != 0)
+           status = 2;
+       else
+           status = 0;
+
+       /* OK, this is really stupid.  RCS_checkout carefully removes
+          write permissions, and we carefully put them back.  But
+          until someone gets around to fixing it, that seems like the
+          easiest way to get what would seem to be the right mode.
+          I don't check CVSWRITE or _watched; I haven't thought about
+          that in great detail, but it seems like a watched file should
+          be checked out (writable) after a merge.  */
+       xchmod (finfo->file, 1);
+
+       /* Hmm.  We don't give them REV1 anywhere.  I guess most people
+          probably don't have a 3-way merge tool for the file type in
+          question, and might just get confused if we tried to either
+          provide them with a copy of the file from REV1, or even just
+          told them what REV1 is so they can get it themself, but it
+          might be worth thinking about.  */
+       /* See comment in merge_file about the "nonmergeable file"
+          terminology.  */
+       error (0, 0, "nonmergeable file needs merge");
+       error (0, 0, "revision %s from repository is now in %s",
+              rev2, finfo->fullname);
+       error (0, 0, "file from working directory is now in %s", backup);
+       write_letter (finfo, 'C');
+    }
+    else
+       status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file,
+                           t_options, rev1, rev2);
+
+    if (status != 0)
+    {
+       if (status != 1)
+       {
+           error (0, status == -1 ? errno : 0,
+                  "could not merge revision %s of %s", rev2, finfo->fullname);
+           error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
+                  finfo->fullname, backup);
+           rename_file (backup, finfo->file);
+       }
+    }
+    else /* status == 0 */
+    {
+       /* FIXME: the noexec case is broken.  RCS_merge could be doing the
+          xcmp on the temporary files without much hassle, I think.  */
+       if (!noexec && !xcmp (backup, finfo->file))
+       {
+           if (!really_quiet)
+           {
+               cvs_output (finfo->fullname, 0);
+               cvs_output (" already contains the differences between ", 0);
+               cvs_output (rev1, 0);
+               cvs_output (" and ", 0);
+               cvs_output (rev2, 0);
+               cvs_output ("\n", 1);
+           }
+
+           /* and skip the registering and sending the new file since it
+            * hasn't been updated.
+            */
+           goto out;
+       }
+    }
+
+    /* The file has changed, but if we just checked it out it may
+       still have the same timestamp it did when it was first
+       registered above in checkout_file.  We register it again with a
+       dummy timestamp to make sure that later runs of CVS will
+       recognize that it has changed.
+
+       We don't actually need to register again if we called
+       RCS_checkout above, and we aren't running as the server.
+       However, that is not the normal case, and calling Register
+       again won't cost much in that case.  */
+    RegisterMerge (finfo, vers, backup, status);
+
+out:
+    free (rev1);
+    free (rev2);
+    free (backup);
+}
+
+
+
+/*
+ * Report whether revisions REV1 and REV2 of FINFO agree on:
+ *   . file ownership
+ *   . permissions
+ *   . major and minor device numbers
+ *   . symbolic links
+ *   . hard links
+ *
+ * If either REV1 or REV2 is NULL, the working copy is used instead.
+ *
+ * Return 1 if the files differ on these data.
+ */
+
+int
+special_file_mismatch (struct file_info *finfo, char *rev1, char *rev2)
+{
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    struct stat sb;
+    RCSVers *vp;
+    Node *n;
+    uid_t rev1_uid, rev2_uid;
+    gid_t rev1_gid, rev2_gid;
+    mode_t rev1_mode, rev2_mode;
+    unsigned long dev_long;
+    dev_t rev1_dev, rev2_dev;
+    char *rev1_symlink = NULL;
+    char *rev2_symlink = NULL;
+    List *rev1_hardlinks = NULL;
+    List *rev2_hardlinks = NULL;
+    int check_uids, check_gids, check_modes;
+    int result;
+
+    /* If we don't care about special file info, then
+       don't report a mismatch in any case. */
+    if (!preserve_perms)
+       return 0;
+
+    /* When special_file_mismatch is called from No_Difference, the
+       RCS file has been only partially parsed.  We must read the
+       delta tree in order to compare special file info recorded in
+       the delta nodes.  (I think this is safe. -twp) */
+    if (finfo->rcs->flags & PARTIAL)
+       RCS_reparsercsfile (finfo->rcs, NULL, NULL);
+
+    check_uids = check_gids = check_modes = 1;
+
+    /* Obtain file information for REV1.  If this is null, then stat
+       finfo->file and use that info. */
+    /* If a revision does not know anything about its status,
+       then presumably it doesn't matter, and indicates no conflict. */
+
+    if (rev1 == NULL)
+    {
+       ssize_t rsize;
+
+       if ((rsize = islink (finfo->file)) > 0)
+           rev1_symlink = Xreadlink (finfo->file, rsize);
+       else
+       {
+# ifdef HAVE_STRUCT_STAT_ST_RDEV
+           if (lstat (finfo->file, &sb) < 0)
+               error (1, errno, "could not get file information for %s",
+                      finfo->file);
+           rev1_uid = sb.st_uid;
+           rev1_gid = sb.st_gid;
+           rev1_mode = sb.st_mode;
+           if (S_ISBLK (rev1_mode) || S_ISCHR (rev1_mode))
+               rev1_dev = sb.st_rdev;
+# else
+           error (1, 0, "cannot handle device files on this system (%s)",
+                  finfo->file);
+# endif
+       }
+       rev1_hardlinks = list_linked_files_on_disk (finfo->file);
+    }
+    else
+    {
+       n = findnode (finfo->rcs->versions, rev1);
+       vp = n->data;
+
+       n = findnode (vp->other_delta, "symlink");
+       if (n != NULL)
+           rev1_symlink = xstrdup (n->data);
+       else
+       {
+           n = findnode (vp->other_delta, "owner");
+           if (n == NULL)
+               check_uids = 0; /* don't care */
+           else
+               rev1_uid = strtoul (n->data, NULL, 10);
+
+           n = findnode (vp->other_delta, "group");
+           if (n == NULL)
+               check_gids = 0; /* don't care */
+           else
+               rev1_gid = strtoul (n->data, NULL, 10);
+
+           n = findnode (vp->other_delta, "permissions");
+           if (n == NULL)
+               check_modes = 0;        /* don't care */
+           else
+               rev1_mode = strtoul (n->data, NULL, 8);
+
+           n = findnode (vp->other_delta, "special");
+           if (n == NULL)
+               rev1_mode |= S_IFREG;
+           else
+           {
+               /* If the size of `ftype' changes, fix the sscanf call also */
+               char ftype[16];
+               if (sscanf (n->data, "%15s %lu", ftype,
+                           &dev_long) < 2)
+                   error (1, 0, "%s:%s has bad `special' newphrase %s",
+                          finfo->file, rev1, (char *)n->data);
+               rev1_dev = dev_long;
+               if (strcmp (ftype, "character") == 0)
+                   rev1_mode |= S_IFCHR;
+               else if (strcmp (ftype, "block") == 0)
+                   rev1_mode |= S_IFBLK;
+               else
+                   error (0, 0, "%s:%s unknown file type `%s'",
+                          finfo->file, rev1, ftype);
+           }
+
+           rev1_hardlinks = vp->hardlinks;
+           if (rev1_hardlinks == NULL)
+               rev1_hardlinks = getlist();
+       }
+    }
+
+    /* Obtain file information for REV2. */
+    if (rev2 == NULL)
+    {
+       ssize_t rsize;
+
+       if ((rsize = islink (finfo->file)) > 0)
+           rev2_symlink = Xreadlink (finfo->file, rsize);
+       else
+       {
+# ifdef HAVE_STRUCT_STAT_ST_RDEV
+           if (lstat (finfo->file, &sb) < 0)
+               error (1, errno, "could not get file information for %s",
+                      finfo->file);
+           rev2_uid = sb.st_uid;
+           rev2_gid = sb.st_gid;
+           rev2_mode = sb.st_mode;
+           if (S_ISBLK (rev2_mode) || S_ISCHR (rev2_mode))
+               rev2_dev = sb.st_rdev;
+# else
+           error (1, 0, "cannot handle device files on this system (%s)",
+                  finfo->file);
+# endif
+       }
+       rev2_hardlinks = list_linked_files_on_disk (finfo->file);
+    }
+    else
+    {
+       n = findnode (finfo->rcs->versions, rev2);
+       vp = n->data;
+
+       n = findnode (vp->other_delta, "symlink");
+       if (n != NULL)
+           rev2_symlink = xstrdup (n->data);
+       else
+       {
+           n = findnode (vp->other_delta, "owner");
+           if (n == NULL)
+               check_uids = 0; /* don't care */
+           else
+               rev2_uid = strtoul (n->data, NULL, 10);
+
+           n = findnode (vp->other_delta, "group");
+           if (n == NULL)
+               check_gids = 0; /* don't care */
+           else
+               rev2_gid = strtoul (n->data, NULL, 10);
+
+           n = findnode (vp->other_delta, "permissions");
+           if (n == NULL)
+               check_modes = 0;        /* don't care */
+           else
+               rev2_mode = strtoul (n->data, NULL, 8);
+
+           n = findnode (vp->other_delta, "special");
+           if (n == NULL)
+               rev2_mode |= S_IFREG;
+           else
+           {
+               /* If the size of `ftype' changes, fix the sscanf call also */
+               char ftype[16];
+               if (sscanf (n->data, "%15s %lu", ftype,
+                           &dev_long) < 2)
+                   error (1, 0, "%s:%s has bad `special' newphrase %s",
+                          finfo->file, rev2, (char *)n->data);
+               rev2_dev = dev_long;
+               if (strcmp (ftype, "character") == 0)
+                   rev2_mode |= S_IFCHR;
+               else if (strcmp (ftype, "block") == 0)
+                   rev2_mode |= S_IFBLK;
+               else
+                   error (0, 0, "%s:%s unknown file type `%s'",
+                          finfo->file, rev2, ftype);
+           }
+
+           rev2_hardlinks = vp->hardlinks;
+           if (rev2_hardlinks == NULL)
+               rev2_hardlinks = getlist();
+       }
+    }
+
+    /* Check the user/group ownerships and file permissions, printing
+       an error for each mismatch found.  Return 0 if all characteristics
+       matched, and 1 otherwise. */
+
+    result = 0;
+
+    /* Compare symlinks first, since symlinks are simpler (don't have
+       any other characteristics). */
+    if (rev1_symlink != NULL && rev2_symlink == NULL)
+    {
+       error (0, 0, "%s is a symbolic link",
+              (rev1 == NULL ? "working file" : rev1));
+       result = 1;
+    }
+    else if (rev1_symlink == NULL && rev2_symlink != NULL)
+    {
+       error (0, 0, "%s is a symbolic link",
+              (rev2 == NULL ? "working file" : rev2));
+       result = 1;
+    }
+    else if (rev1_symlink != NULL)
+       result = (strcmp (rev1_symlink, rev2_symlink) == 0);
+    else
+    {
+       /* Compare user ownership. */
+       if (check_uids && rev1_uid != rev2_uid)
+       {
+           error (0, 0, "%s: owner mismatch between %s and %s",
+                  finfo->file,
+                  (rev1 == NULL ? "working file" : rev1),
+                  (rev2 == NULL ? "working file" : rev2));
+           result = 1;
+       }
+
+       /* Compare group ownership. */
+       if (check_gids && rev1_gid != rev2_gid)
+       {
+           error (0, 0, "%s: group mismatch between %s and %s",
+                  finfo->file,
+                  (rev1 == NULL ? "working file" : rev1),
+                  (rev2 == NULL ? "working file" : rev2));
+           result = 1;
+       }
+    
+       /* Compare permissions. */
+       if (check_modes &&
+           (rev1_mode & 07777) != (rev2_mode & 07777))
+       {
+           error (0, 0, "%s: permission mismatch between %s and %s",
+                  finfo->file,
+                  (rev1 == NULL ? "working file" : rev1),
+                  (rev2 == NULL ? "working file" : rev2));
+           result = 1;
+       }
+
+       /* Compare device file characteristics. */
+       if ((rev1_mode & S_IFMT) != (rev2_mode & S_IFMT))
+       {
+           error (0, 0, "%s: %s and %s are different file types",
+                  finfo->file,
+                  (rev1 == NULL ? "working file" : rev1),
+                  (rev2 == NULL ? "working file" : rev2));
+           result = 1;
+       }
+       else if (S_ISBLK (rev1_mode))
+       {
+           if (rev1_dev != rev2_dev)
+           {
+               error (0, 0, "%s: device numbers of %s and %s do not match",
+                      finfo->file,
+                      (rev1 == NULL ? "working file" : rev1),
+                      (rev2 == NULL ? "working file" : rev2));
+               result = 1;
+           }
+       }
+
+       /* Compare hard links. */
+       if (compare_linkage_lists (rev1_hardlinks, rev2_hardlinks) == 0)
+       {
+           error (0, 0, "%s: hard linkage of %s and %s do not match",
+                  finfo->file,
+                  (rev1 == NULL ? "working file" : rev1),
+                  (rev2 == NULL ? "working file" : rev2));
+           result = 1;
+       }
+    }
+
+    if (rev1_symlink != NULL)
+       free (rev1_symlink);
+    if (rev2_symlink != NULL)
+       free (rev2_symlink);
+    if (rev1_hardlinks != NULL)
+       dellist (&rev1_hardlinks);
+    if (rev2_hardlinks != NULL)
+       dellist (&rev2_hardlinks);
+
+    return result;
+#else
+    return 0;
+#endif
+}
+
+
+
+int
+joining (void)
+{
+    return join_rev1 || join_date1;
+}
Index: ccvs/src/vers_ts.c
diff -u /dev/null ccvs/src/vers_ts.c:1.65.8.1
--- /dev/null   Tue Jan 17 15:41:25 2006
+++ ccvs/src/vers_ts.c  Tue Jan 17 15:41:24 2006
@@ -0,0 +1,691 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ */
+
+#include "cvs.h"
+#include "lstat.h"
+
+#ifdef SERVER_SUPPORT
+static void time_stamp_server (const char *, Vers_TS *, Entnode *);
+#endif
+
+/* Fill in and return a Vers_TS structure for the file FINFO.
+ *
+ * INPUTS
+ *   finfo             struct file_info data about the file to be examined.
+ *   options           Keyword expansion options, I think generally from the
+ *                     command line.  Can be either NULL or "" to indicate
+ *                     none are specified here.
+ *   tag               Tag specified by user on the command line (via -r).
+ *   date              Date specified by user on the command line (via -D).
+ *   force_tag_match   If set and TAG is specified, will only set RET->vn_rcs
+ *                     based on TAG.  Otherwise, if TAG is specified and does
+ *                     not exist in the file, RET->vn_rcs will be set to the
+ *                     head revision.
+ *   set_time          If set, set the last modification time of the user file
+ *                     specified by FINFO to the checkin time of RET->vn_rcs.
+ *
+ * RETURNS
+ *   Vers_TS structure for FINFO.
+ */
+Vers_TS *
+Version_TS (struct file_info *finfo, char *options, char *tag, char *date,
+            int force_tag_match, int set_time)
+{
+    Node *p;
+    RCSNode *rcsdata;
+    Vers_TS *vers_ts;
+    struct stickydirtag *sdtp;
+    Entnode *entdata;
+    char *rcsexpand = NULL;
+    bool setstickynumtag = false;
+
+    /* get a new Vers_TS struct */
+
+    vers_ts = xmalloc (sizeof (Vers_TS));
+    memset (vers_ts, 0, sizeof (*vers_ts));
+
+    /*
+     * look up the entries file entry and fill in the version and timestamp
+     * if entries is NULL, there is no entries file so don't bother trying to
+     * look it up (used by checkout -P)
+     */
+    if (finfo->entries == NULL)
+    {
+       sdtp = NULL;
+       p = NULL;
+    }
+    else
+    {
+       p = findnode_fn (finfo->entries, finfo->file);
+       sdtp = finfo->entries->list->data; /* list-private */
+    }
+
+    if (p == NULL)
+    {
+       entdata = NULL;
+    }
+    else
+    {
+       entdata = p->data;
+
+       if (entdata->type == ENT_SUBDIR)
+       {
+           /* According to cvs.texinfo, the various fields in the Entries
+              file for a directory (other than the name) do not have a
+              defined meaning.  We need to pass them along without getting
+              confused based on what is in them.  Therefore we make sure
+              not to set vn_user and the like from Entries, add.c and
+              perhaps other code will expect these fields to be NULL for
+              a directory.  */
+           vers_ts->entdata = entdata;
+       }
+       else
+#ifdef SERVER_SUPPORT
+       /* An entries line with "D" in the timestamp indicates that the
+          client sent Is-modified without sending Entry.  So we want to
+          use the entries line for the sole purpose of telling
+          time_stamp_server what is up; we don't want the rest of CVS
+          to think there is an entries line.  */
+       if (strcmp (entdata->timestamp, "D") != 0)
+#endif
+       {
+           vers_ts->vn_user = xstrdup (entdata->version);
+           vers_ts->ts_rcs = xstrdup (entdata->timestamp);
+           vers_ts->ts_conflict = xstrdup (entdata->conflict);
+           if (!(tag || date) && !(sdtp && sdtp->aflag))
+           {
+               vers_ts->tag = xstrdup (entdata->tag);
+               vers_ts->date = xstrdup (entdata->date);
+           }
+           vers_ts->entdata = entdata;
+       }
+       /* Even if we don't have an "entries line" as such
+          (vers_ts->entdata), we want to pick up options which could
+          have been from a Kopt protocol request.  */
+       if (!options || *options == '\0')
+       {
+           if (!(sdtp && sdtp->aflag))
+               vers_ts->options = xstrdup (entdata->options);
+       }
+    }
+
+    /* Always look up the RCS keyword mode when we have an RCS archive.  It
+     * will either be needed as a default or to avoid allowing the -k options
+     * specified on the command line from overriding binary mode (-kb).
+     */
+    if (finfo->rcs != NULL)
+       rcsexpand = RCS_getexpand (finfo->rcs);
+
+    /*
+     * -k options specified on the command line override (and overwrite)
+     * options stored in the entries file and default options from the RCS
+     * archive, except for binary mode (-kb).
+     */
+    if (options && *options != '\0')
+    {
+       if (vers_ts->options != NULL)
+           free (vers_ts->options);
+       if (rcsexpand != NULL && strcmp (rcsexpand, "b") == 0)
+           vers_ts->options = xstrdup ("-kb");
+       else
+           vers_ts->options = xstrdup (options);
+    }
+    else if ((!vers_ts->options || *vers_ts->options == '\0')
+             && rcsexpand != 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.  */
+       if (vers_ts->options != NULL)
+           free (vers_ts->options);
+       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)
+    {
+        if (tag)
+       {
+           /* If a relative tag extension is used,
+            * prepend the local revision number, and set setstickynumtag
+            * so the sticky tag gets set to the numeric revision number
+            */
+           int position = 0;
+           bool head = false;
+           bool base = false;
+           bool root = false;
+           bool origin = false;
+           bool relative = true;
+           bool prepversion = false;
+           bool dot = false;
+
+           if (*tag == '.')
+              dot = true;
+
+           char *tmp = xstrdup (tag);
+           char *token = strtok (tmp, ".");
+           while (token)
+           {
+               if (position == 0)
+               {
+                   if (dot)
+                   {
+                       if (!strcmp (token, TAG_ROOT))
+                           root = true;
+                       else if (!strcmp (token, TAG_ORIGIN))
+                           origin = true;
+                       else if (!strcmp (token, TAG_DOTHEAD))
+                           head = true;
+                       else if (!strcmp (token, TAG_DOTBASE))
+                       {
+                           base = true;
+                           relative = false;
+                       }
+                       else if (strcmp (token, TAG_TRUNK)
+                                && strcmp (token, TAG_COMMITID))
+                       {
+                           prepversion = true;
+                           setstickynumtag = true;
+                           break;
+                       }
+                       else
+                           relative = false;
+                   }
+                   else
+                       relative = false;
+               }
+               else if (!isdigit ((unsigned char) *token))
+               {
+                   if (!strcmp (token, TAG_DOTHEAD))
+                       setstickynumtag = false;
+                   else if (!strcmp (token, TAG_PREVIOUS)
+                            || !strcmp (token, TAG_NEXT))
+                       setstickynumtag = true;
+               }
+               token = strtok (NULL, ".");
+               ++position;
+           }
+           free (tmp);
+
+           char *preptag = NULL;
+           if (prepversion)
+               preptag = vers_ts->vn_user;
+           else if (head || root || origin)
+           {
+               /* make sure that extensions to static tags
+                * force usage of numeric revisions so we
+                * don't send '.base.xxx' or alike to translate_tag()
+                */
+               if (vers_ts->entdata
+                   && vers_ts->entdata->tag
+                   && !(*vers_ts->entdata->tag == '.'
+                        && !strcmp (vers_ts->entdata->tag+1, TAG_DOTBASE))
+                   && strcmp (vers_ts->entdata->tag, TAG_HEAD)
+                   && strcmp (vers_ts->entdata->tag, TAG_BASE))
+               {
+                   preptag = vers_ts->entdata->tag;
+               }
+               else if (sdtp && !sdtp->aflag && sdtp->tag
+                        && !isdigit (*sdtp->tag)
+                        && !(*sdtp->tag == '.'
+                             && !strcmp (sdtp->tag+1, TAG_DOTBASE))
+                        && strcmp (sdtp->tag, TAG_HEAD)
+                        && strcmp (sdtp->tag, TAG_BASE))
+               {
+                   preptag = sdtp->tag;
+               }
+               else
+                   preptag = vers_ts->vn_user;
+           }
+
+           if (preptag)
+           {
+               char *p = NULL;
+               if (origin
+                   && (p = strrchr (preptag, '.'))
+                   && !strcmp (p+1, TAG_ORIGIN))
+               {
+                   tmp = xstrdup (preptag);
+                   tmp[p-preptag] = '\0';
+                   vers_ts->tag = Xasprintf ("%s%s", tmp, tag);
+                   free (tmp);
+               }
+               else if (head
+                        && (p = strrchr (preptag, '.'))
+                        && !strcmp (p+1, TAG_DOTHEAD))
+               {
+                   tmp = xstrdup (preptag);
+                   tmp[p-preptag] = '\0';
+                   vers_ts->tag = Xasprintf ("%s%s", tmp, tag);
+                   free (tmp);
+               }
+               else
+                   vers_ts->tag = Xasprintf ("%s%s", preptag, tag);
+           }
+           else if (base && position > 1)
+           {
+               const char *p = strchr (tag+1, '.');
+               vers_ts->tag = Xasprintf ("%s%s", vers_ts->vn_user, p);
+           }
+           else if (relative)
+               vers_ts->tag = Xasprintf (".%s%s", TAG_TRUNK, tag);
+           else
+               vers_ts->tag = xstrdup (tag);
+       }
+       if (date)
+       {
+           vers_ts->date = xstrdup (date);
+       }
+    }
+    else if (!vers_ts->entdata && (sdtp && sdtp->aflag == 0))
+    {
+       if (!vers_ts->tag)
+       {
+           vers_ts->tag = xstrdup (sdtp->tag);
+           vers_ts->nonbranch = sdtp->nonbranch;
+       }
+       if (!vers_ts->date)
+           vers_ts->date = xstrdup (sdtp->date);
+    }
+
+    /* Now look up the info on the source controlled file */
+    if (finfo->rcs != NULL)
+    {
+       rcsdata = finfo->rcs;
+       rcsdata->refcount++;
+    }
+    else if (finfo->repository != NULL)
+       rcsdata = RCS_parse (finfo->file, finfo->repository);
+    else
+       rcsdata = NULL;
+
+    if (rcsdata != NULL)
+    {
+       /* squirrel away the rcsdata pointer for others */
+       vers_ts->srcfile = rcsdata;
+
+       if (vers_ts->tag
+           && (!strcmp (vers_ts->tag, TAG_BASE)
+               || (*(vers_ts->tag) == '.'
+                   && !strcmp (vers_ts->tag+1, TAG_DOTBASE)) ) )
+          {
+           vers_ts->vn_rcs = xstrdup (vers_ts->vn_user);
+           vers_ts->vn_tag = xstrdup (vers_ts->vn_user);
+       }
+       else
+       {
+           int simple;
+
+           vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag,
+                                             vers_ts->date, force_tag_match,
+                                             &simple);
+           if (vers_ts->vn_rcs == NULL)
+               vers_ts->vn_tag = NULL;
+           else if (simple)
+               vers_ts->vn_tag = xstrdup (vers_ts->tag);
+           else
+               vers_ts->vn_tag = xstrdup (vers_ts->vn_rcs);
+
+           if (vers_ts->vn_rcs && setstickynumtag)
+           {
+               /* for some relative tags, the sticky tag
+                * needs to be set to its numeric equivalent
+                */
+               free (vers_ts->tag);
+               vers_ts->tag = xstrdup (vers_ts->vn_rcs);
+           }
+       }
+
+       /*
+        * If the source control file exists and has the requested revision,
+        * get the Date the revision was checked in.  If "user" exists, set
+        * its mtime.
+        */
+       if (set_time && vers_ts->vn_rcs != NULL)
+       {
+#ifdef SERVER_SUPPORT
+           if (server_active)
+               server_modtime (finfo, vers_ts);
+           else
+#endif
+           {
+               struct utimbuf t;
+
+               memset (&t, 0, sizeof (t));
+               t.modtime = RCS_getrevtime (rcsdata, vers_ts->vn_rcs, 0, 0);
+               if (t.modtime != (time_t) -1)
+               {
+#ifdef UTIME_EXPECTS_WRITABLE
+                   int change_it_back = 0;
+#endif
+
+                   (void) time (&t.actime);
+
+#ifdef UTIME_EXPECTS_WRITABLE
+                   if (!iswritable (finfo->file))
+                   {
+                       xchmod (finfo->file, 1);
+                       change_it_back = 1;
+                   }
+#endif  /* UTIME_EXPECTS_WRITABLE  */
+
+                   /* This used to need to ignore existence_errors
+                      (for cases like where update.c now clears
+                      set_time if noexec, but didn't used to).  I
+                      think maybe now it doesn't (server_modtime does
+                      not like those kinds of cases).  */
+                   (void) utime (finfo->file, &t);
+
+#ifdef UTIME_EXPECTS_WRITABLE
+                   if (change_it_back)
+                       xchmod (finfo->file, 0);
+#endif  /*  UTIME_EXPECTS_WRITABLE  */
+               }
+           }
+       }
+    }
+
+    /* get user file time-stamp in ts_user */
+    if (finfo->entries != NULL)
+    {
+#ifdef SERVER_SUPPORT
+       if (server_active)
+           time_stamp_server (finfo->file, vers_ts, entdata);
+       else
+#endif
+           vers_ts->ts_user = time_stamp (finfo->file);
+    }
+
+    return (vers_ts);
+}
+
+char *Version_resolve_relTag (struct file_info *finfo, const char *tag, bool 
force_trunk)
+{
+    Node *p;
+    char *entversion = NULL;
+    char *enttag = NULL;
+    Entnode *entdata;
+    struct stickydirtag *sdtp;
+    char *res_vers = NULL;
+
+    /*
+     * 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
+     */
+    if (finfo->entries == NULL)
+    {
+       sdtp = NULL;
+       p = NULL;
+    }
+    else
+    {
+       p = findnode_fn (finfo->entries, finfo->file);
+       sdtp = finfo->entries->list->data; /* list-private */
+    }
+
+    if (p == NULL)
+    {
+       entdata = NULL;
+    }
+    else
+    {
+       entdata = p->data;
+
+       if (entdata->type != ENT_SUBDIR)
+#ifdef SERVER_SUPPORT
+       /* An entries line with "D" in the timestamp indicates that the
+          client sent Is-modified without sending Entry.  So we want to
+          use the entries line for the sole purpose of telling
+          time_stamp_server what is up; we don't want the rest of CVS
+          to think there is an entries line.  */
+       if (strcmp (entdata->timestamp, "D") != 0)
+#endif
+       {
+           entversion = xstrdup (entdata->version);
+            enttag = xstrdup (entdata->tag);
+       }
+    }
+
+    assert (*tag == '.');
+
+    /* If a relative tag extension is used,
+     * prepend the local revision number
+     */
+    if (entversion)
+    {
+        res_vers = Xasprintf ("%s%s", entversion, tag);
+    }
+    else if (enttag)
+    {
+        res_vers = Xasprintf ("%s%s", enttag, tag);
+    }
+    else if (sdtp && sdtp->tag)
+    {
+        res_vers = Xasprintf ("%s%s", sdtp->tag, tag);
+    }
+    else if (force_trunk)
+    {
+        res_vers = Xasprintf (".%s%s", TAG_TRUNK, tag);
+    }
+
+    /* If the version now starts with TAG_DOTBASE,
+     * replace TAG_DOTBASE with the local revision number
+     */
+    if (res_vers && !strncmp (res_vers+1, TAG_DOTBASE, strlen (TAG_DOTBASE)))
+    {
+        char *tmp = res_vers;
+        char *rem = strchr (res_vers+1, '.');
+        if (rem)
+            ++rem;
+        res_vers = Xasprintf ("%s.%s", entversion, rem);
+        free (tmp);
+    }
+
+    return (res_vers);
+}
+
+#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 (const char *file, Vers_TS *vers_ts, Entnode *entdata)
+{
+    struct stat sb;
+    char *cp;
+
+    TRACE (TRACE_FUNCTION, "time_stamp_server (%s, %s, %s, %s)",
+          file,
+          entdata && entdata->version ? entdata->version : "(null)",
+          entdata && entdata->timestamp ? entdata->timestamp : "(null)",
+          entdata && entdata->conflict ? entdata->conflict : "(null)");
+
+    if (lstat (file, &sb) < 0)
+    {
+       if (! existence_error (errno))
+           error (1, errno, "cannot stat temp file");
+
+       /* Missing file means lost or unmodified; check entries
+          file to see which.
+
+          XXX FIXME - If there's no entries file line, we
+          wouldn't be getting the file at all, so consider it
+          lost.  I don't know that that's right, but it's not
+          clear to me that either choice is.  Besides, would we
+          have an RCS string in that case anyways?  */
+       if (entdata == NULL)
+           mark_lost (vers_ts);
+       else if (entdata->timestamp
+                && entdata->timestamp[0] == '='
+                && entdata->timestamp[1] == '\0')
+           mark_unchanged (vers_ts);
+       else if (entdata->conflict
+                && entdata->conflict[0] == '=')
+       {
+           /* These just need matching content.  Might as well minimize it.  */
+           vers_ts->ts_user = xstrdup ("");
+           vers_ts->ts_conflict = xstrdup ("");
+       }
+       else if (entdata->timestamp
+                && (entdata->timestamp[0] == 'M'
+                    || entdata->timestamp[0] == 'D')
+                && entdata->timestamp[1] == '\0')
+           vers_ts->ts_user = xstrdup ("Is-modified");
+       else
+           mark_lost (vers_ts);
+    }
+    else if (sb.st_mtime == 0)
+    {
+       /* We shouldn't reach this case any more!  */
+       abort ();
+    }
+    else
+    {
+        struct tm *tm_p;
+
+       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);
+       cp = tm_p ? asctime (tm_p) : ctime (&sb.st_mtime);
+       cp[24] = 0;
+       /* Fix non-standard format.  */
+       if (cp[8] == '0') cp[8] = ' ';
+       (void) strcpy (vers_ts->ts_user, cp);
+    }
+}
+
+#endif /* SERVER_SUPPORT */
+
+
+
+/* Given a UNIX seconds since the epoch, return a string in the format used by
+ * the Entries file.
+ *
+ *
+ * INPUTS
+ *   UNIXTIME  The timestamp to be formatted.
+ *
+ * RETURNS
+ *   A freshly allocated string the caller is responsible for disposing of.
+ */
+char *
+entries_time (time_t unixtime)
+{
+    struct tm *tm_p;
+    char *cp;
+
+    /* 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 (&unixtime);
+    cp = tm_p ? asctime (tm_p) : ctime (&unixtime);
+    /* Get rid of the EOL */
+    cp[24] = '\0';
+    /* Fix non-standard format.  */
+    if (cp[8] == '0') cp[8] = ' ';
+
+    return Xasprintf ("%s", cp);
+}
+
+
+
+time_t
+unix_time_stamp (const char *file)
+{
+    struct stat sb;
+    time_t mtime = 0L;
+
+    if (!lstat (file, &sb))
+    {
+       mtime = sb.st_mtime;
+    }
+
+    /* If it's a symlink, return whichever is the newest mtime of
+       the link and its target, for safety.
+    */
+    if (!stat (file, &sb))
+    {
+        if (mtime < sb.st_mtime)
+           mtime = sb.st_mtime;
+    }
+
+    return mtime;
+}
+
+
+
+/*
+ * Gets the time-stamp for the file "file" and returns it in space it
+ * allocates
+ */
+char *
+time_stamp (const char *file)
+{
+    time_t mtime = unix_time_stamp (file);
+    return mtime ? entries_time (mtime) : NULL;
+}
+
+
+
+/*
+ * free up a Vers_TS struct
+ */
+void
+freevers_ts (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 = NULL;
+}




reply via email to

[Prev in Thread] Current Thread [Next in Thread]