cvs-cvs
[Top][All Lists]
Advanced

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

[Cvs-cvs] ccvs/src ChangeLog client.c client.h commit.c l... [signed-com


From: Derek Robert Price
Subject: [Cvs-cvs] ccvs/src ChangeLog client.c client.h commit.c l... [signed-commits3]
Date: Fri, 06 Jan 2006 20:04:39 +0000

CVSROOT:        /cvsroot/cvs
Module name:    ccvs
Branch:         signed-commits3
Changes by:     Derek Robert Price <address@hidden>     06/01/06 20:04:38

Modified files:
        src            : ChangeLog client.c client.h commit.c log.c 
                         main.c mkmodules.c patch.c rcs.c rcs.h 
                         sanity.sh server.c server.h sign.c sign.h 
                         status.c 

Log message:
        * client.c, client.h, commit.c, log.c, mkmodules.c, patch.c, server.c,
        status.c: Use stricter include formatting.
        (update_entries): Generalize sig_cache handling.
        (client_write_sigfile): New function factored from...
        (client_base_checkout): ...here.
        (client_base_signatures, handle_base_signatures): New functions.
        (responses): Add Base-signatures.
        (struct send_data, send_files): Remove sign flag.
        (send_fileproc): Compensate.  Send signatures for sign command.
        * client.h [SEND_SIGNATURES]: Remove.
        * commit.c (check_direntproc): Compensate.
        * main.c (cmd): Add `sign' command.
        * rcs.c (RCS_add_openpgp_signature): New function.
        * server.c (server_write_sigfile): New function factored from...
        (serve_modified): ...here.
        (serve_unchanged): Write signatures.
        (requests): Add `sign' request.
        (serve_sign, server_base_signatures): New functions.
        * server.h (server_write_sigfile): Add proto.
        * sign.c (sign_fileproc, sign): New functions.
        (sign_usage): Add.
        
        * sanity.sh (verify): Rename to...
        (openpgp): ...this and add sign test.

CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/ChangeLog.diff?only_with_tag=signed-commits3&tr1=1.3328.2.15&tr2=1.3328.2.16&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/client.c.diff?only_with_tag=signed-commits3&tr1=1.438.2.3&tr2=1.438.2.4&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/client.h.diff?only_with_tag=signed-commits3&tr1=1.61.4.1&tr2=1.61.4.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/commit.c.diff?only_with_tag=signed-commits3&tr1=1.257.2.1&tr2=1.257.2.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/log.c.diff?only_with_tag=signed-commits3&tr1=1.103.6.1&tr2=1.103.6.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/main.c.diff?only_with_tag=signed-commits3&tr1=1.262.6.3&tr2=1.262.6.4&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/mkmodules.c.diff?only_with_tag=signed-commits3&tr1=1.95&tr2=1.95.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/patch.c.diff?only_with_tag=signed-commits3&tr1=1.106&tr2=1.106.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/rcs.c.diff?only_with_tag=signed-commits3&tr1=1.356.6.2&tr2=1.356.6.3&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/rcs.h.diff?only_with_tag=signed-commits3&tr1=1.82.8.1&tr2=1.82.8.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/sanity.sh.diff?only_with_tag=signed-commits3&tr1=1.1105.2.4&tr2=1.1105.2.5&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/server.c.diff?only_with_tag=signed-commits3&tr1=1.453.2.1&tr2=1.453.2.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/server.h.diff?only_with_tag=signed-commits3&tr1=1.44.6.1&tr2=1.44.6.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/sign.c.diff?only_with_tag=signed-commits3&tr1=1.1.6.3&tr2=1.1.6.4&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/sign.h.diff?only_with_tag=signed-commits3&tr1=1.1.6.2&tr2=1.1.6.3&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/status.c.diff?only_with_tag=signed-commits3&tr1=1.68&tr2=1.68.6.1&r1=text&r2=text

Patches:
Index: ccvs/src/ChangeLog
diff -u ccvs/src/ChangeLog:1.3328.2.15 ccvs/src/ChangeLog:1.3328.2.16
--- ccvs/src/ChangeLog:1.3328.2.15      Fri Jan  6 19:34:15 2006
+++ ccvs/src/ChangeLog  Fri Jan  6 20:04:37 2006
@@ -1,5 +1,30 @@
 2006-01-06  Derek Price  <address@hidden>
 
+       * client.c, client.h, commit.c, log.c, mkmodules.c, patch.c, server.c,
+       status.c: Use stricter include formatting.
+       (update_entries): Generalize sig_cache handling.
+       (client_write_sigfile): New function factored from...
+       (client_base_checkout): ...here.
+       (client_base_signatures, handle_base_signatures): New functions.
+       (responses): Add Base-signatures.
+       (struct send_data, send_files): Remove sign flag.
+       (send_fileproc): Compensate.  Send signatures for sign command.
+       * client.h [SEND_SIGNATURES]: Remove.
+       * commit.c (check_direntproc): Compensate.
+       * main.c (cmd): Add `sign' command.  
+       * rcs.c (RCS_add_openpgp_signature): New function.
+       * server.c (server_write_sigfile): New function factored from...
+       (serve_modified): ...here.
+       (serve_unchanged): Write signatures.
+       (requests): Add `sign' request.
+       (serve_sign, server_base_signatures): New functions.
+       * server.h (server_write_sigfile): Add proto.
+       * sign.c (sign_fileproc, sign): New functions.
+       (sign_usage): Add.
+
+       * sanity.sh (verify): Rename to...
+       (openpgp): ...this and add sign test.
+
        * add.c, admin.c, annotate.c, base.c, buffer.c, checkin.c, checkout.c,
        classify.c, diff.c, edit.c, edit.h, entries.c, fileattr.c,
        find_names.c, history.c, ignore.c, import.c, lock.c, logmsg.c, ls.c,
Index: ccvs/src/client.c
diff -u ccvs/src/client.c:1.438.2.3 ccvs/src/client.c:1.438.2.4
--- ccvs/src/client.c:1.438.2.3 Sun Jan  1 23:12:37 2006
+++ ccvs/src/client.c   Fri Jan  6 20:04:37 2006
@@ -14,18 +14,25 @@
 # include "config.h"
 #endif /* HAVE_CONFIG_H */
 
-/* GNULIB */
+/* Verify interface.  */
+#include "client.h"
+
+/* GNULIB headers.  */
 #include "base64.h"
 #include "getline.h"
 #include "save-cwd.h"
 
-/* CVS */
+/* CVS headers.  */
 #include "base.h"
+#include "buffer.h"
 #include "difflib.h"
+#include "edit.h"
+#include "ignore.h"
+#include "recurse.h"
 
 #include "cvs.h"
-#include "buffer.h"
-#include "edit.h"
+
+
 
 #ifdef CLIENT_SUPPORT
 
@@ -1888,6 +1895,7 @@
        /* On checkin, create the base file.  */
        Node *n;
        bool makebase = true;
+
        if ((n = findnode_fn (ent_list, filename)))
        {
            /* This could be a readd of a locally removed file or, for
@@ -1902,31 +1910,26 @@
                /* The version number has not changed.  */
                makebase = false;
        }
+
        if (makebase)
        {
            /* A real checkin.  */
            char *basefile = make_base_file_name (filename, vn);
+
            mkdir_if_needed (CVSADM_BASE);
            copy_file (filename, basefile);
 
-           if (get_sign_commits (false, supported_request ("Signature")))
+           if ((n = findnode_fn (sig_cache, short_pathname)))
            {
-               if ((n = findnode_fn (sig_cache, short_pathname)))
-               {
-                   char *sigfile = Xasprintf ("%s%s", basefile, ".sig");
-                   write_file (sigfile, n->data, n->len);
-                   delnode (n);
-                   free (sigfile);
-               }
-               else
-               {
-                   error (0, 0,
-"Internal error: OpenPGP signature for `%s' not found in cache.",
-                          short_pathname);
-                   printlist (sig_cache);
-               }
+               char *sigfile = Xasprintf ("%s%s", basefile, ".sig");
+               write_file (sigfile, n->data, n->len);
+               delnode (n);
+               free (sigfile);
            }
-
+           else if (get_sign_commits (false, supported_request ("Signature")))
+               error (0, 0,
+"Internal error: OpenPGP signature for `%s' not found in cache.",
+                      short_pathname);
            free (basefile);
        }
     }
@@ -2198,6 +2201,32 @@
 
 
 static void
+client_write_sigfile (const char *sigfile, bool writable)
+{
+    FILE *e;
+
+    if (!stored_signatures)
+       return;
+
+    if (!writable && isfile (sigfile))
+       xchmod (sigfile, true);
+    e = xfopen (sigfile, FOPEN_BINARY_WRITE);
+    if (fwrite (stored_signatures, sizeof *stored_signatures,
+               stored_signatures_len, e) != stored_signatures_len)
+       error (1, errno, "cannot write signature file `%s'", sigfile);
+    if (fclose (e) == EOF)
+       error (0, errno, "cannot close signature file `%s'", sigfile);
+
+    if (!writable)
+       xchmod (sigfile, false);
+
+    free (stored_signatures);
+    stored_signatures = NULL;
+}
+
+
+
+static void
 client_base_checkout (void *data_arg, List *ent_list,
                      const char *short_pathname, const char *filename)
 {
@@ -2332,28 +2361,16 @@
        if (stored_signatures)
        {
            char *sigfile = Xasprintf ("%s.sig", basefile);
-           
-           if (!*istemp && isfile (sigfile))
-               xchmod (sigfile, true);
-           e = xfopen (sigfile, FOPEN_BINARY_WRITE);
-           if (fwrite (stored_signatures, sizeof *stored_signatures,
-                       stored_signatures_len, e) != stored_signatures_len)
-               error (1, errno, "cannot write signature file `%s'", sigfile);
-           if (fclose (e) == EOF)
-               error (0, errno, "cannot close signature file `%s'", sigfile);
 
-           if (!*istemp)
-               xchmod (sigfile, false);
+           client_write_sigfile (sigfile, *istemp);
 
            /* FIXME: Verify the signature here, when configured to do so.  */
 
-           if (*istemp && CVS_UNLINK (sigfile) < 0)
+           if (istemp && CVS_UNLINK (sigfile) < 0)
                error (0, errno, "Failed to remove temp sig file `%s'",
                       sigfile);
 
            free (sigfile);
-           free (stored_signatures);
-           stored_signatures = NULL;
        }
     }
 
@@ -2392,6 +2409,45 @@
 
 
 
+static void
+client_base_signatures (void *data_arg, List *ent_list,
+                       const char *short_pathname, const char *filename)
+{
+    char *rev;
+    char *basefile;
+    char *sigfile;
+
+    TRACE (TRACE_FUNCTION, "client_base_signatures (%s)", short_pathname);
+
+    if (!stored_signatures)
+       error (1, 0,
+              "Server sent `Base-signatures' response without signature.");
+
+    /* Read OPTIONS, PREV, and REV from the server.  */
+    read_line (&rev);
+
+    basefile = make_base_file_name (filename, rev);
+    sigfile = Xasprintf ("%s.sig", basefile);
+
+    client_write_sigfile (sigfile, false);
+
+    free (rev);
+    free (basefile);
+    free (sigfile);
+}
+
+
+
+static void
+handle_base_signatures (char *args, size_t len)
+{
+    if (suppress_bases)
+       error (1, 0, "Server sent Base-* response when asked not to.");
+    call_in_directory (args, client_base_signatures, NULL);
+}
+
+
+
 /* Create am up-to-date temporary workfile from a base file.  */
 static void
 client_base_copy (void *data_arg, List *ent_list, const char *short_pathname,
@@ -3750,6 +3806,8 @@
     RSP_LINE("Base-merged", handle_base_merged, response_type_normal,
             rs_optional),
 
+    RSP_LINE("Base-signatures", handle_base_signatures, response_type_normal,
+            rs_optional),
     RSP_LINE("OpenPGP-signatures", handle_openpgp_signatures,
             response_type_normal, rs_optional),
 
@@ -5141,7 +5199,6 @@
     bool force;
     bool no_contents;
     bool backup_modified;
-    bool sign;
 };
 
 /* Deal with one file.  */
@@ -5247,6 +5304,18 @@
                        ? vers->ts_conflict : vers->ts_rcs, vers->ts_user)
             || (vers->ts_conflict && !strcmp (cvs_cmd_name, "diff")))
     {
+       if (!strcmp (cvs_cmd_name, "sign")
+           || (!strcmp (cvs_cmd_name, "commit")
+               && get_sign_commits (false, supported_request ("Signature"))))
+       {
+           if (!supported_request ("Signature"))
+               error (1, 0, "Server doesn't support commit signatures.");
+
+           send_signature (Short_Repository (finfo->repository),
+                           finfo->file, finfo->fullname,
+                           vers && !strcmp (vers->options, "-kb"));
+       }
+
        if (args->no_contents
            && supported_request ("Is-modified"))
        {
@@ -5255,19 +5324,7 @@
            send_to_server ("\012", 1);
        }
        else
-       {
-           if (args->sign
-               && get_sign_commits (false, supported_request ("Signature")))
-           {
-               if (!supported_request ("Signature"))
-                   error (1, 0, "Server doesn't support commit signatures.");
-
-               send_signature (Short_Repository (finfo->repository),
-                               finfo->file, finfo->fullname,
-                               vers && !strcmp (vers->options, "-kb"));
-           }
            send_modified (filename, finfo->fullname, vers);
-       }
 
         if (args->backup_modified)
         {
@@ -5283,6 +5340,16 @@
     }
     else
     {
+       if (!strcmp (cvs_cmd_name, "sign"))
+       {
+           if (!supported_request ("Signature"))
+               error (1, 0, "Server doesn't support commit signatures.");
+
+           send_signature (Short_Repository (finfo->repository),
+                           finfo->file, finfo->fullname,
+                           vers && !strcmp (vers->options, "-kb"));
+       }
+
        send_to_server ("Unchanged ", 0);
        send_to_server (filename, 0);
        send_to_server ("\012", 1);
@@ -5666,7 +5733,6 @@
  *             server as though they were modified.
  *             FLAGS & SEND_NO_CONTENTS means that this command only needs to
  *             know _whether_ a file is modified, not the contents.
- *   sign      Whether to send files signatures.
  *
  * RETURNS
  *   Nothing.
@@ -5688,7 +5754,6 @@
     args.force = flags & SEND_FORCE;
     args.no_contents = flags & SEND_NO_CONTENTS;
     args.backup_modified = flags & BACKUP_MODIFIED_FILES;
-    args.sign = flags & SEND_SIGNATURES;
     err = start_recursion
        (send_fileproc, send_filesdoneproc, send_dirent_proc,
          send_dirleave_proc, &args, argc, argv, local, W_LOCAL, aflag,
Index: ccvs/src/client.h
diff -u ccvs/src/client.h:1.61.4.1 ccvs/src/client.h:1.61.4.2
--- ccvs/src/client.h:1.61.4.1  Wed Dec 21 13:25:10 2005
+++ ccvs/src/client.h   Fri Jan  6 20:04:37 2006
@@ -14,6 +14,14 @@
 
 /* Interface between the client and the rest of CVS.  */
 
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include <sys/types.h>
+#include "root.h"
+
+
+
 /* Stuff shared with the server.  */
 char *mode_to_string (mode_t);
 int change_mode (const char *, const char *, int);
@@ -120,7 +128,6 @@
 # define SEND_FORCE            (1 << 1)
 # define SEND_NO_CONTENTS      (1 << 2)
 # define BACKUP_MODIFIED_FILES (1 << 3)
-# define SEND_SIGNATURES       (1 << 4)
 
 /* Send an argument to the remote server.  */
 void
@@ -219,3 +226,5 @@
 #endif
 
 #endif /* CLIENT_SUPPORT */
+
+#endif /* CLIENT_H */
Index: ccvs/src/commit.c
diff -u ccvs/src/commit.c:1.257.2.1 ccvs/src/commit.c:1.257.2.2
--- ccvs/src/commit.c:1.257.2.1 Wed Dec 21 13:25:10 2005
+++ ccvs/src/commit.c   Fri Jan  6 20:04:37 2006
@@ -19,12 +19,23 @@
  *
  */
 
-#include "cvs.h"
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* GNULIB headers.  */
 #include "getline.h"
+
+/* CVS headers.  */
 #include "edit.h"
+#include "ignore.h"
+#include "logmsg.h"
+#include "recurse.h"
+#include "sign.h"
+
+#include "cvs.h"
 #include "fileattr.h"
 #include "hardlink.h"
-#include "sign.h"
 
 static Dtype check_direntproc (void *callerdat, const char *dir,
                                const char *repos, const char *update_dir,
@@ -612,7 +623,6 @@
           program, which we used to call, wanted the file to exist,
           then it would be relatively simple to fix in the server.  */
        flags = find_args.force ? SEND_FORCE : 0;
-       flags |= SEND_SIGNATURES;
        send_files (find_args.argc, find_args.argv, local, 0, flags);
 
        /* Sending only the names of the files which were modified, added,
@@ -937,9 +947,10 @@
                 * be intentionally signing a file with keywords.  Such a file
                 * may still be verified when checked out -ko.
                 */
-               error (0, 0,
+               if (!quiet)
+                   error (0, 0,
 "warning: signed file `%s' contains at least one RCS keyword",
-                      finfo->fullname);
+                          finfo->fullname);
            }
 
            if (status == T_REMOVED)
Index: ccvs/src/log.c
diff -u ccvs/src/log.c:1.103.6.1 ccvs/src/log.c:1.103.6.2
--- ccvs/src/log.c:1.103.6.1    Fri Dec 30 23:26:32 2005
+++ ccvs/src/log.c      Fri Jan  6 20:04:38 2006
@@ -29,6 +29,8 @@
 
 /* CVS Headers.  */
 #include "gpg.h"
+#include "ignore.h"
+#include "recurse.h"
 
 #include "cvs.h"
 
Index: ccvs/src/main.c
diff -u ccvs/src/main.c:1.262.6.3 ccvs/src/main.c:1.262.6.4
--- ccvs/src/main.c:1.262.6.3   Sun Jan  1 23:12:37 2006
+++ ccvs/src/main.c     Fri Jan  6 20:04:38 2006
@@ -182,6 +182,7 @@
 #ifdef SERVER_SUPPORT
     { "server",   NULL,       NULL,        server,    
CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
 #endif
+    { "sign",   "sig",        NULL,        sign,      0 },
     { "status",   "st",       "stat",      cvsstatus, CVS_CMD_USES_WORK_DIR },
     { "tag",      "ta",       "freeze",    cvstag,    
CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
     { "unedit",   NULL,       NULL,        unedit,    
CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
Index: ccvs/src/mkmodules.c
diff -u /dev/null ccvs/src/mkmodules.c:1.95.2.1
--- /dev/null   Fri Jan  6 20:04:39 2006
+++ ccvs/src/mkmodules.c        Fri Jan  6 20:04:38 2006
@@ -0,0 +1,1295 @@
+/*
+ * 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.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* GNULIB headers.  */
+#include "getline.h"
+#include "save-cwd.h"
+
+/* CVS headers.  */
+#include "history.h"
+#include "ignore.h"
+
+#include "cvs.h"
+
+
+
+#ifndef DBLKSIZ
+#define        DBLKSIZ 4096                    /* since GNU ndbm doesn't 
define it */
+#endif
+
+static int checkout_file (char *file, char *temp);
+static char *make_tempfile (void);
+static void rename_rcsfile (char *temp, char *real);
+
+#ifndef MY_NDBM
+static void rename_dbmfile (char *temp);
+static void write_dbmfile (char *temp);
+#endif                         /* !MY_NDBM */
+
+/* Structure which describes an administrative file.  */
+struct admin_file {
+   /* Name of the file, within the CVSROOT directory.  */
+   char *filename;
+
+   /* This is a one line description of what the file is for.  It is not
+      currently used, although one wonders whether it should be, somehow.
+      If NULL, then don't process this file in mkmodules (FIXME?: a bit of
+      a kludge; probably should replace this with a flags field).  */
+   char *errormsg;
+
+   /* Contents which the file should have in a new repository.  To avoid
+      problems with brain-dead compilers which choke on long string constants,
+      this is a pointer to an array of char * terminated by NULL--each of
+      the strings is concatenated.
+
+      If this field is NULL, the file is not created in a new
+      repository, but it can be added with "cvs add" (just as if one
+      had created the repository with a version of CVS which didn't
+      know about the file) and the checked-out copy will be updated
+      without having to add it to checkoutlist.  */
+   const char * const *contents;
+};
+
+static const char *const loginfo_contents[] = {
+    "# The \"loginfo\" file controls where \"cvs commit\" log information 
is\n",
+    "# sent. The first entry on a line is a regular expression which must\n",
+    "# match the directory that the change is being made to, relative to 
the\n",
+    "# $CVSROOT.  If a match is found, then the remainder of the line is a\n",
+    "# filter program that should expect log information on its standard 
input.\n",
+    "#\n",
+    "# If the repository name does not match any of the regular expressions in 
this\n",
+    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+    "#\n",
+    "# If the name ALL appears as a regular expression it is always used\n",
+    "# in addition to the first matching regex or DEFAULT.\n",
+    "#\n",
+    "# If any format strings are present in the filter, they will be 
replaced\n",
+    "# as follows:\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#    %{sVv} = attribute list = file name, old version number 
(pre-checkin),\n",
+    "#           new version number (post-checkin).  When either old or new 
revision\n",
+    "#           is unknown, doesn't exist, or isn't applicable, the string 
\"NONE\"\n",
+    "#           will be placed on the command line instead.\n",
+    "#\n",
+    "# Note that %{sVv} is a list operator and not all elements are 
necessary.\n",
+    "# Thus %{sv} is a legal format string, but will only be replaced with\n",
+    "# file name and new revision.\n",
+    "# It also generates multiple arguments for each file being operated 
upon.\n",
+    "# That is, if two files, file1 & file2, are being commited from 1.1 to\n",
+    "# version 1.1.2.1 and from 1.1.2.2 to 1.1.2.3, respectively, %{sVv} 
will\n",
+    "# generate the following six arguments in this order:\n",
+    "# file1, 1.1, 1.1.2.1, file2, 1.1.2.2, 1.1.2.3.\n",
+    "#\n",
+    "# For example:\n",
+    "#DEFAULT (echo \"\"; id; echo %s; date; cat) >> 
$CVSROOT/CVSROOT/commitlog\n",
+    "# or\n",
+    "#DEFAULT (echo \"\"; id; echo %{sVv}; date; cat) >> 
$CVSROOT/CVSROOT/commitlog\n",
+    NULL
+};
+
+static const char *const rcsinfo_contents[] = {
+    "# The \"rcsinfo\" file is used to control templates with which the 
editor\n",
+    "# is invoked on commit and import.\n",
+    "#\n",
+    "# The first entry on a line is a regular expression which is tested\n",
+    "# against the directory that the change is being made to, relative to 
the\n",
+    "# $CVSROOT.  For the first match that is found, then the remainder of 
the\n",
+    "# line is the name of the file that contains the template.\n",
+    "#\n",
+    "# If the repository name does not match any of the regular expressions in 
this\n",
+    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+    "#\n",
+    "# If the name \"ALL\" appears as a regular expression it is always 
used\n",
+    "# in addition to the first matching regex or \"DEFAULT\".\n",
+    NULL
+};
+
+
+
+static const char *const verifymsg_contents[] = {
+    "# The \"verifymsg\" file is used to allow verification of logging\n",
+    "# information.  It works best when a template (as specified in the\n",
+    "# rcsinfo file) is provided for the logging procedure.  Given a\n",
+    "# template with locations for, a bug-id number, a list of people who\n",
+    "# reviewed the code before it can be checked in, and an external\n",
+    "# process to catalog the differences that were code reviewed, the\n",
+    "# following test can be applied to the code:\n",
+    "#\n",
+    "#   Making sure that the entered bug-id number is correct.\n",
+    "#   Validating that the code that was reviewed is indeed the code 
being\n",
+    "#       checked in (using the bug-id number or a seperate review\n",
+    "#       number to identify this particular code set.).\n",
+    "#\n",
+    "# If any of the above test failed, then the commit would be aborted.\n",
+    "#\n",
+    "# Format strings present in the filter will be replaced as follows:\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#    %l = name of log file to be verified.\n",
+    "#\n",
+    "# If no format strings are present in the filter, a default \" %l\" 
will\n",
+    "# be appended to the filter, but this usage is deprecated.\n",
+    "#\n",
+    "# Actions such as mailing a copy of the report to each reviewer are\n",
+    "# better handled by an entry in the loginfo file.\n",
+    "#\n",
+    "# One thing that should be noted is the the ALL keyword is not\n",
+    "# supported.  There can be only one entry that matches a given\n",
+    "# repository.\n",
+    NULL
+};
+
+static const char *const commitinfo_contents[] = {
+    "# The \"commitinfo\" file is used to control pre-commit checks.\n",
+    "# The filter on the right is invoked with the repository and a list \n",
+    "# of files to check.  A non-zero exit of the filter program will \n",
+    "# cause the commit to be aborted.\n",
+    "#\n",
+    "# The first entry on a line is a regular expression which is tested\n",
+    "# against the directory that the change is being committed to, 
relative\n",
+    "# to the $CVSROOT.  For the first match that is found, then the 
remainder\n",
+    "# of the line is the name of the filter to run.\n",
+    "#\n",
+    "# Format strings present in the filter will be replaced as follows:\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#    %{s} = file name, file name, ...\n",
+    "#\n",
+    "# If no format strings are present in the filter string, a default of\n",
+    "# \" %r %s\" will be appended to the filter string, but this usage is\n",
+    "# deprecated.\n",
+    "#\n",
+    "# If the repository name does not match any of the regular expressions in 
this\n",
+    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+    "#\n",
+    "# If the name \"ALL\" appears as a regular expression it is always 
used\n",
+    "# in addition to the first matching regex or \"DEFAULT\".\n",
+    NULL
+};
+
+static const char *const taginfo_contents[] = {
+    "# The \"taginfo\" file is used to control pre-tag checks.\n",
+    "# The filter on the right is invoked with the following arguments\n",
+    "# if no format strings are present:\n",
+    "#\n",
+    "# $1 -- tagname\n",
+    "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for 
tag -d\n",
+    "# $3 -- tagtype \"?\" on delete, \"T\" for branch, \"N\" for static\n",
+    "# $4 -- repository\n",
+    "# $5->  file revision [file revision ...]\n",
+    "#\n",
+    "# If any format strings are present in the filter, they will be 
replaced\n",
+    "# as follows:\n",
+    "#    %b = branch mode = \"?\" (delete ops - unknown) | \"T\" (branch)\n",
+    "#                     | \"N\" (not branch)\n",
+    "#    %o = operation = \"add\" | \"mov\" | \"del\"\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#    %t = tagname\n",
+    "#    %{sVv} = attribute list = file name, old version tag will be 
deleted\n",
+    "#             from, new version tag will be added to (or deleted from, 
but\n",
+    "#             this feature is deprecated.  When either old or new 
revision is\n",
+    "#             unknown, doesn't exist, or isn't applicable, the string 
\"NONE\"\n",
+    "#             will be placed on the command line.\n",
+    "#\n",
+    "# Note that %{sVv} is a list operator and not all elements are 
necessary.\n",
+    "# Thus %{sV} is a legal format string, but will only be replaced with 
file\n",
+    "# name and old revision. it also generates multiple arguments for each 
file\n",
+    "# being operated upon.  i.e. if two files, file1 & file2, are having a 
tag\n",
+    "# moved from version 1.1 to version 1.1.2.9, %{sVv} will generate the\n",
+    "# following six arguments in this order:\n",
+    "# file1, 1.1, 1.1.2.9, file2, 1.1, 1.1.2.9.\n",
+    "#\n",
+    "# A non-zero exit of the filter program will cause the tag to be 
aborted.\n",
+    "#\n",
+    "# The first entry on a line is a regular expression which is tested\n",
+    "# against the directory that the change is being committed to, 
relative\n",
+    "# to the $CVSROOT.  For the first match that is found, then the 
remainder\n",
+    "# of the line is the name of the filter to run.\n",
+    "#\n",
+    "# If the repository name does not match any of the regular expressions in 
this\n",
+    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+    "#\n",
+    "# If the name \"ALL\" appears as a regular expression it is always 
used\n",
+    "# in addition to the first matching regex or \"DEFAULT\".\n",
+    NULL
+};
+
+static const char *const preproxy_contents[] = {
+    "# The \"preproxy\" file is called form the secondary server as soon as\n",
+    "# the secondary server determines that it will be proxying a write\n",
+    "# command to a primary server and immediately before it opens a\n",
+    "# connection to the primary server.  This script might, for example, 
be\n",
+    "# used to launch a dial up or VPN connection to the primary server's\n",
+    "# network.\n",
+    "#\n",
+    "# If any format strings are present in the filter, they will be 
replaced\n",
+    "# as follows:\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository (currently always \".\")\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#\n",
+    "# The first entry on a line is a regular expression which is tested\n",
+    "# against the directory that the change is being committed to, 
relative\n",
+    "# to the $CVSROOT.  For the first match that is found, then the 
remainder\n",
+    "# of the line is the name of the filter to run.\n",
+    "#\n",
+    "# If the repository name does not match any of the regular expressions in 
this\n",
+    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+    "#\n",
+    "# If the name \"ALL\" appears as a regular expression it is always 
used\n",
+    "# in addition to the first matching regex or \"DEFAULT\".\n",
+    NULL
+};
+
+static const char *const postadmin_contents[] = {
+    "# The \"postadmin\" file is called after the \"admin\" command 
finishes\n",
+    "# processing a directory.\n",
+    "#\n",
+    "# If any format strings are present in the filter, they will be 
replaced\n",
+    "# as follows:\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#\n",
+    "# The first entry on a line is a regular expression which is tested\n",
+    "# against the directory that the change is being committed to, 
relative\n",
+    "# to the $CVSROOT.  For the first match that is found, then the 
remainder\n",
+    "# of the line is the name of the filter to run.\n",
+    "#\n",
+    "# If the repository name does not match any of the regular expressions in 
this\n",
+    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+    "#\n",
+    "# If the name \"ALL\" appears as a regular expression it is always 
used\n",
+    "# in addition to the first matching regex or \"DEFAULT\".\n",
+    NULL
+};
+
+static const char *const postproxy_contents[] = {
+    "# The \"postproxy\" file is called from a secondary server as soon as\n",
+    "# the secondary server closes its connection to the primary server.\n",
+    "# This script might, for example, be used to shut down a dial up\n",
+    "# or VPN connection to the primary server's network.\n",
+    "#\n",
+    "# If any format strings are present in the filter, they will be 
replaced\n",
+    "# as follows:\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository (currently always \".\")\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#\n",
+    "# The first entry on a line is a regular expression which is tested\n",
+    "# against the directory that the change is being committed to, 
relative\n",
+    "# to the $CVSROOT.  For the first match that is found, then the 
remainder\n",
+    "# of the line is the name of the filter to run.\n",
+    "#\n",
+    "# If the repository name does not match any of the regular expressions in 
this\n",
+    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+    "#\n",
+    "# If the name \"ALL\" appears as a regular expression it is always 
used\n",
+    "# in addition to the first matching regex or \"DEFAULT\".\n",
+    NULL
+};
+
+static const char *const posttag_contents[] = {
+    "# The \"posttag\" file is called after the \"tag\" command finishes\n",
+    "# processing a directory.\n",
+    "#\n",
+    "# If any format strings are present in the filter, they will be 
replaced\n",
+    "# as follows:\n",
+    "#    %b = branch mode = \"?\" (delete ops - unknown) | \"T\" (branch)\n",
+    "#                     | \"N\" (not branch)\n",
+    "#    %o = operation = \"add\" | \"mov\" | \"del\"\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#    %t = tagname\n",
+    "#    %{sVv} = attribute list = file name, old version tag will be 
deleted\n",
+    "#             from, new version tag will be added to (or deleted from, 
but\n",
+    "#             this feature is deprecated.  When either old or new 
revision is\n",
+    "#             unknown, doesn't exist, or isn't applicable, the string 
\"NONE\"\n",
+    "#             will be placed on the command line.\n",
+    "#\n",
+    "# Note that %{sVv} is a list operator and not all elements are 
necessary.\n",
+    "# Thus %{sV} is a legal format string, but will only be replaced with 
file\n",
+    "# name and old revision. it also generates multiple arguments for each 
file\n",
+    "# being operated upon.  i.e. if two files, file1 & file2, are having a 
tag\n",
+    "# moved from version 1.1 to version 1.1.2.9, %{sVv} will generate the\n",
+    "# following six arguments in this order:\n",
+    "# file1, 1.1, 1.1.2.9, file2, 1.1, 1.1.2.9.\n",
+    "#\n",
+    "# The first entry on a line is a regular expression which is tested\n",
+    "# against the directory that the change is being committed to, 
relative\n",
+    "# to the $CVSROOT.  For the first match that is found, then the 
remainder\n",
+    "# of the line is the name of the filter to run.\n",
+    "#\n",
+    "# If the repository name does not match any of the regular expressions in 
this\n",
+    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+    "#\n",
+    "# If the name \"ALL\" appears as a regular expression it is always 
used\n",
+    "# in addition to the first matching regex or \"DEFAULT\".\n",
+    NULL
+};
+
+static const char *const postwatch_contents[] = {
+    "# The \"postwatch\" file is called after any command finishes writing 
new\n",
+    "# file attibute (watch/edit) information in a directory.\n",
+    "#\n",
+    "# If any format strings are present in the filter, they will be 
replaced\n",
+    "# as follows:\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#\n",
+    "# The first entry on a line is a regular expression which is tested\n",
+    "# against the directory that the change is being committed to, 
relative\n",
+    "# to the $CVSROOT.  For the first match that is found, then the 
remainder\n",
+    "# of the line is the name of the filter to run.\n",
+    "#\n",
+    "# If the repository name does not match any of the regular expressions in 
this\n",
+    "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+    "#\n",
+    "# If the name \"ALL\" appears as a regular expression it is always 
used\n",
+    "# in addition to the first matching regex or \"DEFAULT\".\n",
+    NULL
+};
+
+static const char *const checkoutlist_contents[] = {
+    "# The \"checkoutlist\" file is used to support additional version 
controlled\n",
+    "# administrative files in $CVSROOT/CVSROOT, such as template files.\n",
+    "#\n",
+    "# The first entry on a line is a filename which will be checked out 
from\n",
+    "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n",
+    "# The remainder of the line is an error message to use if the file 
cannot\n",
+    "# be checked out.\n",
+    "#\n",
+    "# File format:\n",
+    "#\n",
+    "# [<whitespace>]<filename>[<whitespace><error message>]<end-of-line>\n",
+    "#\n",
+    "# comment lines begin with '#'\n",
+    NULL
+};
+
+static const char *const cvswrappers_contents[] = {
+    "# This file affects handling of files based on their names.\n",
+    "#\n",
+#if 0    /* see comments in wrap_add in wrapper.c */
+    "# The -t/-f options allow one to treat directories of files\n",
+    "# as a single file, or to transform a file in other ways on\n",
+    "# its way in and out of CVS.\n",
+    "#\n",
+#endif
+    "# The -m option specifies whether CVS attempts to merge files.\n",
+    "#\n",
+    "# The -k option specifies keyword expansion (e.g. -kb for binary).\n",
+    "#\n",
+    "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or 
.cvswrappers)\n",
+    "#\n",
+    "#  wildcard       [option value][option value]...\n",
+    "#\n",
+    "#  where option is one of\n",
+    "#  -f             from cvs filter         value: path to filter\n",
+    "#  -t             to cvs filter           value: path to filter\n",
+    "#  -m             update methodology      value: MERGE or COPY\n",
+    "#  -k             expansion mode          value: b, o, kkv, &c\n",
+    "#\n",
+    "#  and value is a single-quote delimited value.\n",
+    "# For example:\n",
+    "#*.gif -k 'b'\n",
+    NULL
+};
+
+static const char *const notify_contents[] = {
+    "# The \"notify\" file controls where notifications from watches set by\n",
+    "# \"cvs watch add\" or \"cvs edit\" are sent.  The first entry on a line 
is\n",
+    "# a regular expression which is tested against the directory that the\n",
+    "# change is being made to, relative to the $CVSROOT.  If it matches,\n",
+    "# then the remainder of the line is a filter program that should 
contain\n",
+    "# one occurrence of %s for the user to notify, and information on its\n",
+    "# standard input.\n",
+    "#\n",
+    "# \"ALL\" or \"DEFAULT\" can be used in place of the regular 
expression.\n",
+    "#\n",
+    "# format strings are replaceed as follows:\n",
+    "#    %c = canonical name of the command being executed\n",
+#ifdef PROXY_SUPPORT
+    "#    %R = the name of the referrer, if any, otherwise the value NONE\n",
+#endif
+    "#    %p = path relative to repository\n",
+    "#    %r = repository (path portion of $CVSROOT)\n",
+    "#    %s = user to notify\n",
+    "#\n",
+    "# For example:\n",
+    "#ALL (echo Committed to %r/%p; cat) |mail %s -s \"CVS notification\"\n",
+    NULL
+};
+
+static const char *const modules_contents[] = {
+    "# Three different line formats are valid:\n",
+    "# key     -a    aliases...\n",
+    "# key [options] directory\n",
+    "# key [options] directory files...\n",
+    "#\n",
+    "# Where \"options\" are composed of:\n",
+    "# -i prog         Run \"prog\" on \"cvs commit\" from top-level of 
module.\n",
+    "# -o prog         Run \"prog\" on \"cvs checkout\" of module.\n",
+    "# -e prog         Run \"prog\" on \"cvs export\" of module.\n",
+    "# -t prog         Run \"prog\" on \"cvs rtag\" of module.\n",
+    "# -u prog         Run \"prog\" on \"cvs update\" of module.\n",
+    "# -d dir          Place module in directory \"dir\" instead of module 
name.\n",
+    "# -l              Top-level directory only -- do not recurse.\n",
+    "#\n",
+    "# NOTE:  If you change any of the \"Run\" options above, you'll have 
to\n",
+    "# release and re-checkout any working directories of these modules.\n",
+    "#\n",
+    "# And \"directory\" is a path to a directory relative to $CVSROOT.\n",
+    "#\n",
+    "# The \"-a\" option specifies an alias.  An alias is interpreted as if\n",
+    "# everything on the right of the \"-a\" had been typed on the command 
line.\n",
+    "#\n",
+    "# You can encode a module within a module by using the special '&'\n",
+    "# character to interpose another module into the current module.  This\n",
+    "# can be useful for creating a module that consists of many 
directories\n",
+    "# spread out over the entire source repository.\n",
+    NULL
+};
+
+static const char *const config_contents[] = {
+    "# Set `SystemAuth' to `no' if pserver shouldn't check system 
users/passwords.\n",
+    "#SystemAuth=no\n",
+    "\n",
+    "# Set `LocalKeyword' to specify a local alias for a standard keyword.\n",
+    "#LocalKeyword=MYCVS=CVSHeader\n",
+    "\n",
+    "# Set `KeywordExpand' to `i' followed by a list of keywords to expand 
or\n",
+    "# `e' followed by a list of keywords to not expand.\n"
+    "#KeywordExpand=iMYCVS,Name,Date\n",
+    "#KeywordExpand=eCVSHeader\n",
+    "\n",
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+    "# Set `PreservePermissions' to `yes' to save file status information\n",
+    "# in the repository.\n",
+    "#PreservePermissions=no\n",
+    "\n",
+#endif
+    "# Set `TopLevelAdmin' to `yes' to create a CVS directory at the top\n",
+    "# level of the new working directory when using the `cvs checkout'\n",
+    "# command.\n",
+    "#TopLevelAdmin=no\n",
+    "\n",
+    "# Put CVS lock files in this directory rather than directly in the 
repository.\n",
+    "#LockDir=/var/lock/cvs\n",
+    "\n",
+    "# Set `LogHistory' to `all' or `" ALL_HISTORY_REC_TYPES "' to log all 
transactions to the\n",
+    "# history file, or a subset as needed (ie `TMAR' logs all write 
operations)\n",
+    "#LogHistory=" ALL_HISTORY_REC_TYPES "\n",
+    "\n",
+    "# Set `RereadLogAfterVerify' to `always' (the default) to allow the 
verifymsg\n",
+    "# script to change the log message.  Set it to `stat' to force CVS to 
verify\n",
+    "# that the file has changed before reading it (this can take up to an 
extra\n",
+    "# second per directory being committed, so it is not recommended for 
large\n",
+    "# repositories.  Set it to `never' (the previous CVS behavior) to 
prevent\n",
+    "# verifymsg scripts from changing the log message.\n",
+    "#RereadLogAfterVerify=always\n",
+    "\n",
+    "# Set `UserAdminOptions' to the list of `cvs admin' commands (options)\n",
+    "# that users not in the `cvsadmin' group are allowed to run.  This\n",
+    "# defaults to `k', or only allowing the changing of the default\n",
+    "# keyword expansion mode for files for users not in the `cvsadmin' 
group.\n",
+    "# This value is ignored if the `cvsadmin' group does not exist.\n",
+    "#\n",
+    "# The following string would enable all `cvs admin' commands for all\n",
+    "# users:\n",
+    "#UserAdminOptions=aAbceIklLmnNostuU;execute;no-execute\n",
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+    "\n",
+    "# Set `UseNewInfoFmtStrings' to `no' if you must support a legacy system 
by\n",
+    "# enabling the deprecated old style info file command line format 
strings.\n",
+    "# Be warned that these strings could be disabled in any new version of 
CVS.\n",
+    "UseNewInfoFmtStrings=yes\n",
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+    "\n",
+    "# Set `ImportNewFilesToVendorBranchOnly' to `yes' if you wish to force\n",
+    "# every `cvs import' command to behave as if the `-X' flag was\n",
+    "# specified.\n",
+    "#ImportNewFilesToVendorBranchOnly=no\n",
+#ifdef PROXY_SUPPORT
+    "\n",
+    "# Set `PrimaryServer' to the CVSROOT to the primary, or write, server 
when\n",
+    "# establishing one or more read-only mirrors which serve as proxies 
for\n",
+    "# the write server in write mode or redirect the client to the primary 
for\n",
+    "# write requests.\n",
+    "#\n",
+    "# For example:\n",
+    "#\n",
+    "#   PrimaryServer=:fork:localhost/cvsroot\n",
+    "\n",
+    "# Set `MaxProxyBufferSize' to the the maximum allowable secondary\n",
+    "# buffer memory cache size before the buffer begins being stored to disk, 
in\n",
+    "# bytes.  Must be a positive integer but may end in `k', `M', `G', or `T' 
(for\n",
+    "# kiilo, mega, giga, & tera, respectively).  If an otherwise valid number 
you\n",
+    "# specify is greater than the SIZE_MAX defined by your system's C 
compiler,\n",
+    "# then it will be resolved to SIZE_MAX without a warning.  Defaults to 8M 
(8\n",
+    "# megabytes).\n",
+    "#\n",
+    "# High values for MaxProxyBufferSize may speed up a secondary server\n",
+    "# with old hardware and a lot of available memory but can actually slow 
a\n",
+    "# modern system down slightly.\n",
+    "#\n",
+    "# For example:\n",
+    "#\n",
+    "#   MaxProxyBufferSize=1G\n",
+#endif /* PROXY_SUPPORT */
+    "\n",
+    "# Set `MaxCommentLeaderLength' to the maximum length permitted for the\n",
+    "# automagically determined comment leader used when expanding the Log\n",
+    "# keyword, in bytes.  CVS's behavior when the automagically determined\n",
+    "# comment leader exceeds this length is dependant on the value of\n",
+    "# `UseArchiveCommentLeader' set in this file.  `unlimited' is a valid\n",
+    "# setting for this value.  Defaults to 20 bytes.\n",
+    "#\n",
+    "# For example:\n",
+    "#\n",
+    "#   MaxCommentLeaderLength=20\n",
+    "\n",
+    "# Set `UseArchiveCommentLeader' to `yes' to cause CVS to fall back on\n",
+    "# the comment leader set in the RCS archive file, if any, when the\n",
+    "# automagically determined comment leader exceeds 
`MaxCommentLeaderLength'\n",
+    "# bytes.  If `UseArchiveCommentLeader' is not set and a comment leader\n",
+    "# greater than `MaxCommentLeaderLength' is calculated, the Log keyword\n",
+    "# being examined will not be expanded.  Defaults to `no'.\n",
+    "#\n",
+    "# For example:\n",
+    "#\n",
+    "#   UseArchiveCommentLeader=no\n",
+    NULL
+};
+
+static const struct admin_file filelist[] = {
+    {CVSROOTADM_CHECKOUTLIST,
+       "a %s file can specify extra CVSROOT files to auto-checkout",
+       checkoutlist_contents},
+    {CVSROOTADM_COMMITINFO,
+       "a %s file can be used to configure 'cvs commit' checking",
+       commitinfo_contents},
+    {CVSROOTADM_IGNORE,
+       "a %s file can be used to specify files to ignore",
+       NULL},
+    {CVSROOTADM_LOGINFO, 
+       "no logging of 'cvs commit' messages is done without a %s file",
+       &loginfo_contents[0]},
+    {CVSROOTADM_MODULES,
+       /* modules is special-cased in mkmodules.  */
+       NULL,
+       modules_contents},
+    {CVSROOTADM_NOTIFY,
+       "a %s file can be used to specify where notifications go",
+       notify_contents},
+    {CVSROOTADM_POSTADMIN,
+       "a %s file can be used to configure 'cvs admin' logging",
+       postadmin_contents},
+    {CVSROOTADM_POSTPROXY,
+       "a %s file can be used to close or log connections to a primary server",
+       postproxy_contents},
+    {CVSROOTADM_POSTTAG,
+       "a %s file can be used to configure 'cvs tag' logging",
+       posttag_contents},
+    {CVSROOTADM_POSTWATCH,
+       "a %s file can be used to configure 'cvs watch' logging",
+       postwatch_contents},
+    {CVSROOTADM_PREPROXY,
+       "a %s file can be used to open or log connections to a primary server",
+       preproxy_contents},
+    {CVSROOTADM_RCSINFO,
+       "a %s file can be used to configure 'cvs commit' templates",
+       rcsinfo_contents},
+    {CVSROOTADM_READERS,
+       "a %s file specifies read-only users",
+       NULL},
+    {CVSROOTADM_TAGINFO,
+       "a %s file can be used to configure 'cvs tag' checking",
+       taginfo_contents},
+    {CVSROOTADM_VERIFYMSG,
+       "a %s file can be used to validate log messages",
+       verifymsg_contents},
+    {CVSROOTADM_WRAPPER,
+       "a %s file can be used to specify files to treat as wrappers",
+       cvswrappers_contents},
+    {CVSROOTADM_WRITERS,
+       "a %s file specifies read/write users",
+       NULL},
+
+    /* Some have suggested listing CVSROOTADM_PASSWD here too.  This
+       would mean that CVS commands which operate on the
+       CVSROOTADM_PASSWD file would transmit hashed passwords over the
+       net.  This might seem to be no big deal, as pserver normally
+       transmits cleartext passwords, but the difference is that
+       CVSROOTADM_PASSWD contains *all* passwords, not just the ones
+       currently being used.  For example, it could be too easy to
+       accidentally give someone readonly access to CVSROOTADM_PASSWD
+       (e.g. via anonymous CVS or cvsweb), and then if there are any
+       guessable passwords for read/write access (usually there will be)
+       they get read/write access.
+
+       Another worry is the implications of storing old passwords--if
+       someone used a password in the past they might be using it
+       elsewhere, using a similar password, etc, and so saving old
+       passwords, even hashed, is probably not a good idea.  */
+
+    {CVSROOTADM_CONFIG,
+        "a %s file configures various behaviors",
+        config_contents},
+    {NULL, NULL, NULL}
+};
+
+/* Rebuild the checked out administrative files in directory DIR.  */
+int
+mkmodules (char *dir)
+{
+    struct saved_cwd cwd;
+    char *temp;
+    char *cp, *last, *fname;
+#ifdef MY_NDBM
+    DBM *db;
+#endif
+    FILE *fp;
+    char *line = NULL;
+    size_t line_allocated = 0;
+    const struct admin_file *fileptr;
+
+    if (noexec)
+       return 0;
+
+    if (save_cwd (&cwd))
+       error (1, errno, "Failed to save current directory.");
+
+    if (CVS_CHDIR (dir) < 0)
+       error (1, errno, "cannot chdir to %s", dir);
+
+    /*
+     * First, do the work necessary to update the "modules" database.
+     */
+    temp = make_tempfile ();
+    switch (checkout_file (CVSROOTADM_MODULES, temp))
+    {
+
+       case 0:                 /* everything ok */
+#ifdef MY_NDBM
+           /* open it, to generate any duplicate errors */
+           if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL)
+               dbm_close (db);
+#else
+           write_dbmfile (temp);
+           rename_dbmfile (temp);
+#endif
+           rename_rcsfile (temp, CVSROOTADM_MODULES);
+           break;
+
+       default:
+           error (0, 0,
+               "'cvs checkout' is less functional without a %s file",
+               CVSROOTADM_MODULES);
+           break;
+    }                                  /* switch on checkout_file() */
+
+    if (unlink_file (temp) < 0
+       && !existence_error (errno))
+       error (0, errno, "cannot remove %s", temp);
+    free (temp);
+
+    /* Checkout the files that need it in CVSROOT dir */
+    for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) {
+       if (fileptr->errormsg == NULL)
+           continue;
+       temp = make_tempfile ();
+       if (checkout_file (fileptr->filename, temp) == 0)
+           rename_rcsfile (temp, fileptr->filename);
+       /* else
+        *   If there was some problem other than the file not existing,
+        *   checkout_file already printed a real error message.  If the
+        *   file does not exist, it is harmless--it probably just means
+        *   that the repository was created with an old version of CVS
+        *   which didn't have so many files in CVSROOT.
+        */
+
+       if (unlink_file (temp) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", temp);
+       free (temp);
+    }
+
+    fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r");
+    if (fp)
+    {
+       /*
+        * File format:
+        *  [<whitespace>]<filename>[<whitespace><error message>]<end-of-line>
+        *
+        * comment lines begin with '#'
+        */
+       while (getline (&line, &line_allocated, fp) >= 0)
+       {
+           /* skip lines starting with # */
+           if (line[0] == '#')
+               continue;
+
+           if ((last = strrchr (line, '\n')) != NULL)
+               *last = '\0';                   /* strip the newline */
+
+           /* Skip leading white space. */
+           for (fname = line;
+                *fname && isspace ((unsigned char) *fname);
+                fname++)
+               ;
+
+           /* Find end of filename. */
+           for (cp = fname; *cp && !isspace ((unsigned char) *cp); cp++)
+               ;
+           *cp = '\0';
+
+           temp = make_tempfile ();
+           if (checkout_file (fname, temp) == 0)
+           {
+               rename_rcsfile (temp, fname);
+           }
+           else
+           {
+               /* Skip leading white space before the error message.  */
+               for (cp++;
+                    cp < last && *cp && isspace ((unsigned char) *cp);
+                    cp++)
+                   ;
+               if (cp < last && *cp)
+                   error (0, 0, "%s", cp);
+           }
+           if (unlink_file (temp) < 0
+               && !existence_error (errno))
+               error (0, errno, "cannot remove %s", temp);
+           free (temp);
+       }
+       if (line)
+           free (line);
+       if (ferror (fp))
+           error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST);
+       if (fclose (fp) < 0)
+           error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST);
+    }
+    else
+    {
+       /* Error from CVS_FOPEN.  */
+       if (!existence_error (errno))
+           error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST);
+    }
+
+    if (restore_cwd (&cwd))
+       error (1, errno, "Failed to restore current directory, `%s'.",
+              cwd.name);
+    free_cwd (&cwd);
+
+    return 0;
+}
+
+
+
+/*
+ * Yeah, I know, there are NFS race conditions here.
+ */
+static char *
+make_tempfile (void)
+{
+    static int seed = 0;
+    int fd;
+    char *temp;
+
+    if (seed == 0)
+       seed = getpid ();
+    temp = xmalloc (sizeof (BAKPREFIX) + 40);
+    while (1)
+    {
+       (void) sprintf (temp, "%s%d", BAKPREFIX, seed++);
+       if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1)
+           break;
+       if (errno != EEXIST)
+           error (1, errno, "cannot create temporary file %s", temp);
+    }
+    if (close(fd) < 0)
+       error(1, errno, "cannot close temporary file %s", temp);
+    return temp;
+}
+
+
+
+/* Get a file.  If the file does not exist, return 1 silently.  If
+   there is an error, print a message and return 1 (FIXME: probably
+   not a very clean convention).  On success, return 0.  */
+static int
+checkout_file (char *file, char *temp)
+{
+    char *rcs;
+    RCSNode *rcsnode;
+    int retcode = 0;
+
+    if (noexec)
+       return 0;
+
+    rcs = Xasprintf ("%s%s", file, RCSEXT);
+    if (!isfile (rcs))
+    {
+       free (rcs);
+       return 1;
+    }
+
+    rcsnode = RCS_parsercsfile (rcs);
+    if (!rcsnode)
+    {
+       /* Probably not necessary (?); RCS_parsercsfile already printed a
+          message.  */
+       error (0, 0, "Failed to parse `%s'.", rcs);
+       free (rcs);
+       return 1;
+    }
+
+    retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp, NULL, NULL);
+    if (retcode != 0)
+    {
+       /* Probably not necessary (?); RCS_checkout already printed a
+          message.  */
+       error (0, 0, "failed to check out %s file",
+              file);
+    }
+    freercsnode (&rcsnode);
+    free (rcs);
+    return retcode;
+}
+
+
+
+#ifndef MY_NDBM
+
+static void
+write_dbmfile( char *temp )
+{
+    char line[DBLKSIZ], value[DBLKSIZ];
+    FILE *fp;
+    DBM *db;
+    char *cp, *vp;
+    datum key, val;
+    int len, cont, err = 0;
+
+    fp = xfopen (temp, "r");
+    if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL)
+       error (1, errno, "cannot open dbm file %s for creation", temp);
+    for (cont = 0; fgets (line, sizeof (line), fp) != NULL;)
+    {
+       if ((cp = strrchr (line, '\n')) != NULL)
+           *cp = '\0';                 /* strip the newline */
+
+       /*
+        * Add the line to the value, at the end if this is a continuation
+        * line; otherwise at the beginning, but only after any trailing
+        * backslash is removed.
+        */
+       vp = value;
+       if (cont)
+           vp += strlen (value);
+
+       /*
+        * See if the line we read is a continuation line, and strip the
+        * backslash if so.
+        */
+       len = strlen (line);
+       if (len > 0)
+           cp = &line[len - 1];
+       else
+           cp = line;
+       if (*cp == '\\')
+       {
+           cont = 1;
+           *cp = '\0';
+       }
+       else
+       {
+           cont = 0;
+       }
+       (void) strcpy (vp, line);
+       if (value[0] == '#')
+           continue;                   /* comment line */
+       vp = value;
+       while (*vp && isspace ((unsigned char) *vp))
+           vp++;
+       if (*vp == '\0')
+           continue;                   /* empty line */
+
+       /*
+        * If this was not a continuation line, add the entry to the database
+        */
+       if (!cont)
+       {
+           key.dptr = vp;
+           while (*vp && !isspace ((unsigned char) *vp))
+               vp++;
+           key.dsize = vp - key.dptr;
+           *vp++ = '\0';               /* NULL terminate the key */
+           while (*vp && isspace ((unsigned char) *vp))
+               vp++;                   /* skip whitespace to value */
+           if (*vp == '\0')
+           {
+               error (0, 0, "warning: NULL value for key `%s'", key.dptr);
+               continue;
+           }
+           val.dptr = vp;
+           val.dsize = strlen (vp);
+           if (dbm_store (db, key, val, DBM_INSERT) == 1)
+           {
+               error (0, 0, "duplicate key found for `%s'", key.dptr);
+               err++;
+           }
+       }
+    }
+    dbm_close (db);
+    if (fclose (fp) < 0)
+       error (0, errno, "cannot close %s", temp);
+    if (err)
+    {
+       /* I think that the size of the buffer needed here is
+          just determined by sizeof (CVSROOTADM_MODULES), the
+          filenames created by make_tempfile, and other things that won't
+          overflow.  */
+       char dotdir[50], dotpag[50], dotdb[50];
+
+       (void) sprintf (dotdir, "%s.dir", temp);
+       (void) sprintf (dotpag, "%s.pag", temp);
+       (void) sprintf (dotdb, "%s.db", temp);
+       if (unlink_file (dotdir) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", dotdir);
+       if (unlink_file (dotpag) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", dotpag);
+       if (unlink_file (dotdb) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", dotdb);
+       error (1, 0, "DBM creation failed; correct above errors");
+    }
+}
+
+static void
+rename_dbmfile( char *temp )
+{
+    /* I think that the size of the buffer needed here is
+       just determined by sizeof (CVSROOTADM_MODULES), the
+       filenames created by make_tempfile, and other things that won't
+       overflow.  */
+    char newdir[50], newpag[50], newdb[50];
+    char dotdir[50], dotpag[50], dotdb[50];
+    char bakdir[50], bakpag[50], bakdb[50];
+
+    int dir1_errno = 0, pag1_errno = 0, db1_errno = 0;
+    int dir2_errno = 0, pag2_errno = 0, db2_errno = 0;
+    int dir3_errno = 0, pag3_errno = 0, db3_errno = 0;
+
+    (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES);
+    (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES);
+    (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES);
+    (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES);
+    (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES);
+    (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES);
+    (void) sprintf (newdir, "%s.dir", temp);
+    (void) sprintf (newpag, "%s.pag", temp);
+    (void) sprintf (newdb, "%s.db", temp);
+
+    (void) chmod (newdir, 0666);
+    (void) chmod (newpag, 0666);
+    (void) chmod (newdb, 0666);
+
+    /* don't mess with me */
+    SIG_beginCrSect ();
+
+    /* rm .#modules.dir .#modules.pag */
+    if (unlink_file (bakdir) < 0)
+       dir1_errno = errno;
+    if (unlink_file (bakpag) < 0)
+       pag1_errno = errno;
+    if (unlink_file (bakdb) < 0)
+       db1_errno = errno;
+
+    /* mv modules.dir .#modules.dir */
+    if (CVS_RENAME (dotdir, bakdir) < 0)
+       dir2_errno = errno;
+    /* mv modules.pag .#modules.pag */
+    if (CVS_RENAME (dotpag, bakpag) < 0)
+       pag2_errno = errno;
+    /* mv modules.db .#modules.db */
+    if (CVS_RENAME (dotdb, bakdb) < 0)
+       db2_errno = errno;
+
+    /* mv "temp".dir modules.dir */
+    if (CVS_RENAME (newdir, dotdir) < 0)
+       dir3_errno = errno;
+    /* mv "temp".pag modules.pag */
+    if (CVS_RENAME (newpag, dotpag) < 0)
+       pag3_errno = errno;
+    /* mv "temp".db modules.db */
+    if (CVS_RENAME (newdb, dotdb) < 0)
+       db3_errno = errno;
+
+    /* OK -- make my day */
+    SIG_endCrSect ();
+
+    /* I didn't want to call error() when we had signals blocked
+       (unnecessary?), but do it now.  */
+    if (dir1_errno && !existence_error (dir1_errno))
+       error (0, dir1_errno, "cannot remove %s", bakdir);
+    if (pag1_errno && !existence_error (pag1_errno))
+       error (0, pag1_errno, "cannot remove %s", bakpag);
+    if (db1_errno && !existence_error (db1_errno))
+       error (0, db1_errno, "cannot remove %s", bakdb);
+
+    if (dir2_errno && !existence_error (dir2_errno))
+       error (0, dir2_errno, "cannot remove %s", bakdir);
+    if (pag2_errno && !existence_error (pag2_errno))
+       error (0, pag2_errno, "cannot remove %s", bakpag);
+    if (db2_errno && !existence_error (db2_errno))
+       error (0, db2_errno, "cannot remove %s", bakdb);
+
+    if (dir3_errno && !existence_error (dir3_errno))
+       error (0, dir3_errno, "cannot remove %s", bakdir);
+    if (pag3_errno && !existence_error (pag3_errno))
+       error (0, pag3_errno, "cannot remove %s", bakpag);
+    if (db3_errno && !existence_error (db3_errno))
+       error (0, db3_errno, "cannot remove %s", bakdb);
+}
+
+#endif                         /* !MY_NDBM */
+
+static void
+rename_rcsfile (char *temp, char *real)
+{
+    char *bak;
+    struct stat statbuf;
+    char *rcs;
+
+    /* Set "x" bits if set in original. */
+    rcs = Xasprintf ("%s%s", real, RCSEXT);
+    statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */
+    if (stat (rcs, &statbuf) < 0
+       && !existence_error (errno))
+       error (0, errno, "cannot stat %s", rcs);
+    free (rcs);
+
+    if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0)
+       error (0, errno, "warning: cannot chmod %s", temp);
+    bak = Xasprintf ("%s%s", BAKPREFIX, real);
+
+    /* rm .#loginfo */
+    if (unlink_file (bak) < 0
+       && !existence_error (errno))
+       error (0, errno, "cannot remove %s", bak);
+
+    /* mv loginfo .#loginfo */
+    if (CVS_RENAME (real, bak) < 0
+       && !existence_error (errno))
+       error (0, errno, "cannot rename %s to %s", real, bak);
+
+    /* mv "temp" loginfo */
+    if (CVS_RENAME (temp, real) < 0
+       && !existence_error (errno))
+       error (0, errno, "cannot rename %s to %s", temp, real);
+
+    free (bak);
+}
+
+const char *const init_usage[] = {
+    "Usage: %s %s\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+int
+init (int argc, char **argv)
+{
+    /* Name of CVSROOT directory.  */
+    char *adm;
+    /* Name of this administrative file.  */
+    char *info;
+    /* Name of ,v file for this administrative file.  */
+    char *info_v;
+    /* Exit status.  */
+    int err = 0;
+
+    const struct admin_file *fileptr;
+
+    umask (cvsumask);
+
+    if (argc == -1 || argc > 1)
+       usage (init_usage);
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       start_server ();
+
+       ign_setup ();
+       send_init_command ();
+       return get_responses_and_close ();
+    }
+#endif /* CLIENT_SUPPORT */
+
+    /* Note: we do *not* create parent directories as needed like the
+       old cvsinit.sh script did.  Few utilities do that, and a
+       non-existent parent directory is as likely to be a typo as something
+       which needs to be created.  */
+    mkdir_if_needed (current_parsed_root->directory);
+
+    adm = Xasprintf ("%s/%s", current_parsed_root->directory, CVSROOTADM);
+    mkdir_if_needed (adm);
+
+    /* This is needed because we pass "fileptr->filename" not "info"
+       to add_rcs_file below.  I think this would be easy to change,
+       thus nuking the need for CVS_CHDIR here, but I haven't looked
+       closely (e.g. see wrappers calls within add_rcs_file).  */
+    if ( CVS_CHDIR (adm) < 0)
+       error (1, errno, "cannot change to directory %s", adm);
+
+    /* Make Emptydir so it's there if we need it */
+    mkdir_if_needed (CVSNULLREPOS);
+
+    /* 80 is long enough for all the administrative file names, plus
+       "/" and so on.  */
+    info = xmalloc (strlen (adm) + 80);
+    info_v = xmalloc (strlen (adm) + 80);
+    for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr)
+    {
+       if (fileptr->contents == NULL)
+           continue;
+       strcpy (info, adm);
+       strcat (info, "/");
+       strcat (info, fileptr->filename);
+       strcpy (info_v, info);
+       strcat (info_v, RCSEXT);
+       if (isfile (info_v))
+           /* We will check out this file in the mkmodules step.
+              Nothing else is required.  */
+           ;
+       else
+       {
+           int retcode;
+
+           if (!isfile (info))
+           {
+               FILE *fp;
+               const char * const *p;
+
+               fp = xfopen (info, "w");
+               for (p = fileptr->contents; *p != NULL; ++p)
+                   if (fputs (*p, fp) < 0)
+                       error (1, errno, "cannot write %s", info);
+               if (fclose (fp) < 0)
+                   error (1, errno, "cannot close %s", info);
+           }
+           /* The message used to say " of " and fileptr->filename after
+              "initial checkin" but I fail to see the point as we know what
+              file it is from the name.  */
+           retcode = add_rcs_file ("initial checkin", info_v,
+                                   fileptr->filename, "1.1", NULL,
+
+                                   /* No vendor branch.  */
+                                   NULL, NULL, 0, NULL,
+
+                                   NULL, 0, NULL, 0);
+           if (retcode != 0)
+               /* add_rcs_file already printed an error message.  */
+               err = 1;
+       }
+    }
+
+    /* Turn on history logging by default.  The user can remove the file
+       to disable it.  */
+    strcpy (info, adm);
+    strcat (info, "/");
+    strcat (info, CVSROOTADM_HISTORY);
+    if (!isfile (info))
+    {
+       FILE *fp;
+
+       fp = xfopen (info, "w");
+       if (fclose (fp) < 0)
+           error (1, errno, "cannot close %s", info);
+ 
+        /* Make the new history file world-writeable, since every CVS
+           user will need to be able to write to it.  We use chmod()
+           because xchmod() is too shy. */
+        chmod (info, 0666);
+    }
+
+    /* Make an empty val-tags file to prevent problems creating it later.  */
+    strcpy (info, adm);
+    strcat (info, "/");
+    strcat (info, CVSROOTADM_VALTAGS);
+    if (!isfile (info))
+    {
+       FILE *fp;
+
+       fp = xfopen (info, "w");
+       if (fclose (fp) < 0)
+           error (1, errno, "cannot close %s", info);
+ 
+        /* Make the new val-tags file world-writeable, since every CVS
+           user will need to be able to write to it.  We use chmod()
+           because xchmod() is too shy. */
+        chmod (info, 0666);
+    }
+
+    free (info);
+    free (info_v);
+
+    mkmodules (adm);
+
+    free (adm);
+    return err;
+}
Index: ccvs/src/patch.c
diff -u /dev/null ccvs/src/patch.c:1.106.6.1
--- /dev/null   Fri Jan  6 20:04:39 2006
+++ ccvs/src/patch.c    Fri Jan  6 20:04:38 2006
@@ -0,0 +1,858 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* GNULIB headers.  */
+#include "getline.h"
+
+/* CVS headers.  */
+#include "ignore.h"
+#include "recurse.h"
+
+#include "cvs.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;
+
+    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_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match,
+                                   NULL);
+       if (vers_head != NULL && RCS_isdead (rcsfile, vers_head))
+       {
+           free (vers_head);
+           vers_head = NULL;
+       }
+    }
+
+    if (toptwo_diffs)
+    {
+       if (vers_head == NULL)
+       {
+           ret = 1;
+           goto out2;
+       }
+
+       if (!date1)
+           date1 = xmalloc (MAXDATELEN);
+       *date1 = '\0';
+       if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == (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_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match, NULL);
+    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 ccvs/src/rcs.c:1.356.6.2 ccvs/src/rcs.c:1.356.6.3
--- ccvs/src/rcs.c:1.356.6.2    Fri Dec 30 23:28:22 2005
+++ ccvs/src/rcs.c      Fri Jan  6 20:04:38 2006
@@ -4791,6 +4791,72 @@
 
 
 
+void
+RCS_add_openpgp_signature (struct file_info *finfo, const char *rev)
+{
+    RCSVers *vers;
+    Node *n;
+    char *oldsigs;
+    size_t oldlen;
+    char *newsig;
+    size_t newlen;
+
+    TRACE (TRACE_FUNCTION, "RCS_add_openpgp_signature (%s, %s)",
+          finfo->fullname, rev);
+
+    if (finfo->rcs->flags & PARTIAL)
+       RCS_reparsercsfile (finfo->rcs, NULL, NULL);
+
+    n = findnode (finfo->rcs->versions, rev);
+    if (!n)
+       error (1, 0, "internal error: no revision information for %s", rev);
+    vers = n->data;
+
+    n = findnode (vers->other_delta, "openpgp-signatures");
+    if (!n)
+    {
+       n = getnode();
+       n->type = RCSSTRING;
+       n->key = xstrdup ("openpgp-signatures");
+       oldsigs = NULL;
+       oldlen = 0;
+       addnode (vers->other_delta, n);
+    }
+    else
+    {
+       TRACE (TRACE_DATA,
+              "RCS_add_openpgp_signature: found oldsigs = %s, len = %u",
+              (char *)n->data, (unsigned int)n->len);
+       if (!base64_decode_alloc (n->data, n->len, &oldsigs, &oldlen))
+           error (1, 0, "Invalid binhex data in signature (`%s', rev %s)",
+                  finfo->rcs->print_path, rev);
+       if (!oldsigs)
+           error (1, errno, "Memory allocation error");
+       free (n->data);
+    }
+
+    newsig = get_signature (server_active,
+                           Short_Repository (finfo->repository), finfo->file,
+                           finfo->rcs->expand
+                           && STREQ (finfo->rcs->expand, "b"),
+                           &newlen);
+
+    oldsigs = xrealloc (oldsigs, oldlen + newlen);
+    memcpy (oldsigs + oldlen, newsig, newlen);
+    free (newsig);
+
+    n->len = base64_encode_alloc (oldsigs, oldlen + newlen, (char **)&n->data);
+    free (oldsigs);
+
+    TRACE (TRACE_DATA,
+          "RCS_add_openpgp_signature: found oldsigs = %s, len = %u",
+          (char *)n->data, (unsigned int)n->len);
+
+    RCS_rewrite (finfo->rcs, NULL, NULL);
+}
+
+
+
 /* Find the delta currently locked by the user.  From the `ci' man page:
 
        "If rev is omitted, ci tries to  derive  the  new  revision
Index: ccvs/src/rcs.h
diff -u ccvs/src/rcs.h:1.82.8.1 ccvs/src/rcs.h:1.82.8.2
--- ccvs/src/rcs.h:1.82.8.1     Wed Dec 21 13:25:10 2005
+++ ccvs/src/rcs.h      Fri Jan  6 20:04:38 2006
@@ -275,6 +275,7 @@
 int RCS_checkout (RCSNode *, const char *, const char *, const char *,
                   const char *, const char *, RCSCHECKOUTPROC, void *);
 const char *RCS_get_openpgp_signatures (RCSNode *rcs, const char *rev);
+void RCS_add_openpgp_signature (struct file_info *finfo, const char *rev);
 int RCS_checkin (RCSNode *rcs, const char *update_dir, const char *workfile,
                 const char *message, const char *rev, time_t citime,
                 int flags);
Index: ccvs/src/sanity.sh
diff -u ccvs/src/sanity.sh:1.1105.2.4 ccvs/src/sanity.sh:1.1105.2.5
--- ccvs/src/sanity.sh:1.1105.2.4       Tue Jan  3 18:09:24 2006
+++ ccvs/src/sanity.sh  Fri Jan  6 20:04:38 2006
@@ -1872,7 +1872,7 @@
        tests="${tests} dottedroot fork commit-d template"
        tests="${tests} writeproxy writeproxy-noredirect writeproxy-ssh"
        tests="${tests} writeproxy-ssh-noredirect"
-       tests="$tests verify"
+       tests="$tests openpgp"
 else
        tests="$*"
 fi
@@ -32700,28 +32700,33 @@
 
 
 
-       verify)
-         # More tests of basic/miscellaneous functionality.
-         mkdir verify; cd verify
+       openpgp)
+         # More tests of basic/miscellaneous openpgp functionality.
+         mkdir openpgp; cd openpgp
          mkdir top; cd top
-         dotest verify-init-1 "$testcvs -q co -l ."
-         mkdir verify
-         dotest verify-init-2 "$testcvs -Q add verify"
+         dotest openpgp-init-1 "$testcvs -q co -l ."
+         mkdir openpgp
+         dotest openpgp-init-2 "$testcvs -Q add openpgp"
          cd ..
-         dotest verify-init-3 "$testcvs -q co verify"
-         cd verify
+         dotest openpgp-init-3 "$testcvs -q co openpgp"
+         cd openpgp
          echo some content >file1
-         dotest verify-init-4 "$testcvs -Q add file1"
-         dotest verify-init-5 "$testcvs -Q ci -m newfile file1"
-         dotest verify-1 "$testcvs verify file1" \
+         dotest openpgp-init-4 "$testcvs -Q add file1"
+         dotest openpgp-init-5 "$testcvs -Q ci -m newfile file1"
+         dotest openpgp-1 "$testcvs verify file1" \
 "$DOTSTAR Good signature from \"CVS Test Script $DOTSTAR"
-         dotest verify-2 "$testcvs verify -p file1 >tmp"
-         dotest verify-3 "cmp tmp CVS/Base/.#file1.1.1.sig"
+         dotest openpgp-2 "$testcvs verify -p file1 >tmp"
+         dotest openpgp-3 "cmp tmp CVS/Base/.#file1.1.1.sig"
+
+         dotest openpgp-4 "$testcvs sign file1"
+         dotest openpgp-5 "$testcvs verify file1" \
+"$DOTSTAR Good signature from \"CVS Test Script $DOTSTAR
+$DOTSTAR Good signature from \"CVS Test Script $DOTSTAR"
 
          dokeep
          cd ../..
-         rm -rf verify
-         modify_repo rm -rf $CVSROOT_DIRNAME/verify
+         rm -rf openpgp
+         modify_repo rm -rf $CVSROOT_DIRNAME/openpgp
          ;;
 
 
Index: ccvs/src/server.c
diff -u ccvs/src/server.c:1.453.2.1 ccvs/src/server.c:1.453.2.2
--- ccvs/src/server.c:1.453.2.1 Wed Dec 21 13:25:10 2005
+++ ccvs/src/server.c   Fri Jan  6 20:04:38 2006
@@ -15,23 +15,25 @@
 /* Validate API.  */
 #include "server.h"
 
-
-/* GNULIB */
+/* GNULIB headers.  */
 #include "getline.h"
 #include "getnline.h"
 #include "setenv.h"
 #include "wait.h"
 
-/* CVS */
+/* CVS headers.  */
 #include "base.h"
 #include "buffer.h"
+#include "edit.h"
 #include "gpg.h"
+#include "ignore.h"
 
 #include "cvs.h"
-#include "edit.h"
 #include "fileattr.h"
 #include "watch.h"
 
+
+
 int server_active = 0;
 
 #if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
@@ -1879,6 +1881,81 @@
 
 
 
+/* Returns false on errors.
+ */
+static bool
+server_write_sigfile (const char *file, struct buffer *sig_buf)
+{
+    char *sigfile_name, *sig_data;
+    int fd, rc;
+    size_t got;
+    bool err = false;
+
+    /* Write the file.  */
+    sigfile_name = get_sigfile_name (file);
+    fd = CVS_OPEN (sigfile_name, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+    if (fd < 0)
+    {
+       int save_errno = errno;
+       if (alloc_pending (40 + strlen (sigfile_name)))
+           sprintf (pending_error_text, "E cannot open `%s'",
+                    sigfile_name);
+       pending_error = save_errno;
+       err = true;
+       goto done;
+    }
+
+    while (!buf_empty_p (sig_buf))
+    {
+       if ((rc = buf_read_data (sig_buf, buf_length (sig_buf), &sig_data,
+                                &got)))
+       {
+           /* Since !buf_empty_p confirmed that the buffer was not empty,
+            * it should be impossible to get EOF here.
+            */
+           assert (rc != -1);
+
+           if (rc == -2)
+               pending_error = ENOMEM;
+           else if (alloc_pending (80))
+               sprintf (pending_error_text,
+                        "E error reading signature buffer.");
+           pending_error = rc;
+           err = true;
+           goto done;
+       }
+
+       if (write (fd, sig_data, got) < 0)
+       {
+           int save_errno = errno;
+           if (alloc_pending (80 + strlen (sigfile_name)))
+               sprintf (pending_error_text,
+                        "E error writing temporary signature file `%s'.",
+                        sigfile_name);
+           pending_error = save_errno;
+           err = true;
+           goto done;
+        }
+    }
+
+done:
+    if (fd >= 0
+       && close (fd) < 0)
+    {
+       if (!error_pending ()
+           && alloc_pending_warning (80 + strlen (sigfile_name)))
+           sprintf (pending_warning_text,
+                    "E error closing temporary signature file `%s'.",
+                    sigfile_name);
+       err = true;
+    }
+
+    free (sigfile_name);
+    return !err;
+}
+
+
+
 static void
 serve_modified (char *arg)
 {
@@ -2059,64 +2136,13 @@
      */
     if (sig_buf)
     {
-       char *sigfile_name, *sig_data;
-       int fd, rc;
-       size_t got;
-
-       /* Write the file.  */
-       sigfile_name = get_sigfile_name (arg);
-       fd = CVS_OPEN (sigfile_name, O_WRONLY | O_CREAT | O_TRUNC, 0600);
-       if (fd < 0)
-       {
-           int save_errno = errno;
-           if (alloc_pending (40 + strlen (arg)))
-               sprintf (pending_error_text, "E cannot open `%s'",
-                        sigfile_name);
-           pending_error = save_errno;
-           return;
-       }
-
-       while (!buf_empty_p (sig_buf))
-       {
-           if ((rc = buf_read_data (sig_buf, buf_length (sig_buf), &sig_data,
-                                    &got)))
-           {
-               /* Since !buf_empty_p confirmed that the buffer was not empty,
-                * it should be impossible to get EOF here.
-                */
-               assert (rc != -1);
-
-               if (rc == -2)
-                   pending_error = ENOMEM;
-               else if (alloc_pending (80))
-                   sprintf (pending_error_text,
-                            "E error reading signature buffer.");
-               pending_error = rc;
-               return;
-           }
-
-           if (write (fd, sig_data, got) < 0)
-           {
-               int save_errno = errno;
-               if (alloc_pending (80 + strlen (sigfile_name)))
-                   sprintf (pending_error_text,
-                            "E error writing temporary signature file `%s'.",
-                            sigfile_name);
-               pending_error = save_errno;
-               return;
-            }
-       }
-
-       if (close (fd) < 0 
-           && alloc_pending_warning (80 + strlen (sigfile_name)))
-           sprintf (pending_warning_text,
-                    "E error closing temporary signature file `%s'.",
-                    sigfile_name);
-       free (sigfile_name);
+       bool err = !server_write_sigfile (arg, sig_buf);
 
        /* We're done with the SIG_BUF.  */
        buf_free (sig_buf);
        sig_buf = NULL;
+
+       if (err) return;
     }
 }
 
@@ -2264,6 +2290,20 @@
            break;
        }
     }
+
+    /* If an OpenPGP signature was sent for this file, write it to a temp
+     * file.
+     */
+    if (sig_buf)
+    {
+       bool err = !server_write_sigfile (arg, sig_buf);
+
+       /* We're done with the SIG_BUF.  */
+       buf_free (sig_buf);
+       sig_buf = NULL;
+
+       if (err) return;
+    }
 }
 
 
@@ -4756,6 +4796,14 @@
 
 
 static void
+serve_sign (char *arg)
+{
+    do_cvs_command ("sign", sign);
+}
+
+
+
+static void
 serve_status (char *arg)
 {
     do_cvs_command ("status", cvsstatus);
@@ -6111,6 +6159,7 @@
   REQ_LINE("remove", serve_remove, 0),
   REQ_LINE("update-patches", serve_ignore, 0),
   REQ_LINE("gzip-file-contents", serve_gzip_contents, RQ_ROOTLESS),
+  REQ_LINE("sign", serve_sign, 0),
   REQ_LINE("status", serve_status, 0),
   REQ_LINE("rdiff", serve_rdiff, 0),
   REQ_LINE("tag", serve_tag, 0),
@@ -8387,6 +8436,23 @@
 
 
 
+void
+server_base_signatures (struct file_info *finfo, const char *rev)
+{
+    server_send_signatures (finfo->rcs, rev);
+
+    buf_output0 (protocol, "Base-signatures ");
+    output_dir (finfo->update_dir, finfo->repository);
+    buf_output0 (protocol, finfo->file);
+    buf_output (protocol, "\n", 1);
+    buf_output0 (protocol, rev);
+    buf_output (protocol, "\n", 1);
+    buf_send_counted (protocol);
+    return;
+}
+
+
+
 /*
  * void cvs_trace(int level, const char *fmt, ...)
  *
Index: ccvs/src/server.h
diff -u ccvs/src/server.h:1.44.6.1 ccvs/src/server.h:1.44.6.2
--- ccvs/src/server.h:1.44.6.1  Wed Dec 21 13:25:10 2005
+++ ccvs/src/server.h   Fri Jan  6 20:04:38 2006
@@ -242,6 +242,7 @@
                       const char *flags);
 void server_base_merge (struct file_info *finfo, const char *rev1,
                        const char *rev2);
+void server_base_signatures (struct file_info *finfo, const char *rev);
 bool server_use_bases (void);
 
 void cvs_output (const char *, size_t);
Index: ccvs/src/sign.c
diff -u ccvs/src/sign.c:1.1.6.3 ccvs/src/sign.c:1.1.6.4
--- ccvs/src/sign.c:1.1.6.3     Sun Jan  1 23:06:21 2006
+++ ccvs/src/sign.c     Fri Jan  6 20:04:38 2006
@@ -26,6 +26,7 @@
 /* Standard headers.  */
 #include <assert.h>
 #include <errno.h>
+#include <getopt.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
@@ -36,16 +37,27 @@
 #include "xalloc.h"
 
 /* CVS headers.  */
+#include "classify.h"
+#include "client.h"
 #include "filesubr.h"
+#include "ignore.h"
+#include "recurse.h"
 #include "root.h"
 #include "run.h"
+#include "server.h"    /* Get TRACE ().  */
 #include "stack.h"
 #include "stack.h"
 #include "subr.h"
+#include "vers_ts.h"
 
 
 
+/* FIXME: Once cvs.h is pared ot the bare essentials, it may be included for
+ * the following.
+ */
 extern int noexec;
+extern int quiet, really_quiet;
+void usage (const char *const *cpp);
 
 
 
@@ -367,3 +379,147 @@
     if (server_active) return read_signature (filename, len);
     /* else */ return gen_signature (srepos, filename, bin, len);
 }
+
+
+
+static int
+sign_fileproc (void *callerdat, struct file_info *finfo)
+{
+    Vers_TS *vers;
+    int err = 0;
+    Ctype status;
+
+    TRACE (TRACE_FUNCTION, "sign_fileproc (%s)", finfo->fullname);
+
+    status = Classify_File (finfo, NULL, NULL, NULL, true,
+                           false, &vers, false);
+
+    switch (status)
+    {
+       case T_UNKNOWN:                 /* unknown file was explicitly asked
+                                        * about */
+           error (0, 0, "Nothing known about `%s'", finfo->fullname);
+           err++;
+           break;
+       case T_CONFLICT:                /* old punt-type errors */
+       case T_NEEDS_MERGE:             /* needs merging */
+       case T_MODIFIED:                /* locally modified */
+       case T_ADDED:                   /* added but not committed */
+       case T_REMOVED:                 /* removed but not committed */
+           error (0, 0, "Locally modified file `%s' may not be signed.",
+                  finfo->fullname);
+           err++;
+           break;
+       case T_CHECKOUT:                /* needs checkout */
+           if (!vers->ts_user)
+           {
+               assert (vers->vn_user);
+               error (0, 0,
+"File `%s' not present locally (checkout before signing)",
+                      finfo->fullname);
+               err++;
+               break;
+           }
+           /* else, fall through */
+       case T_REMOVE_ENTRY:            /* needs to be un-registered */
+       case T_PATCH:                   /* needs patch */
+       case T_UPTODATE:                /* file was already up-to-date */
+           if (!isfile (finfo->file))
+               RCS_checkout (finfo->rcs, finfo->file, vers->vn_user, 
+                             vers->tag, vers->options, NULL, NULL, NULL);
+           if (file_contains_keyword (finfo))
+           {
+               /* Make this a warning, not an error, because the user may
+                * be intentionally signing a file with keywords.  Such a file
+                * may still be verified when checked out -ko.
+                */
+               if (!quiet)
+                   error (0, 0,
+"warning: signed file `%s' contains at least one RCS keyword",
+                          finfo->fullname);
+           }
+
+           RCS_add_openpgp_signature (finfo, vers->vn_user);
+           if (server_active)
+               server_base_signatures (finfo, vers->vn_user);
+           break;
+       default:                        /* can't ever happen :-) */
+           error (0, 0,
+                  "unknown file status %d for file `%s'",
+                  status, finfo->file);
+           err++;
+           break;
+    }
+
+    return err;
+}
+
+
+
+static const char *const sign_usage[] =
+{
+    "Usage: %s %s [-lR] [files...]\n",
+    "\t-l\tProcess this directory only (not recursive).\n",
+    "\t-R\tProcess directories recursively.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+int
+sign (int argc, char **argv)
+{
+    int c;
+    int err = 0;
+    bool local = false;
+
+    if (argc == -1)
+       usage (sign_usage);
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+lR")) != -1)
+    {
+       switch (c)
+       {
+           case 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case '?':
+           default:
+               usage (sign_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       start_server ();
+
+       ign_setup ();
+
+       if (local)
+           send_arg("-l");
+       send_arg ("--");
+
+       send_files (argc, argv, local, false, 0);
+       send_file_names (argc, argv, SEND_EXPAND_WILD);
+
+       send_to_server ("sign\012", 0);
+       err = get_responses_and_close ();
+
+       return err;
+    }
+#endif
+
+    /* start the recursion processor */
+    err = start_recursion (sign_fileproc, NULL, NULL, NULL, NULL, argc, argv,
+                          local, W_LOCAL, false, CVS_LOCK_WRITE, NULL, true,
+                          NULL);
+
+    return err;
+}
Index: ccvs/src/sign.h
diff -u ccvs/src/sign.h:1.1.6.2 ccvs/src/sign.h:1.1.6.3
--- ccvs/src/sign.h:1.1.6.2     Sat Dec 31 19:51:11 2005
+++ ccvs/src/sign.h     Fri Jan  6 20:04:38 2006
@@ -48,4 +48,8 @@
 /* Other utilities.  */
 bool have_sigfile (bool server_active, const char *fn);
 char *get_sigfile_name (const char *fn);
+
+/* Sign command.  */
+int sign (int argc, char **argv);
+
 #endif /* SIGN_H */
Index: ccvs/src/status.c
diff -u /dev/null ccvs/src/status.c:1.68.6.1
--- /dev/null   Fri Jan  6 20:04:39 2006
+++ ccvs/src/status.c   Fri Jan  6 20:04:38 2006
@@ -0,0 +1,389 @@
+/*
+ * 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
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* CVS headers.  */
+#include "classify.h"
+#include "ignore.h"
+#include "recurse.h"
+
+#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);
+}




reply via email to

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