cvs-cvs
[Top][All Lists]
Advanced

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

[Cvs-cvs] ccvs/src add.c admin.c annotate.c base.c buffer... [signed-com


From: Derek Robert Price
Subject: [Cvs-cvs] ccvs/src add.c admin.c annotate.c base.c buffer... [signed-commits3]
Date: Fri, 06 Jan 2006 19:34:16 +0000

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

Modified files:
        src            : 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 modules.c recurse.c release.c remove.c 
                         tag.c update.c verify.c watch.c cvs.h ChangeLog 
Added files:
        src            : ignore.h logmsg.h recurse.h 

Log message:
        * 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,
        modules.c, recurse.c, release.c, remove.c, tag.c, update.c, verify.c,
        watch.c: Use stricter include formatting.
        * cvs.h: Move decls to...
        * classify.h, ignore.h, logmsg.h, recurse.h: ...these new files.

CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/add.c.diff?only_with_tag=signed-commits3&tr1=1.121.6.1&tr2=1.121.6.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/admin.c.diff?only_with_tag=signed-commits3&tr1=1.111.2.1&tr2=1.111.2.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/annotate.c.diff?only_with_tag=signed-commits3&tr1=1.20&tr2=1.20.8.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/base.c.diff?only_with_tag=signed-commits3&tr1=1.1.4.2&tr2=1.1.4.3&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/buffer.c.diff?only_with_tag=signed-commits3&tr1=1.65.2.1&tr2=1.65.2.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/checkin.c.diff?only_with_tag=signed-commits3&tr1=1.56.2.1&tr2=1.56.2.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/checkout.c.diff?only_with_tag=signed-commits3&tr1=1.145&tr2=1.145.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/classify.c.diff?only_with_tag=signed-commits3&tr1=1.37.6.1&tr2=1.37.6.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/diff.c.diff?only_with_tag=signed-commits3&tr1=1.116.6.1&tr2=1.116.6.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/edit.c.diff?only_with_tag=signed-commits3&tr1=1.90.2.1&tr2=1.90.2.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/edit.h.diff?only_with_tag=signed-commits3&tr1=1.12&tr2=1.12.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/entries.c.diff?only_with_tag=signed-commits3&tr1=1.66.6.1&tr2=1.66.6.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/fileattr.c.diff?only_with_tag=signed-commits3&tr1=1.36&tr2=1.36.8.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/find_names.c.diff?only_with_tag=signed-commits3&tr1=1.43&tr2=1.43.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/history.c.diff?only_with_tag=signed-commits3&tr1=1.95&tr2=1.95.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/ignore.c.diff?only_with_tag=signed-commits3&tr1=1.56&tr2=1.56.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/import.c.diff?only_with_tag=signed-commits3&tr1=1.175.6.1&tr2=1.175.6.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/lock.c.diff?only_with_tag=signed-commits3&tr1=1.117&tr2=1.117.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/logmsg.c.diff?only_with_tag=signed-commits3&tr1=1.99&tr2=1.99.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/ls.c.diff?only_with_tag=signed-commits3&tr1=1.18&tr2=1.18.8.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/modules.c.diff?only_with_tag=signed-commits3&tr1=1.98&tr2=1.98.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/recurse.c.diff?only_with_tag=signed-commits3&tr1=1.114&tr2=1.114.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/release.c.diff?only_with_tag=signed-commits3&tr1=1.71&tr2=1.71.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/remove.c.diff?only_with_tag=signed-commits3&tr1=1.63&tr2=1.63.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/tag.c.diff?only_with_tag=signed-commits3&tr1=1.142.6.1&tr2=1.142.6.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/update.c.diff?only_with_tag=signed-commits3&tr1=1.259.2.1&tr2=1.259.2.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/verify.c.diff?only_with_tag=signed-commits3&tr1=1.1.2.3&tr2=1.1.2.4&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/watch.c.diff?only_with_tag=signed-commits3&tr1=1.45&tr2=1.45.6.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/cvs.h.diff?only_with_tag=signed-commits3&tr1=1.345.4.1&tr2=1.345.4.2&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/ignore.h?only_with_tag=signed-commits3&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/logmsg.h?only_with_tag=signed-commits3&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/recurse.h?only_with_tag=signed-commits3&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/cvs/ccvs/src/ChangeLog.diff?only_with_tag=signed-commits3&tr1=1.3328.2.14&tr2=1.3328.2.15&r1=text&r2=text

Patches:
Index: ccvs/src/ChangeLog
diff -u ccvs/src/ChangeLog:1.3328.2.14 ccvs/src/ChangeLog:1.3328.2.15
--- ccvs/src/ChangeLog:1.3328.2.14      Tue Jan  3 18:09:24 2006
+++ ccvs/src/ChangeLog  Fri Jan  6 19:34:15 2006
@@ -1,3 +1,13 @@
+2006-01-06  Derek Price  <address@hidden>
+
+       * 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,
+       modules.c, recurse.c, release.c, remove.c, tag.c, update.c, verify.c,
+       watch.c: Use stricter include formatting.
+       * cvs.h: Move decls to...
+       * classify.h, ignore.h, logmsg.h, recurse.h: ...these new files.
+
 2006-01-03  Derek Price  <address@hidden>
 
        * verify.c (verify): Accept -p option.
Index: ccvs/src/add.c
diff -u ccvs/src/add.c:1.121.6.1 ccvs/src/add.c:1.121.6.2
--- ccvs/src/add.c:1.121.6.1    Wed Dec 21 13:25:10 2005
+++ ccvs/src/add.c      Fri Jan  6 19:34:14 2006
@@ -31,13 +31,22 @@
 
 #include <assert.h>
 
-/* CVS */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* GNULIB headers.  */
+#include "save-cwd.h"
+
+/* CVS headers.  */
 #include "base.h"
-#include "cvs.h"
 #include "fileattr.h"
+#include "ignore.h"
+#include "logmsg.h"
+
+#include "cvs.h"
+
 
-/* GNULIB */
-#include "save-cwd.h"
 
 static int add_directory (struct file_info *finfo);
 static int build_entry (const char *repository, const char *user,
Index: ccvs/src/admin.c
diff -u ccvs/src/admin.c:1.111.2.1 ccvs/src/admin.c:1.111.2.2
--- ccvs/src/admin.c:1.111.2.1  Wed Dec 21 13:25:10 2005
+++ ccvs/src/admin.c    Fri Jan  6 19:34:14 2006
@@ -14,11 +14,23 @@
  * 
  */
 
-#include "cvs.h"
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* ANSI C headers.  */
 #ifdef CVS_ADMIN_GROUP
-#include <grp.h>
+# include <grp.h>
 #endif
 
+/* CVS headers.  */
+#include "ignore.h"
+#include "recurse.h"
+
+#include "cvs.h"
+
+
+
 static Dtype admin_dirproc (void *callerdat, const char *dir,
                             const char *repos, const char *update_dir,
                             List *entries);
Index: ccvs/src/annotate.c
diff -u /dev/null ccvs/src/annotate.c:1.20.8.1
--- /dev/null   Fri Jan  6 19:34:15 2006
+++ ccvs/src/annotate.c Fri Jan  6 19:34:15 2006
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (c) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * Show last revision where each line modified
+ * 
+ * Prints the specified files with each line annotated with the revision
+ * number where it was last modified.  With no argument, annotates all
+ * all the files in the directory (recursive by default).
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* CVS headers.  */
+#include "ignore.h"
+#include "recurse.h"
+
+#include "cvs.h"
+
+
+
+/* Options from the command line.  */
+
+static int force_tag_match = 1;
+static int force_binary = 0;
+static char *tag = NULL;
+static int tag_validated;
+static char *date = NULL;
+
+static int is_rannotate;
+
+static int annotate_fileproc (void *callerdat, struct file_info *);
+static int rannotate_proc (int argc, char **argv, char *xwhere,
+                                char *mwhere, char *mfile, int shorten,
+                                int local, char *mname, char *msg);
+
+static const char *const annotate_usage[] =
+{
+    "Usage: %s %s [-lRfF] [-r rev] [-D date] [files...]\n",
+    "\t-l\tLocal directory only, no recursion.\n",
+    "\t-R\tProcess directories recursively.\n",
+    "\t-f\tUse head revision if tag/date not found.\n",
+    "\t-F\tAnnotate binary files.\n",
+    "\t-r rev\tAnnotate file as of specified revision/tag.\n",
+    "\t-D date\tAnnotate file as of specified date.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+/* Command to show the revision, date, and author where each line of a
+   file was modified.  */
+
+int
+annotate (int argc, char **argv)
+{
+    int local = 0;
+    int err = 0;
+    int c;
+
+    is_rannotate = (strcmp(cvs_cmd_name, "rannotate") == 0);
+
+    if (argc == -1)
+       usage (annotate_usage);
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+lr:D:fFR")) != -1)
+    {
+       switch (c)
+       {
+           case 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case 'r':
+               parse_tagdate (&tag, &date, optarg);
+               break;
+           case 'D':
+               if (date) free (date);
+               date = Make_Date (optarg);
+               break;
+           case 'f':
+               force_tag_match = 0;
+               break;
+           case 'F':
+               force_binary = 1;
+               break;
+           case '?':
+           default:
+               usage (annotate_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       start_server ();
+
+       if (is_rannotate && !supported_request ("rannotate"))
+           error (1, 0, "server does not support rannotate");
+
+       ign_setup ();
+
+       if (local)
+           send_arg ("-l");
+       if (!force_tag_match)
+           send_arg ("-f");
+       if (force_binary)
+           send_arg ("-F");
+       option_with_arg ("-r", tag);
+       if (date)
+           client_senddate (date);
+       send_arg ("--");
+       if (is_rannotate)
+       {
+           int i;
+           for (i = 0; i < argc; i++)
+               send_arg (argv[i]);
+           send_to_server ("rannotate\012", 0);
+       }
+       else
+       {
+           send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+           send_file_names (argc, argv, SEND_EXPAND_WILD);
+           send_to_server ("annotate\012", 0);
+       }
+       return get_responses_and_close ();
+    }
+#endif /* CLIENT_SUPPORT */
+
+    if (is_rannotate)
+    {
+       DBM *db;
+       int i;
+       db = open_module ();
+       for (i = 0; i < argc; i++)
+       {
+           err += do_module (db, argv[i], MISC, "Annotating", rannotate_proc,
+                             NULL, 0, local, 0, 0, NULL);
+       }
+       close_module (db);
+    }
+    else
+    {
+       err = rannotate_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0,
+                             local, NULL, NULL);
+    }
+
+    return err;
+}
+    
+
+static int
+rannotate_proc (int argc, char **argv, char *xwhere, char *mwhere,
+               char *mfile, int shorten, int local, char *mname, char *msg)
+{
+    /* Begin section which is identical to patch_proc--should this
+       be abstracted out somehow?  */
+    char *myargv[2];
+    int err = 0;
+    int which;
+    char *repository;
+    char *where;
+
+    if (is_rannotate)
+    {
+       repository = xmalloc (strlen (current_parsed_root->directory) + strlen 
(argv[0])
+                             + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
+       (void) sprintf (repository, "%s/%s", current_parsed_root->directory, 
argv[0]);
+       where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) 
+ 1)
+                        + 1);
+       (void) strcpy (where, argv[0]);
+
+       /* if mfile isn't null, we need to set up to do only part of the module 
*/
+       if (mfile != NULL)
+       {
+           char *cp;
+           char *path;
+
+           /* if the portion of the module is a path, put the dir part on 
repos */
+           if ((cp = strrchr (mfile, '/')) != NULL)
+           {
+               *cp = '\0';
+               (void) strcat (repository, "/");
+               (void) strcat (repository, mfile);
+               (void) strcat (where, "/");
+               (void) strcat (where, mfile);
+               mfile = cp + 1;
+           }
+
+           /* take care of the rest */
+           path = Xasprintf ("%s/%s", repository, mfile);
+           if (isdir (path))
+           {
+               /* directory means repository gets the dir tacked on */
+               (void) strcpy (repository, path);
+               (void) strcat (where, "/");
+               (void) strcat (where, mfile);
+           }
+           else
+           {
+               myargv[0] = argv[0];
+               myargv[1] = mfile;
+               argc = 2;
+               argv = myargv;
+           }
+           free (path);
+       }
+
+       /* cd to the starting repository */
+       if (CVS_CHDIR (repository) < 0)
+       {
+           error (0, errno, "cannot chdir to %s", repository);
+           free (repository);
+           free (where);
+           return 1;
+       }
+       /* End section which is identical to patch_proc.  */
+
+       if (force_tag_match && tag != NULL)
+           which = W_REPOS | W_ATTIC;
+       else
+           which = W_REPOS;
+    }
+    else
+    {
+        where = NULL;
+        which = W_LOCAL;
+        repository = "";
+    }
+
+    if (tag != NULL && !tag_validated)
+    {
+       tag_check_valid (tag, argc - 1, argv + 1, local, 0, repository, false);
+       tag_validated = 1;
+    }
+
+    err = start_recursion (annotate_fileproc, NULL, NULL, NULL, NULL,
+                          argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
+                          where, 1, repository);
+    if (which & W_REPOS)
+       free (repository);
+    if (where != NULL)
+       free (where);
+    return err;
+}
+
+
+static int
+annotate_fileproc (void *callerdat, struct file_info *finfo)
+{
+    char *expand, *version;
+
+    if (finfo->rcs == NULL)
+        return 1;
+
+    if (finfo->rcs->flags & PARTIAL)
+        RCS_reparsercsfile (finfo->rcs, NULL, NULL);
+
+    expand = RCS_getexpand (finfo->rcs);
+    version = RCS_getversion (finfo->rcs, tag, date, force_tag_match, NULL);
+
+    if (version == NULL)
+        return 0;
+
+    /* Distinguish output for various files if we are processing
+       several files.  */
+    cvs_outerr ("\nAnnotations for ", 0);
+    cvs_outerr (finfo->fullname, 0);
+    cvs_outerr ("\n***************\n", 0);
+
+    if (!force_binary && expand && expand[0] == 'b')
+    {
+        cvs_outerr ("Skipping binary file -- -F not specified.\n", 0);
+    }
+    else
+    {
+       RCS_deltas (finfo->rcs, NULL, NULL,
+                   version, RCS_ANNOTATE, NULL, NULL, NULL, NULL);
+    }
+    free (version);
+    return 0;
+}
Index: ccvs/src/base.c
diff -u ccvs/src/base.c:1.1.4.2 ccvs/src/base.c:1.1.4.3
--- ccvs/src/base.c:1.1.4.2     Sun Jan  1 22:59:16 2006
+++ ccvs/src/base.c     Fri Jan  6 19:34:15 2006
@@ -32,6 +32,7 @@
 #include "difflib.h"
 #include "server.h"
 #include "subr.h"
+
 #include "cvs.h"       /* For CVSADM_BASE. */
 
 
Index: ccvs/src/buffer.c
diff -u ccvs/src/buffer.c:1.65.2.1 ccvs/src/buffer.c:1.65.2.2
--- ccvs/src/buffer.c:1.65.2.1  Wed Dec 21 13:25:10 2005
+++ ccvs/src/buffer.c   Fri Jan  6 19:34:15 2006
@@ -18,11 +18,17 @@
 # include <config.h>
 #endif
 
+/* Verify interface.  */
 #include "buffer.h"
 
-#include "cvs.h"
+/* GNULIB headers.  */
 #include "pagealign_alloc.h"
 
+/* CVS headers.  */
+#include "cvs.h"
+
+
+
 #if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
 
 # include <sys/socket.h>
Index: ccvs/src/checkin.c
diff -u ccvs/src/checkin.c:1.56.2.1 ccvs/src/checkin.c:1.56.2.2
--- ccvs/src/checkin.c:1.56.2.1 Wed Dec 21 13:25:10 2005
+++ ccvs/src/checkin.c  Fri Jan  6 19:34:15 2006
@@ -26,10 +26,12 @@
 
 /* CVS */
 #include "base.h"
+#include "edit.h"
 
 #include "cvs.h"
 #include "fileattr.h"
-#include "edit.h"
+
+
 
 int
 Checkin (int type, struct file_info *finfo, char *rev, char *tag,
Index: ccvs/src/checkout.c
diff -u /dev/null ccvs/src/checkout.c:1.145.2.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/checkout.c Fri Jan  6 19:34:15 2006
@@ -0,0 +1,1228 @@
+/*
+ * 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.
+ * 
+ * Create Version
+ * 
+ * "checkout" creates a "version" of an RCS repository.  This version is owned
+ * totally by the user and is actually an independent copy, to be dealt with
+ * as seen fit.  Once "checkout" has been called in a given directory, it
+ * never needs to be called again.  The user can keep up-to-date by calling
+ * "update" when he feels like it; this will supply him with a merge of his
+ * own modifications and the changes made in the RCS original.  See "update"
+ * for details.
+ * 
+ * "checkout" can be given a list of directories or files to be updated and in
+ * the case of a directory, will recursivley create any sub-directories that
+ * exist in the repository.
+ * 
+ * When the user is satisfied with his own modifications, the present version
+ * can be committed by "commit"; this keeps the present version in tact,
+ * usually.
+ * 
+ * The call is cvs checkout [options] <module-name>...
+ * 
+ * "checkout" creates a directory ./CVS, in which it keeps its administration,
+ * in two files, Repository and Entries. The first contains the name of the
+ * repository.  The second contains one line for each registered file,
+ * consisting of the version number it derives from, its time stamp at
+ * derivation time and its name.  Both files are normal files and can be
+ * edited by the user, if necessary (when the repository is moved, e.g.)
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* CVS headers.  */
+#include "ignore.h"
+#include "recurse.h"
+
+#include "cvs.h"
+
+
+
+static char *findslash (char *start, char *p);
+static int checkout_proc (int argc, char **argv, char *where,
+                         char *mwhere, char *mfile, int shorten,
+                         int local_specified, char *omodule,
+                         char *msg);
+
+static const char *const checkout_usage[] =
+{
+    "Usage:\n  %s %s [-ANPRcflnps] [-r rev] [-D date] [-d dir]\n",
+    "    [-j rev1] [-j rev2] [-k kopt] modules...\n",
+    "\t-A\tReset any sticky tags/date/kopts.\n",
+    "\t-N\tDon't shorten module paths if -d specified.\n",
+    "\t-P\tPrune empty directories.\n",
+    "\t-R\tProcess directories recursively.\n",
+    "\t-c\t\"cat\" the module database.\n",
+    "\t-f\tForce a head revision match if tag/date not found.\n",
+    "\t-l\tLocal directory only, not recursive\n",
+    "\t-n\tDo not run module program (if any).\n",
+    "\t-p\tCheck out files to standard output (avoids stickiness).\n",
+    "\t-s\tLike -c, but include module status.\n",
+    "\t-r rev\tCheck out revision or tag. (implies -P) (is sticky)\n",
+    "\t-D date\tCheck out revisions as of date. (implies -P) (is sticky)\n",
+    "\t-d dir\tCheck out into dir instead of module name.\n",
+    "\t-k kopt\tUse RCS kopt -k option on checkout. (is sticky)\n",
+    "\t-j rev\tMerge in changes made between current revision and rev.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+static const char *const export_usage[] =
+{
+    "Usage: %s %s [-NRfln] [-r tag] [-D date] [-d dir] [-k kopt] module...\n",
+    "\t-N\tDon't shorten module paths if -d specified.\n",
+    "\t-f\tForce a head revision match if tag/date not found.\n",
+    "\t-l\tLocal directory only, not recursive\n",
+    "\t-R\tProcess directories recursively (default).\n",
+    "\t-n\tDo not run module program (if any).\n",
+    "\t-r tag\tExport tagged revisions.\n",
+    "\t-D date\tExport revisions as of date.\n",
+    "\t-d dir\tExport into dir instead of module name.\n",
+    "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+static int checkout_prune_dirs;
+static int force_tag_match;
+static int pipeout;
+static int aflag;
+static char *options;
+static char *tag;
+static bool tag_validated;
+static char *date;
+static char *join_rev1, *join_date1;
+static char *join_rev2, *join_date2;
+static bool join_tags_validated;
+static char *preload_update_dir;
+static char *history_name;
+static enum mtype m_type;
+
+int
+checkout (int argc, char **argv)
+{
+    int i;
+    int c;
+    DBM *db;
+    int cat = 0, err = 0, status = 0;
+    int run_module_prog = 1;
+    int local = 0;
+    int shorten = -1;
+    char *where = NULL;
+    const char *valid_options;
+    const char *const *valid_usage;
+    char *join_orig1, *join_orig2;
+
+    /* initialize static options */
+    force_tag_match = 1;
+    if (options)
+    {
+       free (options);
+       options = NULL;
+    }
+    tag = date = join_rev1 = join_date1 = join_rev2 = join_date2 =
+         join_orig1 = join_orig2 = preload_update_dir = NULL;
+    history_name = NULL;
+    tag_validated = join_tags_validated = false;
+
+
+    /*
+     * A smaller subset of options are allowed for the export command, which
+     * is essentially like checkout, except that it hard-codes certain
+     * options to be default (like -kv) and takes care to remove the CVS
+     * directory when it has done its duty
+     */
+    if (strcmp (cvs_cmd_name, "export") == 0)
+    {
+        m_type = EXPORT;
+       valid_options = "+Nnk:d:flRQqr:D:";
+       valid_usage = export_usage;
+    }
+    else
+    {
+        m_type = CHECKOUT;
+       valid_options = "+ANnk:d:flRpQqcsr:D:j:P";
+       valid_usage = checkout_usage;
+    }
+
+    if (argc == -1)
+       usage (valid_usage);
+
+    ign_setup ();
+    wrap_setup ();
+
+    optind = 0;
+    while ((c = getopt (argc, argv, valid_options)) != -1)
+    {
+       switch (c)
+       {
+           case 'A':
+               aflag = 1;
+               break;
+           case 'N':
+               shorten = 0;
+               break;
+           case 'k':
+               if (options)
+                   free (options);
+               options = RCS_check_kflag (optarg);
+               break;
+           case 'n':
+               run_module_prog = 0;
+               break;
+           case 'Q':
+           case 'q':
+               /* 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 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case 'P':
+               checkout_prune_dirs = 1;
+               break;
+           case 'p':
+               pipeout = 1;
+               run_module_prog = 0;    /* don't run module prog when piping */
+               noexec = 1;             /* so no locks will be created */
+               break;
+           case 'c':
+               cat = 1;
+               break;
+           case 'd':
+               where = optarg;
+               if (shorten == -1)
+                   shorten = 1;
+               break;
+           case 's':
+               cat = status = 1;
+               break;
+           case 'f':
+               force_tag_match = 0;
+               break;
+           case 'r':
+               parse_tagdate (&tag, &date, optarg);
+               checkout_prune_dirs = 1;
+               break;
+           case 'D':
+               if (date) free (date);
+               date = Make_Date (optarg);
+               checkout_prune_dirs = 1;
+               break;
+           case 'j':
+               if (join_rev2 || join_date2)
+                   error (1, 0, "only two -j options can be specified");
+               if (join_rev1 || join_date1)
+               {
+                   if (join_orig2) free (join_orig2);
+                   join_orig2 = xstrdup (optarg);
+                   parse_tagdate (&join_rev2, &join_date2, optarg);
+               }
+               else
+               {
+                   if (join_orig1) free (join_orig1);
+                   join_orig1 = xstrdup (optarg);
+                   parse_tagdate (&join_rev1, &join_date1, optarg);
+               }
+               break;
+           case '?':
+           default:
+               usage (valid_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (shorten == -1)
+       shorten = 0;
+
+    if (cat && argc != 0)
+       error (1, 0, "-c and -s must not get any arguments");
+
+    if (!cat && argc == 0)
+       error (1, 0, "must specify at least one module or directory");
+
+    if (where && pipeout)
+       error (1, 0, "-d and -p are mutually exclusive");
+
+    if (m_type == EXPORT)
+    {
+       if (!tag && !date)
+           error (1, 0, "must specify a tag or date");
+
+       if (tag && isdigit (tag[0]))
+           error (1, 0, "tag `%s' must be a symbolic tag", tag);
+    }
+
+#ifdef SERVER_SUPPORT
+    if (server_active && where != NULL)
+    {
+       server_pathname_check (where);
+    }
+#endif
+
+    if (!cat && !pipeout && !safe_location (where))
+    {
+        error (1, 0, "Cannot check out files into the repository itself");
+    }
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       int expand_modules;
+
+       start_server ();
+
+       ign_setup ();
+       
+       expand_modules = (!cat && !pipeout
+                         && supported_request ("expand-modules"));
+       
+       if (expand_modules)
+       {
+           /* This is done here because we need to read responses
+               from the server before we send the command checkout or
+               export files. */
+
+           client_expand_modules (argc, argv, local);
+       }
+
+       if (!run_module_prog)
+           send_arg ("-n");
+       if (local)
+           send_arg ("-l");
+       if (pipeout)
+           send_arg ("-p");
+       if (!force_tag_match)
+           send_arg ("-f");
+       if (aflag)
+           send_arg ("-A");
+       if (!shorten)
+           send_arg ("-N");
+       if (checkout_prune_dirs && m_type == CHECKOUT)
+           send_arg ("-P");
+       client_prune_dirs = checkout_prune_dirs;
+       if (cat && !status)
+           send_arg ("-c");
+       if (where != NULL)
+           option_with_arg ("-d", where);
+       if (status)
+           send_arg ("-s");
+       if (options != NULL && options[0] != '\0')
+           send_arg (options);
+       option_with_arg ("-r", tag);
+       if (date)
+           client_senddate (date);
+       if (join_orig1)
+           option_with_arg ("-j", join_orig1);
+       if (join_orig2)
+           option_with_arg ("-j", join_orig2);
+       send_arg ("--");
+
+       if (expand_modules)
+       {
+           client_send_expansions (local, where, 1);
+       }
+       else
+       {
+           int i;
+           for (i = 0; i < argc; ++i)
+               send_arg (argv[i]);
+           client_nonexpanded_setup ();
+       }
+
+       send_to_server (m_type == EXPORT ? "export\012" : "co\012", 0);
+       return get_responses_and_close ();
+    }
+#endif /* CLIENT_SUPPORT */
+
+    if (cat)
+    {
+       cat_module (status);
+       if (options)
+       {
+           free (options);
+           options = NULL;
+       }
+       return 0;
+    }
+    db = open_module ();
+
+
+    /* If we've specified something like "cvs co foo/bar baz/quux"
+       don't try to shorten names.  There are a few cases in which we
+       could shorten (e.g. "cvs co foo/bar foo/baz"), but we don't
+       handle those yet.  Better to have an extra directory created
+       than the thing checked out under the wrong directory name. */
+
+    if (argc > 1)
+       shorten = 0;
+
+
+    /* If we will be calling history_write, work out the name to pass
+       it.  */
+    if (!pipeout)
+    {
+       if (!date)
+           history_name = tag;
+       else if (!tag)
+           history_name = date;
+       else
+           history_name = Xasprintf ("%s:%s", tag, date);
+    }
+
+
+    for (i = 0; i < argc; i++)
+       err += do_module (db, argv[i], m_type, "Updating", checkout_proc,
+                         where, shorten, local, run_module_prog, !pipeout,
+                         NULL);
+    close_module (db);
+    if (options)
+    {
+       free (options);
+       options = NULL;
+    }
+    if (history_name != tag && history_name != date && history_name != NULL)
+       free (history_name);
+    return err;
+}
+
+
+
+/* FIXME: This is and emptydir_name are in checkout.c for historical
+   reasons, probably want to move them.  */
+
+/* int
+ * safe_location ( char *where )
+ *
+ * Return true if where is a safe destination for a checkout.
+ *
+ * INPUTS
+ *  where      The requested destination directory.
+ *
+ * GLOBALS
+ *  current_parsed_root->directory
+ *  current_parsed_root->isremote
+ *             Used to locate our CVSROOT.
+ *
+ * RETURNS
+ *  true       If we are running in client mode or if where is not located
+ *             within the CVSROOT.
+ *  false      Otherwise.
+ *
+ * ERRORS
+ *  Exits with a fatal error message when various events occur, such as not
+ *  being able to resolve a path or failing ot chdir to a path.
+ */
+int
+safe_location (char *where)
+{
+    char *current;
+    char *hardpath;
+    size_t hardpath_len;
+    int retval;
+
+    TRACE (TRACE_FUNCTION, "safe_location( where=%s )",
+           where ? where : "(null)");
+
+    /* Don't compare remote CVSROOTs to our destination directory. */
+    if (current_parsed_root->isremote) return 1;
+
+    /* set current - even if where is set we'll need to cd back... */
+    current = xgetcwd ();
+    if (current == NULL)
+       error (1, errno, "could not get working directory");
+
+    hardpath = xcanonicalize_file_name (current_parsed_root->directory);
+
+    /* if where is set, set current to as much of where as exists,
+     * or fail.
+     */
+    if (where != NULL)
+    {
+       char *where_this_pass = xstrdup (where);
+       while (1)
+       {
+           if (CVS_CHDIR (where_this_pass) != -1)
+           {
+               /* where */
+               free (where_this_pass);
+               where_this_pass = xgetcwd ();
+               if (where_this_pass == NULL)
+                   error (1, errno, "could not get working directory");
+
+               if (CVS_CHDIR (current) == -1)
+                   error (1, errno,
+                          "could not restore directory to `%s'", current);
+
+               free (current);
+               current = where_this_pass;
+               break;
+           }
+           else if (errno == ENOENT)
+           {
+               /* where_this_pass - last_component (where_this_pass) */
+               char *parent;
+
+               /* It's okay to cast out the const below since we know we
+                * allocated where_this_pass and have control of it.
+                */
+               if ((parent = (char *)last_component (where_this_pass))
+                       != where_this_pass)
+               {
+                   /* strip the last_component */
+                   parent[-1] = '\0';
+                   /* continue */
+               }
+               else
+               {
+                   /* ERRNO == ENOENT
+                    *   && last_component (where_this_pass) == where_this_pass
+                    * means we've tried all the parent diretories and not one
+                    * exists, so there is no need to test any portion of where
+                    * - it is all being created.
+                    */
+                   free (where_this_pass);
+                   break;
+               }
+           }
+           else
+               /* we don't know how to handle other errors, so fail */
+               error (1, errno, "\
+could not change directory to requested checkout directory `%s'",
+                      where_this_pass);
+       } /* while (1) */
+    } /* where != NULL */
+
+    hardpath_len = strlen (hardpath);
+    if (strlen (current) >= hardpath_len
+       && strncmp (current, hardpath, hardpath_len) == 0)
+    {
+       if (/* Current is a subdirectory of hardpath.  */
+           current[hardpath_len] == '/'
+
+           /* Current is hardpath itself.  */
+           || current[hardpath_len] == '\0')
+           retval = 0;
+       else
+           /* It isn't a problem.  For example, current is
+              "/foo/cvsroot-bar" and hardpath is "/foo/cvsroot".  */
+           retval = 1;
+    }
+    else
+       retval = 1;
+    free (current);
+    free (hardpath);
+    return retval;
+}
+
+
+
+struct dir_to_build
+{
+    /* What to put in CVS/Repository.  */
+    char *repository;
+    /* The path to the directory.  */
+    char *dirpath;
+
+    struct dir_to_build *next;
+};
+
+
+
+static int build_dirs_and_chdir (struct dir_to_build *list,
+                                       int sticky);
+
+static void
+build_one_dir (char *repository, char *dirpath, int sticky)
+{
+    FILE *fp;
+
+    if (isfile (CVSADM))
+    {
+       if (m_type == EXPORT)
+           error (1, 0, "cannot export into a working directory");
+    }
+    else if (m_type == CHECKOUT)
+    {
+       /* I suspect that this check could be omitted.  */
+       if (!isdir (repository))
+           error (1, 0, "there is no repository %s", repository);
+
+       if (Create_Admin (".", dirpath, repository,
+                         sticky ? tag : NULL,
+                         sticky ? date : NULL,
+
+                         /* FIXME?  This is a guess.  If it is important
+                            for nonbranch to be set correctly here I
+                            think we need to write it one way now and
+                            then rewrite it later via WriteTag, once
+                            we've had a chance to call RCS_nodeisbranch
+                            on each file.  */
+                         0, 1, 1))
+           return;
+
+       if (!noexec)
+       {
+           fp = xfopen (CVSADM_ENTSTAT, "w+");
+           if (fclose (fp) == EOF)
+               error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+           if (server_active)
+               server_set_entstat (dirpath, repository);
+#endif
+       }
+    }
+}
+
+
+
+/*
+ * process_module calls us back here so we do the actual checkout stuff
+ */
+/* ARGSUSED */
+static int
+checkout_proc (int argc, char **argv, char *where_orig, char *mwhere,
+              char *mfile, int shorten, int local_specified, char *omodule,
+              char *msg)
+{
+    char *myargv[2];
+    int err = 0;
+    int which;
+    char *cp;
+    char *repository;
+    char *oldupdate = NULL;
+    char *where;
+
+    TRACE (TRACE_FUNCTION, "checkout_proc (%s, %s, %s, %d, %d, %s, %s)\n",
+          where_orig ? where_orig : "(null)",
+          mwhere ? mwhere : "(null)",
+          mfile ? mfile : "(null)",
+          shorten, local_specified,
+          omodule ? omodule : "(null)",
+          msg ? msg : "(null)"
+         );
+
+    /*
+     * OK, so we're doing the checkout! Our args are as follows: 
+     *  argc,argv contain either dir or dir followed by a list of files 
+     *  where contains where to put it (if supplied by checkout) 
+     *  mwhere contains the module name or -d from module file 
+     *  mfile says do only that part of the module
+     *  shorten = 1 says shorten as much as possible 
+     *  omodule is the original arg to do_module()
+     */
+
+    /* Set up the repository (maybe) for the bottom directory.
+       Allocate more space than we need so we don't need to keep
+       reallocating this string. */
+    repository = xmalloc (strlen (current_parsed_root->directory)
+                         + strlen (argv[0])
+                         + (mfile == NULL ? 0 : strlen (mfile))
+                         + 10);
+    (void) sprintf (repository, "%s/%s",
+                    current_parsed_root->directory, argv[0]);
+    Sanitize_Repository_Name (repository);
+
+
+    /* save the original value of preload_update_dir */
+    if (preload_update_dir != NULL)
+       oldupdate = xstrdup (preload_update_dir);
+
+
+    /* Allocate space and set up the where variable.  We allocate more
+       space than necessary here so that we don't have to keep
+       reallocaing it later on. */
+    
+    where = xmalloc (strlen (argv[0])
+                    + (mfile == NULL ? 0 : strlen (mfile))
+                    + (mwhere == NULL ? 0 : strlen (mwhere))
+                    + (where_orig == NULL ? 0 : strlen (where_orig))
+                    + 10);
+
+    /* Yes, this could be written in a less verbose way, but in this
+       form it is quite easy to read.
+    
+       FIXME?  The following code that sets should probably be moved
+       to do_module in modules.c, since there is similar code in
+       patch.c and rtag.c. */
+    
+    if (shorten)
+    {
+       if (where_orig != NULL)
+       {
+           /* If the user has specified a directory with `-d' on the
+              command line, use it preferentially, even over the `-d'
+              flag in the modules file. */
+    
+           (void) strcpy (where, where_orig);
+       }
+       else if (mwhere != NULL)
+       {
+           /* Second preference is the value of mwhere, which is from
+              the `-d' flag in the modules file. */
+
+           (void) strcpy (where, mwhere);
+       }
+       else
+       {
+           /* Third preference is the directory specified in argv[0]
+              which is this module'e directory in the repository. */
+           
+           (void) strcpy (where, argv[0]);
+       }
+    }
+    else
+    {
+       /* Use the same preferences here, bug don't shorten -- that
+           is, tack on where_orig if it exists. */
+
+       *where = '\0';
+
+       if (where_orig != NULL)
+       {
+           (void) strcat (where, where_orig);
+           (void) strcat (where, "/");
+       }
+
+       /* If the -d flag in the modules file specified an absolute
+           directory, let the user override it with the command-line
+           -d option. */
+
+       if (mwhere && !ISABSOLUTE (mwhere))
+           (void) strcat (where, mwhere);
+       else
+           (void) strcat (where, argv[0]);
+    }
+    strip_trailing_slashes (where); /* necessary? */
+
+
+    /* At this point, the user may have asked for a single file or
+       directory from within a module.  In that case, we should modify
+       where, repository, and argv as appropriate. */
+
+    if (mfile != NULL)
+    {
+       /* The mfile variable can have one or more path elements.  If
+          it has multiple elements, we want to tack those onto both
+          repository and where.  The last element may refer to either
+          a file or directory.  Here's what to do:
+
+          it refers to a directory
+            -> simply tack it on to where and repository
+          it refers to a file
+            -> munge argv to contain `basename mfile` */
+
+       char *cp;
+       char *path;
+
+
+       /* Paranoia check. */
+
+       if (mfile[strlen (mfile) - 1] == '/')
+       {
+           error (0, 0, "checkout_proc: trailing slash on mfile (%s)!",
+                  mfile);
+       }
+
+
+       /* Does mfile have multiple path elements? */
+
+       cp = strrchr (mfile, '/');
+       if (cp != NULL)
+       {
+           *cp = '\0';
+           (void) strcat (repository, "/");
+           (void) strcat (repository, mfile);
+           (void) strcat (where, "/");
+           (void) strcat (where, mfile);
+           mfile = cp + 1;
+       }
+       
+
+       /* Now mfile is a single path element. */
+
+       path = Xasprintf ("%s/%s", repository, mfile);
+       if (isdir (path))
+       {
+           /* It's a directory, so tack it on to repository and
+               where, as we did above. */
+
+           (void) strcat (repository, "/");
+           (void) strcat (repository, mfile);
+           (void) strcat (where, "/");
+           (void) strcat (where, mfile);
+       }
+       else
+       {
+           /* It's a file, which means we have to screw around with
+               argv. */
+           myargv[0] = argv[0];
+           myargv[1] = mfile;
+           argc = 2;
+           argv = myargv;
+       }
+       free (path);
+    }
+
+    if (preload_update_dir != NULL)
+    {
+       preload_update_dir =
+           xrealloc (preload_update_dir,
+                     strlen (preload_update_dir) + strlen (where) + 5);
+       strcat (preload_update_dir, "/");
+       strcat (preload_update_dir, where);
+    }
+    else
+       preload_update_dir = xstrdup (where);
+
+    /*
+     * At this point, where is the directory we want to build, repository is
+     * the repository for the lowest level of the path.
+     *
+     * We need to tell build_dirs not only the path we want it to
+     * build, but also the repositories we want it to populate the
+     * path with.  To accomplish this, we walk the path backwards, one
+     * pathname component at a time, constucting a linked list of
+     * struct dir_to_build.
+     */
+
+    /*
+     * If we are sending everything to stdout, we can skip a whole bunch of
+     * work from here
+     */
+    if (!pipeout)
+    {
+       struct dir_to_build *head;
+       char *reposcopy;
+
+       if (strncmp (repository, current_parsed_root->directory,
+                    strlen (current_parsed_root->directory)) != 0)
+           error (1, 0, "\
+internal error: %s doesn't start with %s in checkout_proc",
+                  repository, current_parsed_root->directory);
+
+       /* We always create at least one directory, which corresponds to
+          the entire strings for WHERE and REPOSITORY.  */
+       head = xmalloc (sizeof (struct dir_to_build));
+       /* Special marker to indicate that we don't want build_dirs_and_chdir
+          to create the CVSADM directory for us.  */
+       head->repository = NULL;
+       head->dirpath = xstrdup (where);
+       head->next = NULL;
+
+       /* Make a copy of the repository name to play with. */
+       reposcopy = xstrdup (repository);
+
+       /* FIXME: this should be written in terms of last_component
+          instead of hardcoding '/'.  This presumably affects OS/2,
+          NT, &c, if the user specifies '\'.  Likewise for the call
+          to findslash.  */
+       cp = where + strlen (where);
+       while (cp > where)
+       {
+           struct dir_to_build *new;
+
+           cp = findslash (where, cp - 1);
+           if (cp == NULL)
+               break;          /* we're done */
+
+           new = xmalloc (sizeof (struct dir_to_build));
+           new->dirpath = xmalloc (strlen (where));
+
+           /* If the user specified an absolute path for where, the
+               last path element we create should be the top-level
+               directory. */
+
+           if (cp > where)
+           {
+               strncpy (new->dirpath, where, cp - where);
+               new->dirpath[cp - where] = '\0';
+           }
+           else
+           {
+               /* where should always be at least one character long. */
+               assert (where[0] != '\0');
+               strcpy (new->dirpath, "/");
+           }
+           new->next = head;
+           head = new;
+
+           /* Now figure out what repository directory to generate.
+               The most complete case would be something like this:
+
+              The modules file contains
+                foo -d bar/baz quux
+
+              The command issued was:
+                cvs co -d what/ever -N foo
+              
+              The results in the CVS/Repository files should be:
+                .     -> (don't touch CVS/Repository)
+                         (I think this case might be buggy currently)
+                what  -> (don't touch CVS/Repository)
+                ever  -> .          (same as "cd what/ever; cvs co -N foo")
+                bar   -> Emptydir   (generated dir -- not in repos)
+                baz   -> quux       (finally!) */
+
+           if (strcmp (reposcopy, current_parsed_root->directory) == 0)
+           {
+               /* We can't walk up past CVSROOT.  Instead, the
+                   repository should be Emptydir. */
+               new->repository = emptydir_name ();
+           }
+           else
+           {
+               /* It's a directory in the repository! */
+                   
+               char *rp;
+                   
+               /* We'll always be below CVSROOT, but check for
+                  paranoia's sake. */
+               rp = strrchr (reposcopy, '/');
+               if (rp == NULL)
+                   error (1, 0,
+                          "internal error: %s doesn't contain a slash",
+                          reposcopy);
+                          
+               *rp = '\0';
+                   
+               if (strcmp (reposcopy, current_parsed_root->directory) == 0)
+               {
+                   /* Special case -- the repository name needs
+                      to be "/path/to/repos/." (the trailing dot
+                      is important).  We might be able to get rid
+                      of this after the we check out the other
+                      code that handles repository names. */
+                   new-> repository = Xasprintf ("%s/.", reposcopy);
+               }
+               else
+                   new->repository = xstrdup (reposcopy);
+           }
+       }
+
+       /* clean up */
+       free (reposcopy);
+
+       /* The top-level CVSADM directory should always be
+          current_parsed_root->directory.  Create it, but only if WHERE is
+          relative.  If WHERE is absolute, our current directory
+          may not have a thing to do with where the sources are
+          being checked out.  If it does, build_dirs_and_chdir
+          will take care of creating adm files here. */
+       /* FIXME: checking where_is_absolute is a horrid kludge;
+          I suspect we probably can just skip the call to
+          build_one_dir whenever the -d command option was specified
+          to checkout.  */
+
+       if (!ISABSOLUTE (where) && config->top_level_admin
+           && m_type == CHECKOUT)
+       {
+           /* It may be argued that we shouldn't set any sticky
+              bits for the top-level repository.  FIXME?  */
+           build_one_dir (current_parsed_root->directory, ".", argc <= 1);
+
+#ifdef SERVER_SUPPORT
+           /* We _always_ want to have a top-level admin
+              directory.  If we're running in client/server mode,
+              send a "Clear-static-directory" command to make
+              sure it is created on the client side.  (See 5.10
+              in cvsclient.dvi to convince yourself that this is
+              OK.)  If this is a duplicate command being sent, it
+              will be ignored on the client side.  */
+
+           if (server_active)
+               server_clear_entstat (".", current_parsed_root->directory);
+#endif
+       }
+
+
+       /* Build dirs on the path if necessary and leave us in the
+          bottom directory (where if where was specified) doesn't
+          contain a CVS subdir yet, but all the others contain
+          CVS and Entries.Static files */
+
+       if (build_dirs_and_chdir (head, argc <= 1) != 0)
+       {
+           error (0, 0, "ignoring module %s", omodule);
+           err = 1;
+           goto out;
+       }
+
+       /* set up the repository (or make sure the old one matches) */
+       if (!isfile (CVSADM))
+       {
+           FILE *fp;
+
+           if (!noexec && argc > 1)
+           {
+               /* I'm not sure whether this check is redundant.  */
+               if (!isdir (repository))
+                   error (1, 0, "there is no repository %s", repository);
+
+               Create_Admin (".", preload_update_dir, repository,
+                             NULL, NULL, 0, 0, m_type == CHECKOUT);
+               fp = xfopen (CVSADM_ENTSTAT, "w+");
+               if (fclose (fp) == EOF)
+                   error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+               if (server_active)
+                   server_set_entstat (where, repository);
+#endif
+           }
+           else
+           {
+               /* I'm not sure whether this check is redundant.  */
+               if (!isdir (repository))
+                   error (1, 0, "there is no repository %s", repository);
+
+               Create_Admin (".", preload_update_dir, repository, tag, date,
+
+                             /* FIXME?  This is a guess.  If it is important
+                                for nonbranch to be set correctly here I
+                                think we need to write it one way now and
+                                then rewrite it later via WriteTag, once
+                                we've had a chance to call RCS_nodeisbranch
+                                on each file.  */
+                             0, 0, m_type == CHECKOUT);
+           }
+       }
+       else
+       {
+           char *repos;
+
+           if (m_type == EXPORT)
+               error (1, 0, "cannot export into working directory");
+
+           /* get the contents of the previously existing repository */
+           repos = Name_Repository (NULL, preload_update_dir);
+           if (fncmp (repository, repos) != 0)
+           {
+               char *prepos = xstrdup (primary_root_inverse_translate (repos));
+               char *prepository =
+                   xstrdup (primary_root_inverse_translate (repository));
+               error (0, 0, "existing repository %s does not match %s",
+                      prepos, prepository);
+               error (0, 0, "ignoring module %s", omodule);
+               free (repos);
+               free (prepos);
+               free (prepository);
+               err = 1;
+               goto out;
+           }
+           free (repos);
+       }
+    }
+
+    /*
+     * If we are going to be updating to stdout, we need to cd to the
+     * repository directory so the recursion processor can use the current
+     * directory as the place to find repository information
+     */
+    if (pipeout)
+    {
+       if (CVS_CHDIR (repository) < 0)
+       {
+           error (0, errno, "cannot chdir to %s", repository);
+           err = 1;
+           goto out;
+       }
+       which = W_REPOS;
+       if (tag && !tag_validated)
+       {
+           tag_check_valid (tag, argc - 1, argv + 1, 0, aflag,
+                            repository, false);
+           tag_validated = true;
+       }
+    }
+    else
+    {
+       which = W_LOCAL | W_REPOS;
+       if (tag && !tag_validated)
+       {
+           tag_check_valid (tag, argc - 1, argv + 1, 0, aflag,
+                            repository, false);
+           tag_validated = true;
+       }
+    }
+
+    if (tag || date || join_rev1 || join_date2)
+       which |= W_ATTIC;
+
+    if (!join_tags_validated)
+    {
+        if (join_rev1)
+           tag_check_valid (join_rev1, argc - 1, argv + 1, 0, aflag,
+                            repository, false);
+       if (join_rev2)
+           tag_check_valid (join_rev2, argc - 1, argv + 1, 0, aflag,
+                            repository, false);
+       join_tags_validated = true;
+    }
+
+    /*
+     * if we are going to be recursive (building dirs), go ahead and call the
+     * update recursion processor.  We will be recursive unless either local
+     * only was specified, or we were passed arguments
+     */
+    if (!(local_specified || argc > 1))
+    {
+       if (!pipeout)
+           history_write (m_type == CHECKOUT ? 'O' : 'E', preload_update_dir,
+                          history_name, where, repository);
+       err += do_update (0, NULL, options, tag, date,
+                         force_tag_match, false /* !local */ ,
+                         true /* update -d */ , aflag, checkout_prune_dirs,
+                         pipeout, which, join_rev1, join_date1,
+                         join_rev2, join_date2,
+                         preload_update_dir, m_type == CHECKOUT,
+                         repository);
+       goto out;
+    }
+
+    /* Don't log "export", just regular "checkouts" */
+    if (m_type == CHECKOUT && !pipeout)
+       history_write ('O', preload_update_dir, history_name, where,
+                      repository);
+
+    /* go ahead and call update now that everything is set */
+    err += do_update (argc - 1, argv + 1, options, tag, date,
+                     force_tag_match, local_specified, true /* update -d */,
+                     aflag, checkout_prune_dirs, pipeout, which, join_rev1,
+                     join_date1, join_rev2, join_date2, preload_update_dir,
+                     m_type == CHECKOUT, repository);
+out:
+    free (preload_update_dir);
+    preload_update_dir = oldupdate;
+    free (where);
+    free (repository);
+    return err;
+}
+
+
+
+static char *
+findslash (char *start, char *p)
+{
+    for (;;)
+    {
+       if (*p == '/') return p;
+       if (p == start) break;
+       --p;
+    }
+    return NULL;
+}
+
+
+
+/* Return a newly malloc'd string containing a pathname for CVSNULLREPOS,
+   and make sure that it exists.  If there is an error creating the
+   directory, give a fatal error.  Otherwise, the directory is guaranteed
+   to exist when we return.  */
+char *
+emptydir_name (void)
+{
+    char *repository;
+
+    repository = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
+                           CVSROOTADM, CVSNULLREPOS);
+    if (!isfile (repository))
+    {
+       mode_t omask;
+       omask = umask (cvsumask);
+       if (CVS_MKDIR (repository, 0777) < 0)
+           error (1, errno, "cannot create %s", repository);
+       (void) umask (omask);
+    }
+    return repository;
+}
+
+
+
+/* Build all the dirs along the path to DIRS with CVS subdirs with appropriate
+ * repositories.  If DIRS->repository is NULL or the directory already exists,
+ * do not create a CVSADM directory for that subdirectory; just CVS_CHDIR into
+ * it.  Frees all storage used by DIRS.
+ *
+ * ASSUMPTIONS
+ *   1. Parent directories will be listed in DIRS before their children.
+ *   2. At most a single directory will need to be changed at one time.  In
+ *      other words, if we are in /a/b/c, and our final destination is
+ *      /a/b/c/d/e/f, then we will build d, then d/e, then d/e/f.
+ *
+ * INPUTS
+ *   dirs      Simple list composed of dir_to_build structures, listing
+ *             information about directories to build.
+ *   sticky    Passed to build_one_dir to tell it whether there are any sticky
+ *             tags or dates to be concerned with.
+ *
+ * RETURNS
+ *   1 on error, 0 otherwise.
+ *
+ * ERRORS
+ *  The only nonfatal error this function may return is if the CHDIR fails.
+ */
+static int
+build_dirs_and_chdir (struct dir_to_build *dirs, int sticky)
+{
+    int retval = 0;
+    struct dir_to_build *nextdir;
+
+    while (dirs != NULL)
+    {
+       const char *dir = last_component (dirs->dirpath);
+       int made_dir = 0;
+
+       made_dir = !mkdir_if_needed (dir);
+       if (made_dir) Subdir_Register (NULL, NULL, dir);
+
+       if (CVS_CHDIR (dir) < 0)
+       {
+           error (0, errno, "cannot chdir to %s", dir);
+           retval = 1;
+           goto out;
+       }
+       if (dirs->repository != NULL)
+       {
+           if (made_dir)
+               build_one_dir (dirs->repository, dirs->dirpath, sticky);
+           free (dirs->repository);
+       }
+       nextdir = dirs->next;
+       free (dirs->dirpath);
+       free (dirs);
+       dirs = nextdir;
+    }
+
+ out:
+    while (dirs != NULL)
+    {
+       if (dirs->repository != NULL)
+           free (dirs->repository);
+       nextdir = dirs->next;
+       free (dirs->dirpath);
+       free (dirs);
+       dirs = nextdir;
+    }
+    return retval;
+}
Index: ccvs/src/classify.c
diff -u ccvs/src/classify.c:1.37.6.1 ccvs/src/classify.c:1.37.6.2
--- ccvs/src/classify.c:1.37.6.1        Wed Dec 21 13:25:10 2005
+++ ccvs/src/classify.c Fri Jan  6 19:34:15 2006
@@ -12,6 +12,14 @@
  * 
  */
 
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Verify interface.  */
+#include "classify.h"
+
+/* CVS Headers.  */
 #include "cvs.h"
 
 static void sticky_ck (struct file_info *finfo, int aflag,
Index: ccvs/src/cvs.h
diff -u ccvs/src/cvs.h:1.345.4.1 ccvs/src/cvs.h:1.345.4.2
--- ccvs/src/cvs.h:1.345.4.1    Wed Dec 21 13:25:10 2005
+++ ccvs/src/cvs.h      Fri Jan  6 19:34:15 2006
@@ -309,30 +309,6 @@
     int subdirs;
 };
 
-/* Flags for find_{names,dirs} routines */
-#define W_LOCAL                        0x01    /* look for files locally */
-#define W_REPOS                        0x02    /* look for files in the 
repository */
-#define W_ATTIC                        0x04    /* look for files in the attic 
*/
-
-/* Flags for return values of direnter procs for the recursion processor */
-enum direnter_type
-{
-    R_PROCESS = 1,                     /* process files and maybe dirs */
-    R_SKIP_FILES,                      /* don't process files in this dir */
-    R_SKIP_DIRS,                       /* don't process sub-dirs */
-    R_SKIP_ALL                         /* don't process files or dirs */
-};
-#ifdef ENUMS_CAN_BE_TROUBLE
-typedef int Dtype;
-#else
-typedef enum direnter_type Dtype;
-#endif
-
-/* Recursion processor lock types */
-#define CVS_LOCK_NONE  0
-#define CVS_LOCK_READ  1
-#define CVS_LOCK_WRITE 2
-
 /* Option flags for Parse_Info() */
 #define PIOPT_ALL 1    /* accept "all" keyword */
 
@@ -513,16 +489,6 @@
 void close_module (DBM * db);
 void fperrmsg (FILE * fp, int status, int errnum, char *message,...);
 
-int ign_name (char *name);
-void ign_add (char *ign, int hold);
-void ign_add_file (char *file, int hold);
-void ign_setup (void);
-void ign_dir_add (char *name);
-int ignore_directory (const char *name);
-typedef void (*Ignore_proc) (const char *, const char *);
-void ignore_files (List *, List *, const char *, Ignore_proc);
-extern int ign_inhibit_server;
-
 #include "update.h"
 
 void make_directories (const char *name);
@@ -558,17 +524,6 @@
        char *mwhere, char *mfile, int shorten, int local_specified,
        char *omodule, char *msg);
 
-
-typedef        int (*FILEPROC) (void *callerdat, struct file_info *finfo);
-typedef        int (*FILESDONEPROC) (void *callerdat, int err,
-                              const char *repository, const char *update_dir,
-                              List *entries);
-typedef        Dtype (*DIRENTPROC) (void *callerdat, const char *dir,
-                             const char *repos, const char *update_dir,
-                             List *entries);
-typedef        int (*DIRLEAVEPROC) (void *callerdat, const char *dir, int err,
-                             const char *update_dir, List *entries);
-
 int mkmodules (char *dir);
 int init (int argc, char **argv);
 
@@ -578,12 +533,6 @@
                char *extra_arg);
 void history_write (int type, const char *update_dir, const char *revs,
                     const char *name, const char *repository);
-int start_recursion (FILEPROC fileproc, FILESDONEPROC filesdoneproc,
-                    DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc,
-                    void *callerdat,
-                    int argc, char *argv[], int local, int which,
-                    int aflag, int locktype, char *update_preload,
-                    int dosrcs, char *repository);
 void SIG_beginCrSect (void);
 void SIG_endCrSect (void);
 int SIG_inCrSect (void);
@@ -618,44 +567,6 @@
 int special_file_mismatch (struct file_info *finfo,
                                  char *rev1, char *rev2);
 
-/*
- * defines for Classify_File() to determine the current state of a file.
- * These are also used as types in the data field for the list we make for
- * Update_Logfile in commit, import, and add.
- */
-enum classify_type
-{
-    T_UNKNOWN = 1,                     /* no old-style analog existed   */
-    T_CONFLICT,                                /* C (conflict) list            
 */
-    T_NEEDS_MERGE,                     /* G (needs merging) list        */
-    T_MODIFIED,                                /* M (needs checked in) list    
 */
-    T_CHECKOUT,                                /* O (needs checkout) list      
 */
-    T_ADDED,                           /* A (added file) list           */
-    T_REMOVED,                         /* R (removed file) list         */
-    T_REMOVE_ENTRY,                    /* W (removed entry) list        */
-    T_UPTODATE,                                /* File is up-to-date           
 */
-    T_PATCH,                           /* P Like C, but can patch       */
-    T_TITLE                            /* title for node type           */
-};
-typedef enum classify_type Ctype;
-
-Ctype Classify_File (struct file_info *finfo, char *tag, char *date, char 
*options,
-      int force_tag_match, int aflag, Vers_TS **versp, int pipeout);
-
-/*
- * structure used for list nodes passed to Update_Logfile() and
- * do_editor().
- */
-struct logfile_info
-{
-  enum classify_type type;
-  char *tag;
-  char *rev_old;               /* rev number before a commit/modify,
-                                  NULL for add or import */
-  char *rev_new;               /* rev number after a commit/modify,
-                                  add, or import, NULL for remove */
-};
-
 /* Wrappers.  */
 
 typedef enum { WRAP_MERGE, WRAP_COPY } WrapMergeMethod;
Index: ccvs/src/diff.c
diff -u ccvs/src/diff.c:1.116.6.1 ccvs/src/diff.c:1.116.6.2
--- ccvs/src/diff.c:1.116.6.1   Wed Dec 21 13:25:10 2005
+++ ccvs/src/diff.c     Fri Jan  6 19:34:15 2006
@@ -19,8 +19,18 @@
  * files.
  */
 
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* CVS headers.  */
+#include "ignore.h"
+#include "recurse.h"
+
 #include "cvs.h"
 
+
+
 enum diff_file
 {
     DIFF_ERROR,
Index: ccvs/src/edit.c
diff -u ccvs/src/edit.c:1.90.2.1 ccvs/src/edit.c:1.90.2.2
--- ccvs/src/edit.c:1.90.2.1    Wed Dec 21 13:25:10 2005
+++ ccvs/src/edit.c     Fri Jan  6 19:34:15 2006
@@ -10,13 +10,27 @@
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.  */
 
-#include "cvs.h"
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Verify interface.  */
+#include "edit.h"
+
+/* GNULIB headers.  */
 #include "getline.h"
 #include "yesno.h"
+
+/* CVS headers.  */
+#include "base.h"
+#include "ignore.h"
+#include "recurse.h"
+
+#include "cvs.h"
 #include "watch.h"
-#include "edit.h"
 #include "fileattr.h"
-#include "base.h"
+
+
 
 static bool check_edited = false;
 static int setting_default;
Index: ccvs/src/edit.h
diff -u /dev/null ccvs/src/edit.h:1.12.2.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/edit.h     Fri Jan  6 19:34:15 2006
@@ -0,0 +1,53 @@
+/* Interface to "cvs edit", "cvs watch on", and related features
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.  */
+
+#ifndef EDIT_H
+#define EDIT_H
+
+#include "hash.h"
+
+
+
+int watch_on (int argc, char **argv);
+int watch_off (int argc, char **argv);
+
+#ifdef CLIENT_SUPPORT
+/* Check to see if any notifications are sitting around in need of being
+   sent.  These are the notifications stored in CVSADM_NOTIFY (edit,unedit);
+   commit calls notify_do directly.  */
+void notify_check (const char *repository, const char *update_dir);
+#endif /* CLIENT_SUPPORT */
+
+/* Issue a notification for file FILENAME.  TYPE is 'E' for edit, 'U'
+   for unedit, and 'C' for commit.  WHO is the user currently running.
+   For TYPE 'E', VAL is the time+host+directory data which goes in
+   _editors, and WATCHES is zero or more of E,U,C, in that order, to specify
+   what kinds of temporary watches to set.  */
+void notify_do (int type, const char *filename, const char *upadte_dir,
+               const char *who, const char *val, const char *watches,
+               const char *repository);
+
+/* Set attributes to reflect the fact that EDITOR is editing FILENAME.
+   VAL is time+host+directory, or NULL if we are to say that EDITOR is
+   *not* editing FILENAME.  */
+void editor_set (const char *filename, const char *editor, const char *val);
+
+/* Take note of the fact that FILE is up to date (this munges CVS/Base;
+   processing of CVS/Entries is done separately).  */
+void mark_up_to_date (const char *update_dir, const char *file);
+
+void editors_output (const char *fullname, const char *them);
+
+void edit_file (void *data, List *ent_list, const char *short_pathname,
+               const char *filename);
+
+#endif /* EDIT_H */
Index: ccvs/src/entries.c
diff -u ccvs/src/entries.c:1.66.6.1 ccvs/src/entries.c:1.66.6.2
--- ccvs/src/entries.c:1.66.6.1 Wed Dec 21 13:25:10 2005
+++ ccvs/src/entries.c  Fri Jan  6 19:34:15 2006
@@ -31,6 +31,8 @@
 
 #include "cvs.h"
 
+
+
 static Node *AddEntryNode (List * list, Entnode *entnode);
 
 static Entnode *fgetentent (FILE *, char *, int *);
Index: ccvs/src/fileattr.c
diff -u /dev/null ccvs/src/fileattr.c:1.36.8.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/fileattr.c Fri Jan  6 19:34:15 2006
@@ -0,0 +1,706 @@
+/* Implementation for file attribute munging features.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Verify interface.  */
+#include "fileattr.h"
+
+/* GNULIB headers.  */
+#include "getline.h"
+
+/* CVS headers.  */
+#include "cvs.h"
+
+
+
+static void fileattr_read (void);
+static int writeattr_proc (Node *, void *);
+
+/* Where to look for CVSREP_FILEATTR.  */
+static char *fileattr_stored_repos;
+
+/* The in-memory attributes.  */
+static List *attrlist;
+static char *fileattr_default_attrs;
+/* We have already tried to read attributes and failed in this directory
+   (for example, there is no CVSREP_FILEATTR file).  */
+static int attr_read_attempted;
+
+/* Have the in-memory attributes been modified since we read them?  */
+static int attrs_modified;
+
+/* More in-memory attributes: linked list of unrecognized
+   fileattr lines.  We pass these on unchanged.  */
+struct unrecog {
+    char *line;
+    struct unrecog *next;
+};
+static struct unrecog *unrecog_head;
+
+
+
+/* Note that if noone calls fileattr_get, this is very cheap.  No stat(),
+   no open(), no nothing.  */
+void
+fileattr_startdir (const char *repos)
+{
+    assert (fileattr_stored_repos == NULL);
+    fileattr_stored_repos = xstrdup (repos);
+    assert (attrlist == NULL);
+    attr_read_attempted = 0;
+    assert (unrecog_head == NULL);
+}
+
+
+
+static void
+fileattr_delproc (Node *node)
+{
+    assert (node->data != NULL);
+    free (node->data);
+    node->data = NULL;
+}
+
+/* Read all the attributes for the current directory into memory.  */
+static void
+fileattr_read (void)
+{
+    char *fname;
+    FILE *fp;
+    char *line = NULL;
+    size_t line_len = 0;
+
+    /* If there are no attributes, don't waste time repeatedly looking
+       for the CVSREP_FILEATTR file.  */
+    if (attr_read_attempted)
+       return;
+
+    /* If NULL was passed to fileattr_startdir, then it isn't kosher to look
+       at attributes.  */
+    assert (fileattr_stored_repos != NULL);
+
+    fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
+
+    attr_read_attempted = 1;
+    fp = CVS_FOPEN (fname, FOPEN_BINARY_READ);
+    if (fp == NULL)
+    {
+       if (!existence_error (errno))
+           error (0, errno, "cannot read %s", fname);
+       free (fname);
+       return;
+    }
+    attrlist = getlist ();
+    while (1) {
+       int nread;
+       nread = getline (&line, &line_len, fp);
+       if (nread < 0)
+           break;
+       /* Remove trailing newline.
+        * It is okay to reference line[nread - 1] here, since getline must
+        * always return 1 character or EOF, but we need to verify that the
+        * character we eat is the newline, since getline can return a line
+        * w/o a newline just before returning EOF.
+        */
+       if (line[nread - 1] == '\n') line[nread - 1] = '\0';
+       if (line[0] == 'F')
+       {
+           char *p;
+           Node *newnode;
+
+           p = strchr (line, '\t');
+           if (p == NULL)
+               error (1, 0,
+                      "file attribute database corruption: tab missing in %s",
+                      primary_root_inverse_translate (fname));
+           *p++ = '\0';
+           newnode = getnode ();
+           newnode->type = FILEATTR;
+           newnode->delproc = fileattr_delproc;
+           newnode->key = xstrdup (line + 1);
+           newnode->data = xstrdup (p);
+           if (addnode (attrlist, newnode) != 0)
+               /* If the same filename appears twice in the file, discard
+                  any line other than the first for that filename.  This
+                  is the way that CVS has behaved since file attributes
+                  were first introduced.  */
+               freenode (newnode);
+       }
+       else if (line[0] == 'D')
+       {
+           char *p;
+           /* Currently nothing to skip here, but for future expansion,
+              ignore anything located here.  */
+           p = strchr (line, '\t');
+           if (p == NULL)
+               error (1, 0,
+                      "file attribute database corruption: tab missing in %s",
+                      fname);
+           ++p;
+           if (fileattr_default_attrs) free (fileattr_default_attrs);
+           fileattr_default_attrs = xstrdup (p);
+       }
+       else
+       {
+           /* Unrecognized type, we want to just preserve the line without
+              changing it, for future expansion.  */
+           struct unrecog *new;
+
+           new = xmalloc (sizeof (struct unrecog));
+           new->line = xstrdup (line);
+           new->next = unrecog_head;
+           unrecog_head = new;
+       }
+    }
+    if (ferror (fp))
+       error (0, errno, "cannot read %s", fname);
+    if (line != NULL)
+       free (line);
+    if (fclose (fp) < 0)
+       error (0, errno, "cannot close %s", fname);
+    attrs_modified = 0;
+    free (fname);
+}
+
+
+
+char *
+fileattr_get (const char *filename, const char *attrname)
+{
+    Node *node;
+    size_t attrname_len = strlen (attrname);
+    char *p;
+
+    if (attrlist == NULL)
+       fileattr_read ();
+    if (attrlist == NULL)
+       /* Either nothing has any attributes, or fileattr_read already printed
+          an error message.  */
+       return NULL;
+
+    if (filename == NULL)
+       p = fileattr_default_attrs;
+    else
+    {
+       node = findnode (attrlist, filename);
+       if (node == NULL)
+           /* A file not mentioned has no attributes.  */
+           return NULL;
+       p = node->data;
+    }
+    while (p)
+    {
+       if (strncmp (attrname, p, attrname_len) == 0
+           && p[attrname_len] == '=')
+       {
+           /* Found it.  */
+           return p + attrname_len + 1;
+       }
+       p = strchr (p, ';');
+       if (p == NULL)
+           break;
+       ++p;
+    }
+    /* The file doesn't have this attribute.  */
+    return NULL;
+}
+
+
+
+char *
+fileattr_get0 (const char *filename, const char *attrname)
+{
+    char *cp;
+    char *cpend;
+    char *retval;
+
+    cp = fileattr_get (filename, attrname);
+    if (cp == NULL)
+       return NULL;
+    cpend = strchr (cp, ';');
+    if (cpend == NULL)
+       cpend = cp + strlen (cp);
+    retval = xmalloc (cpend - cp + 1);
+    strncpy (retval, cp, cpend - cp);
+    retval[cpend - cp] = '\0';
+    return retval;
+}
+
+
+
+char *
+fileattr_modify (char *list, const char *attrname, const char *attrval, int 
namevalsep, int entsep)
+{
+    char *retval;
+    char *rp;
+    size_t attrname_len = strlen (attrname);
+
+    /* Portion of list before the attribute to be replaced.  */
+    char *pre;
+    char *preend;
+    /* Portion of list after the attribute to be replaced.  */
+    char *post;
+
+    char *p;
+    char *p2;
+
+    p = list;
+    pre = list;
+    preend = NULL;
+    /* post is NULL unless set otherwise.  */
+    post = NULL;
+    p2 = NULL;
+    if (list != NULL)
+    {
+       while (1) {
+           p2 = strchr (p, entsep);
+           if (p2 == NULL)
+           {
+               p2 = p + strlen (p);
+               if (preend == NULL)
+                   preend = p2;
+           }
+           else
+               ++p2;
+           if (strncmp (attrname, p, attrname_len) == 0
+               && p[attrname_len] == namevalsep)
+           {
+               /* Found it.  */
+               preend = p;
+               if (preend > list)
+                   /* Don't include the preceding entsep.  */
+                   --preend;
+
+               post = p2;
+           }
+           if (p2[0] == '\0')
+               break;
+           p = p2;
+       }
+    }
+    if (post == NULL)
+       post = p2;
+
+    if (preend == pre && attrval == NULL && post == p2)
+       return NULL;
+
+    retval = xmalloc ((preend - pre)
+                     + 1
+                     + (attrval == NULL ? 0 : (attrname_len + 1
+                                               + strlen (attrval)))
+                     + 1
+                     + (p2 - post)
+                     + 1);
+    if (preend != pre)
+    {
+       strncpy (retval, pre, preend - pre);
+       rp = retval + (preend - pre);
+       if (attrval != NULL)
+           *rp++ = entsep;
+       *rp = '\0';
+    }
+    else
+       retval[0] = '\0';
+    if (attrval != NULL)
+    {
+       strcat (retval, attrname);
+       rp = retval + strlen (retval);
+       *rp++ = namevalsep;
+       strcpy (rp, attrval);
+    }
+    if (post != p2)
+    {
+       rp = retval + strlen (retval);
+       if (preend != pre || attrval != NULL)
+           *rp++ = entsep;
+       strncpy (rp, post, p2 - post);
+       rp += p2 - post;
+       *rp = '\0';
+    }
+    return retval;
+}
+
+void
+fileattr_set (const char *filename, const char *attrname, const char *attrval)
+{
+    Node *node;
+    char *p;
+
+    if (filename == NULL)
+    {
+       p = fileattr_modify (fileattr_default_attrs, attrname, attrval,
+                            '=', ';');
+       if (fileattr_default_attrs != NULL)
+           free (fileattr_default_attrs);
+       fileattr_default_attrs = p;
+       attrs_modified = 1;
+       return;
+    }
+    if (attrlist == NULL)
+       fileattr_read ();
+    if (attrlist == NULL)
+    {
+       /* Not sure this is a graceful way to handle things
+          in the case where fileattr_read was unable to read the file.  */
+        /* No attributes existed previously.  */
+       attrlist = getlist ();
+    }
+
+    node = findnode (attrlist, filename);
+    if (node == NULL)
+    {
+       if (attrval == NULL)
+           /* Attempt to remove an attribute which wasn't there.  */
+           return;
+
+       /* First attribute for this file.  */
+       node = getnode ();
+       node->type = FILEATTR;
+       node->delproc = fileattr_delproc;
+       node->key = xstrdup (filename);
+       node->data = Xasprintf ("%s=%s", attrname, attrval);
+       addnode (attrlist, node);
+    }
+
+    p = fileattr_modify (node->data, attrname, attrval, '=', ';');
+    if (p == NULL)
+       delnode (node);
+    else
+    {
+       free (node->data);
+       node->data = p;
+    }
+
+    attrs_modified = 1;
+}
+
+
+
+char *
+fileattr_getall (const char *filename)
+{
+    Node *node;
+    char *p;
+
+    if (attrlist == NULL)
+       fileattr_read ();
+    if (attrlist == NULL)
+       /* Either nothing has any attributes, or fileattr_read already printed
+          an error message.  */
+       return NULL;
+
+    if (filename == NULL)
+       p = fileattr_default_attrs;
+    else
+    {
+       node = findnode (attrlist, filename);
+       if (node == NULL)
+           /* A file not mentioned has no attributes.  */
+           return NULL;
+       p = node->data;
+    }
+    return xstrdup (p);
+}
+
+
+
+void
+fileattr_setall (const char *filename, const char *attrs)
+{
+    Node *node;
+
+    if (filename == NULL)
+    {
+       if (fileattr_default_attrs != NULL)
+           free (fileattr_default_attrs);
+       fileattr_default_attrs = xstrdup (attrs);
+       attrs_modified = 1;
+       return;
+    }
+    if (attrlist == NULL)
+       fileattr_read ();
+    if (attrlist == NULL)
+    {
+       /* Not sure this is a graceful way to handle things
+          in the case where fileattr_read was unable to read the file.  */
+        /* No attributes existed previously.  */
+       attrlist = getlist ();
+    }
+
+    node = findnode (attrlist, filename);
+    if (node == NULL)
+    {
+       /* The file had no attributes.  Add them if we have any to add.  */
+       if (attrs != NULL)
+       {
+           node = getnode ();
+           node->type = FILEATTR;
+           node->delproc = fileattr_delproc;
+           node->key = xstrdup (filename);
+           node->data = xstrdup (attrs);
+           addnode (attrlist, node);
+       }
+    }
+    else
+    {
+       if (attrs == NULL)
+           delnode (node);
+       else
+       {
+           free (node->data);
+           node->data = xstrdup (attrs);
+       }
+    }
+
+    attrs_modified = 1;
+}
+
+
+
+void
+fileattr_newfile (const char *filename)
+{
+    Node *node;
+
+    if (attrlist == NULL)
+       fileattr_read ();
+
+    if (fileattr_default_attrs == NULL)
+       return;
+
+    if (attrlist == NULL)
+    {
+       /* Not sure this is a graceful way to handle things
+          in the case where fileattr_read was unable to read the file.  */
+        /* No attributes existed previously.  */
+       attrlist = getlist ();
+    }
+
+    node = getnode ();
+    node->type = FILEATTR;
+    node->delproc = fileattr_delproc;
+    node->key = xstrdup (filename);
+    node->data = xstrdup (fileattr_default_attrs);
+    addnode (attrlist, node);
+    attrs_modified = 1;
+}
+
+
+
+static int
+writeattr_proc (Node *node, void *data)
+{
+    FILE *fp = (FILE *)data;
+    fputs ("F", fp);
+    fputs (node->key, fp);
+    fputs ("\t", fp);
+    fputs (node->data, fp);
+    fputs ("\012", fp);
+    return 0;
+}
+
+
+
+/*
+ * callback proc to run a script when fileattrs are updated.
+ */
+static int
+postwatch_proc (const char *repository, const char *filter, void *closure)
+{
+    char *cmdline;
+    const char *srepos = Short_Repository (repository);
+
+    TRACE (TRACE_FUNCTION, "postwatch_proc (%s, %s)", repository, filter);
+
+    /* %c = command name
+     * %p = shortrepos
+     * %r = repository
+     */
+    /*
+     * Cast any NULL arguments as appropriate pointers as this is an
+     * stdarg function and we need to be certain the caller gets what
+     * is expected.
+     */
+    cmdline = format_cmdline (
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                             false, srepos,
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                             filter,
+                             "c", "s", cvs_cmd_name,
+#ifdef SERVER_SUPPORT
+                             "R", "s", referrer ? referrer->original : "NONE",
+#endif /* SERVER_SUPPORT */
+                             "p", "s", srepos,
+                             "r", "s", current_parsed_root->directory,
+                             (char *) NULL);
+
+    if (!cmdline || !strlen (cmdline))
+    {
+       if (cmdline) free (cmdline);
+       error (0, 0, "postwatch proc resolved to the empty string!");
+       return 1;
+    }
+
+    run_setup (cmdline);
+
+    free (cmdline);
+
+    /* FIXME - read the comment in verifymsg_proc() about why we use abs()
+     * below() and shouldn't.
+     */
+    return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+                         RUN_NORMAL | RUN_SIGIGNORE));
+}
+
+
+
+void
+fileattr_write (void)
+{
+    FILE *fp;
+    char *fname;
+    mode_t omask;
+    struct unrecog *p;
+
+    if (!attrs_modified)
+       return;
+
+    if (noexec)
+       return;
+
+    /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
+       attributes.  */
+    assert (fileattr_stored_repos != NULL);
+
+    fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
+
+    if (list_isempty (attrlist)
+       && fileattr_default_attrs == NULL
+       && unrecog_head == NULL)
+    {
+       /* There are no attributes.  */
+       if (unlink_file (fname) < 0)
+       {
+           if (!existence_error (errno))
+           {
+               error (0, errno, "cannot remove %s", fname);
+           }
+       }
+
+       /* Now remove CVSREP directory, if empty.  The main reason we bother
+          is that CVS 1.6 and earlier will choke if a CVSREP directory
+          exists, so provide the user a graceful way to remove it.  */
+       strcpy (fname, fileattr_stored_repos);
+       strcat (fname, "/");
+       strcat (fname, CVSREP);
+       if (CVS_RMDIR (fname) < 0)
+       {
+           if (errno != ENOTEMPTY
+
+               /* Don't know why we would be here if there is no CVSREP
+                  directory, but it seemed to be happening anyway, so
+                  check for it.  */
+               && !existence_error (errno))
+               error (0, errno, "cannot remove %s", fname);
+       }
+
+       free (fname);
+       return;
+    }
+
+    omask = umask (cvsumask);
+    fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
+    if (fp == NULL)
+    {
+       if (existence_error (errno))
+       {
+           /* Maybe the CVSREP directory doesn't exist.  Try creating it.  */
+           char *repname;
+
+           repname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP);
+
+           if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
+           {
+               error (0, errno, "cannot make directory %s", repname);
+               (void) umask (omask);
+               free (fname);
+               free (repname);
+               return;
+           }
+           free (repname);
+
+           fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
+       }
+       if (fp == NULL)
+       {
+           error (0, errno, "cannot write %s", fname);
+           (void) umask (omask);
+           free (fname);
+           return;
+       }
+    }
+    (void) umask (omask);
+
+    /* First write the "F" attributes.  */
+    walklist (attrlist, writeattr_proc, fp);
+
+    /* Then the "D" attribute.  */
+    if (fileattr_default_attrs != NULL)
+    {
+       fputs ("D\t", fp);
+       fputs (fileattr_default_attrs, fp);
+       fputs ("\012", fp);
+    }
+
+    /* Then any other attributes.  */
+    for (p = unrecog_head; p != NULL; p = p->next)
+    {
+       fputs (p->line, fp);
+       fputs ("\012", fp);
+    }
+
+    if (fclose (fp) < 0)
+       error (0, errno, "cannot close %s", fname);
+    attrs_modified = 0;
+    free (fname);
+
+    Parse_Info (CVSROOTADM_POSTWATCH, fileattr_stored_repos, postwatch_proc,
+               PIOPT_ALL, NULL);
+}
+
+
+
+void
+fileattr_free (void)
+{
+    /* Note that attrs_modified will ordinarily be zero, but there are
+       a few cases in which fileattr_write will fail to zero it (if
+       noexec is set, or error conditions).  This probably is the way
+       it should be.  */
+    dellist (&attrlist);
+    if (fileattr_stored_repos != NULL)
+       free (fileattr_stored_repos);
+    fileattr_stored_repos = NULL;
+    if (fileattr_default_attrs != NULL)
+       free (fileattr_default_attrs);
+    fileattr_default_attrs = NULL;
+    while (unrecog_head)
+    {
+       struct unrecog *p = unrecog_head;
+       unrecog_head = p->next;
+       free (p->line);
+       free (p);
+    }
+}
Index: ccvs/src/find_names.c
diff -u /dev/null ccvs/src/find_names.c:1.43.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/find_names.c       Fri Jan  6 19:34:15 2006
@@ -0,0 +1,543 @@
+/*
+ * 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.
+ * 
+ * Find Names
+ * 
+ * Finds all the pertinent file names, both from the administration and from 
the
+ * repository
+ * 
+ * Find Dirs
+ * 
+ * Finds all pertinent sub-directories of the checked out instantiation and the
+ * repository (and optionally the attic)
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* ANSI C headers.  */
+#include <assert.h>
+#include <glob.h>
+
+/* CVS headers.  */
+#include "recurse.h"
+
+#include "cvs.h"
+
+
+
+static int find_dirs (char *dir, List * list, int checkadm,
+                           List *entries);
+static int find_rcs (const char *dir, List * list);
+static int add_subdir_proc (Node *, void *);
+static int register_subdir_proc (Node *, void *);
+
+/*
+ * add the key from entry on entries list to the files list
+ */
+static int add_entries_proc (Node *, void *);
+static int
+add_entries_proc (Node *node, void *closure)
+{
+    Node *fnode;
+    List *filelist = closure;
+    Entnode *entnode = node->data;
+
+    if (entnode->type != ENT_FILE)
+       return (0);
+
+    fnode = getnode ();
+    fnode->type = FILES;
+    fnode->key = xstrdup (node->key);
+    if (addnode (filelist, fnode) != 0)
+       freenode (fnode);
+    return (0);
+}
+
+/* Find files in the repository and/or working directory.  On error,
+   may either print a nonfatal error and return NULL, or just give
+   a fatal error.  On success, return non-NULL (even if it is an empty
+   list).  */
+
+List *
+Find_Names (char *repository, int which, int aflag, List **optentries)
+{
+    List *entries;
+    List *files;
+
+    /* make a list for the files */
+    files = getlist ();
+
+    /* look at entries (if necessary) */
+    if (which & W_LOCAL)
+    {
+       /* parse the entries file (if it exists) */
+       entries = Entries_Open (aflag, NULL);
+       if (entries != NULL)
+       {
+           /* walk the entries file adding elements to the files list */
+           (void) walklist (entries, add_entries_proc, files);
+
+           /* if our caller wanted the entries list, return it; else free it */
+           if (optentries != NULL)
+               *optentries = entries;
+           else
+               Entries_Close (entries);
+       }
+    }
+
+    if ((which & W_REPOS) && repository && !isreadable (CVSADM_ENTSTAT))
+    {
+       /* search the repository */
+       if (find_rcs (repository, files) != 0)
+       {
+           error (0, errno, "cannot open directory %s",
+                  primary_root_inverse_translate (repository));
+           goto error_exit;
+       }
+
+       /* search the attic too */
+       if (which & W_ATTIC)
+       {
+           char *dir = Xasprintf ("%s/%s", repository, CVSATTIC);
+           if (find_rcs (dir, files) != 0
+               && !existence_error (errno))
+               /* For now keep this a fatal error, seems less useful
+                  for access control than the case above.  */
+               error (1, errno, "cannot open directory %s",
+                      primary_root_inverse_translate (dir));
+           free (dir);
+       }
+    }
+
+    /* sort the list into alphabetical order and return it */
+    sortlist (files, fsortcmp);
+    return files;
+ error_exit:
+    dellist (&files);
+    return NULL;
+}
+
+/*
+ * Add an entry from the subdirs list to the directories list.  This
+ * is called via walklist.
+ */
+
+static int
+add_subdir_proc (Node *p, void *closure)
+{
+    List *dirlist = closure;
+    Entnode *entnode = p->data;
+    Node *dnode;
+
+    if (entnode->type != ENT_SUBDIR)
+       return 0;
+
+    dnode = getnode ();
+    dnode->type = DIRS;
+    dnode->key = xstrdup (entnode->user);
+    if (addnode (dirlist, dnode) != 0)
+       freenode (dnode);
+    return 0;
+}
+
+/*
+ * Register a subdirectory.  This is called via walklist.
+ */
+
+/*ARGSUSED*/
+static int
+register_subdir_proc (Node *p, void *closure)
+{
+    List *entries = (List *) closure;
+
+    Subdir_Register (entries, NULL, p->key);
+    return 0;
+}
+
+/*
+ * create a list of directories to traverse from the current directory
+ */
+List *
+Find_Directories (char *repository, int which, List *entries)
+{
+    List *dirlist;
+
+    /* make a list for the directories */
+    dirlist = getlist ();
+
+    /* find the local ones */
+    if (which & W_LOCAL)
+    {
+       List *tmpentries;
+       struct stickydirtag *sdtp;
+
+       /* Look through the Entries file.  */
+
+       if (entries != NULL)
+           tmpentries = entries;
+       else if (isfile (CVSADM_ENT))
+           tmpentries = Entries_Open (0, NULL);
+       else
+           tmpentries = NULL;
+
+       if (tmpentries != NULL)
+           sdtp = tmpentries->list->data;
+
+       /* If we do have an entries list, then if sdtp is NULL, or if
+           sdtp->subdirs is nonzero, all subdirectory information is
+           recorded in the entries list.  */
+       if (tmpentries != NULL && (sdtp == NULL || sdtp->subdirs))
+           walklist (tmpentries, add_subdir_proc, (void *) dirlist);
+       else
+       {
+           /* This is an old working directory, in which subdirectory
+               information is not recorded in the Entries file.  Find
+               the subdirectories the hard way, and, if possible, add
+               it to the Entries file for next time.  */
+
+           /* FIXME-maybe: find_dirs is bogus for this usage because
+              it skips CVSATTIC and CVSLCK directories--those names
+              should be special only in the repository.  However, in
+              the interests of not perturbing this code, we probably
+              should leave well enough alone unless we want to write
+              a sanity.sh test case (which would operate by manually
+              hacking on the CVS/Entries file).  */
+
+           if (find_dirs (".", dirlist, 1, tmpentries) != 0)
+               error (1, errno, "cannot open current directory");
+           if (tmpentries != NULL)
+           {
+               if (! list_isempty (dirlist))
+                   walklist (dirlist, register_subdir_proc,
+                             (void *) tmpentries);
+               else
+                   Subdirs_Known (tmpentries);
+           }
+       }
+
+       if (entries == NULL && tmpentries != NULL)
+           Entries_Close (tmpentries);
+    }
+
+    /* look for sub-dirs in the repository */
+    if ((which & W_REPOS) && repository)
+    {
+       /* search the repository */
+       if (find_dirs (repository, dirlist, 0, entries) != 0)
+           error (1, errno, "cannot open directory %s", repository);
+
+       /* We don't need to look in the attic because directories
+          never go in the attic.  In the future, there hopefully will
+          be a better mechanism for detecting whether a directory in
+          the repository is alive or dead; it may or may not involve
+          moving directories to the attic.  */
+    }
+
+    /* sort the list into alphabetical order and return it */
+    sortlist (dirlist, fsortcmp);
+    return (dirlist);
+}
+
+
+
+/* Finds all the files matching PAT.  If DIR is NULL, PAT will be interpreted
+ * as either absolute or relative to the PWD and read errors, e.g. failure to
+ * open a directory, will be ignored.  If DIR is not NULL, PAT is
+ * always interpreted as relative to DIR.  Adds all matching files and
+ * directories to a new List.  Returns the new List for success and NULL in
+ * case of error, in which case ERRNO will also be set.
+ *
+ * NOTES
+ *   When DIR is NULL, this is really just a thinly veiled wrapper for glob().
+ *
+ *   Much of the cruft in this function could be avoided if DIR was eliminated.
+ *
+ * INPUTS
+ *   dir       The directory to match relative to.
+ *   pat       The pattern to match against, via glob().
+ *
+ * GLOBALS
+ *   errno             Set on error.
+ *   really_quiet      Used to decide whether to print warnings.
+ *
+ * RETURNS
+ *   A pointer to a List of matching file and directory names, on success.
+ *   NULL, on error.
+ *
+ * ERRORS
+ *   Error returns can be caused if glob() returns an error.  ERRNO will be
+ *   set.  When !REALLY_QUIET and the failure was not a read error, a warning
+ *   message will be printed via error (0, errno, ...).
+ */
+List *
+find_files (const char *dir, const char *pat)
+{
+    List *retval;
+    glob_t glist;
+    int err, i;
+    char *catpat = NULL;
+    bool dirslash = false;
+
+    if (dir && *dir)
+    {
+       size_t catpatlen = 0;
+       const char *p;
+       if (glob_pattern_p (dir, false))
+       {
+           /* Escape special characters in DIR.  */
+           size_t len = 0;
+           p = dir;
+           while (*p)
+           {
+               switch (*p)
+               {
+                   case '\\':
+                   case '*':
+                   case '[':
+                   case ']':
+                   case '?':
+                       expand_string (&catpat, &catpatlen, len + 1);
+                       catpat[len++] = '\\';
+                   default:
+                       expand_string (&catpat, &catpatlen, len + 1);
+                       catpat[len++] = *p++;
+                       break;
+               }
+           }
+           catpat[len] = '\0';
+       }
+       else
+       {
+           xrealloc_and_strcat (&catpat, &catpatlen, dir);
+           p = dir + strlen (dir);
+       }
+
+       dirslash = *p - 1 == '/';
+       if (!dirslash)
+           xrealloc_and_strcat (&catpat, &catpatlen, "/");
+
+       xrealloc_and_strcat (&catpat, &catpatlen, pat);
+       pat = catpat;
+    }
+
+    err = glob (pat, GLOB_PERIOD | (dir ? GLOB_ERR : 0), NULL, &glist);
+    if (err && err != GLOB_NOMATCH)
+    {
+       if (err == GLOB_ABORTED)
+           /* Let our caller handle the problem.  */
+           return NULL;
+       if (err == GLOB_NOSPACE) errno = ENOMEM;
+       if (!really_quiet)
+           error (0, errno, "glob failed");
+       if (catpat) free (catpat);
+       return NULL;
+    }
+
+    /* Copy what glob() returned into a List for our caller.  */
+    retval = getlist ();
+    for (i = 0; i < glist.gl_pathc; i++)
+    {
+       Node *p;
+       const char *tmp;
+
+       /* Ignore `.' && `..'.  */
+       tmp = last_component (glist.gl_pathv[i]);
+       if (!strcmp (tmp, ".") || !strcmp (tmp, ".."))
+           continue;
+
+       p = getnode ();
+       p->type = FILES;
+       p->key = xstrdup (glist.gl_pathv[i]
+                         + (dir ? strlen (dir) + !dirslash : 0));
+       if (addnode (retval, p)) freenode (p);
+    }
+
+    if (catpat) free (catpat);
+    globfree (&glist);
+    return retval;
+}
+
+
+
+/* walklist() proc which strips a trailing RCSEXT from node keys.
+ */
+static int
+strip_rcsext (Node *p, void *closure)
+{
+    char *s = p->key + strlen (p->key) - strlen (RCSEXT);
+    assert (!strcmp (s, RCSEXT));
+    *s = '\0'; /* strip the ,v */
+    return 0;
+}
+
+
+
+/*
+ * Finds all the ,v files in the directory DIR, and adds them to the LIST.
+ * Returns 0 for success and non-zero if DIR cannot be opened, in which case
+ * ERRNO is set to indicate the error.  In the error case, LIST is left in some
+ * reasonable state (unchanged, or containing the files which were found before
+ * the error occurred).
+ *
+ * INPUTS
+ *   dir       The directory to open for read.
+ *
+ * OUTPUTS
+ *   list      Where to store matching file entries.
+ *
+ * GLOBALS
+ *   errno     Set on error.
+ *
+ * RETURNS
+ *   0, for success.
+ *   <> 0, on error.
+ */
+static int
+find_rcs (dir, list)
+    const char *dir;
+    List *list;
+{
+    List *newlist;
+    if (!(newlist = find_files (dir, RCSPAT)))
+       return 1;
+    walklist (newlist, strip_rcsext, NULL);
+    mergelists (list, &newlist);
+    return 0;
+}
+
+
+
+/*
+ * Finds all the subdirectories of the argument dir and adds them to
+ * the specified list.  Sub-directories without a CVS administration
+ * directory are optionally ignored.  If ENTRIES is not NULL, all
+ * files on the list are ignored.  Returns 0 for success or 1 on
+ * error, in which case errno is set to indicate the error.
+ */
+static int
+find_dirs (char *dir, List *list, int checkadm, List *entries)
+{
+    Node *p;
+    char *tmp = NULL;
+    size_t tmp_size = 0;
+    struct dirent *dp;
+    DIR *dirp;
+    int skip_emptydir = 0;
+
+    /* First figure out whether we need to skip directories named
+       Emptydir.  Except in the CVSNULLREPOS case, Emptydir is just
+       a normal directory name.  */
+    if (ISABSOLUTE (dir)
+       && strncmp (dir, current_parsed_root->directory, strlen 
(current_parsed_root->directory)) == 0
+       && ISSLASH (dir[strlen (current_parsed_root->directory)])
+       && strcmp (dir + strlen (current_parsed_root->directory) + 1, 
CVSROOTADM) == 0)
+       skip_emptydir = 1;
+
+    /* set up to read the dir */
+    if ((dirp = CVS_OPENDIR (dir)) == NULL)
+       return (1);
+
+    /* read the dir, grabbing sub-dirs */
+    errno = 0;
+    while ((dp = CVS_READDIR (dirp)) != NULL)
+    {
+       if (strcmp (dp->d_name, ".") == 0 ||
+           strcmp (dp->d_name, "..") == 0 ||
+           strcmp (dp->d_name, CVSATTIC) == 0 ||
+           strcmp (dp->d_name, CVSLCK) == 0 ||
+           strcmp (dp->d_name, CVSREP) == 0)
+           goto do_it_again;
+
+       /* findnode() is going to be significantly faster than stat()
+          because it involves no system calls.  That is why we bother
+          with the entries argument, and why we check this first.  */
+       if (entries != NULL && findnode (entries, dp->d_name) != NULL)
+           goto do_it_again;
+
+       if (skip_emptydir
+           && strcmp (dp->d_name, CVSNULLREPOS) == 0)
+           goto do_it_again;
+
+#ifdef DT_DIR
+       if (dp->d_type != DT_DIR) 
+       {
+           if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_LNK)
+               goto do_it_again;
+#endif
+           /* don't bother stating ,v files */
+           if (CVS_FNMATCH (RCSPAT, dp->d_name, 0) == 0)
+               goto do_it_again;
+
+           expand_string (&tmp,
+                          &tmp_size,
+                          strlen (dir) + strlen (dp->d_name) + 10);
+           sprintf (tmp, "%s/%s", dir, dp->d_name);
+           if (!isdir (tmp))
+               goto do_it_again;
+
+#ifdef DT_DIR
+       }
+#endif
+
+       /* check for administration directories (if needed) */
+       if (checkadm)
+       {
+           /* blow off symbolic links to dirs in local dir */
+#ifdef DT_DIR
+           if (dp->d_type != DT_DIR)
+           {
+               /* we're either unknown or a symlink at this point */
+               if (dp->d_type == DT_LNK)
+                   goto do_it_again;
+#endif
+               /* Note that we only get here if we already set tmp
+                  above.  */
+               if (islink (tmp))
+                   goto do_it_again;
+#ifdef DT_DIR
+           }
+#endif
+
+           /* check for new style */
+           expand_string (&tmp,
+                          &tmp_size,
+                          (strlen (dir) + strlen (dp->d_name)
+                           + sizeof (CVSADM) + 10));
+           (void) sprintf (tmp, "%s/%s/%s", dir, dp->d_name, CVSADM);
+           if (!isdir (tmp))
+               goto do_it_again;
+       }
+
+       /* put it in the list */
+       p = getnode ();
+       p->type = DIRS;
+       p->key = xstrdup (dp->d_name);
+       if (addnode (list, p) != 0)
+           freenode (p);
+
+    do_it_again:
+       errno = 0;
+    }
+    if (errno != 0)
+    {
+       int save_errno = errno;
+       (void) CVS_CLOSEDIR (dirp);
+       errno = save_errno;
+       return 1;
+    }
+    (void) CVS_CLOSEDIR (dirp);
+    if (tmp != NULL)
+       free (tmp);
+    return (0);
+}
Index: ccvs/src/history.c
diff -u /dev/null ccvs/src/history.c:1.95.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/history.c  Fri Jan  6 19:34:15 2006
@@ -0,0 +1,1701 @@
+/*
+ * Copyright (C) 1994-2005 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Verify interface.  */
+#include "history.h"
+
+/* GNULIB headers.  */
+#include "save-cwd.h"
+
+/* CVS headers.  */
+#include "ignore.h"
+
+#include "cvs.h"
+
+
+
+/* **************** History of Users and Module ****************
+ *
+ * LOGGING:  Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
+ *
+ * On For each Tag, Add, Checkout, Commit, Update or Release command,
+ * one line of text is written to a History log.
+ *
+ *     X date | user | CurDir | special | rev(s) | argument '\n'
+ *
+ * where: [The spaces in the example line above are not in the history file.]
+ *
+ *  X          is a single character showing the type of event:
+ *             T       "Tag" cmd.
+ *             O       "Checkout" cmd.
+ *              E       "Export" cmd.
+ *             F       "Release" cmd.
+ *             W       "Update" cmd - No User file, Remove from Entries file.
+ *             U       "Update" cmd - File was checked out over User file.
+ *             P       "Update" cmd - User file was patched.
+ *             G       "Update" cmd - File was merged successfully.
+ *             C       "Update" cmd - File was merged and shows overlaps.
+ *             M       "Commit" cmd - "Modified" file.
+ *             A       "Commit" cmd - "Added" file.
+ *             R       "Commit" cmd - "Removed" file.
+ *
+ *  date       is a fixed length 8-char hex representation of a Unix time_t.
+ *             [Starting here, variable fields are delimited by '|' chars.]
+ *
+ *  user       is the username of the person who typed the command.
+ *
+ *  CurDir     The directory where the action occurred.  This should be the
+ *             absolute path of the directory which is at the same level as
+ *             the "Repository" field (for W,U,P,G,C & M,A,R).
+ *
+ *  Repository For record types [W,U,P,G,C,M,A,R] this field holds the
+ *             repository read from the administrative data where the
+ *             command was typed.
+ *             T       "A" --> New Tag, "D" --> Delete Tag
+ *                     Otherwise it is the Tag or Date to modify.
+ *             O,F,E   A "" (null field)
+ *
+ *  rev(s)     Revision number or tag.
+ *             T       The Tag to apply.
+ *             O,E     The Tag or Date, if specified, else "" (null field).
+ *             F       "" (null field)
+ *             W       The Tag or Date, if specified, else "" (null field).
+ *             U,P     The Revision checked out over the User file.
+ *             G,C     The Revision(s) involved in merge.
+ *             M,A,R   RCS Revision affected.
+ *
+ *  argument   The module (for [TOEF]) or file (for [WUPGCMAR]) affected.
+ *
+ *
+ *** Report categories: "User" and "Since" modifiers apply to all reports.
+ *                     [For "sort" ordering see the "sort_order" routine.]
+ *
+ *   Extract list of record types
+ *
+ *     -e, -x [TOEFWUPGCMAR]
+ *
+ *             Extracted records are simply printed, No analysis is performed.
+ *             All "field" modifiers apply.  -e chooses all types.
+ *
+ *   Checked 'O'ut modules
+ *
+ *     -o, -w
+ *             Checked out modules.  'F' and 'O' records are examined and if
+ *             the last record for a repository/file is an 'O', a line is
+ *             printed.  "-w" forces the "working dir" to be used in the
+ *             comparison instead of the repository.
+ *
+ *   Committed (Modified) files
+ *
+ *     -c, -l, -w
+ *             All 'M'odified, 'A'dded and 'R'emoved records are examined.
+ *             "Field" modifiers apply.  -l forces a sort by file within user
+ *             and shows only the last modifier.  -w works as in Checkout.
+ *
+ *             Warning: Be careful with what you infer from the output of
+ *                      "cvs hi -c -l".  It means the last time *you*
+ *                      changed the file, not the list of files for which
+ *                      you were the last changer!!!
+ *
+ *   Module history for named modules.
+ *     -m module, -l
+ *
+ *             This is special.  If one or more modules are specified, the
+ *             module names are remembered and the files making up the
+ *             modules are remembered.  Only records matching exactly those
+ *             files and repositories are shown.  Sorting by "module", then
+ *             filename, is implied.  If -l ("last modified") is specified,
+ *             then "update" records (types WUPCG), tag and release records
+ *             are ignored and the last (by date) "modified" record.
+ *
+ *   TAG history
+ *
+ *     -T      All Tag records are displayed.
+ *
+ *** Modifiers.
+ *
+ *   Since ...         [All records contain a timestamp, so any report
+ *                      category can be limited by date.]
+ *
+ *     -D date         - The "date" is parsed into a Unix "time_t" and
+ *                       records with an earlier time stamp are ignored.
+ *     -r rev/tag      - A "rev" begins with a digit.  A "tag" does not.  If
+ *                       you use this option, every file is searched for the
+ *                       indicated rev/tag.
+ *     -t tag          - The "tag" is searched for in the history file and no
+ *                       record is displayed before the tag is found.  An
+ *                       error is printed if the tag is never found.
+ *     -b string       - Records are printed only back to the last reference
+ *                       to the string in the "module", "file" or
+ *                       "repository" fields.
+ *
+ *   Field Selections  [Simple comparisons on existing fields.  All field
+ *                      selections are repeatable.]
+ *
+ *     -a              - All users.
+ *     -u user         - If no user is given and '-a' is not given, only
+ *                       records for the user typing the command are shown.
+ *                       ==> If -a or -u is not specified, just use "self".
+ *
+ *     -f filematch    - Only records in which the "file" field contains the
+ *                       string "filematch" are considered.
+ *
+ *     -p repository   - Only records in which the "repository" string is a
+ *                       prefix of the "repos" field are considered.
+ *
+ *     -n modulename   - Only records which contain "modulename" in the
+ *                       "module" field are considered.
+ *
+ *
+ * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
+ *
+ *** Checked out files for username.  (default self, e.g. "dgg")
+ *     cvs hi                  [equivalent to: "cvs hi -o -u dgg"]
+ *     cvs hi -u user          [equivalent to: "cvs hi -o -u user"]
+ *     cvs hi -o               [equivalent to: "cvs hi -o -u dgg"]
+ *
+ *** Committed (modified) files from the beginning of the file.
+ *     cvs hi -c [-u user]
+ *
+ *** Committed (modified) files since Midnight, January 1, 1990:
+ *     cvs hi -c -D 'Jan 1 1990' [-u user]
+ *
+ *** Committed (modified) files since tag "TAG" was stored in the history file:
+ *     cvs hi -c -t TAG [-u user]
+ *
+ *** Committed (modified) files since tag "TAG" was placed on the files:
+ *     cvs hi -c -r TAG [-u user]
+ *
+ *** Who last committed file/repository X?
+ *     cvs hi -c -l -[fp] X
+ *
+ *** Modified files since tag/date/file/repos?
+ *     cvs hi -c {-r TAG | -D Date | -b string}
+ *
+ *** Tag history
+ *     cvs hi -T
+ *
+ *** History of file/repository/module X.
+ *     cvs hi -[fpn] X
+ *
+ *** History of user "user".
+ *     cvs hi -e -u user
+ *
+ *** Dump (eXtract) specified record types
+ *     cvs hi -x [TOEFWUPGCMAR]
+ *
+ *
+ * FUTURE:             J[Join], I[Import]  (Not currently implemented.)
+ *
+ */
+
+
+
+static struct hrec
+{
+    char *type;                /* Type of record (In history record) */
+    char *user;                /* Username (In history record) */
+    char *dir;         /* "Compressed" Working dir (In history record) */
+    char *repos;       /* (Tag is special.) Repository (In history record) */
+    char *rev;         /* Revision affected (In history record) */
+    char *file;                /* Filename (In history record) */
+    char *end;         /* Ptr into repository to copy at end of workdir */
+    char *mod;         /* The module within which the file is contained */
+    time_t date;       /* Calculated from date stored in record */
+    long idx;          /* Index of record, for "stable" sort. */
+} *hrec_head;
+static long hrec_idx;
+
+
+static void fill_hrec (char *line, struct hrec * hr);
+static int accept_hrec (struct hrec * hr, struct hrec * lr);
+static int select_hrec (struct hrec * hr);
+static int sort_order (const void *l, const void *r);
+static int within (char *find, char *string);
+static void expand_modules (void);
+static void read_hrecs (List *flist);
+static void report_hrecs (void);
+static void save_file (char *dir, char *name, char *module);
+static void save_module (char *module);
+static void save_user (char *name);
+
+#define USER_INCREMENT 2
+#define FILE_INCREMENT 128
+#define MODULE_INCREMENT 5
+#define HREC_INCREMENT 128
+
+static short report_count;
+
+static short extract;
+static short extract_all;
+static short v_checkout;
+static short modified;
+static short tag_report;
+static short module_report;
+static short working;
+static short last_entry;
+static short all_users;
+
+static short user_sort;
+static short repos_sort;
+static short file_sort;
+static short module_sort;
+
+static short tz_local;
+static time_t tz_seconds_east_of_GMT;
+static char *tz_name = "+0000";
+
+/* -r, -t, or -b options, malloc'd.  These are "" if the option in
+   question is not specified or is overridden by another option.  The
+   main reason for using "" rather than NULL is historical.  Together
+   with since_date, these are a mutually exclusive set; one overrides the
+   others.  */
+static char *since_rev;
+static char *since_tag;
+static char *backto;
+/* -D option, or 0 if not specified.  RCS format.  */
+static char * since_date;
+
+static struct hrec *last_since_tag;
+static struct hrec *last_backto;
+
+/* Record types to look for, malloc'd.  Probably could be statically
+   allocated, but only if we wanted to check for duplicates more than
+   we do.  */
+static char *rec_types;
+
+static int hrec_count;
+static int hrec_max;
+
+static char **user_list;       /* Ptr to array of ptrs to user names */
+static int user_max;           /* Number of elements allocated */
+static int user_count;         /* Number of elements used */
+
+static struct file_list_str
+{
+    char *l_file;
+    char *l_module;
+} *file_list;                  /* Ptr to array file name structs */
+static int file_max;           /* Number of elements allocated */
+static int file_count;         /* Number of elements used */
+
+static char **mod_list;                /* Ptr to array of ptrs to module names 
*/
+static int mod_max;            /* Number of elements allocated */
+static int mod_count;          /* Number of elements used */
+
+/* This is pretty unclear.  First of all, separating "flags" vs.
+   "options" (I think the distinction is that "options" take arguments)
+   is nonstandard, and not something we do elsewhere in CVS.  Second of
+   all, what does "reports" mean?  I think it means that you can only
+   supply one of those options, but "reports" hardly has that meaning in
+   a self-explanatory way.  */
+static const char *const history_usg[] =
+{
+    "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
+    "   Reports:\n",
+    "        -T              Produce report on all TAGs\n",
+    "        -c              Committed (Modified) files\n",
+    "        -o              Checked out modules\n",
+    "        -m <module>     Look for specified module (repeatable)\n",
+    "        -x [" ALL_HISTORY_REC_TYPES "] Extract by record type\n",
+    "        -e              Everything (same as -x, but all record types)\n",
+    "   Flags:\n",
+    "        -a              All users (Default is self)\n",
+    "        -l              Last modified (committed or modified report)\n",
+    "        -w              Working directory must match\n",
+    "   Options:\n",
+    "        -D <date>       Since date (Many formats)\n",
+    "        -b <str>        Back to record with str in module/file/repos 
field\n",
+    "        -f <file>       Specified file (same as command line) 
(repeatable)\n",
+    "        -n <modulename> In module (repeatable)\n",
+    "        -p <repos>      In repository (repeatable)\n",
+    "        -r <rev/tag>    Since rev or tag (looks inside RCS files!)\n",
+    "        -t <tag>        Since tag record placed in history file (by 
anyone).\n",
+    "        -u <user>       For user name (repeatable)\n",
+    "        -z <tz>         Output for time zone <tz> (e.g. -z -0700)\n",
+    NULL};
+
+/* Sort routine for qsort:
+   - If a user is selected at all, sort it first. User-within-file is useless.
+   - If a module was selected explicitly, sort next on module.
+   - Then sort by file.  "File" is "repository/file" unless "working" is set,
+     then it is "workdir/file".  (Revision order should always track date.)
+   - Always sort timestamp last.
+*/
+static int
+sort_order (const void *l, const void *r)
+{
+    int i;
+    const struct hrec *left = l;
+    const struct hrec *right = r;
+
+    if (user_sort)     /* If Sort by username, compare users */
+    {
+       if ((i = strcmp (left->user, right->user)) != 0)
+           return i;
+    }
+    if (module_sort)   /* If sort by modules, compare module names */
+    {
+       if (left->mod && right->mod)
+           if ((i = strcmp (left->mod, right->mod)) != 0)
+               return i;
+    }
+    if (repos_sort)    /* If sort by repository, compare them. */
+    {
+       if ((i = strcmp (left->repos, right->repos)) != 0)
+           return i;
+    }
+    if (file_sort)     /* If sort by filename, compare files, NOT dirs. */
+    {
+       if ((i = strcmp (left->file, right->file)) != 0)
+           return i;
+
+       if (working)
+       {
+           if ((i = strcmp (left->dir, right->dir)) != 0)
+               return i;
+
+           if ((i = strcmp (left->end, right->end)) != 0)
+               return i;
+       }
+    }
+
+    /*
+     * By default, sort by date, time
+     * XXX: This fails after 2030 when date slides into sign bit
+     */
+    if ((i = ((long) (left->date) - (long) (right->date))) != 0)
+       return i;
+
+    /* For matching dates, keep the sort stable by using record index */
+    return left->idx - right->idx;
+}
+
+
+
+/* Get the name of the history log, either from CVSROOT/config, or via the
+ * hard-coded default.
+ */
+static const char *
+get_history_log_name (time_t now)
+{
+    char *log_name;
+
+    if (config->HistoryLogPath)
+    {
+       /* ~, $VARs, and were expanded via expand_path() when CVSROOT/config
+        * was parsed.
+        */
+       log_name = xmalloc (PATH_MAX);
+       if (!now) now = time (NULL);
+       if (!strftime (log_name, PATH_MAX, config->HistoryLogPath,
+                      localtime (&now)))
+       {
+           error (0, 0, "Invalid date format in HistoryLogPath.");
+           free (config->HistoryLogPath);
+           config->HistoryLogPath = NULL;
+       }
+    }
+
+    if (!config->HistoryLogPath)
+    {
+       /* Use the default.  */
+       log_name = xmalloc (strlen (current_parsed_root->directory)
+                           + sizeof (CVSROOTADM)
+                           + sizeof (CVSROOTADM_HISTORY) + 3);
+       sprintf (log_name, "%s/%s/%s", current_parsed_root->directory,
+                CVSROOTADM, CVSROOTADM_HISTORY);
+    }
+
+    return log_name;
+}
+
+
+
+int
+history (int argc, char **argv)
+{
+    int i, c;
+    const char *fname = NULL;
+    List *flist;
+
+    if (argc == -1)
+       usage (history_usg);
+
+    since_rev = xstrdup ("");
+    since_tag = xstrdup ("");
+    backto = xstrdup ("");
+    rec_types = xstrdup ("");
+    optind = 0;
+    while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != 
-1)
+    {
+       switch (c)
+       {
+           case 'T':                   /* Tag list */
+               report_count++;
+               tag_report++;
+               break;
+           case 'a':                   /* For all usernames */
+               all_users++;
+               break;
+           case 'c':
+               report_count++;
+               modified = 1;
+               break;
+           case 'e':
+               report_count++;
+               extract_all++;
+               free (rec_types);
+               rec_types = xstrdup (ALL_HISTORY_REC_TYPES);
+               break;
+           case 'l':                   /* Find Last file record */
+               last_entry = 1;
+               break;
+           case 'o':
+               report_count++;
+               v_checkout = 1;
+               break;
+           case 'w':                   /* Match Working Dir (CurDir) fields */
+               working = 1;
+               break;
+           case 'X':                   /* Undocumented debugging flag */
+#ifdef DEBUG
+               fname = optarg;
+#endif
+               break;
+
+           case 'D':                   /* Since specified date */
+               if (*since_rev || *since_tag || *backto)
+               {
+                   error (0, 0, "date overriding rev/tag/backto");
+                   *since_rev = *since_tag = *backto = '\0';
+               }
+               since_date = Make_Date (optarg);
+               break;
+           case 'b':                   /* Since specified file/Repos */
+               if (since_date || *since_rev || *since_tag)
+               {
+                   error (0, 0, "backto overriding date/rev/tag");
+                   *since_rev = *since_tag = '\0';
+                   if (since_date != NULL)
+                       free (since_date);
+                   since_date = NULL;
+               }
+               free (backto);
+               backto = xstrdup (optarg);
+               break;
+           case 'f':                   /* For specified file */
+               save_file (NULL, optarg, NULL);
+               break;
+           case 'm':                   /* Full module report */
+               if (!module_report++) report_count++;
+               /* fall through */
+           case 'n':                   /* Look for specified module */
+               save_module (optarg);
+               break;
+           case 'p':                   /* For specified directory */
+               save_file (optarg, NULL, NULL);
+               break;
+           case 'r':                   /* Since specified Tag/Rev */
+               if (since_date || *since_tag || *backto)
+               {
+                   error (0, 0, "rev overriding date/tag/backto");
+                   *since_tag = *backto = '\0';
+                   if (since_date != NULL)
+                       free (since_date);
+                   since_date = NULL;
+               }
+               free (since_rev);
+               since_rev = xstrdup (optarg);
+               break;
+           case 't':                   /* Since specified Tag/Rev */
+               if (since_date || *since_rev || *backto)
+               {
+                   error (0, 0, "tag overriding date/marker/file/repos");
+                   *since_rev = *backto = '\0';
+                   if (since_date != NULL)
+                       free (since_date);
+                   since_date = NULL;
+               }
+               free (since_tag);
+               since_tag = xstrdup (optarg);
+               break;
+           case 'u':                   /* For specified username */
+               save_user (optarg);
+               break;
+           case 'x':
+               report_count++;
+               extract++;
+               {
+                   char *cp;
+
+                   for (cp = optarg; *cp; cp++)
+                       if (!strchr (ALL_HISTORY_REC_TYPES, *cp))
+                           error (1, 0, "%c is not a valid report type", *cp);
+               }
+               free (rec_types);
+               rec_types = xstrdup (optarg);
+               break;
+           case 'z':
+               tz_local = 
+                   (optarg[0] == 'l' || optarg[0] == 'L')
+                   && (optarg[1] == 't' || optarg[1] == 'T')
+                   && !optarg[2];
+               if (tz_local)
+                   tz_name = optarg;
+               else
+               {
+                   /*
+                    * Convert a known time with the given timezone to time_t.
+                    * Use the epoch + 23 hours, so timezones east of GMT work.
+                    */
+                   struct timespec t;
+                   char *buf = Xasprintf ("1/1/1970 23:00 %s", optarg);
+                   if (get_date (&t, buf, NULL))
+                   {
+                       /*
+                        * Convert to seconds east of GMT, removing the
+                        * 23-hour offset mentioned above.
+                        */
+                       tz_seconds_east_of_GMT = (time_t)23 * 60 * 60
+                                                - t.tv_sec;
+                       tz_name = optarg;
+                   }
+                   else
+                       error (0, 0, "%s is not a known time zone", optarg);
+                   free (buf);
+               }
+               break;
+           case '?':
+           default:
+               usage (history_usg);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+    for (i = 0; i < argc; i++)
+       save_file (NULL, argv[i], NULL);
+
+
+    /* ================ Now analyze the arguments a bit */
+    if (!report_count)
+       v_checkout++;
+    else if (report_count > 1)
+       error (1, 0, "Only one report type allowed from: \"-Tcomxe\".");
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       struct file_list_str *f1;
+       char **mod;
+
+       /* We're the client side.  Fire up the remote server.  */
+       start_server ();
+       
+       ign_setup ();
+
+       if (tag_report)
+           send_arg ("-T");
+       if (all_users)
+           send_arg ("-a");
+       if (modified)
+           send_arg ("-c");
+       if (last_entry)
+           send_arg ("-l");
+       if (v_checkout)
+           send_arg ("-o");
+       if (working)
+           send_arg ("-w");
+       if (fname)
+           option_with_arg ("-X", fname);
+       if (since_date)
+           client_senddate (since_date);
+       if (backto[0] != '\0')
+           option_with_arg ("-b", backto);
+       for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
+       {
+           if (f1->l_file[0] == '*')
+               option_with_arg ("-p", f1->l_file + 1);
+           else
+               option_with_arg ("-f", f1->l_file);
+       }
+       if (module_report)
+           send_arg ("-m");
+       for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
+           option_with_arg ("-n", *mod);
+       if (*since_rev)
+           option_with_arg ("-r", since_rev);
+       if (*since_tag)
+           option_with_arg ("-t", since_tag);
+       for (mod = user_list; mod < &user_list[user_count]; ++mod)
+           option_with_arg ("-u", *mod);
+       if (extract_all)
+           send_arg ("-e");
+       if (extract)
+           option_with_arg ("-x", rec_types);
+       option_with_arg ("-z", tz_name);
+
+       send_to_server ("history\012", 0);
+        return get_responses_and_close ();
+    }
+#endif
+
+    if (all_users)
+       save_user ("");
+
+    if (mod_list)
+       expand_modules ();
+
+    if (tag_report)
+    {
+       if (!strchr (rec_types, 'T'))
+       {
+           rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
+           (void) strcat (rec_types, "T");
+       }
+    }
+    else if (extract || extract_all)
+    {
+       if (user_list)
+           user_sort++;
+    }
+    else if (modified)
+    {
+       free (rec_types);
+       rec_types = xstrdup ("MAR");
+       /*
+        * If the user has not specified a date oriented flag ("Since"), sort
+        * by Repository/file before date.  Default is "just" date.
+        */
+       if (last_entry
+           || (!since_date && !*since_rev && !*since_tag && !*backto))
+       {
+           repos_sort++;
+           file_sort++;
+           /*
+            * If we are not looking for last_modified and the user specified
+            * one or more users to look at, sort by user before filename.
+            */
+           if (!last_entry && user_list)
+               user_sort++;
+       }
+    }
+    else if (module_report)
+    {
+       free (rec_types);
+       rec_types = xstrdup (last_entry ? "OMAR" : ALL_HISTORY_REC_TYPES);
+       module_sort++;
+       repos_sort++;
+       file_sort++;
+       working = 0;                    /* User's workdir doesn't count here */
+    }
+    else
+       /* Must be "checkout" or default */
+    {
+       free (rec_types);
+       rec_types = xstrdup ("OF");
+       /* See comments in "modified" above */
+       if (!last_entry && user_list)
+           user_sort++;
+       if (last_entry
+           || (!since_date && !*since_rev && !*since_tag && !*backto))
+           file_sort++;
+    }
+
+    /* If no users were specified, use self (-a saves a universal ("") user) */
+    if (!user_list)
+       save_user (getcaller ());
+
+    /* If we're looking back to a Tag value, must consider "Tag" records */
+    if (*since_tag && !strchr (rec_types, 'T'))
+    {
+       rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
+       (void) strcat (rec_types, "T");
+    }
+
+    if (fname)
+    {
+       Node *p;
+
+       flist = getlist ();
+       p = getnode ();
+       p->type = FILES;
+       p->key = Xasprintf ("%s/%s/%s",
+                           current_parsed_root->directory, CVSROOTADM, fname);
+       addnode (flist, p);
+    }
+    else
+    {
+       char *pat;
+
+       if (config->HistorySearchPath)
+           pat = config->HistorySearchPath;
+       else
+           pat = Xasprintf ("%s/%s/%s",
+                            current_parsed_root->directory, CVSROOTADM,
+                            CVSROOTADM_HISTORY);
+
+       flist = find_files (NULL, pat);
+       if (pat != config->HistorySearchPath) free (pat);
+    }
+
+    read_hrecs (flist);
+    if (hrec_count > 0)
+       qsort (hrec_head, hrec_count, sizeof (struct hrec), sort_order);
+    report_hrecs ();
+    if (since_date != NULL)
+       free (since_date);
+    free (since_rev);
+    free (since_tag);
+    free (backto);
+    free (rec_types);
+
+    return 0;
+}
+
+
+
+/* An empty LogHistory string in CVSROOT/config will turn logging off.
+ */
+void
+history_write (int type, const char *update_dir, const char *revs,
+               const char *name, const char *repository)
+{
+    const char *fname;
+    char *workdir;
+    char *username = getcaller ();
+    int fd;
+    char *line;
+    char *slash = "", *cp;
+    const char *cp2, *repos;
+    int i;
+    static char *tilde = "";
+    static char *PrCurDir = NULL;
+    time_t now;
+
+    if (logoff)                        /* History is turned off by noexec or
+                                * readonlyfs.
+                                */
+       return;
+    if (!strchr (config->logHistory, type))    
+       return;
+
+    if (noexec)
+       goto out;
+
+    repos = Short_Repository (repository);
+
+    if (!PrCurDir)
+    {
+       char *pwdir;
+
+       pwdir = get_homedir ();
+       PrCurDir = CurDir;
+       if (pwdir != NULL)
+       {
+           /* Assumes neither CurDir nor pwdir ends in '/' */
+           i = strlen (pwdir);
+           if (!strncmp (CurDir, pwdir, i))
+           {
+               PrCurDir += i;          /* Point to '/' separator */
+               tilde = "~";
+           }
+           else
+           {
+               /* Try harder to find a "homedir" */
+               struct saved_cwd cwd;
+               char *homedir;
+
+               if (save_cwd (&cwd))
+                   error (1, errno, "Failed to save current directory.");
+
+               if (CVS_CHDIR (pwdir) < 0 || (homedir = xgetcwd ()) == NULL)
+                   homedir = pwdir;
+
+               if (restore_cwd (&cwd))
+                   error (1, errno,
+                          "Failed to restore current directory, `%s'.",
+                          cwd.name);
+               free_cwd (&cwd);
+
+               i = strlen (homedir);
+               if (!strncmp (CurDir, homedir, i))
+               {
+                   PrCurDir += i;      /* Point to '/' separator */
+                   tilde = "~";
+               }
+
+               if (homedir != pwdir)
+                   free (homedir);
+           }
+       }
+    }
+
+    if (type == 'T')
+    {
+       repos = update_dir;
+       update_dir = "";
+    }
+    else if (update_dir && *update_dir)
+       slash = "/";
+    else
+       update_dir = "";
+
+    workdir = Xasprintf ("%s%s%s%s", tilde, PrCurDir, slash, update_dir);
+
+    /*
+     * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
+     * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
+     *
+     * "$workdir/$name" is the working file name.
+     * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
+     *
+     * First, note that the history format was intended to save space, not
+     * to be human readable.
+     *
+     * The working file directory ("workdir") and the Repository ("repos")
+     * usually end with the same one or more directory elements.  To avoid
+     * duplication (and save space), the "workdir" field ends with
+     * an integer offset into the "repos" field.  This offset indicates the
+     * beginning of the "tail" of "repos", after which all characters are
+     * duplicates.
+     *
+     * In other words, if the "workdir" field has a '*' (a very stupid thing
+     * to put in a filename) in it, then every thing following the last '*'
+     * is a hex offset into "repos" of the first character from "repos" to
+     * append to "workdir" to finish the pathname.
+     *
+     * It might be easier to look at an example:
+     *
+     *  M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
+     *
+     * Indicates that the workdir is really "~/work/cvs/examples", saving
+     * 10 characters, where "~/work*d" would save 6 characters and mean that
+     * the workdir is really "~/work/examples".  It will mean more on
+     * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
+     *
+     * "workdir" is always an absolute pathname (~/xxx is an absolute path)
+     * "repos" is always a relative pathname.  So we can assume that we will
+     * never run into the top of "workdir" -- there will always be a '/' or
+     * a '~' at the head of "workdir" that is not matched by anything in
+     * "repos".  On the other hand, we *can* run off the top of "repos".
+     *
+     * Only "compress" if we save characters.
+     */
+
+    cp = workdir + strlen (workdir) - 1;
+    cp2 = repos + strlen (repos) - 1;
+    for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
+       i++;
+
+    if (i > 2)
+    {
+       i = strlen (repos) - i;
+       (void) sprintf ((cp + 1), "*%x", i);
+    }
+
+    if (!revs)
+       revs = "";
+    now = time (NULL);
+    line = Xasprintf ("%c%08lx|%s|%s|%s|%s|%s\n", type, (long) now,
+                     username, workdir, repos, revs, name);
+
+    fname = get_history_log_name (now);
+
+    if (!history_lock (current_parsed_root->directory))
+       /* history_lock() will already have printed an error on failure.  */
+       goto out;
+
+    fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | O_CREAT | OPEN_BINARY, 0666);
+    if (fd < 0)
+    {
+       if (!really_quiet)
+            error (0, errno,
+                  "warning: cannot open history file `%s' for write", fname);
+        goto out;
+    }
+
+    TRACE (TRACE_FUNCTION, "open (`%s', a)", fname);
+
+    /* Lessen some race conditions on non-Posix-compliant hosts.
+     *
+     * FIXME:  I'm guessing the following was necessary for NFS when multiple
+     * simultaneous writes to the same file are possible, since NFS does not
+     * natively support append mode and it must be emulated via lseek().  Now
+     * that the history file is locked for write, the following lseek() may be
+     * unnecessary.
+     */
+    if (lseek (fd, (off_t) 0, SEEK_END) == -1)
+       error (1, errno, "cannot seek to end of history file: %s", fname);
+
+    if (write (fd, line, strlen (line)) < 0)
+       error (1, errno, "cannot write to history file: %s", fname);
+    free (line);
+    if (close (fd) != 0)
+       error (1, errno, "cannot close history file: %s", fname);
+    free (workdir);
+ out:
+    clear_history_lock ();
+}
+
+
+
+/*
+ * save_user() adds a user name to the user list to select.  Zero-length
+ *             username ("") matches any user.
+ */
+static void
+save_user (char *name)
+{
+    if (user_count == user_max)
+    {
+       user_max = xsum (user_max, USER_INCREMENT);
+       if (size_overflow_p (xtimes (user_max, sizeof (char *))))
+       {
+           error (0, 0, "save_user: too many users");
+           return;
+       }
+       user_list = xnrealloc (user_list, user_max, sizeof (char *));
+    }
+    user_list[user_count++] = xstrdup (name);
+}
+
+/*
+ * save_file() adds file name and associated module to the file list to select.
+ *
+ * If "dir" is null, store a file name as is.
+ * If "name" is null, store a directory name with a '*' on the front.
+ * Else, store concatenated "dir/name".
+ *
+ * Later, in the "select" stage:
+ *     - if it starts with '*', it is prefix-matched against the repository.
+ *     - if it has a '/' in it, it is matched against the repository/file.
+ *     - else it is matched against the file name.
+ */
+static void
+save_file (char *dir, char *name, char *module)
+{
+    struct file_list_str *fl;
+
+    if (file_count == file_max)
+    {
+       file_max = xsum (file_max, FILE_INCREMENT);
+       if (size_overflow_p (xtimes (file_max, sizeof (*fl))))
+       {
+           error (0, 0, "save_file: too many files");
+           return;
+       }
+       file_list = xnrealloc (file_list, file_max, sizeof (*fl));
+    }
+    fl = &file_list[file_count++];
+    fl->l_module = module;
+
+    if (dir && *dir)
+    {
+       if (name && *name)
+           fl->l_file = Xasprintf ("%s/%s", dir, name);
+       else
+           fl->l_file = Xasprintf ("*%s", dir);
+    }
+    else
+    {
+       if (name && *name)
+           fl->l_file = xstrdup (name);
+       else
+           error (0, 0, "save_file: null dir and file name");
+    }
+}
+
+static void
+save_module (char *module)
+{
+    if (mod_count == mod_max)
+    {
+       mod_max = xsum (mod_max, MODULE_INCREMENT);
+       if (size_overflow_p (xtimes (mod_max, sizeof (char *))))
+       {
+           error (0, 0, "save_module: too many modules");
+           return;
+       }
+       mod_list = xnrealloc (mod_list, mod_max, sizeof (char *));
+    }
+    mod_list[mod_count++] = xstrdup (module);
+}
+
+static void
+expand_modules (void)
+{
+}
+
+/* fill_hrec
+ *
+ * Take a ptr to 7-part history line, ending with a newline, for example:
+ *
+ *     M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
+ *
+ * Split it into 7 parts and drop the parts into a "struct hrec".
+ * Return a pointer to the character following the newline.
+ * 
+ */
+
+#define NEXT_BAR(here) do { \
+       while (isspace (*line)) line++; \
+       hr->here = line; \
+       while ((c = *line++) && c != '|') ; \
+       if (!c) return; line[-1] = '\0'; \
+       } while (0)
+
+static void
+fill_hrec (char *line, struct hrec *hr)
+{
+    char *cp;
+    int c;
+
+    hr->type = hr->user = hr->dir = hr->repos = hr->rev = hr->file =
+       hr->end = hr->mod = NULL;
+    hr->date = -1;
+    hr->idx = ++hrec_idx;
+
+    while (isspace ((unsigned char) *line))
+       line++;
+
+    hr->type = line++;
+    hr->date = strtoul (line, &cp, 16);
+    if (cp == line || *cp != '|')
+       return;
+    line = cp + 1;
+    NEXT_BAR (user);
+    NEXT_BAR (dir);
+    if ((cp = strrchr (hr->dir, '*')) != NULL)
+    {
+       *cp++ = '\0';
+       hr->end = line + strtoul (cp, NULL, 16);
+    }
+    else
+       hr->end = line - 1;             /* A handy pointer to '\0' */
+    NEXT_BAR (repos);
+    NEXT_BAR (rev);
+    if (strchr ("FOET", *(hr->type)))
+       hr->mod = line;
+
+    NEXT_BAR (file);
+}
+
+
+#ifndef STAT_BLOCKSIZE
+#if HAVE_STRUCT_STAT_ST_BLKSIZE
+#define STAT_BLOCKSIZE(s) (s).st_blksize
+#else
+#define STAT_BLOCKSIZE(s) (4 * 1024)
+#endif
+#endif
+
+
+/* read_hrecs_file's job is to read a history file and fill in new "hrec"
+ * (history record) array elements with the ones we need to print.
+ *
+ * Logic:
+ * - Read a block from the file. 
+ * - Walk through the block parsing line into hr records. 
+ * - if the hr isn't used, free its strings, if it is, bump the hrec counter
+ * - at the end of a block, copy the end of the current block to the start 
+ * of space for the next block, then read in the next block.  If we get less
+ * than the whole block, we're done. 
+ */
+static int
+read_hrecs_file (Node *p, void *closure)
+{
+    char *cpstart, *cpend, *cp, *nl;
+    char *hrline;
+    int i;
+    int fd;
+    struct stat st_buf;
+    const char *fname = p->key;
+
+    if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0)
+    {
+       error (0, errno, "cannot open history file `%s'", fname);
+       return 0;
+    }
+
+    if (fstat (fd, &st_buf) < 0)
+    {
+       error (0, errno, "can't stat history file `%s'", fname);
+       return 0;
+    }
+
+    if (!(st_buf.st_size))
+    {
+       error (0, 0, "history file `%s' is empty", fname);
+       return 0;
+    }
+
+    cpstart = xnmalloc (2, STAT_BLOCKSIZE (st_buf));
+    cpstart[0] = '\0';
+    cp = cpend = cpstart;
+
+    for (;;)
+    {
+       for (nl = cp; nl < cpend && *nl != '\n'; nl++)
+           if (!isprint (*nl)) *nl = ' ';
+
+       if (nl >= cpend)
+       {
+           if (nl - cp >= STAT_BLOCKSIZE (st_buf))
+           {
+               error(1, 0, "history line %ld too long (> %lu)", hrec_idx + 1,
+                     (unsigned long) STAT_BLOCKSIZE(st_buf));
+           }
+           if (nl > cp)
+               memmove (cpstart, cp, nl - cp);
+           nl = cpstart + (nl - cp);
+           cp = cpstart;
+           i = read (fd, nl, STAT_BLOCKSIZE(st_buf));
+           if (i > 0)
+           {
+               cpend = nl + i;
+               *cpend = '\0';
+               continue;
+           }
+           if (i < 0)
+           {
+               error (0, errno, "error reading history file `%s'", fname);
+               return 0;
+           }
+           if (nl == cp) break;
+           error (0, 0, "warning: no newline at end of history file `%s'",
+                  fname);
+       }
+       *nl = '\0';
+
+       if (hrec_count == hrec_max)
+       {
+           struct hrec *old_head = hrec_head;
+
+           hrec_max += HREC_INCREMENT;
+           hrec_head = xnrealloc (hrec_head, hrec_max, sizeof (struct hrec));
+           if (last_since_tag)
+               last_since_tag = hrec_head + (last_since_tag - old_head);
+           if (last_backto)
+               last_backto = hrec_head + (last_backto - old_head);
+       }
+
+       /* fill_hrec dates from when history read the entire 
+          history file in one chunk, and then records were pulled out
+          by pointing to the various parts of this big chunk.  This is
+          why there are ugly hacks here:  I don't want to completely
+          re-write the whole history stuff right now.  */
+
+       hrline = xstrdup (cp);
+       fill_hrec (hrline, &hrec_head[hrec_count]);
+       if (select_hrec (&hrec_head[hrec_count]))
+           hrec_count++;
+       else 
+           free (hrline);
+
+       cp = nl + 1;
+    }
+    free (cpstart);
+    close (fd);
+    return 1;
+}
+
+
+
+/* Read the history records in from a list of history files.  */
+static void
+read_hrecs (List *flist)
+{
+    int files_read;
+
+    /* The global history records are already initialized to 0 according to
+     * ANSI C.
+     */
+    hrec_max = HREC_INCREMENT;
+    hrec_head = xmalloc (hrec_max * sizeof (struct hrec));
+    hrec_idx = 0;
+
+    files_read = walklist (flist, read_hrecs_file, NULL);
+    if (!files_read)
+       error (1, 0, "No history files read.");
+
+    /* Special selection problem: If "since_tag" is set, we have saved every
+     * record from the 1st occurrence of "since_tag", when we want to save
+     * records since the *last* occurrence of "since_tag".  So what we have
+     * to do is bump hrec_head forward and reduce hrec_count accordingly.
+     */
+    if (last_since_tag)
+    {
+       hrec_count -= (last_since_tag - hrec_head);
+       hrec_head = last_since_tag;
+    }
+
+    /* Much the same thing is necessary for the "backto" option. */
+    if (last_backto)
+    {
+       hrec_count -= (last_backto - hrec_head);
+       hrec_head = last_backto;
+    }
+}
+
+
+
+/* Utility program for determining whether "find" is inside "string" */
+static int
+within (char *find, char *string)
+{
+    int c, len;
+
+    if (!find || !string)
+       return 0;
+
+    c = *find++;
+    len = strlen (find);
+
+    while (*string)
+    {
+       if (!(string = strchr (string, c)))
+           return 0;
+       string++;
+       if (!strncmp (find, string, len))
+           return 1;
+    }
+    return 0;
+}
+
+/* The purpose of "select_hrec" is to apply the selection criteria based on
+ * the command arguments and defaults and return a flag indicating whether
+ * this record should be remembered for printing.
+ */
+static int
+select_hrec (struct hrec *hr)
+{
+    char **cpp, *cp, *cp2;
+    struct file_list_str *fl;
+    int count;
+
+    /* basic validity checking */
+    if (!hr->type || !hr->user || !hr->dir || !hr->repos || !hr->rev ||
+       !hr->file || !hr->end)
+    {
+       error (0, 0, "warning: history line %ld invalid", hr->idx);
+       return 0;
+    }
+
+    /* "Since" checking:  The argument parser guarantees that only one of the
+     *                   following four choices is set:
+     *
+     * 1. If "since_date" is set, it contains the date specified on the
+     *    command line. hr->date fields earlier than "since_date" are ignored.
+     * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
+     *    number (which is of limited use) or a symbolic TAG.  Each RCS file
+     *    is examined and the date on the specified revision (or the revision
+     *    corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
+     *    compared against hr->date as in 1. above.
+     * 3. If "since_tag" is set, matching tag records are saved.  The field
+     *    "last_since_tag" is set to the last one of these.  Since we don't
+     *    know where the last one will be, all records are saved from the
+     *    first occurrence of the TAG.  Later, at the end of "select_hrec"
+     *    records before the last occurrence of "since_tag" are skipped.
+     * 4. If "backto" is set, all records with a module name or file name
+     *    matching "backto" are saved.  In addition, all records with a
+     *    repository field with a *prefix* matching "backto" are saved.
+     *    The field "last_backto" is set to the last one of these.  As in
+     *    3. above, "select_hrec" adjusts to include the last one later on.
+     */
+    if (since_date)
+    {
+       char *ourdate = date_from_time_t (hr->date);
+       count = RCS_datecmp (ourdate, since_date);
+       free (ourdate);
+       if (count < 0)
+           return 0;
+    }
+    else if (*since_rev)
+    {
+       Vers_TS *vers;
+       time_t t;
+       struct file_info finfo;
+
+       memset (&finfo, 0, sizeof finfo);
+       finfo.file = hr->file;
+       /* Not used, so don't worry about it.  */
+       finfo.update_dir = NULL;
+       finfo.fullname = finfo.file;
+       finfo.repository = hr->repos;
+       finfo.entries = NULL;
+       finfo.rcs = NULL;
+
+       vers = Version_TS (&finfo, NULL, since_rev, NULL, 1, 0);
+       if (vers->vn_rcs)
+       {
+           if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, NULL, 0))
+               != (time_t) 0)
+           {
+               if (hr->date < t)
+               {
+                   freevers_ts (&vers);
+                   return 0;
+               }
+           }
+       }
+       freevers_ts (&vers);
+    }
+    else if (*since_tag)
+    {
+       if (*(hr->type) == 'T')
+       {
+           /*
+            * A 'T'ag record, the "rev" field holds the tag to be set,
+            * while the "repos" field holds "D"elete, "A"dd or a rev.
+            */
+           if (within (since_tag, hr->rev))
+           {
+               last_since_tag = hr;
+               return 1;
+           }
+           else
+               return 0;
+       }
+       if (!last_since_tag)
+           return 0;
+    }
+    else if (*backto)
+    {
+       if (within (backto, hr->file) || within (backto, hr->mod) ||
+           within (backto, hr->repos))
+           last_backto = hr;
+       else
+           return 0;
+    }
+
+    /* User checking:
+     *
+     * Run down "user_list", match username ("" matches anything)
+     * If "" is not there and actual username is not there, return failure.
+     */
+    if (user_list && hr->user)
+    {
+       for (cpp = user_list, count = user_count; count; cpp++, count--)
+       {
+           if (!**cpp)
+               break;                  /* null user == accept */
+           if (!strcmp (hr->user, *cpp))       /* found listed user */
+               break;
+       }
+       if (!count)
+           return 0;                   /* Not this user */
+    }
+
+    /* Record type checking:
+     *
+     * 1. If Record type is not in rec_types field, skip it.
+     * 2. If mod_list is null, keep everything.  Otherwise keep only modules
+     *    on mod_list.
+     * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list".  If
+     *    file_list is null, keep everything.  Otherwise, keep only files on
+     *    file_list, matched appropriately.
+     */
+    if (!strchr (rec_types, *(hr->type)))
+       return 0;
+    if (!strchr ("TFOE", *(hr->type))) /* Don't bother with "file" if "TFOE" */
+    {
+       if (file_list)                  /* If file_list is null, accept all */
+       {
+           for (fl = file_list, count = file_count; count; fl++, count--)
+           {
+               /* 1. If file_list entry starts with '*', skip the '*' and
+                *    compare it against the repository in the hrec.
+                * 2. If file_list entry has a '/' in it, compare it against
+                *    the concatenation of the repository and file from hrec.
+                * 3. Else compare the file_list entry against the hrec file.
+                */
+               char *cmpfile = NULL;
+
+               if (*(cp = fl->l_file) == '*')
+               {
+                   cp++;
+                   /* if argument to -p is a prefix of repository */
+                   if (!strncmp (cp, hr->repos, strlen (cp)))
+                   {
+                       hr->mod = fl->l_module;
+                       break;
+                   }
+               }
+               else
+               {
+                   if (strchr (cp, '/'))
+                   {
+                       cmpfile = Xasprintf ("%s/%s", hr->repos, hr->file);
+                       cp2 = cmpfile;
+                   }
+                   else
+                   {
+                       cp2 = hr->file;
+                   }
+
+                   /* if requested file is found within {repos}/file fields */
+                   if (within (cp, cp2))
+                   {
+                       hr->mod = fl->l_module;
+                       if (cmpfile != NULL)
+                           free (cmpfile);
+                       break;
+                   }
+                   if (cmpfile != NULL)
+                       free (cmpfile);
+               }
+           }
+           if (!count)
+               return 0;               /* String specified and no match */
+       }
+    }
+    if (mod_list)
+    {
+       for (cpp = mod_list, count = mod_count; count; cpp++, count--)
+       {
+           if (hr->mod && !strcmp (hr->mod, *cpp))     /* found module */
+               break;
+       }
+       if (!count)
+           return 0;   /* Module specified & this record is not one of them. */
+    }
+
+    return 1;          /* Select this record unless rejected above. */
+}
+
+/* The "sort_order" routine (when handed to qsort) has arranged for the
+ * hrecs files to be in the right order for the report.
+ *
+ * Most of the "selections" are done in the select_hrec routine, but some
+ * selections are more easily done after the qsort by "accept_hrec".
+ */
+static void
+report_hrecs (void)
+{
+    struct hrec *hr, *lr;
+    struct tm *tm;
+    int i, count, ty;
+    char *cp;
+    int user_len, file_len, rev_len, mod_len, repos_len;
+
+    if (*since_tag && !last_since_tag)
+    {
+       (void) printf ("No tag found: %s\n", since_tag);
+       return;
+    }
+    else if (*backto && !last_backto)
+    {
+       (void) printf ("No module, file or repository with: %s\n", backto);
+       return;
+    }
+    else if (hrec_count < 1)
+    {
+       (void) printf ("No records selected.\n");
+       return;
+    }
+
+    user_len = file_len = rev_len = mod_len = repos_len = 0;
+
+    /* Run through lists and find maximum field widths */
+    hr = lr = hrec_head;
+    hr++;
+    for (count = hrec_count; count--; lr = hr, hr++)
+    {
+       char *repos;
+
+       if (!count)
+           hr = NULL;
+       if (!accept_hrec (lr, hr))
+           continue;
+
+       ty = *(lr->type);
+       repos = xstrdup (lr->repos);
+       if ((cp = strrchr (repos, '/')) != NULL)
+       {
+           if (lr->mod && !strcmp (++cp, lr->mod))
+           {
+               (void) strcpy (cp, "*");
+           }
+       }
+       if ((i = strlen (lr->user)) > user_len)
+           user_len = i;
+       if ((i = strlen (lr->file)) > file_len)
+           file_len = i;
+       if (ty != 'T' && (i = strlen (repos)) > repos_len)
+           repos_len = i;
+       if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
+           rev_len = i;
+       if (lr->mod && (i = strlen (lr->mod)) > mod_len)
+           mod_len = i;
+       free (repos);
+    }
+
+    /* Walk through hrec array setting "lr" (Last Record) to each element.
+     * "hr" points to the record following "lr" -- It is NULL in the last
+     * pass.
+     *
+     * There are two sections in the loop below:
+     * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
+     *    decide whether the record should be printed.
+     * 2. Based on the record type, format and print the data.
+     */
+    for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
+    {
+       char *workdir;
+       char *repos;
+
+       if (!hrec_count)
+           hr = NULL;
+       if (!accept_hrec (lr, hr))
+           continue;
+
+       ty = *(lr->type);
+       if (!tz_local)
+       {
+           time_t t = lr->date + tz_seconds_east_of_GMT;
+           tm = gmtime (&t);
+       }
+       else
+           tm = localtime (&(lr->date));
+
+       (void) printf ("%c %04d-%02d-%02d %02d:%02d %s %-*s", ty,
+                 tm->tm_year+1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
+                 tm->tm_min, tz_name, user_len, lr->user);
+
+       workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10);
+       (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
+       if ((cp = strrchr (workdir, '/')) != NULL)
+       {
+           if (lr->mod && !strcmp (++cp, lr->mod))
+           {
+               (void) strcpy (cp, "*");
+           }
+       }
+       repos = xmalloc (strlen (lr->repos) + 10);
+       (void) strcpy (repos, lr->repos);
+       if ((cp = strrchr (repos, '/')) != NULL)
+       {
+           if (lr->mod && !strcmp (++cp, lr->mod))
+           {
+               (void) strcpy (cp, "*");
+           }
+       }
+
+       switch (ty)
+       {
+           case 'T':
+               /* 'T'ag records: repository is a "tag type", rev is the tag */
+               (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
+                              repos);
+               if (working)
+                   (void) printf (" {%s}", workdir);
+               break;
+           case 'F':
+           case 'E':
+           case 'O':
+               if (lr->rev && *(lr->rev))
+                   (void) printf (" [%s]", lr->rev);
+               (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
+                              mod_len + 1 - (int) strlen (lr->mod),
+                              "=", workdir);
+               break;
+           case 'W':
+           case 'U':
+           case 'P':
+           case 'C':
+           case 'G':
+           case 'M':
+           case 'A':
+           case 'R':
+               (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
+                              file_len, lr->file, repos_len, repos,
+                              lr->mod ? lr->mod : "", workdir);
+               break;
+           default:
+               (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
+               break;
+       }
+       (void) putchar ('\n');
+       free (workdir);
+       free (repos);
+    }
+}
+
+static int
+accept_hrec (struct hrec *lr, struct hrec *hr)
+{
+    int ty;
+
+    ty = *(lr->type);
+
+    if (last_since_tag && ty == 'T')
+       return 1;
+
+    if (v_checkout)
+    {
+       if (ty != 'O')
+           return 0;                   /* Only interested in 'O' records */
+
+       /* We want to identify all the states that cause the next record
+        * ("hr") to be different from the current one ("lr") and only
+        * print a line at the allowed boundaries.
+        */
+
+       if (!hr ||                      /* The last record */
+           strcmp (hr->user, lr->user) ||      /* User has changed */
+           strcmp (hr->mod, lr->mod) ||/* Module has changed */
+           (working &&                 /* If must match "workdir" */
+            (strcmp (hr->dir, lr->dir) ||      /*    and the 1st parts or */
+             strcmp (hr->end, lr->end))))      /*    the 2nd parts differ */
+
+           return 1;
+    }
+    else if (modified)
+    {
+       if (!last_entry ||              /* Don't want only last rec */
+           !hr ||                      /* Last entry is a "last entry" */
+           strcmp (hr->repos, lr->repos) ||    /* Repository has changed */
+           strcmp (hr->file, lr->file))/* File has changed */
+           return 1;
+
+       if (working)
+       {                               /* If must match "workdir" */
+           if (strcmp (hr->dir, lr->dir) ||    /*    and the 1st parts or */
+               strcmp (hr->end, lr->end))      /*    the 2nd parts differ */
+               return 1;
+       }
+    }
+    else if (module_report)
+    {
+       if (!last_entry ||              /* Don't want only last rec */
+           !hr ||                      /* Last entry is a "last entry" */
+           strcmp (hr->mod, lr->mod) ||/* Module has changed */
+           strcmp (hr->repos, lr->repos) ||    /* Repository has changed */
+           strcmp (hr->file, lr->file))/* File has changed */
+           return 1;
+    }
+    else
+    {
+       /* "extract" and "tag_report" always print selected records. */
+       return 1;
+    }
+
+    return 0;
+}
Index: ccvs/src/ignore.c
diff -u /dev/null ccvs/src/ignore.c:1.56.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/ignore.c   Fri Jan  6 19:34:15 2006
@@ -0,0 +1,498 @@
+/* This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.  */
+
+/*
+ * .cvsignore file support contributed by David G. Grubbs <address@hidden>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Verify interface.  */
+#include "ignore.h"
+
+/* GNULIB headers.  */
+#include "getline.h"
+#include "lstat.h"
+
+/* CVS headers.  */
+#include "cvs.h"
+
+
+
+/*
+ * Ignore file section.
+ * 
+ *     "!" may be included any time to reset the list (i.e. ignore nothing);
+ *     "*" may be specified to ignore everything.  It stays as the first
+ *         element forever, unless a "!" clears it out.
+ */
+
+static char **ign_list;                        /* List of files to ignore in 
update
+                                        * and import */
+static char **s_ign_list = NULL;
+static int ign_count;                  /* Number of active entries */
+static int s_ign_count = 0;
+static int ign_size;                   /* This many slots available (plus
+                                        * one for a NULL) */
+static int ign_hold = -1;              /* Index where first "temporary" item
+                                        * is held */
+
+const char *ign_default = ". .. core RCSLOG tags TAGS RCS SCCS .make.state\
+ .nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj\
+ *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$";
+
+#define IGN_GROW 16                    /* grow the list by 16 elements at a
+                                        * time */
+
+/* Nonzero if we have encountered an -I ! directive, which means one should
+   no longer ask the server about what is in CVSROOTADM_IGNORE.  */
+int ign_inhibit_server;
+
+
+
+/*
+ * To the "ignore list", add the hard-coded default ignored wildcards above,
+ * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in
+ * ~/.cvsignore and the wildcards found in the CVSIGNORE environment
+ * variable.
+ */
+void
+ign_setup (void)
+{
+    char *home_dir;
+    char *tmp;
+
+    ign_inhibit_server = 0;
+
+    /* Start with default list and special case */
+    tmp = xstrdup (ign_default);
+    ign_add (tmp, 0);
+    free (tmp);
+
+    /* The client handles another way, by (after it does its own ignore file
+       processing, and only if !ign_inhibit_server), letting the server
+       know about the files and letting it decide whether to ignore
+       them based on CVSROOOTADM_IGNORE.  */
+    if (!current_parsed_root->isremote)
+    {
+       char *file = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
+                               CVSROOTADM, CVSROOTADM_IGNORE);
+       /* Then add entries found in repository, if it exists */
+       ign_add_file (file, 0);
+       free (file);
+    }
+
+    /* Then add entries found in home dir, (if user has one) and file exists */
+    home_dir = get_homedir ();
+    /* If we can't find a home directory, ignore ~/.cvsignore.  This may
+       make tracking down problems a bit of a pain, but on the other
+       hand it might be obnoxious to complain when CVS will function
+       just fine without .cvsignore (and many users won't even know what
+       .cvsignore is).  */
+    if (home_dir)
+    {
+       char *file = strcat_filename_onto_homedir (home_dir, CVSDOTIGNORE);
+       ign_add_file (file, 0);
+       free (file);
+    }
+
+    /* Then add entries found in CVSIGNORE environment variable. */
+    ign_add (getenv (IGNORE_ENV), 0);
+
+    /* Later, add ignore entries found in -I arguments */
+}
+
+
+
+/*
+ * Open a file and read lines, feeding each line to a line parser. Arrange
+ * for keeping a temporary list of wildcards at the end, if the "hold"
+ * argument is set.
+ */
+void
+ign_add_file (char *file, int hold)
+{
+    FILE *fp;
+    char *line = NULL;
+    size_t line_allocated = 0;
+
+    /* restore the saved list (if any) */
+    if (s_ign_list != NULL)
+    {
+       int i;
+
+       for (i = 0; i < s_ign_count; i++)
+           ign_list[i] = s_ign_list[i];
+       ign_count = s_ign_count;
+       ign_list[ign_count] = NULL;
+
+       s_ign_count = 0;
+       free (s_ign_list);
+       s_ign_list = NULL;
+    }
+
+    /* is this a temporary ignore file? */
+    if (hold)
+    {
+       /* re-set if we had already done a temporary file */
+       if (ign_hold >= 0)
+       {
+           int i;
+
+           for (i = ign_hold; i < ign_count; i++)
+               free (ign_list[i]);
+           ign_count = ign_hold;
+           ign_list[ign_count] = NULL;
+       }
+       else
+       {
+           ign_hold = ign_count;
+       }
+    }
+
+    /* load the file */
+    fp = CVS_FOPEN (file, "r");
+    if (fp == NULL)
+    {
+       if (! existence_error (errno))
+           error (0, errno, "cannot open %s", file);
+       return;
+    }
+    while (getline (&line, &line_allocated, fp) >= 0)
+       ign_add (line, hold);
+    if (ferror (fp))
+       error (0, errno, "cannot read %s", file);
+    if (fclose (fp) < 0)
+       error (0, errno, "cannot close %s", file);
+    free (line);
+}
+
+
+
+/* Parse a line of space-separated wildcards and add them to the list. */
+void
+ign_add (char *ign, int hold)
+{
+    if (!ign || !*ign)
+       return;
+
+    for (; *ign; ign++)
+    {
+       char *mark;
+       char save;
+
+       /* ignore whitespace before the token */
+       if (isspace ((unsigned char) *ign))
+           continue;
+
+       /* If we have used up all the space, add some more.  Do this before
+          processing `!', since an "empty" list still contains the `CVS'
+          entry.  */
+       if (ign_count >= ign_size)
+       {
+           ign_size += IGN_GROW;
+           ign_list = xnrealloc (ign_list, ign_size + 1, sizeof (char *));
+       }
+
+       /*
+        * if we find a single character !, we must re-set the ignore list
+        * (saving it if necessary).  We also catch * as a special case in a
+        * global ignore file as an optimization
+        */
+       if ((!*(ign+1) || isspace ((unsigned char) *(ign+1)))
+           && (*ign == '!' || *ign == '*'))
+       {
+           if (!hold)
+           {
+               /* permanently reset the ignore list */
+               int i;
+
+               for (i = 0; i < ign_count; i++)
+                   free (ign_list[i]);
+               ign_count = 1;
+               /* Always ignore the "CVS" directory.  */
+               ign_list[0] = xstrdup ("CVS");
+               ign_list[1] = NULL;
+
+               /* if we are doing a '!', continue; otherwise add the '*' */
+               if (*ign == '!')
+               {
+                   ign_inhibit_server = 1;
+                   continue;
+               }
+           }
+           else if (*ign == '!')
+           {
+               /* temporarily reset the ignore list */
+               int i;
+
+               if (ign_hold >= 0)
+               {
+                   for (i = ign_hold; i < ign_count; i++)
+                       free (ign_list[i]);
+                   ign_hold = -1;
+               }
+               if (s_ign_list)
+               {
+                   /* Don't save the ignore list twice - if there are two
+                    * bangs in a local .cvsignore file then we don't want to
+                    * save the new list the first bang created.
+                    *
+                    * We still need to free the "new" ignore list.
+                    */
+                   for (i = 0; i < ign_count; i++)
+                       free (ign_list[i]);
+               }
+               else
+               {
+                   /* Save the ignore list for later.  */
+                   s_ign_list = xnmalloc (ign_count, sizeof (char *));
+                   for (i = 0; i < ign_count; i++)
+                       s_ign_list[i] = ign_list[i];
+                   s_ign_count = ign_count;
+               }
+               ign_count = 1;
+                   /* Always ignore the "CVS" directory.  */
+               ign_list[0] = xstrdup ("CVS");
+               ign_list[1] = NULL;
+               continue;
+           }
+       }
+
+       /* find the end of this token */
+       for (mark = ign; *mark && !isspace ((unsigned char) *mark); mark++)
+            /* do nothing */ ;
+
+       save = *mark;
+       *mark = '\0';
+
+       ign_list[ign_count++] = xstrdup (ign);
+       ign_list[ign_count] = NULL;
+
+       *mark = save;
+       if (save)
+           ign = mark;
+       else
+           ign = mark - 1;
+    }
+}
+
+
+
+/* Return true if the given filename should be ignored by update or import,
+ * else return false.
+ */
+int
+ign_name (char *name)
+{
+    char **cpp = ign_list;
+
+    if (cpp == NULL)
+       return 0;
+
+    while (*cpp)
+       if (CVS_FNMATCH (*cpp++, name, 0) == 0)
+           return 1;
+
+    return 0;
+}
+
+
+
+/* FIXME: This list of dirs to ignore stuff seems not to be used.
+   Really?  send_dirent_proc and update_dirent_proc both call
+   ignore_directory and do_module calls ign_dir_add.  No doubt could
+   use some documentation/testsuite work.  */
+
+static char **dir_ign_list = NULL;
+static int dir_ign_max = 0;
+static int dir_ign_current = 0;
+
+/* Add a directory to list of dirs to ignore.  */
+void
+ign_dir_add (char *name)
+{
+    /* Make sure we've got the space for the entry.  */
+    if (dir_ign_current <= dir_ign_max)
+    {
+       dir_ign_max += IGN_GROW;
+       dir_ign_list = xnrealloc (dir_ign_list,
+                                 dir_ign_max + 1, sizeof (char *));
+    }
+
+    dir_ign_list[dir_ign_current++] = xstrdup (name);
+}
+
+
+/* Return nonzero if NAME is part of the list of directories to ignore.  */
+
+int
+ignore_directory (const char *name)
+{
+    int i;
+
+    if (!dir_ign_list)
+       return 0;
+
+    i = dir_ign_current;
+    while (i--)
+    {
+       if (strncmp (name, dir_ign_list[i], strlen (dir_ign_list[i])+1) == 0)
+           return 1;
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Process the current directory, looking for files not in ILIST and
+ * not on the global ignore list for this directory.  If we find one,
+ * call PROC passing it the name of the file and the update dir.
+ * ENTRIES is the entries list, which is used to identify known
+ * directories.  ENTRIES may be NULL, in which case we assume that any
+ * directory with a CVS administration directory is known.
+ */
+void
+ignore_files (List *ilist, List *entries, const char *update_dir,
+              Ignore_proc proc)
+{
+    int subdirs;
+    DIR *dirp;
+    struct dirent *dp;
+    struct stat sb;
+    char *file;
+    const char *xdir;
+    List *files;
+    Node *p;
+
+    /* Set SUBDIRS if we have subdirectory information in ENTRIES.  */
+    if (entries == NULL)
+       subdirs = 0;
+    else
+    {
+       struct stickydirtag *sdtp = entries->list->data;
+
+       subdirs = sdtp == NULL || sdtp->subdirs;
+    }
+
+    /* we get called with update_dir set to "." sometimes... strip it */
+    if (strcmp (update_dir, ".") == 0)
+       xdir = "";
+    else
+       xdir = update_dir;
+
+    dirp = CVS_OPENDIR (".");
+    if (dirp == NULL)
+    {
+       error (0, errno, "cannot open current directory");
+       return;
+    }
+
+    ign_add_file (CVSDOTIGNORE, 1);
+    wrap_add_file (CVSDOTWRAPPER, 1);
+
+    /* Make a list for the files.  */
+    files = getlist ();
+
+    while (errno = 0, (dp = CVS_READDIR (dirp)) != NULL)
+    {
+       file = dp->d_name;
+       if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
+           continue;
+       if (findnode_fn (ilist, file) != NULL)
+           continue;
+       if (subdirs)
+       {
+           Node *node;
+
+           node = findnode_fn (entries, file);
+           if (node != NULL
+               && ((Entnode *) node->data)->type == ENT_SUBDIR)
+           {
+               char *p;
+               int dir;
+
+               /* For consistency with past behaviour, we only ignore
+                  this directory if there is a CVS subdirectory.
+                  This will normally be the case, but the user may
+                  have messed up the working directory somehow.  */
+               p = Xasprintf ("%s/%s", file, CVSADM);
+               dir = isdir (p);
+               free (p);
+               if (dir)
+                   continue;
+           }
+       }
+
+       /* We could be ignoring FIFOs and other files which are neither
+          regular files nor directories here.  */
+       if (ign_name (file))
+           continue;
+
+       if (
+#ifdef DT_DIR
+           dp->d_type != DT_UNKNOWN ||
+#endif
+           lstat (file, &sb) != -1)
+       {
+
+           if (
+#ifdef DT_DIR
+               dp->d_type == DT_DIR
+               || (dp->d_type == DT_UNKNOWN && S_ISDIR (sb.st_mode))
+#else
+               S_ISDIR (sb.st_mode)
+#endif
+               )
+           {
+               if (!subdirs)
+               {
+                   char *temp = Xasprintf ("%s/%s", file, CVSADM);
+                   if (isdir (temp))
+                   {
+                       free (temp);
+                       continue;
+                   }
+                   free (temp);
+               }
+           }
+#ifdef S_ISLNK
+           else if (
+#ifdef DT_DIR
+                    dp->d_type == DT_LNK
+                    || (dp->d_type == DT_UNKNOWN && S_ISLNK (sb.st_mode))
+#else
+                    S_ISLNK (sb.st_mode)
+#endif
+                    )
+           {
+               continue;
+           }
+#endif
+       }
+
+       p = getnode ();
+       p->type = FILES;
+       p->key = xstrdup (file);
+       (void) addnode (files, p);
+    }
+    if (errno != 0)
+       error (0, errno, "error reading current directory");
+    (void) CVS_CLOSEDIR (dirp);
+
+    sortlist (files, fsortcmp);
+    for (p = files->list->next; p != files->list; p = p->next)
+       (*proc) (p->key, xdir);
+    dellist (&files);
+}
Index: ccvs/src/ignore.h
diff -u /dev/null ccvs/src/ignore.h:1.1.2.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/ignore.h   Fri Jan  6 19:34:15 2006
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2006 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef IGNORE_H
+#define IGNORE_H
+
+#include "hash.h"
+
+int ign_name (char *name);
+void ign_add (char *ign, int hold);
+void ign_add_file (char *file, int hold);
+void ign_setup (void);
+void ign_dir_add (char *name);
+int ignore_directory (const char *name);
+typedef void (*Ignore_proc) (const char *, const char *);
+void ignore_files (List *, List *, const char *, Ignore_proc);
+extern int ign_inhibit_server;
+
+#endif /* IGNORE_H */
Index: ccvs/src/import.c
diff -u ccvs/src/import.c:1.175.6.1 ccvs/src/import.c:1.175.6.2
--- ccvs/src/import.c:1.175.6.1 Wed Dec 21 13:25:10 2005
+++ ccvs/src/import.c   Fri Jan  6 19:34:15 2006
@@ -21,10 +21,22 @@
  * Additional arguments specify more Vendor Release Tags.
  */
 
-#include "cvs.h"
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* GNULIB headers.  */
 #include "lstat.h"
 #include "save-cwd.h"
 
+/* CVS headers.  */
+#include "ignore.h"
+#include "logmsg.h"
+
+#include "cvs.h"
+
+
+
 static char *get_comment (const char *user);
 static int add_rev (char *message, RCSNode *rcs, char *vfile,
                          char *vers);
Index: ccvs/src/lock.c
diff -u /dev/null ccvs/src/lock.c:1.117.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/lock.c     Fri Jan  6 19:34:15 2006
@@ -0,0 +1,1380 @@
+/*
+ * 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.
+ * 
+ * Set Lock
+ * 
+ * Lock file support for CVS.
+ */
+
+/* The node Concurrency in doc/cvs.texinfo has a brief introduction to
+   how CVS locks function, and some of the user-visible consequences of
+   their existence.  Here is a summary of why they exist (and therefore,
+   the consequences of hacking CVS to read a repository without creating
+   locks):
+
+   There are two uses.  One is the ability to prevent there from being
+   two writers at the same time.  This is necessary for any number of
+   reasons (fileattr code, probably others).  Commit needs to lock the
+   whole tree so that nothing happens between the up-to-date check and
+   the actual checkin.
+
+   The second use is the ability to ensure that there is not a writer
+   and a reader at the same time (several readers are allowed).  Reasons
+   for this are:
+
+   * Readlocks ensure that once CVS has found a collection of rcs
+   files using Find_Names, the files will still exist when it reads
+   them (they may have moved in or out of the attic).
+
+   * Readlocks provide some modicum of consistency, although this is
+   kind of limited--see the node Concurrency in cvs.texinfo.
+
+   * Readlocks ensure that the RCS file does not change between
+   RCS_parse and RCS_reparsercsfile time.  This one strikes me as
+   important, although I haven't thought up what bad scenarios might
+   be.
+
+   * Readlocks ensure that we won't find the file in the state in
+   which it is in between the calls to add_rcs_file and RCS_checkin in
+   commit.c (when a file is being added).  This state is a state in
+   which the RCS file parsing routines in rcs.c cannot parse the file.
+
+   * Readlocks ensure that a reader won't try to look at a
+   half-written fileattr file (fileattr is not updated atomically).
+
+   (see also the description of anonymous read-only access in
+   "Password authentication security" node in doc/cvs.texinfo).
+
+   While I'm here, I'll try to summarize a few random suggestions
+   which periodically get made about how locks might be different:
+
+   1.  Check for EROFS.  Maybe useful, although in the presence of NFS
+   EROFS does *not* mean that the file system is unchanging.
+
+   2.  Provide an option to disable locks for operations which only
+   read (see above for some of the consequences).
+
+   3.  Have a server internally do the locking.  Probably a good
+   long-term solution, and many people have been working hard on code
+   changes which would eventually make it possible to have a server
+   which can handle various connections in one process, but there is
+   much, much work still to be done before this is feasible.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* CVS headers.  */
+#include "recurse.h"
+
+#include "cvs.h"
+
+
+
+struct lock {
+    /* This is the directory in which we may have a lock named by the
+       readlock variable, a lock named by the writelock variable, and/or
+       a lock named CVSLCK.  The storage is not allocated along with the
+       struct lock; it is allocated by the Reader_Lock caller or in the
+       case of promotablelocks, it is just a pointer to the storage allocated
+       for the ->key field.  */
+    const char *repository;
+
+    /* The name of the lock files. */
+    char *file1;
+#ifdef LOCK_COMPATIBILITY
+    char *file2;
+#endif /* LOCK_COMPATIBILITY */
+
+    /* The name of the master lock dir.  Usually CVSLCK.  */
+    const char *lockdirname;
+
+    /* The full path to the lock dir, if we are currently holding it.
+     *
+     * This will be LOCKDIRNAME catted onto REPOSITORY.  We waste a little
+     * space by storing it, but save a later malloc/free.
+     */
+    char *lockdir;
+
+    /* Note there is no way of knowing whether the readlock and writelock
+       exist.  The code which sets the locks doesn't use SIG_beginCrSect
+       to set a flag like we do for CVSLCK.  */
+    bool free_repository;
+};
+
+static void remove_locks (void);
+static int set_lock (struct lock *lock, int will_wait);
+static void clear_lock (struct lock *lock);
+static void set_lockers_name (struct stat *statp);
+
+/* Malloc'd array containing the username of the whoever has the lock.
+   Will always be non-NULL in the cases where it is needed.  */
+static char *lockers_name;
+/* Malloc'd array specifying name of a readlock within a directory.
+   Or NULL if none.  */
+static char *readlock;
+/* Malloc'd array specifying name of a writelock within a directory.
+   Or NULL if none.  */
+static char *writelock;
+/* Malloc'd array specifying name of a promotablelock within a directory.
+   Or NULL if none.  */
+static char *promotablelock;
+static List *locklist;
+
+#define L_OK           0               /* success */
+#define L_ERROR                1               /* error condition */
+#define L_LOCKED       2               /* lock owned by someone else */
+
+/* This is the (single) readlock which is set by Reader_Lock.  The
+   repository field is NULL if there is no such lock.  */
+#ifdef LOCK_COMPATIBILITY
+static struct lock global_readlock = {NULL, NULL, NULL, CVSLCK, NULL, false};
+static struct lock global_writelock = {NULL, NULL, NULL, CVSLCK, NULL, false};
+
+static struct lock global_history_lock = {NULL, NULL, NULL, CVSHISTORYLCK,
+                                         NULL, false};
+static struct lock global_val_tags_lock = {NULL, NULL, NULL, CVSVALTAGSLCK,
+                                          NULL, false};
+#else
+static struct lock global_readlock = {NULL, NULL, CVSLCK, NULL, false};
+static struct lock global_writelock = {NULL, NULL, CVSLCK, NULL, false};
+
+static struct lock global_history_lock = {NULL, NULL, CVSHISTORYLCK, NULL,
+                                         false};
+static struct lock global_val_tags_lock = {NULL, NULL, CVSVALTAGSLCK, NULL,
+                                          false};
+#endif /* LOCK_COMPATIBILITY */
+
+/* List of locks set by lock_tree_for_write.  This is redundant
+   with locklist, sort of.  */
+static List *lock_tree_list;
+
+
+
+/* Return a newly malloc'd string containing the name of the lock for the
+   repository REPOSITORY and the lock file name within that directory
+   NAME.  Also create the directories in which to put the lock file
+   if needed (if we need to, could save system call(s) by doing
+   that only if the actual operation fails.  But for now we'll keep
+   things simple).  */
+static char *
+lock_name (const char *repository, const char *name)
+{
+    char *retval;
+    const char *p;
+    char *q;
+    const char *short_repos;
+    mode_t save_umask = 0000;
+    int saved_umask = 0;
+
+    TRACE (TRACE_FLOW, "lock_name (%s, %s)",
+          repository  ? repository : "(null)", name ? name : "(null)");
+
+    if (!config->lock_dir)
+    {
+       /* This is the easy case.  Because the lock files go directly
+          in the repository, no need to create directories or anything.  */
+       assert (name != NULL);
+       assert (repository != NULL);
+       retval = Xasprintf ("%s/%s", repository, name);
+    }
+    else
+    {
+       struct stat sb;
+       mode_t new_mode = 0;
+
+       /* The interesting part of the repository is the part relative
+          to CVSROOT.  */
+       assert (current_parsed_root != NULL);
+       assert (current_parsed_root->directory != NULL);
+       assert (strncmp (repository, current_parsed_root->directory,
+                        strlen (current_parsed_root->directory)) == 0);
+       short_repos = repository + strlen (current_parsed_root->directory) + 1;
+
+       if (strcmp (repository, current_parsed_root->directory) == 0)
+           short_repos = ".";
+       else
+           assert (short_repos[-1] == '/');
+
+       retval = xmalloc (strlen (config->lock_dir)
+                         + strlen (short_repos)
+                         + strlen (name)
+                         + 10);
+       strcpy (retval, config->lock_dir);
+       q = retval + strlen (retval);
+       *q++ = '/';
+
+       strcpy (q, short_repos);
+
+       /* In the common case, where the directory already exists, let's
+          keep it to one system call.  */
+       if (stat (retval, &sb) < 0)
+       {
+           /* If we need to be creating more than one directory, we'll
+              get the existence_error here.  */
+           if (!existence_error (errno))
+               error (1, errno, "cannot stat directory %s", retval);
+       }
+       else
+       {
+           if (S_ISDIR (sb.st_mode))
+               goto created;
+           else
+               error (1, 0, "%s is not a directory", retval);
+       }
+
+       /* Now add the directories one at a time, so we can create
+          them if needed.
+
+          The idea behind the new_mode stuff is that the directory we
+          end up creating will inherit permissions from its parent
+          directory (we re-set new_mode with each EEXIST).  CVSUMASK
+          isn't right, because typically the reason for LockDir is to
+          use a different set of permissions.  We probably want to
+          inherit group ownership also (but we don't try to deal with
+          that, some systems do it for us either always or when g+s is on).
+
+          We don't try to do anything about the permissions on the lock
+          files themselves.  The permissions don't really matter so much
+          because the locks will generally be removed by the process
+          which created them.  */
+
+       if (stat (config->lock_dir, &sb) < 0)
+           error (1, errno, "cannot stat %s", config->lock_dir);
+       new_mode = sb.st_mode;
+       save_umask = umask (0000);
+       saved_umask = 1;
+
+       p = short_repos;
+       while (1)
+       {
+           while (!ISSLASH (*p) && *p != '\0')
+               ++p;
+           if (ISSLASH (*p))
+           {
+               strncpy (q, short_repos, p - short_repos);
+               q[p - short_repos] = '\0';
+               if (!ISSLASH (q[p - short_repos - 1])
+                   && CVS_MKDIR (retval, new_mode) < 0)
+               {
+                   int saved_errno = errno;
+                   if (saved_errno != EEXIST)
+                       error (1, errno, "cannot make directory %s", retval);
+                   else
+                   {
+                       if (stat (retval, &sb) < 0)
+                           error (1, errno, "cannot stat %s", retval);
+                       new_mode = sb.st_mode;
+                   }
+               }
+               ++p;
+           }
+           else
+           {
+               strcpy (q, short_repos);
+               if (CVS_MKDIR (retval, new_mode) < 0
+                   && errno != EEXIST)
+                   error (1, errno, "cannot make directory %s", retval);
+               goto created;
+           }
+       }
+    created:;
+
+       strcat (retval, "/");
+       strcat (retval, name);
+
+       if (saved_umask)
+       {
+           assert (umask (save_umask) == 0000);
+           saved_umask = 0;
+       }
+    }
+    return retval;
+}
+
+
+
+/* Remove the lock files.  For interrupt purposes, it can be assumed that the
+ * first thing this function does is set lock->repository to NULL.
+ *
+ * INPUTS
+ *   lock      The lock to remove.
+ *   free      True if this lock directory will not be reused (free
+ *             lock->repository if necessary).
+ */
+static void
+remove_lock_files (struct lock *lock, bool free_repository)
+{
+    TRACE (TRACE_FLOW, "remove_lock_files (%s)", lock->repository);
+
+    /* If lock->file is set, the lock *might* have been created, but since
+     * Reader_Lock & lock_dir_for_write don't use SIG_beginCrSect the way that
+     * set_lock does, we don't know that.  That is why we need to check for
+     * existence_error here.
+     */
+    if (lock->file1)
+    {
+       char *tmp = lock->file1;
+       lock->file1 = NULL;
+       if (CVS_UNLINK (tmp) < 0 && ! existence_error (errno))
+           error (0, errno, "failed to remove lock %s", tmp);
+       free (tmp);
+    }
+#ifdef LOCK_COMPATIBILITY
+    if (lock->file2)
+    {
+       char *tmp = lock->file2;
+       lock->file2 = NULL;
+       if (CVS_UNLINK (tmp) < 0 && ! existence_error (errno))
+           error (0, errno, "failed to remove lock %s", tmp);
+       free (tmp);
+    }
+#endif /* LOCK_COMPATIBILITY */
+
+    clear_lock (lock);
+
+    /* And free the repository string.  We don't really have to set the
+     * repository string to NULL first since there is no harm in running any of
+     * the above code twice.
+     *
+     * Use SIG_beginCrSect since otherwise we might be interrupted between
+     * checking whether free_repository is set and freeing stuff.
+     */
+    if (free_repository)
+    {
+       SIG_beginCrSect ();
+       if (lock->free_repository)
+       {
+           free ((char *)lock->repository);
+           lock->free_repository = false;
+       }
+       lock->repository = NULL;
+       SIG_endCrSect ();
+    }
+}
+
+
+
+/*
+ * Clean up outstanding read and write locks and free their storage.
+ */
+void
+Simple_Lock_Cleanup (void)
+{
+    TRACE (TRACE_FUNCTION, "Simple_Lock_Cleanup()");
+
+    /* Avoid interrupts while accessing globals the interrupt handlers might
+     * make use of.
+     */
+    SIG_beginCrSect();
+
+    /* clean up simple read locks (if any) */
+    if (global_readlock.repository != NULL)
+       remove_lock_files (&global_readlock, true);
+    /* See note in Lock_Cleanup() below.  */
+    SIG_endCrSect();
+
+    SIG_beginCrSect();
+
+    /* clean up simple write locks (if any) */
+    if (global_writelock.repository != NULL)
+       remove_lock_files (&global_writelock, true);
+    /* See note in Lock_Cleanup() below.  */
+    SIG_endCrSect();
+
+    SIG_beginCrSect();
+
+    /* clean up simple write locks (if any) */
+    if (global_history_lock.repository)
+       remove_lock_files (&global_history_lock, true);
+    SIG_endCrSect();
+
+    SIG_beginCrSect();
+
+    if (global_val_tags_lock.repository)
+       remove_lock_files (&global_val_tags_lock, true);
+    /* See note in Lock_Cleanup() below.  */
+    SIG_endCrSect();
+}
+
+
+
+/*
+ * Clean up all outstanding locks and free their storage.
+ *
+ * NOTES
+ *   This function needs to be reentrant since a call to exit() can cause a
+ *   call to this function, which can then be interrupted by a signal, which
+ *   can cause a second call to this function.
+ *
+ * RETURNS
+ *   Nothing.
+ */
+void
+Lock_Cleanup (void)
+{
+    TRACE (TRACE_FUNCTION, "Lock_Cleanup()");
+
+    /* FIXME: Do not perform buffered I/O from an interrupt handler like
+     * this (via error).  However, I'm leaving the error-calling code there
+     * in the hope that on the rare occasion the error call is actually made
+     * (e.g., a fluky I/O error or permissions problem prevents the deletion
+     * of a just-created file) reentrancy won't be an issue.
+     */
+
+    remove_locks ();
+
+    /* Avoid being interrupted during calls which set globals to NULL.  This
+     * avoids having interrupt handlers attempt to use these global variables
+     * in inconsistent states.
+     *
+     * This isn't always necessary, because sometimes we are called via exit()
+     * or the interrupt handler, in which case signals will already be blocked,
+     * but sometimes we might be called from elsewhere.
+     */
+    SIG_beginCrSect();
+    dellist (&lock_tree_list);
+    /*  Unblocking allows any signal to be processed as soon as possible.  This
+     *  isn't really necessary, but since we know signals can cause us to be
+     *  called, why not avoid having blocks of code run twice.
+     */
+    SIG_endCrSect();
+}
+
+
+
+/*
+ * walklist proc for removing a list of locks
+ */
+static int
+unlock_proc (Node *p, void *closure)
+{
+    remove_lock_files (p->data, false);
+    return 0;
+}
+
+
+
+/*
+ * Remove locks without discarding the lock information.
+ */
+static void
+remove_locks (void)
+{
+    TRACE (TRACE_FLOW, "remove_locks()");
+
+    Simple_Lock_Cleanup ();
+
+    /* clean up promotable locks (if any) */
+    SIG_beginCrSect();
+    if (locklist != NULL)
+    {
+       /* Use a tmp var since any of these functions could call exit, causing
+        * us to be called a second time.
+        */
+       List *tmp = locklist;
+       locklist = NULL;
+       walklist (tmp, unlock_proc, NULL);
+    }
+    SIG_endCrSect();
+}
+
+
+
+/*
+ * Set the global readlock variable if it isn't already.
+ */
+static void
+set_readlock_name (void)
+{
+    if (readlock == NULL)
+    {
+       readlock = Xasprintf (
+#ifdef HAVE_LONG_FILE_NAMES
+                             "%s.%s.%ld", CVSRFL, hostname,
+#else
+                             "%s.%ld", CVSRFL,
+#endif
+                             (long) getpid ());
+    }
+}
+
+
+
+/*
+ * Create a lock file for readers
+ */
+int
+Reader_Lock (char *xrepository)
+{
+    int err = 0;
+    FILE *fp;
+
+    TRACE (TRACE_FUNCTION, "Reader_Lock(%s)", xrepository);
+
+    if (noexec || readonlyfs)
+       return 0;
+
+    /* we only do one directory at a time for read locks!  */
+    if (global_readlock.repository != NULL)
+    {
+       error (0, 0, "Reader_Lock called while read locks set - Help!");
+       return 1;
+    }
+
+    set_readlock_name ();
+
+    /* remember what we're locking (for Lock_Cleanup) */
+    global_readlock.repository = xstrdup (xrepository);
+    global_readlock.free_repository = true;
+
+    /* get the lock dir for our own */
+    if (set_lock (&global_readlock, 1) != L_OK)
+    {
+       error (0, 0, "failed to obtain dir lock in repository `%s'",
+              xrepository);
+       if (readlock != NULL)
+           free (readlock);
+       readlock = NULL;
+       /* We don't set global_readlock.repository to NULL.  I think this
+          only works because recurse.c will give a fatal error if we return
+          a nonzero value.  */
+       return 1;
+    }
+
+    /* write a read-lock */
+    global_readlock.file1 = lock_name (xrepository, readlock);
+    if ((fp = CVS_FOPEN (global_readlock.file1, "w+")) == NULL
+       || fclose (fp) == EOF)
+    {
+       error (0, errno, "cannot create read lock in repository `%s'",
+              xrepository);
+       err = 1;
+    }
+
+    /* free the lock dir */
+    clear_lock (&global_readlock);
+
+    return err;
+}
+
+
+
+/*
+ * lock_exists() returns 0 if there is no lock file matching FILEPAT in
+ * the repository but not IGNORE; else 1 is returned, to indicate that the
+ * caller should sleep a while and try again.
+ *
+ * INPUTS
+ *   repository                The repository directory to search for locks.
+ *   filepat           The file name pattern to search for.
+ *   ignore            The name of a single file which can be ignored.
+ *
+ * GLOBALS
+ *   lockdir           The lock dir external to the repository, if any.
+ *
+ * RETURNS
+ *   0         No lock matching FILEPAT and not IGNORE exists.
+ *   1         Otherwise and on error.
+ *
+ * ERRORS
+ *  In the case where errors are encountered reading the directory, a warning
+ *  message is printed, 1 is is returned and ERRNO is left set.
+ */
+static int
+lock_exists (const char *repository, const char *filepat, const char *ignore)
+{
+    char *lockdir;
+    char *line;
+    DIR *dirp;
+    struct dirent *dp;
+    struct stat sb;
+    int ret;
+#ifdef CVS_FUDGELOCKS
+    time_t now;
+    (void)time (&now);
+#endif
+
+    TRACE (TRACE_FLOW, "lock_exists (%s, %s, %s)",
+          repository, filepat, ignore ? ignore : "(null)");
+
+    lockdir = lock_name (repository, "");
+    lockdir[strlen (lockdir) - 1] = '\0';   /* remove trailing slash */
+
+    do {
+       if ((dirp = CVS_OPENDIR (lockdir)) == NULL)
+           error (1, 0, "cannot open directory %s", lockdir);
+
+       ret = 0;
+       errno = 0;
+       while ((dp = CVS_READDIR (dirp)) != NULL)
+       {
+           if (CVS_FNMATCH (filepat, dp->d_name, 0) == 0)
+           {
+               /* FIXME: the basename conversion below should be replaced with
+                * a call to the GNULIB basename function once it is imported.
+                */
+               /* ignore our plock, if any */
+               if (ignore && !fncmp (ignore, dp->d_name))
+                   continue;
+
+               line = Xasprintf ("%s/%s", lockdir, dp->d_name);
+               if (stat (line, &sb) != -1)
+               {
+#ifdef CVS_FUDGELOCKS
+                   /*
+                    * If the create time of the file is more than CVSLCKAGE 
+                    * seconds ago, try to clean-up the lock file, and if
+                    * successful, re-open the directory and try again.
+                    */
+                   if (now >= (sb.st_ctime + CVSLCKAGE) &&
+                        CVS_UNLINK (line) != -1)
+                   {
+                       free (line);
+                       ret = -1;
+                       break;
+                   }
+#endif
+                   set_lockers_name (&sb);
+               }
+               else
+               {
+                   /* If the file doesn't exist, it just means that it
+                    * disappeared between the time we did the readdir and the
+                    * time we did the stat.
+                    */
+                   if (!existence_error (errno))
+                       error (0, errno, "cannot stat %s", line);
+               }
+               errno = 0;
+               free (line);
+               ret = 1;
+               break;
+           }
+           errno = 0;
+       }
+       if (errno != 0)
+           error (0, errno, "error reading directory %s", repository);
+
+       CVS_CLOSEDIR (dirp);
+    } while (ret < 0);
+
+    if (lockdir != NULL)
+       free (lockdir);
+    return ret;
+}
+
+
+
+/*
+ * readers_exist() returns 0 if there are no reader lock files remaining in
+ * the repository; else 1 is returned, to indicate that the caller should
+ * sleep a while and try again.
+ *
+ * See lock_exists() for argument detail.
+ */
+static int
+readers_exist (const char *repository)
+{
+    TRACE (TRACE_FLOW, "readers_exist (%s)", repository);
+
+    /* It is only safe to ignore a readlock set by our process if it was set as
+     * a safety measure to prevent older CVS processes from ignoring our
+     * promotable locks.  The code to ignore these readlocks can be removed
+     * once it is deemed unlikely that anyone will be using CVS servers earlier
+     * than version 1.12.4.
+     */
+    return lock_exists (repository, CVSRFLPAT,
+#ifdef LOCK_COMPATIBILITY
+                         findnode (locklist, repository) ? readlock : 
+#endif /* LOCK_COMPATIBILITY */
+                        NULL);
+}
+
+
+
+/*
+ * promotable_exists() returns 0 if there is no promotable lock file in
+ * the repository; else 1 is returned, to indicate that the caller should
+ * sleep a while and try again.
+ *
+ * See lock_exists() for argument detail.
+ */
+static int
+promotable_exists (const char *repository)
+{
+    TRACE (TRACE_FLOW, "promotable_exists (%s)", repository);
+    return lock_exists (repository, CVSPFLPAT, promotablelock);
+}
+
+
+
+/*
+ * Lock a list of directories for writing
+ */
+static char *lock_error_repos;
+static int lock_error;
+
+
+
+/*
+ * Create a lock file for potential writers returns L_OK if lock set ok,
+ * L_LOCKED if lock held by someone else or L_ERROR if an error occurred.
+ */
+static int
+set_promotable_lock (struct lock *lock)
+{
+    int status;
+    FILE *fp;
+
+    TRACE (TRACE_FUNCTION, "set_promotable_lock(%s)",
+          lock->repository ? lock->repository : "(null)");
+
+    if (promotablelock == NULL)
+    {
+       promotablelock = Xasprintf (
+#ifdef HAVE_LONG_FILE_NAMES
+                                   "%s.%s.%ld", CVSPFL, hostname,
+#else
+                                   "%s.%ld", CVSPFL,
+#endif
+                                   (long) getpid());
+    }
+
+    /* make sure the lock dir is ours (not necessarily unique to us!) */
+    status = set_lock (lock, 0);
+    if (status == L_OK)
+    {
+       /* we now own a promotable lock - make sure there are no others */
+       if (promotable_exists (lock->repository))
+       {
+           /* clean up the lock dir */
+           clear_lock (lock);
+
+           /* indicate we failed due to read locks instead of error */
+           return L_LOCKED;
+       }
+
+       /* write the promotable-lock file */
+       lock->file1 = lock_name (lock->repository, promotablelock);
+       if ((fp = CVS_FOPEN (lock->file1, "w+")) == NULL || fclose (fp) == EOF)
+       {
+           int xerrno = errno;
+
+           if (CVS_UNLINK (lock->file1) < 0 && ! existence_error (errno))
+               error (0, errno, "failed to remove lock %s", lock->file1);
+
+           /* free the lock dir */
+           clear_lock (lock);
+
+           /* return the error */
+           error (0, xerrno,
+                  "cannot create promotable lock in repository `%s'",
+                  lock->repository);
+           return L_ERROR;
+       }
+
+#ifdef LOCK_COMPATIBILITY
+       /* write the read-lock file.  We only do this so that older versions of
+        * CVS will not think it is okay to create a write lock.  When it is
+        * decided that versions of CVS earlier than 1.12.4 are not likely to
+        * be used, this code can be removed.
+        */
+       set_readlock_name ();
+       lock->file2 = lock_name (lock->repository, readlock);
+       if ((fp = CVS_FOPEN (lock->file2, "w+")) == NULL || fclose (fp) == EOF)
+       {
+           int xerrno = errno;
+
+           if ( CVS_UNLINK (lock->file2) < 0 && ! existence_error (errno))
+               error (0, errno, "failed to remove lock %s", lock->file2);
+
+           /* free the lock dir */
+           clear_lock (lock);
+
+           /* Remove the promotable lock.  */
+           lock->file2 = NULL;
+           remove_lock_files (lock, false);
+
+           /* return the error */
+           error (0, xerrno,
+                  "cannot create read lock in repository `%s'",
+                  lock->repository);
+           return L_ERROR;
+       }
+#endif /* LOCK_COMPATIBILITY */
+
+       clear_lock (lock);
+
+       return L_OK;
+    }
+    else
+       return status;
+}
+
+
+
+/*
+ * walklist proc for setting write locks.  Mostly just a wrapper for the
+ * set_promotable_lock function, which has a prettier API, but no other good
+ * reason for existing separately.
+ *
+ * INPUTS
+ *   p         The current node, as determined by walklist().
+ *   closure   Not used.
+ *
+ * GLOBAL INPUTS
+ *   lock_error                Any previous error encountered while attempting 
to get
+ *                      a lock.
+ *
+ * GLOBAL OUTPUTS
+ *   lock_error                Set if we encounter an error attempting to get 
axi
+ *                     promotable lock.
+ *   lock_error_repos  Set so that if we set lock_error later functions will
+ *                     be able to report where the other process's lock was
+ *                     encountered.
+ *
+ * RETURNS
+ *   0 for no error.
+ */
+static int
+set_promotablelock_proc (Node *p, void *closure)
+{
+    /* if some lock was not OK, just skip this one */
+    if (lock_error != L_OK)
+       return 0;
+
+    /* apply the write lock */
+    lock_error_repos = p->key;
+    lock_error = set_promotable_lock ((struct lock *)p->data);
+    return 0;
+}
+
+
+
+/*
+ * Print out a message that the lock is still held, then sleep a while.
+ */
+static void
+lock_wait (const char *repos)
+{
+    time_t now;
+    char *msg;
+    struct tm *tm_p;
+
+    (void) time (&now);
+    tm_p = gmtime (&now);
+    msg = Xasprintf ("[%8.8s] waiting for %s's lock in %s",
+                    (tm_p ? asctime (tm_p) : ctime (&now)) + 11,
+                    lockers_name, repos);
+    error (0, 0, "%s", msg);
+    /* Call cvs_flusherr to ensure that the user sees this message as
+       soon as possible.  */
+    cvs_flusherr ();
+    free (msg);
+    (void)sleep (CVSLCKSLEEP);
+}
+
+
+
+/*
+ * Print out a message when we obtain a lock.
+ */
+static void
+lock_obtained (const char *repos)
+{
+    time_t now;
+    char *msg;
+    struct tm *tm_p;
+
+    (void) time (&now);
+    tm_p = gmtime (&now);
+    msg = Xasprintf ("[%8.8s] obtained lock in %s",
+                    (tm_p ? asctime (tm_p) : ctime (&now)) + 11, repos);
+    error (0, 0, "%s", msg);
+    /* Call cvs_flusherr to ensure that the user sees this message as
+       soon as possible.  */
+    cvs_flusherr ();
+    free (msg);
+}
+
+
+
+static int
+lock_list_promotably (List *list)
+{
+    char *wait_repos;
+
+    TRACE (TRACE_FLOW, "lock_list_promotably ()");
+
+    if (noexec)
+       return 0;
+
+    if (readonlyfs) {
+       error (0, 0,
+              "promotable lock failed.\n\
+WARNING: Read-only repository access mode selected via `cvs -R'.\n\
+Attempting to write to a read-only filesystem is not allowed.");
+       return 1;
+    }
+
+    /* We only know how to do one list at a time */
+    if (locklist != NULL)
+    {
+       error (0, 0,
+              "lock_list_promotably called while promotable locks set - 
Help!");
+       return 1;
+    }
+
+    wait_repos = NULL;
+    for (;;)
+    {
+       /* try to lock everything on the list */
+       lock_error = L_OK;              /* init for set_promotablelock_proc */
+       lock_error_repos = NULL;        /* init for set_promotablelock_proc */
+       locklist = list;                /* init for Lock_Cleanup */
+       if (lockers_name != NULL)
+           free (lockers_name);
+       lockers_name = xstrdup ("unknown");
+
+       (void) walklist (list, set_promotablelock_proc, NULL);
+
+       switch (lock_error)
+       {
+           case L_ERROR:               /* Real Error */
+               if (wait_repos != NULL)
+                   free (wait_repos);
+               Lock_Cleanup ();        /* clean up any locks we set */
+               error (0, 0, "lock failed - giving up");
+               return 1;
+
+           case L_LOCKED:              /* Someone already had a lock */
+               remove_locks ();        /* clean up any locks we set */
+               lock_wait (lock_error_repos); /* sleep a while and try again */
+               wait_repos = xstrdup (lock_error_repos);
+               continue;
+
+           case L_OK:                  /* we got the locks set */
+               if (wait_repos != NULL)
+               {
+                   lock_obtained (wait_repos);
+                   free (wait_repos);
+               }
+               return 0;
+
+           default:
+               if (wait_repos != NULL)
+                   free (wait_repos);
+               error (0, 0, "unknown lock status %d in lock_list_promotably",
+                      lock_error);
+               return 1;
+       }
+    }
+}
+
+
+
+/*
+ * Set the static variable lockers_name appropriately, based on the stat
+ * structure passed in.
+ */
+static void
+set_lockers_name (struct stat *statp)
+{
+    struct passwd *pw;
+
+    if (lockers_name != NULL)
+       free (lockers_name);
+    pw = (struct passwd *) getpwuid (statp->st_uid);
+    if (pw != NULL)
+       lockers_name = xstrdup (pw->pw_name);
+    else
+       lockers_name = Xasprintf ("uid%lu", (unsigned long) statp->st_uid);
+}
+
+
+
+/*
+ * Persistently tries to make the directory "lckdir", which serves as a
+ * lock.
+ *
+ * #ifdef CVS_FUDGELOCKS
+ * If the create time on the directory is greater than CVSLCKAGE
+ * seconds old, just try to remove the directory.
+ * #endif
+ *
+ */
+static int
+set_lock (struct lock *lock, int will_wait)
+{
+    int waited;
+    long us;
+    struct stat sb;
+    mode_t omask;
+    char *masterlock;
+    int status;
+#ifdef CVS_FUDGELOCKS
+    time_t now;
+#endif
+
+    TRACE (TRACE_FLOW, "set_lock (%s, %d)",
+          lock->repository ? lock->repository : "(null)", will_wait);
+
+    masterlock = lock_name (lock->repository, lock->lockdirname);
+
+    /*
+     * Note that it is up to the callers of set_lock() to arrange for signal
+     * handlers that do the appropriate things, like remove the lock
+     * directory before they exit.
+     */
+    waited = 0;
+    us = 1;
+    for (;;)
+    {
+       status = -1;
+       omask = umask (cvsumask);
+       SIG_beginCrSect ();
+       if (CVS_MKDIR (masterlock, 0777) == 0)
+       {
+           lock->lockdir = masterlock;
+           SIG_endCrSect ();
+           status = L_OK;
+           if (waited)
+               lock_obtained (lock->repository);
+           goto after_sig_unblock;
+       }
+       SIG_endCrSect ();
+    after_sig_unblock:
+       (void) umask (omask);
+       if (status != -1)
+           goto done;
+
+       if (errno != EEXIST)
+       {
+           error (0, errno,
+                  "failed to create lock directory for `%s' (%s)",
+                  lock->repository, masterlock);
+           status = L_ERROR;
+           goto done;
+       }
+
+       /* Find out who owns the lock.  If the lock directory is
+          non-existent, re-try the loop since someone probably just
+          removed it (thus releasing the lock).  */
+       if (stat (masterlock, &sb) < 0)
+       {
+           if (existence_error (errno))
+               continue;
+
+           error (0, errno, "couldn't stat lock directory `%s'", masterlock);
+           status = L_ERROR;
+           goto done;
+       }
+
+#ifdef CVS_FUDGELOCKS
+       /*
+        * If the create time of the directory is more than CVSLCKAGE seconds
+        * ago, try to clean-up the lock directory, and if successful, just
+        * quietly retry to make it.
+        */
+       (void) time (&now);
+       if (now >= (sb.st_ctime + CVSLCKAGE))
+       {
+           if (CVS_RMDIR (masterlock) >= 0)
+               continue;
+       }
+#endif
+
+       /* set the lockers name */
+       set_lockers_name (&sb);
+
+       /* if he wasn't willing to wait, return an error */
+       if (!will_wait)
+       {
+           status = L_LOCKED;
+           goto done;
+       }
+
+       /* if possible, try a very short sleep without a message */
+       if (!waited && us < 1000)
+       {
+           us += us;
+           {
+               struct timespec ts;
+               ts.tv_sec = 0;
+               ts.tv_nsec = us * 1000;
+               (void)nanosleep (&ts, NULL);
+               continue;
+           }
+       }
+
+       lock_wait (lock->repository);
+       waited = 1;
+    }
+
+done:
+    if (!lock->lockdir)
+       free (masterlock);
+    return status;
+}
+
+
+
+/*
+ * Clear master lock.
+ *
+ * INPUTS
+ *   lock      The lock information.
+ *
+ * OUTPUTS
+ *   Sets LOCK->lockdir to NULL after removing the directory it names and
+ *   freeing the storage.
+ *
+ * ASSUMPTIONS
+ *   If we own the master lock directory, its name is stored in LOCK->lockdir.
+ *   We may free LOCK->lockdir.
+ */
+static void
+clear_lock (struct lock *lock)
+{
+    SIG_beginCrSect ();
+    if (lock->lockdir)
+    {
+       if (CVS_RMDIR (lock->lockdir) < 0)
+           error (0, errno, "failed to remove lock dir `%s'", lock->lockdir);
+       free (lock->lockdir);
+       lock->lockdir = NULL;
+    }
+    SIG_endCrSect ();
+}
+
+
+
+/*
+ * Create a list of repositories to lock
+ */
+/* ARGSUSED */
+static int
+lock_filesdoneproc (void *callerdat, int err, const char *repository,
+                    const char *update_dir, List *entries)
+{
+    Node *p;
+
+    p = getnode ();
+    p->type = LOCK;
+    p->key = xstrdup (repository);
+    p->data = xmalloc (sizeof (struct lock));
+    ((struct lock *)p->data)->repository = p->key;
+    ((struct lock *)p->data)->file1 = NULL;
+#ifdef LOCK_COMPATIBILITY
+    ((struct lock *)p->data)->file2 = NULL;
+#endif /* LOCK_COMPATIBILITY */
+    ((struct lock *)p->data)->lockdirname = CVSLCK;
+    ((struct lock *)p->data)->lockdir = NULL;
+    ((struct lock *)p->data)->free_repository = false;
+
+    /* FIXME-KRP: this error condition should not simply be passed by. */
+    if (p->key == NULL || addnode (lock_tree_list, p) != 0)
+       freenode (p);
+    return err;
+}
+
+
+
+void
+lock_tree_promotably (int argc, char **argv, int local, int which, int aflag)
+{
+    TRACE (TRACE_FUNCTION, "lock_tree_promotably (%d, argv, %d, %d, %d)",
+          argc, local, which, aflag);
+
+    /*
+     * Run the recursion processor to find all the dirs to lock and lock all
+     * the dirs
+     */
+    lock_tree_list = getlist ();
+    start_recursion
+       (NULL, lock_filesdoneproc,
+        NULL, NULL, NULL, argc,
+        argv, local, which, aflag, CVS_LOCK_NONE,
+        NULL, 0, NULL );
+    sortlist (lock_tree_list, fsortcmp);
+    if (lock_list_promotably (lock_tree_list) != 0)
+       error (1, 0, "lock failed - giving up");
+}
+
+
+
+/* Lock a single directory in REPOSITORY.  It is OK to call this if
+ * a lock has been set with lock_dir_for_write; the new lock will replace
+ * the old one.  If REPOSITORY is NULL, don't do anything.
+ *
+ * We do not clear the dir lock after writing the lock file name since write
+ * locks are exclusive to all other locks.
+ */
+void
+lock_dir_for_write (const char *repository)
+{
+    int waiting = 0;
+
+    TRACE (TRACE_FLOW, "lock_dir_for_write (%s)", repository);
+
+    if (repository != NULL
+       && (global_writelock.repository == NULL
+           || !strcmp (global_writelock.repository, repository)))
+    {
+       if (writelock == NULL)
+       {
+           writelock = Xasprintf (
+#ifdef HAVE_LONG_FILE_NAMES
+                                  "%s.%s.%ld", CVSWFL, hostname,
+#else
+                                  "%s.%ld", CVSWFL,
+#endif
+                                  (long) getpid());
+       }
+
+       if (global_writelock.repository != NULL)
+           remove_lock_files (&global_writelock, true);
+
+       global_writelock.repository = xstrdup (repository);
+       global_writelock.free_repository = true;
+
+       for (;;)
+       {
+           FILE *fp;
+
+           if (set_lock (&global_writelock, 1) != L_OK)
+               error (1, 0, "failed to obtain write lock in repository `%s'",
+                      repository);
+
+           /* check if readers exist */
+           if (readers_exist (repository)
+               || promotable_exists (repository))
+           {
+               clear_lock (&global_writelock);
+               lock_wait (repository); /* sleep a while and try again */
+               waiting = 1;
+               continue;
+           }
+
+           if (waiting)
+               lock_obtained (repository);
+
+           /* write the write-lock file */
+           global_writelock.file1 = lock_name (global_writelock.repository,
+                                               writelock);
+           if ((fp = CVS_FOPEN (global_writelock.file1, "w+")) == NULL
+               || fclose (fp) == EOF)
+           {
+               int xerrno = errno;
+
+               if (CVS_UNLINK (global_writelock.file1) < 0
+                   && !existence_error (errno))
+               {
+                   error (0, errno, "failed to remove write lock %s",
+                          global_writelock.file1);
+               }
+
+               /* free the lock dir */
+               clear_lock (&global_writelock);
+
+               /* return the error */
+               error (1, xerrno,
+                      "cannot create write lock in repository `%s'",
+                      global_writelock.repository);
+           }
+
+           /* If we upgraded from a promotable lock, remove it. */
+           if (locklist)
+           {
+               Node *p = findnode (locklist, repository);
+               if (p)
+               {
+                   remove_lock_files (p->data, true);
+                   delnode (p);
+               }
+           }
+
+           break;
+       }
+    }
+}
+
+
+
+/* This is the internal implementation behind history_lock & val_tags_lock.  It
+ * gets a write lock for the history or val-tags file.
+ *
+ * RETURNS
+ *   true, on success
+ *   false, on error
+ */
+static inline int
+internal_lock (struct lock *lock, const char *xrepository)
+{
+    /* remember what we're locking (for Lock_Cleanup) */
+    assert (!lock->repository);
+    lock->repository = Xasprintf ("%s/%s", xrepository, CVSROOTADM);
+    lock->free_repository = true;
+
+    /* get the lock dir for our own */
+    if (set_lock (lock, 1) != L_OK)
+    {
+       if (!really_quiet)
+           error (0, 0, "failed to obtain history lock in repository `%s'",
+                  xrepository);
+
+       return 0;
+    }
+
+    return 1;
+}
+
+
+
+/* Lock the CVSROOT/history file for write.
+ */
+int
+history_lock (const char *xrepository)
+{
+    return internal_lock (&global_history_lock, xrepository);
+}
+
+
+
+/* Remove the CVSROOT/history lock, if it exists.
+ */
+void
+clear_history_lock ()
+{
+    remove_lock_files (&global_history_lock, true);
+}
+
+
+
+/* Lock the CVSROOT/val-tags file for write.
+ */
+int
+val_tags_lock (const char *xrepository)
+{
+    return internal_lock (&global_val_tags_lock, xrepository);
+}
+
+
+
+/* Remove the CVSROOT/val-tags lock, if it exists.
+ */
+void
+clear_val_tags_lock ()
+{
+    remove_lock_files (&global_val_tags_lock, true);
+}
Index: ccvs/src/logmsg.c
diff -u /dev/null ccvs/src/logmsg.c:1.99.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/logmsg.c   Fri Jan  6 19:34:15 2006
@@ -0,0 +1,986 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Verify interface.  */
+#include "logmsg.h"
+
+/* GNULIB Headers.  */
+#include "getline.h"
+
+/* CVS Headers.  */
+#include "cvs.h"
+
+static int find_type (Node * p, void *closure);
+static int fmt_proc (Node * p, void *closure);
+static int logfile_write (const char *repository, const char *filter,
+                         const char *message, FILE * logfp, List * changes);
+static int logmsg_list_to_args_proc (Node *p, void *closure);
+static int rcsinfo_proc (const char *repository, const char *template,
+                         void *closure );
+static int update_logfile_proc (const char *repository, const char *filter,
+                                void *closure);
+static void setup_tmpfile (FILE * xfp, char *xprefix, List * changes);
+static int verifymsg_proc (const char *repository, const char *script,
+                           void *closure );
+
+static FILE *fp;
+static Ctype type;
+
+struct verifymsg_proc_data
+{
+    /* The name of the temp file storing the log message to be verified.  This
+     * is initially NULL and verifymsg_proc() writes message into it so that it
+     * can be shared when multiple verifymsg scripts exist.  do_verify() is
+     * responsible for rereading the message from the file when
+     * RereadLogAfterVerify is in effect and the file has changed.
+     */
+    char *fname;
+    /* The initial message text to be verified.
+     */
+    char *message;
+    /* The initial stats of the temp file so we can tell that the temp file has
+     * been changed when RereadLogAfterVerify is STAT.
+     */
+    struct stat pre_stbuf;
+   /* The list of files being changed, with new and old version numbers.
+    */
+   List *changes;
+};
+
+/*
+ * Puts a standard header on the output which is either being prepared for an
+ * editor session, or being sent to a logfile program.  The modified, added,
+ * and removed files are included (if any) and formatted to look pretty. */
+static char *prefix;
+static int col;
+static char *tag;
+static void
+setup_tmpfile (FILE *xfp, char *xprefix, List *changes)
+{
+    /* set up statics */
+    fp = xfp;
+    prefix = xprefix;
+
+    type = T_MODIFIED;
+    if (walklist (changes, find_type, NULL) != 0)
+    {
+       (void) fprintf (fp, "%sModified Files:\n", prefix);
+       col = 0;
+       (void) walklist (changes, fmt_proc, NULL);
+       (void) fprintf (fp, "\n");
+       if (tag != NULL)
+       {
+           free (tag);
+           tag = NULL;
+       }
+    }
+    type = T_ADDED;
+    if (walklist (changes, find_type, NULL) != 0)
+    {
+       (void) fprintf (fp, "%sAdded Files:\n", prefix);
+       col = 0;
+       (void) walklist (changes, fmt_proc, NULL);
+       (void) fprintf (fp, "\n");
+       if (tag != NULL)
+       {
+           free (tag);
+           tag = NULL;
+       }
+    }
+    type = T_REMOVED;
+    if (walklist (changes, find_type, NULL) != 0)
+    {
+       (void) fprintf (fp, "%sRemoved Files:\n", prefix);
+       col = 0;
+       (void) walklist (changes, fmt_proc, NULL);
+       (void) fprintf (fp, "\n");
+       if (tag != NULL)
+       {
+           free (tag);
+           tag = NULL;
+       }
+    }
+}
+
+/*
+ * Looks for nodes of a specified type and returns 1 if found
+ */
+static int
+find_type (Node *p, void *closure)
+{
+    struct logfile_info *li = p->data;
+
+    if (li->type == type)
+       return (1);
+    else
+       return (0);
+}
+
+/*
+ * Breaks the files list into reasonable sized lines to avoid line wrap...
+ * all in the name of pretty output.  It only works on nodes whose types
+ * match the one we're looking for
+ */
+static int
+fmt_proc (Node *p, void *closure)
+{
+    struct logfile_info *li;
+
+    li = p->data;
+    if (li->type == type)
+    {
+        if (li->tag == NULL
+           ? tag != NULL
+           : tag == NULL || strcmp (tag, li->tag) != 0)
+       {
+           if (col > 0)
+               (void) fprintf (fp, "\n");
+           (void) fputs (prefix, fp);
+           col = strlen (prefix);
+           while (col < 6)
+           {
+               (void) fprintf (fp, " ");
+               ++col;
+           }
+
+           if (li->tag == NULL)
+               (void) fprintf (fp, "No tag");
+           else
+               (void) fprintf (fp, "Tag: %s", li->tag);
+
+           if (tag != NULL)
+               free (tag);
+           tag = xstrdup (li->tag);
+
+           /* Force a new line.  */
+           col = 70;
+       }
+
+       if (col == 0)
+       {
+           (void) fprintf (fp, "%s\t", prefix);
+           col = 8;
+       }
+       else if (col > 8 && (col + (int) strlen (p->key)) > 70)
+       {
+           (void) fprintf (fp, "\n%s\t", prefix);
+           col = 8;
+       }
+       (void) fprintf (fp, "%s ", p->key);
+       col += strlen (p->key) + 1;
+    }
+    return (0);
+}
+
+/*
+ * Builds a temporary file using setup_tmpfile() and invokes the user's
+ * editor on the file.  The header garbage in the resultant file is then
+ * stripped and the log message is stored in the "message" argument.
+ * 
+ * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
+ * is NULL, use the CVSADM_TEMPLATE file instead.  REPOSITORY should be
+ * NULL when running in client mode.
+ *
+ * GLOBALS
+ *   Editor     Set to a default value by configure and overridable using the
+ *              -e option to the CVS executable.
+ */
+void
+do_editor (const char *dir, char **messagep, const char *repository,
+           List *changes)
+{
+    static int reuse_log_message = 0;
+    char *line;
+    int line_length;
+    size_t line_chars_allocated;
+    char *fname;
+    struct stat pre_stbuf, post_stbuf;
+    int retcode = 0;
+
+    assert (!current_parsed_root->isremote != !repository);
+
+    if (noexec || reuse_log_message)
+       return;
+
+    /* Abort before creation of the temp file if no editor is defined. */
+    if (strcmp (Editor, "") == 0)
+        error(1, 0, "no editor defined, must use -e or -m");
+
+  again:
+    /* Create a temporary file.  */
+    if( ( fp = cvs_temp_file( &fname ) ) == NULL )
+       error( 1, errno, "cannot create temporary file" );
+
+    if (*messagep)
+    {
+       (void) fputs (*messagep, fp);
+
+       if ((*messagep)[0] == '\0' ||
+           (*messagep)[strlen (*messagep) - 1] != '\n')
+           (void) fprintf (fp, "\n");
+    }
+
+    if (repository != NULL)
+       /* tack templates on if necessary */
+       (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc,
+               PIOPT_ALL, NULL);
+    else
+    {
+       FILE *tfp;
+       char buf[1024];
+       size_t n;
+       size_t nwrite;
+
+       /* Why "b"?  */
+       tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
+       if (tfp == NULL)
+       {
+           if (!existence_error (errno))
+               error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
+       }
+       else
+       {
+           while (!feof (tfp))
+           {
+               char *p = buf;
+               n = fread (buf, 1, sizeof buf, tfp);
+               nwrite = n;
+               while (nwrite > 0)
+               {
+                   n = fwrite (p, 1, nwrite, fp);
+                   nwrite -= n;
+                   p += n;
+               }
+               if (ferror (tfp))
+                   error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
+           }
+           if (fclose (tfp) < 0)
+               error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
+       }
+    }
+
+    (void) fprintf (fp,
+  "%s----------------------------------------------------------------------\n",
+                   CVSEDITPREFIX);
+    (void) fprintf (fp,
+  "%sEnter Log.  Lines beginning with `%.*s' are removed automatically\n%s\n",
+                   CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
+                   CVSEDITPREFIX);
+    if (dir != NULL && *dir)
+       (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
+                       dir, CVSEDITPREFIX);
+    if (changes != NULL)
+       setup_tmpfile (fp, CVSEDITPREFIX, changes);
+    (void) fprintf (fp,
+  "%s----------------------------------------------------------------------\n",
+                   CVSEDITPREFIX);
+
+    /* finish off the temp file */
+    if (fclose (fp) == EOF)
+        error (1, errno, "%s", fname);
+    if (stat (fname, &pre_stbuf) == -1)
+       pre_stbuf.st_mtime = 0;
+
+    /* run the editor */
+    run_setup (Editor);
+    run_add_arg (fname);
+    if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+                            RUN_NORMAL | RUN_SIGIGNORE)) != 0)
+       error (0, retcode == -1 ? errno : 0, "warning: editor session failed");
+
+    /* put the entire message back into the *messagep variable */
+
+    fp = xfopen (fname, "r");
+
+    if (*messagep)
+       free (*messagep);
+
+    if (stat (fname, &post_stbuf) != 0)
+           error (1, errno, "cannot find size of temp file %s", fname);
+
+    if (post_stbuf.st_size == 0)
+       *messagep = NULL;
+    else
+    {
+       /* On NT, we might read less than st_size bytes, but we won't
+          read more.  So this works.  */
+       *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
+       (*messagep)[0] = '\0';
+    }
+
+    line = NULL;
+    line_chars_allocated = 0;
+
+    if (*messagep)
+    {
+       size_t message_len = post_stbuf.st_size + 1;
+       size_t offset = 0;
+       while (1)
+       {
+           line_length = getline (&line, &line_chars_allocated, fp);
+           if (line_length == -1)
+           {
+               if (ferror (fp))
+                   error (0, errno, "warning: cannot read %s", fname);
+               break;
+           }
+           if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
+               continue;
+           if (offset + line_length >= message_len)
+               expand_string (messagep, &message_len,
+                               offset + line_length + 1);
+           (void) strcpy (*messagep + offset, line);
+           offset += line_length;
+       }
+    }
+    if (fclose (fp) < 0)
+       error (0, errno, "warning: cannot close %s", fname);
+
+    /* canonicalize emply messages */
+    if (*messagep != NULL &&
+        (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
+    {
+       free (*messagep);
+       *messagep = NULL;
+    }
+
+    if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
+    {
+       for (;;)
+       {
+           (void) printf ("\nLog message unchanged or not specified\n");
+           (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message 
unchanged for remaining dirs\n");
+           (void) printf ("Action: (continue) ");
+           (void) fflush (stdout);
+           line_length = getline (&line, &line_chars_allocated, stdin);
+           if (line_length < 0)
+           {
+               error (0, errno, "cannot read from stdin");
+               if (unlink_file (fname) < 0)
+                   error (0, errno,
+                          "warning: cannot remove temp file %s", fname);
+               error (1, 0, "aborting");
+           }
+           else if (line_length == 0
+                    || *line == '\n' || *line == 'c' || *line == 'C')
+               break;
+           if (*line == 'a' || *line == 'A')
+               {
+                   if (unlink_file (fname) < 0)
+                       error (0, errno, "warning: cannot remove temp file %s", 
fname);
+                   error (1, 0, "aborted by user");
+               }
+           if (*line == 'e' || *line == 'E')
+               goto again;
+           if (*line == '!')
+           {
+               reuse_log_message = 1;
+               break;
+           }
+           (void) printf ("Unknown input\n");
+       }
+    }
+    if (line)
+       free (line);
+    if (unlink_file (fname) < 0)
+       error (0, errno, "warning: cannot remove temp file %s", fname);
+    free (fname);
+}
+
+/* Runs the user-defined verification script as part of the commit or import 
+   process.  This verification is meant to be run whether or not the user 
+   included the -m attribute.  unlike the do_editor function, this is 
+   independant of the running of an editor for getting a message.
+ */
+void
+do_verify (char **messagep, const char *repository, List *changes)
+{
+    int err;
+    struct verifymsg_proc_data data;
+    struct stat post_stbuf;
+
+    if (current_parsed_root->isremote)
+       /* The verification will happen on the server.  */
+       return;
+
+    /* FIXME? Do we really want to skip this on noexec?  What do we do
+       for the other administrative files?  */
+    /* EXPLAIN: Why do we check for repository == NULL here? */
+    if (noexec || repository == NULL)
+       return;
+
+    /* Get the name of the verification script to run  */
+
+    data.message = *messagep;
+    data.fname = NULL;
+    data.changes = changes;
+    if ((err = Parse_Info (CVSROOTADM_VERIFYMSG, repository,
+                         verifymsg_proc, 0, &data)) != 0)
+    {
+       int saved_errno = errno;
+       /* Since following error() exits, delete the temp file now.  */
+       if (data.fname != NULL && unlink_file( data.fname ) < 0)
+           error (0, errno, "cannot remove %s", data.fname);
+       free (data.fname);
+
+       errno = saved_errno;
+       error (1, err == -1 ? errno : 0, "Message verification failed");
+    }
+
+    /* Return if no temp file was created.  That means that we didn't call any
+     * verifymsg scripts.
+     */
+    if (data.fname == NULL)
+       return;
+
+    /* Get the mod time and size of the possibly new log message
+     * in always and stat modes.
+     */
+    if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
+       config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
+    {
+       if(stat (data.fname, &post_stbuf) != 0)
+           error (1, errno, "cannot find size of temp file %s", data.fname);
+    }
+
+    /* And reread the log message in `always' mode or in `stat' mode when it's
+     * changed.
+     */
+    if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
+       (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
+         (data.pre_stbuf.st_mtime != post_stbuf.st_mtime ||
+           data.pre_stbuf.st_size != post_stbuf.st_size)))
+    {
+       /* put the entire message back into the *messagep variable */
+
+       if (*messagep) free (*messagep);
+
+       if (post_stbuf.st_size == 0)
+           *messagep = NULL;
+       else
+       {
+           char *line = NULL;
+           int line_length;
+           size_t line_chars_allocated = 0;
+           char *p;
+           FILE *fp;
+
+           fp = xfopen (data.fname, "r");
+
+           /* On NT, we might read less than st_size bytes,
+              but we won't read more.  So this works.  */
+           p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
+           *messagep[0] = '\0';
+
+           for (;;)
+           {
+               line_length = getline( &line,
+                                      &line_chars_allocated,
+                                      fp);
+               if (line_length == -1)
+               {
+                   if (ferror (fp))
+                       /* Fail in this case because otherwise we will have no
+                        * log message
+                        */
+                       error (1, errno, "cannot read %s", data.fname);
+                   break;
+               }
+               if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
+                   continue;
+               (void) strcpy (p, line);
+               p += line_length;
+           }
+           if (line) free (line);
+           if (fclose (fp) < 0)
+               error (0, errno, "warning: cannot close %s", data.fname);
+       }
+    }
+    /* Delete the temp file  */
+    if (unlink_file (data.fname) < 0)
+       error (0, errno, "cannot remove `%s'", data.fname);
+    free (data.fname);
+}
+
+
+
+/*
+ * callback proc for Parse_Info for rcsinfo templates this routine basically
+ * copies the matching template onto the end of the tempfile we are setting
+ * up
+ */
+/* ARGSUSED */
+static int
+rcsinfo_proc (const char *repository, const char *template, void *closure)
+{
+    static char *last_template;
+    FILE *tfp;
+
+    /* nothing to do if the last one included is the same as this one */
+    if (last_template && strcmp (last_template, template) == 0)
+       return (0);
+    if (last_template)
+       free (last_template);
+    last_template = xstrdup (template);
+
+    if ((tfp = CVS_FOPEN (template, "r")) != NULL)
+    {
+       char *line = NULL;
+       size_t line_chars_allocated = 0;
+
+       while (getline (&line, &line_chars_allocated, tfp) >= 0)
+           (void) fputs (line, fp);
+       if (ferror (tfp))
+           error (0, errno, "warning: cannot read %s", template);
+       if (fclose (tfp) < 0)
+           error (0, errno, "warning: cannot close %s", template);
+       if (line)
+           free (line);
+       return (0);
+    }
+    else
+    {
+       error (0, errno, "Couldn't open rcsinfo template file %s", template);
+       return (1);
+    }
+}
+
+/*
+ * Uses setup_tmpfile() to pass the updated message on directly to any
+ * logfile programs that have a regular expression match for the checked in
+ * directory in the source repository.  The log information is fed into the
+ * specified program as standard input.
+ */
+struct ulp_data {
+    FILE *logfp;
+    const char *message;
+    List *changes;
+};
+
+
+
+void
+Update_Logfile (const char *repository, const char *xmessage, FILE *xlogfp,
+                List *xchanges)
+{
+    struct ulp_data ud;
+
+    /* nothing to do if the list is empty */
+    if (xchanges == NULL || xchanges->list->next == xchanges->list)
+       return;
+
+    /* set up vars for update_logfile_proc */
+    ud.message = xmessage;
+    ud.logfp = xlogfp;
+    ud.changes = xchanges;
+
+    /* call Parse_Info to do the actual logfile updates */
+    (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc,
+                      PIOPT_ALL, &ud);
+}
+
+
+
+/*
+ * callback proc to actually do the logfile write from Update_Logfile
+ */
+static int
+update_logfile_proc (const char *repository, const char *filter, void *closure)
+{
+    struct ulp_data *udp = closure;
+    TRACE (TRACE_FUNCTION, "update_logfile_proc(%s,%s)", repository, filter);
+    return logfile_write (repository, filter, udp->message, udp->logfp,
+                          udp->changes);
+}
+
+
+
+/* static int
+ * logmsg_list_to_args_proc( Node *p, void *closure )
+ * This function is intended to be passed into walklist() with a list of tags
+ * (nodes in the same format as pretag_list_proc() accepts - p->key = tagname
+ * and p->data = a revision.
+ *
+ * closure will be a struct format_cmdline_walklist_closure
+ * where closure is undefined.
+ */
+static int
+logmsg_list_to_args_proc (Node *p, void *closure)
+{
+    struct format_cmdline_walklist_closure *c = closure;
+    struct logfile_info *li;
+    char *arg = NULL;
+    const char *f;
+    char *d;
+    size_t doff;
+
+    if (p->data == NULL) return 1;
+
+    f = c->format;
+    d = *c->d;
+    /* foreach requested attribute */
+    while (*f)
+    {
+       switch (*f++)
+       {
+           case 's':
+               arg = p->key;
+               break;
+           case 'T':
+               li = p->data;
+               arg = li->tag ? li->tag : "";
+               break;
+           case 'V':
+               li = p->data;
+               arg = li->rev_old ? li->rev_old : "NONE";
+               break;
+           case 'v':
+               li = p->data;
+               arg = li->rev_new ? li->rev_new : "NONE";
+               break;
+           default:
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+               if (c->onearg)
+               {
+                   /* The old deafult was to print the empty string for
+                    * unknown args.
+                    */
+                   arg = "\0";
+               }
+               else
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                   error (1, 0,
+                          "Unknown format character or not a list attribute: 
%c", f[-1]);
+               /* NOTREACHED */
+               break;
+       }
+       /* copy the attribute into an argument */
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+       if (c->onearg)
+       {
+           if (c->firstpass)
+           {
+               c->firstpass = 0;
+               doff = d - *c->buf;
+               expand_string (c->buf, c->length,
+                              doff + strlen (c->srepos) + 1);
+               d = *c->buf + doff;
+               strncpy (d, c->srepos, strlen (c->srepos));
+               d += strlen (c->srepos);
+               *d++ = ' ';
+           }
+       }
+       else /* c->onearg */
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+       {
+           if (c->quotes)
+           {
+               arg = cmdlineescape (c->quotes, arg);
+           }
+           else
+           {
+               arg = cmdlinequote ('"', arg);
+           }
+       } /* !c->onearg */
+       doff = d - *c->buf;
+       expand_string (c->buf, c->length, doff + strlen (arg));
+       d = *c->buf + doff;
+       strncpy (d, arg, strlen (arg));
+       d += strlen (arg);
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+       if (!c->onearg)
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+           free (arg);
+
+       /* Always put the extra space on.  we'll have to back up a char
+        * when we're done, but that seems most efficient.
+        */
+       doff = d - *c->buf;
+       expand_string (c->buf, c->length, doff + 1);
+       d = *c->buf + doff;
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+       if (c->onearg && *f) *d++ = ',';
+       else
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+           *d++ = ' ';
+    }
+    /* correct our original pointer into the buff */
+    *c->d = d;
+    return 0;
+}
+
+
+
+/*
+ * Writes some stuff to the logfile "filter" and returns the status of the
+ * filter program.
+ */
+static int
+logfile_write (const char *repository, const char *filter, const char *message,
+               FILE *logfp, List *changes)
+{
+    char *cmdline;
+    FILE *pipefp;
+    char *cp;
+    int c;
+    int pipestatus;
+    const char *srepos = Short_Repository (repository);
+
+    assert (repository);
+
+    /* The user may specify a format string as part of the filter.
+       Originally, `%s' was the only valid string.  The string that
+       was substituted for it was:
+
+         <repository-name> <file1> <file2> <file3> ...
+
+       Each file was either a new directory/import (T_TITLE), or a
+       added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
+       file.
+
+       It is desirable to preserve that behavior so lots of commitlog
+       scripts won't die when they get this new code.  At the same
+       time, we'd like to pass other information about the files (like
+       version numbers, statuses, or checkin times).
+
+       The solution is to allow a format string that allows us to
+       specify those other pieces of information.  The format string
+       will be composed of `%' followed by a single format character,
+       or followed by a set of format characters surrounded by `{' and
+       `}' as separators.  The format characters are:
+
+         s = file name
+        V = old version number (pre-checkin)
+        v = new version number (post-checkin)
+
+       For example, valid format strings are:
+
+         %{}
+        %s
+        %{s}
+        %{sVv}
+
+       There's no reason that more items couldn't be added (like
+       modification date or file status [added, modified, updated,
+       etc.]) -- the code modifications would be minimal (logmsg.c
+       (title_proc) and commit.c (check_fileproc)).
+
+       The output will be a string of tokens separated by spaces.  For
+       backwards compatibility, the the first token will be the
+       repository name.  The rest of the tokens will be
+       comma-delimited lists of the information requested in the
+       format string.  For example, if `/u/src/master' is the
+       repository, `%{sVv}' is the format string, and three files
+       (ChangeLog, Makefile, foo.c) were modified, the output might
+       be:
+
+         /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
+
+       Why this duplicates the old behavior when the format string is
+       `%s' is left as an exercise for the reader. */
+
+    /* %c = cvs_cmd_name
+     * %p = shortrepos
+     * %r = repository
+     * %{sVv} = file name, old revision (precommit), new revision (postcommit)
+     */
+    /*
+     * Cast any NULL arguments as appropriate pointers as this is an
+     * stdarg function and we need to be certain the caller gets what
+     * is expected.
+     */
+    cmdline = format_cmdline (
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                             !config->UseNewInfoFmtStrings, srepos,
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                             filter,
+                             "c", "s", cvs_cmd_name,
+#ifdef SERVER_SUPPORT
+                             "R", "s", referrer ? referrer->original : "NONE",
+#endif /* SERVER_SUPPORT */
+                             "p", "s", srepos,
+                             "r", "s", current_parsed_root->directory,
+                             "sVv", ",", changes,
+                             logmsg_list_to_args_proc, (void *) NULL,
+                             (char *) NULL);
+    if (!cmdline || !strlen (cmdline))
+    {
+       if (cmdline) free (cmdline);
+       error (0, 0, "logmsg proc resolved to the empty string!");
+       return 1;
+    }
+
+    if ((pipefp = run_popen (cmdline, "w")) == NULL)
+    {
+       if (!noexec)
+           error (0, 0, "cannot write entry to log filter: %s", cmdline);
+       free (cmdline);
+       return 1;
+    }
+    (void) fprintf (pipefp, "Update of %s\n", repository);
+    (void) fprintf (pipefp, "In directory %s:", hostname);
+    cp = xgetcwd ();
+    if (cp == NULL)
+       fprintf (pipefp, "<cannot get working directory: %s>\n\n",
+                strerror (errno));
+    else
+    {
+       fprintf (pipefp, "%s\n\n", cp);
+       free (cp);
+    }
+
+    setup_tmpfile (pipefp, "", changes);
+    (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
+    if (logfp)
+    {
+       (void) fprintf (pipefp, "Status:\n");
+       rewind (logfp);
+       while ((c = getc (logfp)) != EOF)
+           (void) putc (c, pipefp);
+    }
+    free (cmdline);
+    pipestatus = pclose (pipefp);
+    return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
+}
+
+
+
+/*  This routine is called by Parse_Info.  It runs the
+ *  message verification script.
+ */
+static int
+verifymsg_proc (const char *repository, const char *script, void *closure)
+{
+    char *verifymsg_script;
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+    char *newscript = NULL;
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+    struct verifymsg_proc_data *vpd = closure;
+    const char *srepos = Short_Repository (repository);
+
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+    if (!strchr (script, '%'))
+    {
+       error (0, 0,
+              "warning: verifymsg line doesn't contain any format strings:\n"
+               "    \"%s\"\n"
+               "Appending default format string (\" %%l\"), but be aware that 
this usage is\n"
+               "deprecated.", script);
+       script = newscript = Xasprintf ("%s %%l", script);
+    }
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+
+    /* If we don't already have one, open a temporary file, write the message
+     * to the temp file, and close the file.
+     *
+     * We do this here so that we only create the file when there is a
+     * verifymsg script specified and we only create it once when there is
+     * more than one verifymsg script specified.
+     */
+    if (vpd->fname == NULL)
+    {
+       FILE *fp;
+       if ((fp = cvs_temp_file (&(vpd->fname))) == NULL)
+           error (1, errno, "cannot create temporary file %s", vpd->fname);
+
+       if (vpd->message != NULL)
+           fputs (vpd->message, fp);
+       if (vpd->message == NULL ||
+           (vpd->message)[0] == '\0' ||
+           (vpd->message)[strlen (vpd->message) - 1] != '\n')
+           putc ('\n', fp);
+       if (fclose (fp) == EOF)
+           error (1, errno, "%s", vpd->fname);
+
+       if (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
+       {
+           /* Remember the status of the temp file for later */
+           if (stat (vpd->fname, &(vpd->pre_stbuf)) != 0)
+               error (1, errno, "cannot stat temp file %s", vpd->fname);
+
+           /*
+            * See if we need to sleep before running the verification
+            * script to avoid time-stamp races.
+            */
+           sleep_past (vpd->pre_stbuf.st_mtime);
+       }
+    } /* if (vpd->fname == NULL) */
+
+    /*
+     * Cast any NULL arguments as appropriate pointers as this is an
+     * stdarg function and we need to be certain the caller gets what
+     * is expected.
+     */
+    verifymsg_script = format_cmdline (
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                                       false, srepos,
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                                       script,
+                                      "c", "s", cvs_cmd_name,
+#ifdef SERVER_SUPPORT
+                                      "R", "s", referrer
+                                      ? referrer->original : "NONE",
+#endif /* SERVER_SUPPORT */
+                                       "p", "s", srepos,
+                                       "r", "s",
+                                       current_parsed_root->directory,
+                                       "l", "s", vpd->fname,
+                                      "sV", ",", vpd->changes,
+                                      logmsg_list_to_args_proc, (void *) NULL,
+                                      (char *) NULL);
+
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+    if (newscript) free (newscript);
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+
+    if (!verifymsg_script || !strlen (verifymsg_script))
+    {
+       if (verifymsg_script) free (verifymsg_script);
+       verifymsg_script = NULL;
+       error (0, 0, "verifymsg proc resolved to the empty string!");
+       return 1;
+    }
+
+    run_setup (verifymsg_script);
+
+    free (verifymsg_script);
+
+    /* FIXME - because run_exec can return negative values and Parse_Info adds
+     * the values of each call to this function to get a total error, we are
+     * calling abs on the value of run_exec to ensure two errors do not sum to
+     * zero.
+     *
+     * The only REALLY obnoxious thing about this, I guess, is that a -1 return
+     * code from run_exec can mean we failed to call the process for some
+     * reason and should care about errno or that the process we called
+     * returned -1 and the value of errno is undefined.  In other words,
+     * run_exec should probably be rewritten to have two return codes.  one
+     * which is its own exit status and one which is the child process's.  So
+     * there.  :P
+     *
+     * Once run_exec is returning two error codes, we should probably be
+     * failing here with an error message including errno when we get the
+     * return code which means we care about errno, in case you missed that
+     * little tidbit.
+     *
+     * I do happen to know we just fail for a non-zero value anyway and I
+     * believe the docs actually state that if the verifymsg_proc returns a
+     * "non-zero" value we will fail.
+     */
+    return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+                         RUN_NORMAL | RUN_SIGIGNORE));
+}
Index: ccvs/src/logmsg.h
diff -u /dev/null ccvs/src/logmsg.h:1.1.2.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/logmsg.h   Fri Jan  6 19:34:15 2006
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2005-2006 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef LOGMSG_H
+#define LOGMSG_H
+
+/* CVS Headers.  */
+#include "classify.h"
+
+
+
+/*
+ * structure used for list nodes passed to Update_Logfile() and
+ * do_editor().
+ */
+struct logfile_info
+{
+  Ctype type;
+  char *tag;
+  char *rev_old;               /* rev number before a commit/modify,
+                                  NULL for add or import */
+  char *rev_new;               /* rev number after a commit/modify,
+                                  add, or import, NULL for remove */
+};
+
+#endif /* LOGMSG_H */
Index: ccvs/src/ls.c
diff -u /dev/null ccvs/src/ls.c:1.18.8.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/ls.c       Fri Jan  6 19:34:15 2006
@@ -0,0 +1,700 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ * Copyright (c) 2001, Tony Hoyle
+ * Copyright (c) 2004, Derek R. Price & Ximbiot <http://ximbiot.com>
+ *
+ * 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.
+ *
+ * Query CVS/Entries from server
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* ANSI C headers.  */
+#include <stdbool.h>
+
+/* CVS headers.  */
+#include "ignore.h"
+#include "recurse.h"
+
+#include "cvs.h"
+
+
+
+static int ls_proc (int argc, char **argv, char *xwhere, char *mwhere,
+                    char *mfile, int shorten, int local, char *mname,
+                    char *msg);
+
+static const char *const ls_usage[] =
+{
+    "Usage: %s %s [-e | -l] [-RP] [-r rev] [-D date] [path...]\n",
+    "\t-d\tShow dead revisions (with tag when specified).\n",
+    "\t-e\tDisplay in CVS/Entries format.\n",
+    "\t-l\tDisplay all details.\n",
+    "\t-P\tPrune empty directories.\n",
+    "\t-R\tList recursively.\n",
+    "\t-r rev\tShow files with revision or tag.\n",
+    "\t-D date\tShow files from date.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+static bool entries_format;
+static bool long_format;
+static char *show_tag;
+static char *show_date;
+static bool set_tag;
+static char *created_dir;
+static bool tag_validated;
+static bool recurse;
+static bool ls_prune_dirs;
+static char *regexp_match;
+static bool is_rls;
+static bool show_dead_revs;
+
+
+
+int
+ls (int argc, char **argv)
+{
+    int c;
+    int err = 0;
+
+    is_rls = strcmp (cvs_cmd_name, "rls") == 0;
+
+    if (argc == -1)
+       usage (ls_usage);
+
+    entries_format = false;
+    long_format = false;
+    show_tag = NULL;
+    show_date = NULL;
+    tag_validated = false;
+    recurse = false;
+    ls_prune_dirs = false;
+    show_dead_revs = false;
+
+    optind = 0;
+
+    while ((c = getopt (argc, argv,
+#ifdef SERVER_SUPPORT
+           server_active ? "qdelr:D:PR" :
+#endif /* SERVER_SUPPORT */
+           "delr:D:RP"
+           )) != -1)
+    {
+       switch (c)
+       {
+#ifdef SERVER_SUPPORT
+           case 'q':
+               if (server_active)
+               {
+                   error (0, 0,
+"`%s ls -q' is deprecated.  Please use the global `-q' option instead.",
+                           program_name);
+                   quiet = true;
+               }
+               else
+                   usage (ls_usage);
+               break;
+#endif /* SERVER_SUPPORT */
+           case 'd':
+               show_dead_revs = true;
+               break;
+           case 'e':
+               entries_format = true;
+               break;
+           case 'l':
+               long_format = true;
+               break;
+           case 'r':
+               parse_tagdate (&show_tag, &show_date, optarg);
+               break;
+           case 'D':
+               if (show_date) free (show_date);
+               show_date = Make_Date (optarg);
+               break;
+           case 'P':
+               ls_prune_dirs = true;
+               break;
+           case 'R':
+               recurse = true;
+               break;
+           case '?':
+           default:
+               usage (ls_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (entries_format && long_format)
+    {
+        error (0, 0, "`-e' & `-l' are mutually exclusive.");
+        usage (ls_usage);
+    }
+
+    wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       /* We're the local client.  Fire up the remote server.  */
+       start_server ();
+
+       ign_setup ();
+
+       if (is_rls ? !(supported_request ("rlist") || supported_request ("ls"))
+                   : !supported_request ("list"))
+           error (1, 0, "server does not support %s", cvs_cmd_name);
+
+       if (quiet && !supported_request ("global-list-quiet"))
+           send_arg ("-q");
+       if (entries_format)
+           send_arg ("-e");
+       if (long_format)
+           send_arg ("-l");
+       if (ls_prune_dirs)
+           send_arg ("-P");
+       if (recurse)
+           send_arg ("-R");
+       if (show_dead_revs)
+           send_arg ("-d");
+       if (show_tag)
+           option_with_arg ("-r", show_tag);
+       if (show_date)
+           client_senddate (show_date);
+
+       send_arg ("--");
+
+       if (is_rls)
+       {
+           int i;
+           for (i = 0; i < argc; i++)
+               send_arg (argv[i]);
+            if (supported_request ("rlist"))
+               send_to_server ("rlist\012", 0);
+           else
+               /* For backwards compatibility with CVSNT...  */
+               send_to_server ("ls\012", 0);
+       }
+       else
+       {
+           /* Setting this means, I think, that any empty directories created
+            * by the server will be deleted by the client.  Since any dirs
+            * created at all by ls should remain empty, this should cause any
+            * dirs created by the server for the ls command to be deleted.
+            */
+           client_prune_dirs = 1;
+
+           /* I explicitly decide not to send contents here.  We *could* let
+            * the user pull status information with this command, but why
+            * don't they just use update or status?
+            */
+           send_files (argc, argv, !recurse, 0, SEND_NO_CONTENTS);
+           send_file_names (argc, argv, SEND_EXPAND_WILD);
+           send_to_server ("list\012", 0);
+       }
+
+       err = get_responses_and_close ();
+       return err;
+    }
+#endif
+
+    if (is_rls)
+    {
+       DBM *db;
+       int i;
+       db = open_module ();
+       if (argc)
+       {
+           for (i = 0; i < argc; i++)
+           {
+               char *mod = xstrdup (argv[i]);
+               char *p;
+
+               for (p=strchr (mod,'\\'); p; p=strchr (p,'\\'))
+                   *p='/';
+
+               p = strrchr (mod,'/');
+               if (p && (strchr (p,'?') || strchr (p,'*')))
+               {
+                   *p='\0';
+                   regexp_match = p+1;
+               }
+               else
+                   regexp_match = NULL;
+
+               /* Frontends like to do 'ls -q /', so we support it explicitly.
+                 */
+               if (!strcmp (mod,"/"))
+               {
+                   *mod='\0';
+               }
+
+               err += do_module (db, mod, MISC, "Listing",
+                                 ls_proc, NULL, 0, 0, 0, 0, NULL);
+
+               free (mod);
+           }
+       }
+       else
+       {
+           /* should be ".", but do_recursion() 
+              fails this: assert ( strstr ( repository, "/./" ) == NULL ); */
+           char *topmod = xstrdup ("");
+           err += do_module (db, topmod, MISC, "Listing",
+                             ls_proc, NULL, 0, 0, 0, 0, NULL);
+           free (topmod);
+       }
+       close_module (db);
+    }
+    else
+       ls_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, 0, NULL, NULL);
+
+    return err;
+}
+
+
+
+struct long_format_data
+{
+    char *header;
+    char *time;
+    char *footer;
+};
+
+static int
+ls_print (Node *p, void *closure)
+{
+    if (long_format)
+    {
+       struct long_format_data *data = p->data;
+       cvs_output_tagged ("text", data->header);
+       if (data->time)
+           cvs_output_tagged ("date", data->time);
+       if (data->footer)
+           cvs_output_tagged ("text", data->footer);
+       cvs_output_tagged ("newline", NULL);
+    }
+    else
+       cvs_output (p->data, 0);
+    return 0;
+}
+
+
+
+static int
+ls_print_dir (Node *p, void *closure)
+{
+    static bool printed = false;
+
+    if (recurse && !(ls_prune_dirs && list_isempty (p->data)))
+    {
+        /* Keep track of whether we've printed.  If we have, then put a blank
+         * line before directory headers, to separate the header from the
+         * listing of the previous directory.
+         */
+        if (printed)
+            cvs_output ("\n", 1);
+        else
+            printed = true;
+
+        if (!strcmp (p->key, ""))
+            cvs_output (".", 1);
+        else
+           cvs_output (p->key, 0);
+        cvs_output (":\n", 2);
+    }
+    walklist (p->data, ls_print, NULL);
+    return 0;
+}
+
+
+
+/*
+ * Delproc for a node containing a struct long_format_data as data.
+ */
+static void
+long_format_data_delproc (Node *n)
+{
+       struct long_format_data *data = (struct long_format_data *)n->data;
+       if (data->header) free (data->header);
+       if (data->time) free (data->time);
+       if (data->footer) free (data->footer);
+       free (data);
+}
+
+
+
+/* A delproc for a List Node containing a List *.  */
+static void
+ls_delproc (Node *p)
+{
+    dellist ((List **)&p->data);
+}
+
+
+
+/*
+ * Add a file to our list of data to print for a directory.
+ */
+/* ARGSUSED */
+static int
+ls_fileproc (void *callerdat, struct file_info *finfo)
+{
+    Vers_TS *vers;
+    char *regex_err;
+    Node *p, *n;
+    bool isdead;
+    const char *filename;
+
+    if (regexp_match)
+    {
+#ifdef FILENAMES_CASE_INSENSITIVE
+         re_set_syntax (REG_ICASE|RE_SYNTAX_EGREP);
+#else
+         re_set_syntax (RE_SYNTAX_EGREP);
+#endif
+         if ((regex_err = re_comp (regexp_match)) != NULL)
+         {
+             error (1, 0, "bad regular expression passed to 'ls': %s",
+                     regex_err);
+         }
+         if (re_exec (finfo->file) == 0)
+             return 0;                         /* no match */
+    }
+
+    vers = Version_TS (finfo, NULL, show_tag, show_date, 1, 0);
+    /* Skip dead revisions unless specifically requested to do otherwise.
+     * We also bother to check for long_format so we can print the state.
+     */
+    if (vers->vn_rcs && (!show_dead_revs || long_format))
+       isdead = RCS_isdead (finfo->rcs, vers->vn_rcs);
+    else
+       isdead = false;
+    if (!vers->vn_rcs || (!show_dead_revs && isdead))
+    {
+        freevers_ts (&vers);
+       return 0;
+    }
+
+    p = findnode (callerdat, finfo->update_dir);
+    if (!p)
+    {
+       /* This only occurs when a complete path to a file is specified on the
+        * command line.  Put the file in the root list.
+        */
+       filename = finfo->fullname;
+
+       /* Add update_dir node.  */
+       p = findnode (callerdat, ".");
+       if (!p)
+       {
+           p = getnode ();
+           p->key = xstrdup (".");
+           p->data = getlist ();
+           p->delproc = ls_delproc;
+           addnode (callerdat, p);
+       }
+    }
+    else
+       filename = finfo->file;
+
+    n = getnode();
+    if (entries_format)
+    {
+       char *outdate = entries_time (RCS_getrevtime (finfo->rcs, vers->vn_rcs,
+                                                      0, 0));
+       n->data = Xasprintf ("/%s/%s/%s/%s/%s%s\n",
+                             filename, vers->vn_rcs,
+                             outdate, vers->options,
+                             show_tag ? "T" : "", show_tag ? show_tag : "");
+       free (outdate);
+    }
+    else if (long_format)
+    {
+       struct long_format_data *out =
+               xmalloc (sizeof (struct long_format_data));
+       out->header = Xasprintf ("%-5.5s",
+                                 vers->options[0] != '\0' ? vers->options
+                                                          : "----");
+       /* FIXME: Do we want to mimc the real `ls' command's date format?  */
+       out->time = gmformat_time_t (RCS_getrevtime (finfo->rcs, vers->vn_rcs,
+                                                     0, 0));
+       out->footer = Xasprintf (" %-9.9s%s %s%s", vers->vn_rcs,
+                                 strlen (vers->vn_rcs) > 9 ? "+" : " ",
+                                 show_dead_revs ? (isdead ? "dead " : "     ")
+                                                : "",
+                                 filename);
+       n->data = out;
+       n->delproc = long_format_data_delproc;
+    }
+    else
+       n->data = Xasprintf ("%s\n", filename);
+
+    addnode (p->data, n);
+
+    freevers_ts (&vers);
+    return 0;
+}
+
+
+
+/*
+ * Add this directory to the list of data to be printed for a directory and
+ * decide whether to tell the recursion processor whether to continue
+ * recursing or not.
+ */
+static Dtype
+ls_direntproc (void *callerdat, const char *dir, const char *repos,
+               const char *update_dir, List *entries)
+{
+    Dtype retval;
+    Node *p;
+
+    /* Due to the way we called start_recursion() from ls_proc() with a single
+     * argument at a time, we can assume that if we don't yet have a parent
+     * directory in DIRS then this directory should be processed.
+     */
+
+    if (strcmp (dir, "."))
+    {
+        /* Search for our parent directory.  */
+       char *parent;
+        parent = xmalloc (strlen (update_dir) - strlen (dir) + 1);
+        strncpy (parent, update_dir, strlen (update_dir) - strlen (dir));
+        parent[strlen (update_dir) - strlen (dir)] = '\0';
+        strip_trailing_slashes (parent);
+        p = findnode (callerdat, parent);
+    }
+    else
+        p = NULL;
+
+    if (p)
+    {
+       /* Push this dir onto our parent directory's listing.  */
+       Node *n = getnode();
+
+       if (entries_format)
+           n->data = Xasprintf ("D/%s////\n", dir);
+       else if (long_format)
+       {
+           struct long_format_data *out =
+                   xmalloc (sizeof (struct long_format_data));
+           out->header = xstrdup ("d--- ");
+           out->time = gmformat_time_t (unix_time_stamp (repos));
+           out->footer = Xasprintf ("%12s%s%s", "",
+                                     show_dead_revs ? "     " : "", dir);
+           n->data = out;
+           n->delproc = long_format_data_delproc;
+       }
+       else
+           n->data = Xasprintf ("%s\n", dir);
+
+       addnode (p->data, n);
+    }
+
+    if (!p || recurse)
+    {
+       /* Create a new list for this directory.  */
+       p = getnode ();
+       p->key = xstrdup (strcmp (update_dir, ".") ? update_dir : "");
+       p->data = getlist ();
+        p->delproc = ls_delproc;
+       addnode (callerdat, p);
+
+       /* Create a local directory and mark it as needing deletion.  This is
+         * the behavior the recursion processor relies upon, a la update &
+         * checkout.
+         */
+       if (!isdir (dir))
+        {
+           int nonbranch;
+           if (show_tag == NULL && show_date == NULL)
+           {
+               ParseTag (&show_tag, &show_date, &nonbranch);
+               set_tag = true;
+           }
+
+           if (!created_dir)
+               created_dir = xstrdup (update_dir);
+
+           make_directory (dir);
+           Create_Admin (dir, update_dir, repos, show_tag, show_date,
+                         nonbranch, 0, 0);
+           Subdir_Register (entries, NULL, dir);
+       }
+
+       /* Tell do_recursion to keep going.  */
+       retval = R_PROCESS;
+    }
+    else
+        retval = R_SKIP_ALL;
+
+    return retval;
+}
+
+
+
+/* Clean up tags, dates, and dirs if we created this directory.
+ */
+static int
+ls_dirleaveproc (void *callerdat, const char *dir, int err,
+                 const char *update_dir, List *entries)
+{
+       if (created_dir && !strcmp (created_dir, update_dir))
+       {
+               if (set_tag)
+               {
+                   if (show_tag) free (show_tag);
+                   if (show_date) free (show_date);
+                   show_tag = show_date = NULL;
+                   set_tag = false;
+               }
+
+               (void)CVS_CHDIR ("..");
+               if (unlink_file_dir (dir))
+                   error (0, errno, "Failed to remove directory `%s'",
+                          created_dir);
+               Subdir_Deregister (entries, NULL, dir);
+
+               free (created_dir);
+               created_dir = NULL;
+       }
+       return err;
+}
+
+
+
+static int
+ls_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
+         int shorten, int local, char *mname, char *msg)
+{
+    char *repository;
+    int err = 0;
+    int which;
+    char *where;
+    int i;
+
+    if (is_rls)
+    {
+       char *myargv[2];
+
+       if (!quiet)
+           error (0, 0, "Listing module: `%s'",
+                  strcmp (mname, "") ? mname : ".");
+
+       repository = xmalloc (strlen (current_parsed_root->directory)
+                             + strlen (argv[0])
+                             + (mfile == NULL ? 0 : strlen (mfile) + 1)
+                             + 2);
+       (void)sprintf (repository, "%s/%s", current_parsed_root->directory,
+                      argv[0]);
+       where = xmalloc (strlen (argv[0])
+                        + (mfile == NULL ? 0 : strlen (mfile) + 1)
+                        + 1);
+       (void)strcpy (where, argv[0]);
+
+       /* If mfile isn't null, we need to set up to do only part of the
+        * module.
+        */
+       if (mfile != NULL)
+       {
+           char *cp;
+           char *path;
+
+           /* If the portion of the module is a path, put the dir part on
+            * repos.
+            */
+           if ((cp = strrchr (mfile, '/')) != NULL)
+           {
+               *cp = '\0';
+               (void)strcat (repository, "/");
+               (void)strcat (repository, mfile);
+               (void)strcat (where, "/");
+               (void)strcat (where, mfile);
+               mfile = cp + 1;
+           }
+
+           /* take care of the rest */
+           path = Xasprintf ("%s/%s", repository, mfile);
+           if (isdir (path))
+           {
+               /* directory means repository gets the dir tacked on */
+               (void)strcpy (repository, path);
+               (void)strcat (where, "/");
+               (void)strcat (where, mfile);
+           }
+           else
+           {
+               myargv[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;
+       }
+
+       which = W_REPOS;
+    }
+    else /* !is_rls */
+    {
+        repository = NULL;
+        where = NULL;
+        which = W_LOCAL | W_REPOS;
+    }
+
+    if (show_tag || show_date || show_dead_revs)
+       which |= W_ATTIC;
+
+    if (show_tag != NULL && !tag_validated)
+    {
+       tag_check_valid (show_tag, argc - 1, argv + 1, local, 0, repository,
+                        false);
+       tag_validated = true;
+    }
+
+    /* Loop on argc so that we are guaranteed that any directory passed to
+     * ls_direntproc should be processed if its parent is not yet in DIRS.
+     */
+    if (argc == 1)
+    {
+       List *dirs = getlist ();
+       err = start_recursion (ls_fileproc, NULL, ls_direntproc,
+                              ls_dirleaveproc, dirs, 0, NULL, local, which, 0,
+                              CVS_LOCK_READ, where, 1, repository);
+       walklist (dirs, ls_print_dir, NULL);
+       dellist (&dirs);
+    }
+    else
+    {
+       for (i = 1; i < argc; i++)
+       {
+           List *dirs = getlist ();
+           err = start_recursion (ls_fileproc, NULL, ls_direntproc,
+                                  NULL, dirs, 1, argv + i, local, which, 0,
+                                  CVS_LOCK_READ, where, 1, repository);
+           walklist (dirs, ls_print_dir, NULL);
+           dellist (&dirs);
+       }
+    }
+
+    if (!(which & W_LOCAL)) free (repository);
+    if (where) free (where);
+
+    return err;
+}
Index: ccvs/src/modules.c
diff -u /dev/null ccvs/src/modules.c:1.98.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/modules.c  Fri Jan  6 19:34:15 2006
@@ -0,0 +1,1052 @@
+/*
+ * 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.
+ *
+ * Modules
+ *
+ *     Functions for accessing the modules file.
+ *
+ *     The modules file supports basically three formats of lines:
+ *             key [options] directory files... [ -x directory [files] ] ...
+ *             key [options] directory [ -x directory [files] ] ...
+ *             key -a aliases...
+ *
+ *     The -a option allows an aliasing step in the parsing of the modules
+ *     file.  The "aliases" listed on a line following the -a are
+ *     processed one-by-one, as if they were specified as arguments on the
+ *     command line.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* GNULIB headers.  */
+#include "save-cwd.h"
+
+/* CVS headers.  */
+#include "ignore.h"
+
+#include "cvs.h"
+
+
+
+/* Defines related to the syntax of the modules file.  */
+
+/* Options in modules file.  Note that it is OK to use GNU getopt features;
+   we already are arranging to make sure we are using the getopt distributed
+   with CVS.  */
+#define        CVSMODULE_OPTS  "+ad:lo:e:s:t:"
+
+/* Special delimiter.  */
+#define CVSMODULE_SPEC '&'
+
+struct sortrec
+{
+    /* Name of the module, malloc'd.  */
+    char *modname;
+    /* If Status variable is set, this is either def_status or the malloc'd
+       name of the status.  If Status is not set, the field is left
+       uninitialized.  */
+    char *status;
+    /* Pointer to a malloc'd array which contains (1) the raw contents
+       of the options and arguments, excluding comments, (2) a '\0',
+       and (3) the storage for the "comment" field.  */
+    char *rest;
+    char *comment;
+};
+
+static int sort_order (const void *l, const void *r);
+static void save_d (char *k, int ks, char *d, int ds);
+
+
+/*
+ * Open the modules file, and die if the CVSROOT environment variable
+ * was not set.  If the modules file does not exist, that's fine, and
+ * a warning message is displayed and a NULL is returned.
+ */
+DBM *
+open_module (void)
+{
+    char *mfile;
+    DBM *retval;
+
+    if (current_parsed_root == NULL)
+    {
+       error (0, 0, "must set the CVSROOT environment variable");
+       error (1, 0, "or specify the '-d' global option");
+    }
+    mfile = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
+                      CVSROOTADM, CVSROOTADM_MODULES);
+    retval = dbm_open (mfile, O_RDONLY, 0666);
+    free (mfile);
+    return retval;
+}
+
+/*
+ * Close the modules file, if the open succeeded, that is
+ */
+void
+close_module (DBM *db)
+{
+    if (db != NULL)
+       dbm_close (db);
+}
+
+
+
+/*
+ * This is the recursive function that processes a module name.
+ * It calls back the passed routine for each directory of a module
+ * It runs the post checkout or post tag proc from the modules file
+ */
+int
+my_module (DBM *db, char *mname, enum mtype m_type, char *msg,
+            CALLBACKPROC callback_proc, char *where, int shorten,
+            int local_specified, int run_module_prog, int build_dirs,
+            char *extra_arg, List *stack)
+{
+    char *checkout_prog = NULL;
+    char *export_prog = NULL;
+    char *tag_prog = NULL;
+    struct saved_cwd cwd;
+    int cwd_saved = 0;
+    char *line;
+    int modargc;
+    int xmodargc;
+    char **modargv = NULL;
+    char **xmodargv = NULL;
+    /* Found entry from modules file, including options and such.  */
+    char *value = NULL;
+    char *mwhere = NULL;
+    char *mfile = NULL;
+    char *spec_opt = NULL;
+    char *xvalue = NULL;
+    int alias = 0;
+    datum key, val;
+    char *cp;
+    int c, err = 0;
+    int nonalias_opt = 0;
+
+#ifdef SERVER_SUPPORT
+    int restore_server_dir = 0;
+    char *server_dir_to_restore = NULL;
+#endif
+
+    TRACE (TRACE_FUNCTION, "my_module (%s, %s, %s, %s)",
+           mname ? mname : "(null)", msg ? msg : "(null)",
+           where ? where : "NULL", extra_arg ? extra_arg : "NULL");
+
+    /* Don't process absolute directories.  Anything else could be a security
+     * problem.  Before this check was put in place:
+     *
+     *   $ cvs -d:fork:/cvsroot co /foo
+     *   cvs server: warning: cannot make directory CVS in /: Permission denied
+     *   cvs [server aborted]: cannot make directory /foo: Permission denied
+     *   $
+     */
+    if (ISABSOLUTE (mname))
+       error (1, 0, "Absolute module reference invalid: `%s'", mname);
+
+    /* Similarly for directories that attempt to step above the root of the
+     * repository.
+     */
+    if (pathname_levels (mname) > 0)
+       error (1, 0, "up-level in module reference (`..') invalid: `%s'.",
+               mname);
+
+    /* if this is a directory to ignore, add it to that list */
+    if (mname[0] == '!' && mname[1] != '\0')
+    {
+       ign_dir_add (mname+1);
+       goto do_module_return;
+    }
+
+    /* strip extra stuff from the module name */
+    strip_trailing_slashes (mname);
+
+    /*
+     * Look up the module using the following scheme:
+     * 1) look for mname as a module name
+     * 2) look for mname as a directory
+     * 3) look for mname as a file
+     *  4) take mname up to the first slash and look it up as a module name
+     *    (this is for checking out only part of a module)
+     */
+
+    /* look it up as a module name */
+    key.dptr = mname;
+    key.dsize = strlen (key.dptr);
+    if (db != NULL)
+       val = dbm_fetch (db, key);
+    else
+       val.dptr = NULL;
+    if (val.dptr != NULL)
+    {
+       /* copy and null terminate the value */
+       value = xmalloc (val.dsize + 1);
+       memcpy (value, val.dptr, val.dsize);
+       value[val.dsize] = '\0';
+
+       /* If the line ends in a comment, strip it off */
+       if ((cp = strchr (value, '#')) != NULL)
+           *cp = '\0';
+       else
+           cp = value + val.dsize;
+
+       /* Always strip trailing spaces */
+       while (cp > value && isspace ((unsigned char) *--cp))
+           *cp = '\0';
+
+       mwhere = xstrdup (mname);
+       goto found;
+    }
+    else
+    {
+       char *file;
+       char *attic_file;
+       char *acp;
+       int is_found = 0;
+
+       /* check to see if mname is a directory or file */
+       file = xmalloc (strlen (current_parsed_root->directory)
+                       + strlen (mname) + sizeof(RCSEXT) + 2);
+       (void) sprintf (file, "%s/%s", current_parsed_root->directory, mname);
+       attic_file = xmalloc (strlen (current_parsed_root->directory)
+                             + strlen (mname)
+                             + sizeof (CVSATTIC) + sizeof (RCSEXT) + 3);
+       if ((acp = strrchr (mname, '/')) != NULL)
+       {
+           *acp = '\0';
+           (void) sprintf (attic_file, "%s/%s/%s/%s%s", 
current_parsed_root->directory,
+                           mname, CVSATTIC, acp + 1, RCSEXT);
+           *acp = '/';
+       }
+       else
+           (void) sprintf (attic_file, "%s/%s/%s%s",
+                           current_parsed_root->directory,
+                           CVSATTIC, mname, RCSEXT);
+
+       if (isdir (file))
+       {
+           modargv = xmalloc (sizeof (*modargv));
+           modargv[0] = xstrdup (mname);
+           modargc = 1;
+           is_found = 1;
+       }
+       else
+       {
+           (void) strcat (file, RCSEXT);
+           if (isfile (file) || isfile (attic_file))
+           {
+               /* if mname was a file, we have to split it into "dir file" */
+               if ((cp = strrchr (mname, '/')) != NULL && cp != mname)
+               {
+                   modargv = xnmalloc (2, sizeof (*modargv));
+                   modargv[0] = xmalloc (strlen (mname) + 2);
+                   strncpy (modargv[0], mname, cp - mname);
+                   modargv[0][cp - mname] = '\0';
+                   modargv[1] = xstrdup (cp + 1);
+                   modargc = 2;
+               }
+               else
+               {
+                   /*
+                    * the only '/' at the beginning or no '/' at all
+                    * means the file we are interested in is in CVSROOT
+                    * itself so the directory should be '.'
+                    */
+                   if (cp == mname)
+                   {
+                       /* drop the leading / if specified */
+                       modargv = xnmalloc (2, sizeof (*modargv));
+                       modargv[0] = xstrdup (".");
+                       modargv[1] = xstrdup (mname + 1);
+                       modargc = 2;
+                   }
+                   else
+                   {
+                       /* otherwise just copy it */
+                       modargv = xnmalloc (2, sizeof (*modargv));
+                       modargv[0] = xstrdup (".");
+                       modargv[1] = xstrdup (mname);
+                       modargc = 2;
+                   }
+               }
+               is_found = 1;
+           }
+       }
+       free (attic_file);
+       free (file);
+
+       if (is_found)
+       {
+           assert (value == NULL);
+
+           /* OK, we have now set up modargv with the actual
+              file/directory we want to work on.  We duplicate a
+              small amount of code here because the vast majority of
+              the code after the "found" label does not pertain to
+              the case where we found a file/directory rather than
+              finding an entry in the modules file.  */
+           if (save_cwd (&cwd))
+               error (1, errno, "Failed to save current directory.");
+           cwd_saved = 1;
+
+           err += callback_proc (modargc, modargv, where, mwhere, mfile,
+                                 shorten,
+                                 local_specified, mname, msg);
+
+           free_names (&modargc, modargv);
+
+           /* cd back to where we started.  */
+           if (restore_cwd (&cwd))
+               error (1, errno, "Failed to restore current directory, `%s'.",
+                      cwd.name);
+           free_cwd (&cwd);
+           cwd_saved = 0;
+
+           goto do_module_return;
+       }
+    }
+
+    /* look up everything to the first / as a module */
+    if (mname[0] != '/' && (cp = strchr (mname, '/')) != NULL)
+    {
+       /* Make the slash the new end of the string temporarily */
+       *cp = '\0';
+       key.dptr = mname;
+       key.dsize = strlen (key.dptr);
+
+       /* do the lookup */
+       if (db != NULL)
+           val = dbm_fetch (db, key);
+       else
+           val.dptr = NULL;
+
+       /* if we found it, clean up the value and life is good */
+       if (val.dptr != NULL)
+       {
+           char *cp2;
+
+           /* copy and null terminate the value */
+           value = xmalloc (val.dsize + 1);
+           memcpy (value, val.dptr, val.dsize);
+           value[val.dsize] = '\0';
+
+           /* If the line ends in a comment, strip it off */
+           if ((cp2 = strchr (value, '#')) != NULL)
+               *cp2 = '\0';
+           else
+               cp2 = value + val.dsize;
+
+           /* Always strip trailing spaces */
+           while (cp2 > value  &&  isspace ((unsigned char) *--cp2))
+               *cp2 = '\0';
+
+           /* mwhere gets just the module name */
+           mwhere = xstrdup (mname);
+           mfile = cp + 1;
+           assert (strlen (mfile));
+
+           /* put the / back in mname */
+           *cp = '/';
+
+           goto found;
+       }
+
+       /* put the / back in mname */
+       *cp = '/';
+    }
+
+    /* if we got here, we couldn't find it using our search, so give up */
+    error (0, 0, "cannot find module `%s' - ignored", mname);
+    err++;
+    goto do_module_return;
+
+
+    /*
+     * At this point, we found what we were looking for in one
+     * of the many different forms.
+     */
+  found:
+
+    /* remember where we start */
+    if (save_cwd (&cwd))
+       error (1, errno, "Failed to save current directory.");
+    cwd_saved = 1;
+
+    assert (value != NULL);
+
+    /* search the value for the special delimiter and save for later */
+    if ((cp = strchr (value, CVSMODULE_SPEC)) != NULL)
+    {
+       *cp = '\0';                     /* null out the special char */
+       spec_opt = cp + 1;              /* save the options for later */
+
+       /* strip whitespace if necessary */
+       while (cp > value  &&  isspace ((unsigned char) *--cp))
+           *cp = '\0';
+    }
+
+    /* don't do special options only part of a module was specified */
+    if (mfile != NULL)
+       spec_opt = NULL;
+
+    /*
+     * value now contains one of the following:
+     *    1) dir
+     *   2) dir file
+     *    3) the value from modules without any special args
+     *             [ args ] dir [file] [file] ...
+     *      or     -a module [ module ] ...
+     */
+
+    /* Put the value on a line with XXX prepended for getopt to eat */
+    line = Xasprintf ("XXX %s", value);
+
+    /* turn the line into an argv[] array */
+    line2argv (&xmodargc, &xmodargv, line, " \t");
+    free (line);
+    modargc = xmodargc;
+    modargv = xmodargv;
+
+    /* parse the args */
+    optind = 0;
+    while ((c = getopt (modargc, modargv, CVSMODULE_OPTS)) != -1)
+    {
+       switch (c)
+       {
+           case 'a':
+               alias = 1;
+               break;
+           case 'd':
+               if (mwhere)
+                   free (mwhere);
+               mwhere = xstrdup (optarg);
+               nonalias_opt = 1;
+               break;
+           case 'l':
+               local_specified = 1;
+               nonalias_opt = 1;
+               break;
+           case 'o':
+               if (checkout_prog)
+                   free (checkout_prog);
+               checkout_prog = xstrdup (optarg);
+               nonalias_opt = 1;
+               break;
+           case 'e':
+               if (export_prog)
+                   free (export_prog);
+               export_prog = xstrdup (optarg);
+               nonalias_opt = 1;
+               break;
+           case 't':
+               if (tag_prog)
+                   free (tag_prog);
+               tag_prog = xstrdup (optarg);
+               nonalias_opt = 1;
+               break;
+           case '?':
+               error (0, 0,
+                      "modules file has invalid option for key %s value %s",
+                      key.dptr, value);
+               err++;
+               goto do_module_return;
+       }
+    }
+    modargc -= optind;
+    modargv += optind;
+    if (modargc == 0  &&  spec_opt == NULL)
+    {
+       error (0, 0, "modules file missing directory for module %s", mname);
+       ++err;
+       goto do_module_return;
+    }
+
+    if (alias && nonalias_opt)
+    {
+       /* The documentation has never said it is valid to specify
+          -a along with another option.  And I believe that in the past
+          CVS has ignored the options other than -a, more or less, in this
+          situation.  */
+       error (0, 0, "\
+-a cannot be specified in the modules file along with other options");
+       ++err;
+       goto do_module_return;
+    }
+
+    /* if this was an alias, call ourselves recursively for each module */
+    if (alias)
+    {
+       int i;
+
+       for (i = 0; i < modargc; i++)
+       {
+           /* 
+            * Recursion check: if an alias module calls itself or a module
+            * which causes the first to be called again, print an error
+            * message and stop recursing.
+            *
+            * Algorithm:
+            *
+            *   1. Check that MNAME isn't in the stack.
+            *   2. Push MNAME onto the stack.
+            *   3. Call do_module().
+            *   4. Pop MNAME from the stack.
+            */
+           if (stack && findnode (stack, mname))
+               error (0, 0,
+                      "module `%s' in modules file contains infinite loop",
+                      mname);
+           else
+           {
+               if (!stack) stack = getlist();
+               push_string (stack, mname);
+               err += my_module (db, modargv[i], m_type, msg, callback_proc,
+                                   where, shorten, local_specified,
+                                   run_module_prog, build_dirs, extra_arg,
+                                   stack);
+               pop_string (stack);
+               if (isempty (stack)) dellist (&stack);
+           }
+       }
+       goto do_module_return;
+    }
+
+    if (mfile != NULL && modargc > 1)
+    {
+       error (0, 0, "\
+module `%s' is a request for a file in a module which is not a directory",
+              mname);
+       ++err;
+       goto do_module_return;
+    }
+
+    /* otherwise, process this module */
+    if (modargc > 0)
+    {
+       err += callback_proc (modargc, modargv, where, mwhere, mfile, shorten,
+                             local_specified, mname, msg);
+    }
+    else
+    {
+       /*
+        * we had nothing but special options, so we must
+        * make the appropriate directory and cd to it
+        */
+       char *dir;
+
+       if (!build_dirs)
+           goto do_special;
+
+       dir = where ? where : (mwhere ? mwhere : mname);
+       /* XXX - think about making null repositories at each dir here
+                instead of just at the bottom */
+       make_directories (dir);
+       if (CVS_CHDIR (dir) < 0)
+       {
+           error (0, errno, "cannot chdir to %s", dir);
+           spec_opt = NULL;
+           err++;
+           goto do_special;
+       }
+       if (!isfile (CVSADM))
+       {
+           char *nullrepos;
+
+           nullrepos = emptydir_name ();
+
+           Create_Admin (".", dir, nullrepos, NULL, NULL, 0, 0, 1);
+           if (!noexec)
+           {
+               FILE *fp;
+
+               fp = xfopen (CVSADM_ENTSTAT, "w+");
+               if (fclose (fp) == EOF)
+                   error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+               if (server_active)
+                   server_set_entstat (dir, nullrepos);
+#endif
+           }
+           free (nullrepos);
+       }
+    }
+
+    /* if there were special include args, process them now */
+
+  do_special:
+
+    free_names (&xmodargc, xmodargv);
+    xmodargv = NULL;
+
+    /* blow off special options if -l was specified */
+    if (local_specified)
+       spec_opt = NULL;
+
+#ifdef SERVER_SUPPORT
+    /* We want to check out into the directory named by the module.
+       So we set a global variable which tells the server to glom that
+       directory name onto the front.  A cleaner approach would be some
+       way of passing it down to the recursive call, through the
+       callback_proc, to start_recursion, and then into the update_dir in
+       the struct file_info.  That way the "Updating foo" message could
+       print the actual directory we are checking out into.
+
+       For local CVS, this is handled by the chdir call above
+       (directly or via the callback_proc).  */
+    if (server_active && spec_opt != NULL)
+    {
+       char *change_to;
+
+       change_to = where ? where : (mwhere ? mwhere : mname);
+       server_dir_to_restore = server_dir;
+       restore_server_dir = 1;
+       if (server_dir_to_restore != NULL)
+           server_dir = Xasprintf ("%s/%s", server_dir_to_restore, change_to);
+       else
+           server_dir = xstrdup (change_to);
+    }
+#endif
+
+    while (spec_opt != NULL)
+    {
+       char *next_opt;
+
+       cp = strchr (spec_opt, CVSMODULE_SPEC);
+       if (cp != NULL)
+       {
+           /* save the beginning of the next arg */
+           next_opt = cp + 1;
+
+           /* strip whitespace off the end */
+           do
+               *cp = '\0';
+           while (cp > spec_opt  &&  isspace ((unsigned char) *--cp));
+       }
+       else
+           next_opt = NULL;
+
+       /* strip whitespace from front */
+       while (isspace ((unsigned char) *spec_opt))
+           spec_opt++;
+
+       if (*spec_opt == '\0')
+           error (0, 0, "Mal-formed %c option for module %s - ignored",
+                  CVSMODULE_SPEC, mname);
+       else
+           err += my_module (db, spec_opt, m_type, msg, callback_proc,
+                             NULL, 0, local_specified, run_module_prog,
+                             build_dirs, extra_arg, stack);
+       spec_opt = next_opt;
+    }
+
+#ifdef SERVER_SUPPORT
+    if (server_active && restore_server_dir)
+    {
+       free (server_dir);
+       server_dir = server_dir_to_restore;
+    }
+#endif
+
+    /* cd back to where we started */
+    if (restore_cwd (&cwd))
+       error (1, errno, "Failed to restore current directory, `%s'.",
+              cwd.name);
+    free_cwd (&cwd);
+    cwd_saved = 0;
+
+    /* run checkout or tag prog if appropriate */
+    if (err == 0 && run_module_prog)
+    {
+       if ((m_type == TAG && tag_prog != NULL) ||
+           (m_type == CHECKOUT && checkout_prog != NULL) ||
+           (m_type == EXPORT && export_prog != NULL))
+       {
+           /*
+            * If a relative pathname is specified as the checkout, tag
+            * or export proc, try to tack on the current "where" value.
+            * if we can't find a matching program, just punt and use
+            * whatever is specified in the modules file.
+            */
+           char *real_prog = NULL;
+           char *prog = (m_type == TAG ? tag_prog :
+                         (m_type == CHECKOUT ? checkout_prog : export_prog));
+           char *real_where = (where != NULL ? where : mwhere);
+           char *expanded_path;
+
+           if ((*prog != '/') && (*prog != '.'))
+           {
+               real_prog = Xasprintf ("%s/%s", real_where, prog);
+               if (isfile (real_prog))
+                   prog = real_prog;
+           }
+
+           /* XXX can we determine the line number for this entry??? */
+           expanded_path = expand_path (prog, current_parsed_root->directory,
+                                        false, "modules", 0);
+           if (expanded_path != NULL)
+           {
+               run_setup (expanded_path);
+               run_add_arg (real_where);
+
+               if (extra_arg)
+                   run_add_arg (extra_arg);
+
+               if (!quiet)
+               {
+                   cvs_output (program_name, 0);
+                   cvs_output (" ", 1);
+                   cvs_output (cvs_cmd_name, 0);
+                   cvs_output (": Executing '", 0);
+                   run_print (stdout);
+                   cvs_output ("'\n", 0);
+                   cvs_flushout ();
+               }
+               err += run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+               free (expanded_path);
+           }
+           if (real_prog) free (real_prog);
+       }
+    }
+
+ do_module_return:
+    /* clean up */
+    if (xmodargv != NULL)
+       free_names (&xmodargc, xmodargv);
+    if (mwhere)
+       free (mwhere);
+    if (checkout_prog)
+       free (checkout_prog);
+    if (export_prog)
+       free (export_prog);
+    if (tag_prog)
+       free (tag_prog);
+    if (cwd_saved)
+       free_cwd (&cwd);
+    if (value != NULL)
+       free (value);
+
+    if (xvalue != NULL)
+       free (xvalue);
+    return (err);
+}
+
+
+
+/* External face of do_module so that we can have an internal version which
+ * accepts a stack argument to track alias recursion.
+ */
+int
+do_module (DBM *db, char *mname, enum mtype m_type, char *msg,
+           CALLBACKPROC callback_proc, char *where, int shorten,
+           int local_specified, int run_module_prog, int build_dirs,
+           char *extra_arg)
+{
+    return my_module (db, mname, m_type, msg, callback_proc, where, shorten,
+                       local_specified, run_module_prog, build_dirs, extra_arg,
+                       NULL);
+}
+
+
+
+/* - Read all the records from the modules database into an array.
+   - Sort the array depending on what format is desired.
+   - Print the array in the format desired.
+
+   Currently, there are only two "desires":
+
+   1. Sort by module name and format the whole entry including switches,
+      files and the comment field: (Including aliases)
+
+      modulename       -s switches, one per line, even if
+                       it has many switches.
+                       Directories and files involved, formatted
+                       to cover multiple lines if necessary.
+                       # Comment, also formatted to cover multiple
+                       # lines if necessary.
+
+   2. Sort by status field string and print:  (*not* including aliases)
+
+      modulename    STATUS     Directories and files involved, formatted
+                               to cover multiple lines if necessary.
+                               # Comment, also formatted to cover multiple
+                               # lines if necessary.
+*/
+
+static struct sortrec *s_head;
+
+static int s_max = 0;                  /* Number of elements allocated */
+static int s_count = 0;                        /* Number of elements used */
+
+static int Status;                     /* Nonzero if the user is
+                                          interested in status
+                                          information as well as
+                                          module name */
+static char def_status[] = "NONE";
+
+/* Sort routine for qsort:
+   - If we want the "Status" field to be sorted, check it first.
+   - Then compare the "module name" fields.  Since they are unique, we don't
+     have to look further.
+*/
+static int
+sort_order (const void *l, const void *r)
+{
+    int i;
+    const struct sortrec *left = (const struct sortrec *) l;
+    const struct sortrec *right = (const struct sortrec *) r;
+
+    if (Status)
+    {
+       /* If Sort by status field, compare them. */
+       if ((i = strcmp (left->status, right->status)) != 0)
+           return (i);
+    }
+    return (strcmp (left->modname, right->modname));
+}
+
+static void
+save_d (char *k, int ks, char *d, int ds)
+{
+    char *cp, *cp2;
+    struct sortrec *s_rec;
+
+    if (Status && *d == '-' && *(d + 1) == 'a')
+       return;                         /* We want "cvs co -s" and it is an 
alias! */
+
+    if (s_count == s_max)
+    {
+       s_max += 64;
+       s_head = xnrealloc (s_head, s_max, sizeof (*s_head));
+    }
+    s_rec = &s_head[s_count];
+    s_rec->modname = cp = xmalloc (ks + 1);
+    (void) strncpy (cp, k, ks);
+    *(cp + ks) = '\0';
+
+    s_rec->rest = cp2 = xmalloc (ds + 1);
+    cp = d;
+    *(cp + ds) = '\0'; /* Assumes an extra byte at end of static dbm buffer */
+
+    while (isspace ((unsigned char) *cp))
+       cp++;
+    /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */
+    while (*cp)
+    {
+       if (isspace ((unsigned char) *cp))
+       {
+           *cp2++ = ' ';
+           while (isspace ((unsigned char) *cp))
+               cp++;
+       }
+       else
+           *cp2++ = *cp++;
+    }
+    *cp2 = '\0';
+
+    /* Look for the "-s statusvalue" text */
+    if (Status)
+    {
+       s_rec->status = def_status;
+
+       for (cp = s_rec->rest; (cp2 = strchr (cp, '-')) != NULL; cp = ++cp2)
+       {
+           if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ')
+           {
+               char *status_start;
+
+               cp2 += 3;
+               status_start = cp2;
+               while (*cp2 != ' ' && *cp2 != '\0')
+                   cp2++;
+               s_rec->status = xmalloc (cp2 - status_start + 1);
+               strncpy (s_rec->status, status_start, cp2 - status_start);
+               s_rec->status[cp2 - status_start] = '\0';
+               cp = cp2;
+               break;
+           }
+       }
+    }
+    else
+       cp = s_rec->rest;
+
+    /* Find comment field, clean up on all three sides & compress blanks */
+    if ((cp2 = cp = strchr (cp, '#')) != NULL)
+    {
+       if (*--cp2 == ' ')
+           *cp2 = '\0';
+       if (*++cp == ' ')
+           cp++;
+       s_rec->comment = cp;
+    }
+    else
+       s_rec->comment = "";
+
+    s_count++;
+}
+
+/* Print out the module database as we know it.  If STATUS is
+   non-zero, print out status information for each module. */
+
+void
+cat_module (int status)
+{
+    DBM *db;
+    datum key, val;
+    int i, c, wid, argc, cols = 80, indent, fill;
+    int moduleargc;
+    struct sortrec *s_h;
+    char *cp, *cp2, **argv;
+    char **moduleargv;
+
+    Status = status;
+
+    /* Read the whole modules file into allocated records */
+    if (!(db = open_module ()))
+       error (1, 0, "failed to open the modules file");
+
+    for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db))
+    {
+       val = dbm_fetch (db, key);
+       if (val.dptr != NULL)
+           save_d (key.dptr, key.dsize, val.dptr, val.dsize);
+    }
+
+    close_module (db);
+
+    /* Sort the list as requested */
+    qsort ((void *) s_head, s_count, sizeof (struct sortrec), sort_order);
+
+    /*
+     * Run through the sorted array and format the entries
+     * indent = space for modulename + space for status field
+     */
+    indent = 12 + (status * 12);
+    fill = cols - (indent + 2);
+    for (s_h = s_head, i = 0; i < s_count; i++, s_h++)
+    {
+       char *line;
+
+       /* Print module name (and status, if wanted) */
+       line = Xasprintf ("%-12s", s_h->modname);
+       cvs_output (line, 0);
+       free (line);
+       if (status)
+       {
+           line = Xasprintf (" %-11s", s_h->status);
+           cvs_output (line, 0);
+           free (line);
+       }
+
+       /* Parse module file entry as command line and print options */
+       line = Xasprintf ("%s %s", s_h->modname, s_h->rest);
+       line2argv (&moduleargc, &moduleargv, line, " \t");
+       free (line);
+       argc = moduleargc;
+       argv = moduleargv;
+
+       optind = 0;
+       wid = 0;
+       while ((c = getopt (argc, argv, CVSMODULE_OPTS)) != -1)
+       {
+           if (!status)
+           {
+               if (c == 'a' || c == 'l')
+               {
+                   char buf[5];
+
+                   sprintf (buf, " -%c", c);
+                   cvs_output (buf, 0);
+                   wid += 3;           /* Could just set it to 3 */
+               }
+               else
+               {
+                   char buf[10];
+
+                   if (strlen (optarg) + 4 + wid > (unsigned) fill)
+                   {
+                       int j;
+
+                       cvs_output ("\n", 1);
+                       for (j = 0; j < indent; ++j)
+                           cvs_output (" ", 1);
+                       wid = 0;
+                   }
+                   sprintf (buf, " -%c ", c);
+                   cvs_output (buf, 0);
+                   cvs_output (optarg, 0);
+                   wid += strlen (optarg) + 4;
+               }
+           }
+       }
+       argc -= optind;
+       argv += optind;
+
+       /* Format and Print all the files and directories */
+       for (; argc--; argv++)
+       {
+           if (strlen (*argv) + wid > (unsigned) fill)
+           {
+               int j;
+
+               cvs_output ("\n", 1);
+               for (j = 0; j < indent; ++j)
+                   cvs_output (" ", 1);
+               wid = 0;
+           }
+           cvs_output (" ", 1);
+           cvs_output (*argv, 0);
+           wid += strlen (*argv) + 1;
+       }
+       cvs_output ("\n", 1);
+
+       /* Format the comment field -- save_d (), compressed spaces */
+       for (cp2 = cp = s_h->comment; *cp; cp2 = cp)
+       {
+           int j;
+
+           for (j = 0; j < indent; ++j)
+               cvs_output (" ", 1);
+           cvs_output (" # ", 0);
+           if (strlen (cp2) < (unsigned) (fill - 2))
+           {
+               cvs_output (cp2, 0);
+               cvs_output ("\n", 1);
+               break;
+           }
+           cp += fill - 2;
+           while (*cp != ' ' && cp > cp2)
+               cp--;
+           if (cp == cp2)
+           {
+               cvs_output (cp2, 0);
+               cvs_output ("\n", 1);
+               break;
+           }
+
+           *cp++ = '\0';
+           cvs_output (cp2, 0);
+           cvs_output ("\n", 1);
+       }
+
+       free_names(&moduleargc, moduleargv);
+       /* FIXME-leak: here is where we would free s_h->modname, s_h->rest,
+          and if applicable, s_h->status.  Not exactly a memory leak,
+          in the sense that we are about to exit(), but may be worth
+          noting if we ever do a multithreaded server or something of
+          the sort.  */
+    }
+    /* FIXME-leak: as above, here is where we would free s_head.  */
+}
Index: ccvs/src/recurse.c
diff -u /dev/null ccvs/src/recurse.c:1.114.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/recurse.c  Fri Jan  6 19:34:15 2006
@@ -0,0 +1,1375 @@
+/*
+ * 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.
+ *
+ * General recursion handler
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Verify interface.  */
+#include "recurse.h"
+
+/* GNULIB headers.  */
+#include "save-cwd.h"
+
+/* CVS headers.  */
+#include "cvs.h"
+#include "fileattr.h"
+#include "edit.h"
+
+
+
+static int do_dir_proc (Node * p, void *closure);
+static int do_file_proc (Node * p, void *closure);
+static void addlist (List ** listp, char *key);
+static int unroll_files_proc (Node *p, void *closure);
+static void addfile (List **listp, char *dir, char *file);
+
+static char *update_dir;
+static char *repository = NULL;
+static List *filelist = NULL; /* holds list of files on which to operate */
+static List *dirlist = NULL; /* holds list of directories on which to operate 
*/
+
+struct recursion_frame {
+    FILEPROC fileproc;
+    FILESDONEPROC filesdoneproc;
+    DIRENTPROC direntproc;
+    DIRLEAVEPROC dirleaveproc;
+    void *callerdat;
+    Dtype flags;
+    int which;
+    int aflag;
+    int locktype;
+    int dosrcs;
+    char *repository;                  /* Keep track of repository for rtag */
+};
+
+static int do_recursion (struct recursion_frame *frame);
+
+/* I am half tempted to shove a struct file_info * into the struct
+   recursion_frame (but then we would need to modify or create a
+   recursion_frame for each file), or shove a struct recursion_frame *
+   into the struct file_info (more tempting, although it isn't completely
+   clear that the struct file_info should contain info about recursion
+   processor internals).  So instead use this struct.  */
+
+struct frame_and_file {
+    struct recursion_frame *frame;
+    struct file_info *finfo;
+};
+
+/* Similarly, we need to pass the entries list to do_dir_proc.  */
+
+struct frame_and_entries {
+    struct recursion_frame *frame;
+    List *entries;
+};
+
+
+/* Start a recursive command.
+ *
+ * INPUT
+ *
+ *   fileproc
+ *     Function called with each file as an argument.
+ *
+ *   filesdoneproc
+ *     Function called after all the files in a directory have been processed,
+ *     before subdirectories have been processed.
+ *
+ *   direntproc
+ *     Function called immediately upon entering a directory, before processing
+ *     any files or subdirectories.
+ *
+ *   dirleaveproc
+ *     Function called upon finishing a directory, immediately before leaving
+ *     it and returning control to the function processing the parent
+ *     directory.
+ *
+ *   callerdat
+ *     This void * is passed to the functions above.
+ *
+ *   argc, argv
+ *     The files on which to operate, interpreted as command line arguments.
+ *     In the special case of no arguments, defaults to operating on the
+ *     current directory (`.').
+ *
+ *   local
+ *
+ *   which
+ *     specifies the kind of recursion.  There are several cases:
+ *
+ *       1.  W_LOCAL is not set but W_REPOS or W_ATTIC is.  The current
+ *       directory when we are called must be the repository and
+ *       recursion proceeds according to what exists in the repository.
+ *
+ *       2a.  W_LOCAL is set but W_REPOS and W_ATTIC are not.  The
+ *       current directory when we are called must be the working
+ *       directory.  Recursion proceeds according to what exists in the
+ *       working directory, never (I think) consulting any part of the
+ *       repository which does not correspond to the working directory
+ *       ("correspond" == Name_Repository).
+ *
+ *       2b.  W_LOCAL is set and so is W_REPOS or W_ATTIC.  This is the
+ *       weird one.  The current directory when we are called must be
+ *       the working directory.  We recurse through working directories,
+ *       but we recurse into a directory if it is exists in the working
+ *       directory *or* it exists in the repository.  If a directory
+ *       does not exist in the working directory, the direntproc must
+ *       either tell us to skip it (R_SKIP_ALL), or must create it (I
+ *       think those are the only two cases).
+ *
+ *   aflag
+ *   locktype
+ *   update_preload
+ *   dosrcs
+ *
+ *   repository_in
+ *     keeps track of the repository string.  This is only for the remote mode,
+ *     specifically, r* commands (rtag, rdiff, co, ...) where xgetcwd() was 
used
+ *     to locate the repository.  Things would break when xgetcwd() was used
+ *     with a symlinked repository because xgetcwd() would return the true path
+ *     and in some cases this would cause the path to be printed as other than
+ *     the user specified in error messages and in other cases some of CVS's
+ *     security assertions would fail.
+ *
+ * GLOBALS
+ *   ???
+ *
+ * OUTPUT
+ *
+ *  callerdat can be modified by the FILEPROC, FILESDONEPROC, DIRENTPROC, and
+ *  DIRLEAVEPROC.
+ *
+ * RETURNS
+ *   A count of errors counted by walking the argument list with
+ *   unroll_files_proc() and do_recursion().
+ *
+ * ERRORS
+ *   Fatal errors occur:
+ *     1.  when there were no arguments and the current directory
+ *         does not contain CVS metadata.
+ *     2.  when all but the last path element from an argument from ARGV cannot
+ *         be found to be a local directory.
+ */
+int
+start_recursion (FILEPROC fileproc, FILESDONEPROC filesdoneproc,
+                 DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc,
+                 void *callerdat, int argc, char **argv, int local,
+                 int which, int aflag, int locktype,
+                 char *update_preload, int dosrcs, char *repository_in)
+{
+    int i, err = 0;
+#ifdef CLIENT_SUPPORT
+    List *args_to_send_when_finished = NULL;
+#endif
+    List *files_by_dir = NULL;
+    struct recursion_frame frame;
+
+#ifdef HAVE_PRINTF_PTR
+    TRACE ( TRACE_FLOW,
+           "start_recursion ( fileproc=%p, filesdoneproc=%p,\n"
+       "                       direntproc=%p, dirleavproc=%p,\n"
+       "                       callerdat=%p, argc=%d, argv=%p,\n"
+       "                       local=%d, which=%d, aflag=%d,\n"
+       "                       locktype=%d, update_preload=%s\n"
+       "                       dosrcs=%d, repository_in=%s )",
+              (void *) fileproc, (void *) filesdoneproc,
+              (void *) direntproc, (void *) dirleaveproc,
+              (void *) callerdat, argc, (void *) argv,
+              local, which, aflag, locktype,
+              update_preload ? update_preload : "(null)", dosrcs,
+              repository_in ? repository_in : "(null)");
+#else
+    TRACE ( TRACE_FLOW,
+           "start_recursion ( fileproc=%lx, filesdoneproc=%lx,\n"
+       "                       direntproc=%lx, dirleavproc=%lx,\n"
+       "                       callerdat=%lx, argc=%d, argv=%lx,\n"
+       "                       local=%d, which=%d, aflag=%d,\n"
+       "                       locktype=%d, update_preload=%s\n"
+       "                       dosrcs=%d, repository_in=%s )",
+              (unsigned long) fileproc, (unsigned long) filesdoneproc,
+              (unsigned long) direntproc, (unsigned long) dirleaveproc,
+              (unsigned long) callerdat, argc, (unsigned long) argv,
+              local, which, aflag, locktype,
+              update_preload ? update_preload : "(null)", dosrcs,
+              repository_in ? repository_in : "(null)");
+#endif
+
+    frame.fileproc = fileproc;
+    frame.filesdoneproc = filesdoneproc;
+    frame.direntproc = direntproc;
+    frame.dirleaveproc = dirleaveproc;
+    frame.callerdat = callerdat;
+    frame.flags = local ? R_SKIP_DIRS : R_PROCESS;
+    frame.which = which;
+    frame.aflag = aflag;
+    frame.locktype = locktype;
+    frame.dosrcs = dosrcs;
+
+    /* If our repository_in has a trailing "/.", remove it before storing it
+     * for do_recursion().
+     *
+     * FIXME: This is somewhat of a hack in the sense that many of our callers
+     * painstakingly compute and add the trailing '.' we now remove.
+     */
+    while (repository_in && strlen (repository_in) >= 2
+           && repository_in[strlen (repository_in) - 2] == '/'
+           && repository_in[strlen (repository_in) - 1] == '.')
+    {
+       /* Beware the case where the string is exactly "/." or "//.".
+        * Paths with a leading "//" are special on some early UNIXes.
+        */
+       if (strlen (repository_in) == 2 || strlen (repository_in) == 3)
+           repository_in[strlen (repository_in) - 1] = '\0';
+       else
+           repository_in[strlen (repository_in) - 2] = '\0';
+    }
+    frame.repository = repository_in;
+
+    expand_wild (argc, argv, &argc, &argv);
+
+    if (update_preload == NULL)
+       update_dir = xstrdup ("");
+    else
+       update_dir = xstrdup (update_preload);
+
+    /* clean up from any previous calls to start_recursion */
+    if (repository)
+    {
+       free (repository);
+       repository = NULL;
+    }
+    if (filelist)
+       dellist (&filelist); /* FIXME-krp: no longer correct. */
+    if (dirlist)
+       dellist (&dirlist);
+
+#ifdef SERVER_SUPPORT
+    if (server_active)
+    {
+       for (i = 0; i < argc; ++i)
+           server_pathname_check (argv[i]);
+    }
+#endif
+
+    if (argc == 0)
+    {
+       int just_subdirs = (which & W_LOCAL) && !isdir (CVSADM);
+
+#ifdef CLIENT_SUPPORT
+       if (!just_subdirs
+           && CVSroot_cmdline == NULL
+           && current_parsed_root->isremote)
+       {
+           cvsroot_t *root = Name_Root (NULL, update_dir);
+           if (root)
+           {
+               if (strcmp (root->original, original_parsed_root->original))
+                   /* We're skipping this directory because it is for
+                    * a different root.  Therefore, we just want to
+                    * do the subdirectories only.  Processing files would
+                    * cause a working directory from one repository to be
+                    * processed against a different repository, which could
+                    * cause all kinds of spurious conflicts and such.
+                    *
+                    * Question: what about the case of "cvs update foo"
+                    * where we process foo/bar and not foo itself?  That
+                    * seems to be handled somewhere (else) but why should
+                    * it be a separate case?  Needs investigation...  */
+                   just_subdirs = 1;
+           }
+       }
+#endif
+
+       /*
+        * There were no arguments, so we'll probably just recurse. The
+        * exception to the rule is when we are called from a directory
+        * without any CVS administration files.  That has always meant to
+        * process each of the sub-directories, so we pretend like we were
+        * called with the list of sub-dirs of the current dir as args
+        */
+       if (just_subdirs)
+       {
+           dirlist = Find_Directories (NULL, W_LOCAL, NULL);
+           /* If there are no sub-directories, there is a certain logic in
+              favor of doing nothing, but in fact probably the user is just
+              confused about what directory they are in, or whether they
+              cvs add'd a new directory.  In the case of at least one
+              sub-directory, at least when we recurse into them we
+              notice (hopefully) whether they are under CVS control.  */
+           if (list_isempty (dirlist))
+           {
+               if (update_dir[0] == '\0')
+                   error (0, 0, "in directory .:");
+               else
+                   error (0, 0, "in directory %s:", update_dir);
+               error (1, 0,
+                      "there is no version here; run '%s checkout' first",
+                      program_name);
+           }
+#ifdef CLIENT_SUPPORT
+           else if (current_parsed_root->isremote && server_started)
+           {
+               /* In the the case "cvs update foo bar baz", a call to
+                  send_file_names in update.c will have sent the
+                  appropriate "Argument" commands to the server.  In
+                  this case, that won't have happened, so we need to
+                  do it here.  While this example uses "update", this
+                  generalizes to other commands.  */
+
+               /* This is the same call to Find_Directories as above.
+                   FIXME: perhaps it would be better to write a
+                   function that duplicates a list. */
+               args_to_send_when_finished = Find_Directories (NULL,
+                                                              W_LOCAL,
+                                                              NULL);
+           }
+#endif
+       }
+       else
+           addlist (&dirlist, ".");
+
+       goto do_the_work;
+    }
+
+
+    /*
+     * There were arguments, so we have to handle them by hand. To do
+     * that, we set up the filelist and dirlist with the arguments and
+     * call do_recursion.  do_recursion recognizes the fact that the
+     * lists are non-null when it starts and doesn't update them.
+     *
+     * explicitly named directories are stored in dirlist.
+     * explicitly named files are stored in filelist.
+     * other possibility is named entities whicha are not currently in
+     * the working directory.
+     */
+
+    for (i = 0; i < argc; i++)
+    {
+       /* if this argument is a directory, then add it to the list of
+          directories. */
+
+       if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
+       {
+           strip_trailing_slashes (argv[i]);
+           addlist (&dirlist, argv[i]);
+       }
+       else
+       {
+           /* otherwise, split argument into directory and component names. */
+           char *dir;
+           char *comp;
+           char *file_to_try;
+
+           /* Now break out argv[i] into directory part (DIR) and file part
+            * (COMP).  DIR and COMP will each point to a newly malloc'd
+            * string.
+            */
+           dir = xstrdup (argv[i]);
+           /* It's okay to cast out last_component's const below since we know
+            * we just allocated dir here and have control of it.
+            */
+           comp = (char *)last_component (dir);
+           if (comp == dir)
+           {
+               /* no dir component.  What we have is an implied "./" */
+               dir = xstrdup(".");
+           }
+           else
+           {
+               comp[-1] = '\0';
+               comp = xstrdup (comp);
+           }
+
+           /* if this argument exists as a file in the current
+              working directory tree, then add it to the files list.  */
+
+           if (!(which & W_LOCAL))
+           {
+               /* If doing rtag, we've done a chdir to the repository. */
+               file_to_try = Xasprintf ("%s%s", argv[i], RCSEXT);
+           }
+           else
+               file_to_try = xstrdup (argv[i]);
+
+           if (isfile (file_to_try))
+               addfile (&files_by_dir, dir, comp);
+           else if (isdir (dir))
+           {
+               if ((which & W_LOCAL) && isdir (CVSADM) &&
+                   !current_parsed_root->isremote)
+               {
+                   /* otherwise, look for it in the repository. */
+                   char *tmp_update_dir;
+                   char *repos;
+                   char *reposfile;
+
+                   tmp_update_dir = xmalloc (strlen (update_dir)
+                                             + strlen (dir)
+                                             + 5);
+                   strcpy (tmp_update_dir, update_dir);
+
+                   if (*tmp_update_dir != '\0')
+                       strcat (tmp_update_dir, "/");
+
+                   strcat (tmp_update_dir, dir);
+
+                   /* look for it in the repository. */
+                   repos = Name_Repository (dir, tmp_update_dir);
+                   reposfile = Xasprintf ("%s/%s", repos, comp);
+                   free (repos);
+
+                   if (!wrap_name_has (comp, WRAP_TOCVS) && isdir (reposfile))
+                       addlist (&dirlist, argv[i]);
+                   else
+                       addfile (&files_by_dir, dir, comp);
+
+                   free (tmp_update_dir);
+                   free (reposfile);
+               }
+               else
+                   addfile (&files_by_dir, dir, comp);
+           }
+           else
+               error (1, 0, "no such directory `%s'", dir);
+
+           free (file_to_try);
+           free (dir);
+           free (comp);
+       }
+    }
+
+    /* At this point we have looped over all named arguments and built
+       a coupla lists.  Now we unroll the lists, setting up and
+       calling do_recursion. */
+
+    err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
+    dellist(&files_by_dir);
+
+    /* then do_recursion on the dirlist. */
+    if (dirlist != NULL)
+    {
+    do_the_work:
+       err += do_recursion (&frame);
+    }
+       
+    /* Free the data which expand_wild allocated.  */
+    free_names (&argc, argv);
+
+    free (update_dir);
+    update_dir = NULL;
+
+#ifdef CLIENT_SUPPORT
+    if (args_to_send_when_finished != NULL)
+    {
+       /* FIXME (njc): in the multiroot case, we don't want to send
+          argument commands for those top-level directories which do
+          not contain any subdirectories which have files checked out
+          from current_parsed_root.  If we do, and two repositories
+          have a module with the same name, nasty things could happen.
+
+          This is hard.  Perhaps we should send the Argument commands
+          later in this procedure, after we've had a chance to notice
+          which directores we're using (after do_recursion has been
+          called once).  This means a _lot_ of rewriting, however.
+
+          What we need to do for that to happen is descend the tree
+          and construct a list of directories which are checked out
+          from current_cvsroot.  Now, we eliminate from the list all
+          of those directories which are immediate subdirectories of
+          another directory in the list.  To say that the opposite
+          way, we keep the directories which are not immediate
+          subdirectories of any other in the list.  Here's a picture:
+
+                             a
+                            / \
+                           B   C
+                          / \
+                         D   e
+                            / \
+                           F   G
+                              / \
+                             H   I
+
+          The node in capitals are those directories which are
+          checked out from current_cvsroot.  We want the list to
+          contain B, C, F, and G.  D, H, and I are not included,
+          because their parents are also checked out from
+          current_cvsroot.
+
+          The algorithm should be:
+
+          1) construct a tree of all directory names where each
+          element contains a directory name and a flag which notes if
+          that directory is checked out from current_cvsroot
+
+                             a0
+                            / \
+                           B1  C1
+                          / \
+                         D1  e0
+                            / \
+                           F1  G1
+                              / \
+                             H1  I1
+
+          2) Recursively descend the tree.  For each node, recurse
+          before processing the node.  If the flag is zero, do
+          nothing.  If the flag is 1, check the node's parent.  If
+          the parent's flag is one, change the current entry's flag
+          to zero.
+
+                             a0
+                            / \
+                           B1  C1
+                          / \
+                         D0  e0
+                            / \
+                           F1  G1
+                              / \
+                             H0  I0
+
+          3) Walk the tree and spit out "Argument" commands to tell
+          the server which directories to munge.
+
+          Yuck.  It's not clear this is worth spending time on, since
+          we might want to disable cvs commands entirely from
+          directories that do not have CVSADM files...
+
+          Anyways, the solution as it stands has modified server.c
+          (dirswitch) to create admin files [via server.c
+          (create_adm_p)] in all path elements for a client's
+          "Directory xxx" command, which forces the server to descend
+          and serve the files there.  client.c (send_file_names) has
+          also been modified to send only those arguments which are
+          appropriate to current_parsed_root.
+
+       */
+               
+       /* Construct a fake argc/argv pair. */
+               
+       int our_argc = 0, i;
+       char **our_argv = NULL;
+
+       if (! list_isempty (args_to_send_when_finished))
+       {
+           Node *head, *p;
+
+           head = args_to_send_when_finished->list;
+
+           /* count the number of nodes */
+           i = 0;
+           for (p = head->next; p != head; p = p->next)
+               i++;
+           our_argc = i;
+
+           /* create the argument vector */
+           our_argv = xmalloc (sizeof (char *) * our_argc);
+
+           /* populate it */
+           i = 0;
+           for (p = head->next; p != head; p = p->next)
+               our_argv[i++] = xstrdup (p->key);
+       }
+
+       /* We don't want to expand widcards, since we've just created
+          a list of directories directly from the filesystem. */
+       send_file_names (our_argc, our_argv, 0);
+
+       /* Free our argc/argv. */
+       if (our_argv != NULL)
+       {
+           for (i = 0; i < our_argc; i++)
+               free (our_argv[i]);
+           free (our_argv);
+       }
+
+       dellist (&args_to_send_when_finished);
+    }
+#endif
+
+    return err;
+}
+
+
+
+/*
+ * Implement the recursive policies on the local directory.  This may be
+ * called directly, or may be called by start_recursion.
+ */
+static int
+do_recursion (struct recursion_frame *frame)
+{
+    int err = 0;
+    int dodoneproc = 1;
+    char *srepository = NULL;
+    List *entries = NULL;
+    int locktype;
+    bool process_this_directory = true;
+
+#ifdef HAVE_PRINT_PTR
+    TRACE (TRACE_FLOW, "do_recursion ( frame=%p )", (void *) frame);
+#else
+    TRACE (TRACE_FLOW, "do_recursion ( frame=%lx )", (unsigned long) frame);
+#endif
+
+    /* do nothing if told */
+    if (frame->flags == R_SKIP_ALL)
+       return 0;
+
+    locktype = noexec ? CVS_LOCK_NONE : frame->locktype;
+
+    /* The fact that locks are not active here is what makes us fail to have
+       the
+
+           If someone commits some changes in one cvs command,
+          then an update by someone else will either get all the
+          changes, or none of them.
+
+       property (see node Concurrency in cvs.texinfo).
+
+       The most straightforward fix would just to readlock the whole
+       tree before starting an update, but that means that if a commit
+       gets blocked on a big update, it might need to wait a *long*
+       time.
+
+       A more adequate fix would be a two-pass design for update,
+       checkout, etc.  The first pass would go through the repository,
+       with the whole tree readlocked, noting what versions of each
+       file we want to get.  The second pass would release all locks
+       (except perhaps short-term locks on one file at a
+       time--although I think RCS already deals with this) and
+       actually get the files, specifying the particular versions it wants.
+
+       This could be sped up by separating out the data needed for the
+       first pass into a separate file(s)--for example a file
+       attribute for each file whose value contains the head revision
+       for each branch.  The structure should be designed so that
+       commit can relatively quickly update the information for a
+       single file or a handful of files (file attributes, as
+       implemented in Jan 96, are probably acceptable; improvements
+       would be possible such as branch attributes which are in
+       separate files for each branch).  */
+
+#if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
+    /*
+     * Now would be a good time to check to see if we need to stop
+     * generating data, to give the buffers a chance to drain to the
+     * remote client.  We should not have locks active at this point,
+     * but if there are writelocks around, we cannot pause here.  */
+    if (server_active && locktype != CVS_LOCK_WRITE)
+       server_pause_check();
+#endif
+
+    /* Check the value in CVSADM_ROOT and see if it's in the list.  If
+       not, add it to our lists of CVS/Root directories and do not
+       process the files in this directory.  Otherwise, continue as
+       usual.  THIS_ROOT might be NULL if we're doing an initial
+       checkout -- check before using it.  The default should be that
+       we process a directory's contents and only skip those contents
+       if a CVS/Root file exists.
+
+       If we're running the server, we want to process all
+       directories, since we're guaranteed to have only one CVSROOT --
+       our own.  */
+
+    /* If -d was specified, it should override CVS/Root.
+
+       In the single-repository case, it is long-standing CVS behavior
+       and makes sense - the user might want another access method,
+       another server (which mounts the same repository), &c.
+
+       In the multiple-repository case, -d overrides all CVS/Root
+       files.  That is the only plausible generalization I can
+       think of.  */
+    if (CVSroot_cmdline == NULL && !server_active)
+    {
+       cvsroot_t *this_root = Name_Root (NULL, update_dir);
+       if (this_root != NULL)
+       {
+           if (findnode (root_directories, this_root->original))
+           {
+               process_this_directory =
+                   !strcmp (original_parsed_root->original,
+                            this_root->original);
+           }
+           else
+           {
+               /* Add it to our list. */
+
+               Node *n = getnode ();
+               n->type = NT_UNKNOWN;
+               n->key = xstrdup (this_root->original);
+               n->data = this_root;
+
+               if (addnode (root_directories, n))
+                   error (1, 0, "cannot add new CVSROOT %s",
+                          this_root->original);
+
+               process_this_directory = false;
+           }
+       }
+    }
+
+    /*
+     * Fill in repository with the current repository
+     */
+    if (frame->which & W_LOCAL)
+    {
+       if (isdir (CVSADM))
+       {
+           repository = Name_Repository (NULL, update_dir);
+           srepository = repository;           /* remember what to free */
+       }
+       else
+           repository = NULL;
+    }
+    else
+    {
+       repository = frame->repository;
+       assert (repository != NULL);
+       assert (strstr (repository, "/./") == NULL);
+    }
+
+    fileattr_startdir (repository);
+
+    /*
+     * The filesdoneproc needs to be called for each directory where files
+     * processed, or each directory that is processed by a call where no
+     * directories were passed in.  In fact, the only time we don't want to
+     * call back the filesdoneproc is when we are processing directories that
+     * were passed in on the command line (or in the special case of `.' when
+     * we were called with no args
+     */
+    if (dirlist != NULL && filelist == NULL)
+       dodoneproc = 0;
+
+    /*
+     * If filelist or dirlist is already set, we don't look again. Otherwise,
+     * find the files and directories
+     */
+    if (filelist == NULL && dirlist == NULL)
+    {
+       /* both lists were NULL, so start from scratch */
+       if (frame->fileproc != NULL && frame->flags != R_SKIP_FILES)
+       {
+           int lwhich = frame->which;
+
+           /* be sure to look in the attic if we have sticky tags/date */
+           if ((lwhich & W_ATTIC) == 0)
+               if (isreadable (CVSADM_TAG))
+                   lwhich |= W_ATTIC;
+
+           /* In the !(which & W_LOCAL) case, we filled in repository
+              earlier in the function.  In the (which & W_LOCAL) case,
+              the Find_Names function is going to look through the
+              Entries file.  If we do not have a repository, that
+              does not make sense, so we insist upon having a
+              repository at this point.  Name_Repository will give a
+              reasonable error message.  */
+           if (repository == NULL)
+           {
+               Name_Repository (NULL, update_dir);
+               assert (!"Not reached.  Please report this problem to <"
+                       PACKAGE_BUGREPORT ">");
+           }
+           /* find the files and fill in entries if appropriate */
+           if (process_this_directory)
+           {
+               filelist = Find_Names (repository, lwhich, frame->aflag,
+                                      &entries);
+               if (filelist == NULL)
+               {
+                   error (0, 0, "skipping directory %s", update_dir);
+                   /* Note that Find_Directories and the filesdoneproc
+                      in particular would do bad things ("? foo.c" in
+                      the case of some filesdoneproc's).  */
+                   goto skip_directory;
+               }
+           }
+       }
+
+       /* find sub-directories if we will recurse */
+       if (frame->flags != R_SKIP_DIRS)
+           dirlist = Find_Directories (
+               process_this_directory ? repository : NULL,
+               frame->which, entries);
+    }
+    else
+    {
+       /* something was passed on the command line */
+       if (filelist != NULL && frame->fileproc != NULL)
+       {
+           /* we will process files, so pre-parse entries */
+           if (frame->which & W_LOCAL)
+               entries = Entries_Open (frame->aflag, NULL);
+       }
+    }
+
+    /* process the files (if any) */
+    if (process_this_directory && filelist != NULL && frame->fileproc)
+    {
+       struct file_info finfo_struct;
+       struct frame_and_file frfile;
+
+       /* Lock the repository, if necessary. */
+       if (repository)
+       {
+           if (locktype == CVS_LOCK_READ)
+           {
+               if (Reader_Lock (repository) != 0)
+                   error (1, 0, "read lock failed - giving up");
+           }
+           else if (locktype == CVS_LOCK_WRITE)
+               lock_dir_for_write (repository);
+       }
+
+#ifdef CLIENT_SUPPORT
+       /* For the server, we handle notifications in a completely different
+          place (server_notify).  For local, we can't do them here--we don't
+          have writelocks in place, and there is no way to get writelocks
+          here.  */
+       if (current_parsed_root->isremote)
+           notify_check (repository, update_dir);
+#endif /* CLIENT_SUPPORT */
+
+       finfo_struct.repository = repository;
+       finfo_struct.update_dir = update_dir;
+       finfo_struct.entries = entries;
+       /* do_file_proc will fill in finfo_struct.file.  */
+
+       frfile.finfo = &finfo_struct;
+       frfile.frame = frame;
+
+       /* process the files */
+       err += walklist (filelist, do_file_proc, &frfile);
+
+       /* unlock it */
+       if (/* We only lock the repository above when repository is set */
+           repository
+           /* and when asked for a read or write lock. */
+           && locktype != CVS_LOCK_NONE)
+           Simple_Lock_Cleanup ();
+
+       /* clean up */
+       dellist (&filelist);
+    }
+
+    /* call-back files done proc (if any) */
+    if (process_this_directory && dodoneproc && frame->filesdoneproc != NULL)
+       err = frame->filesdoneproc (frame->callerdat, err, repository,
+                                   update_dir[0] ? update_dir : ".",
+                                   entries);
+
+ skip_directory:
+    fileattr_write ();
+    fileattr_free ();
+
+    /* process the directories (if necessary) */
+    if (dirlist != NULL)
+    {
+       struct frame_and_entries frent;
+
+       frent.frame = frame;
+       frent.entries = entries;
+       err += walklist (dirlist, do_dir_proc, &frent);
+    }
+#if 0
+    else if (frame->dirleaveproc != NULL)
+       err += frame->dirleaveproc (frame->callerdat, ".", err, ".");
+#endif
+    dellist (&dirlist);
+
+    if (entries)
+    {
+       Entries_Close (entries);
+       entries = NULL;
+    }
+
+    /* free the saved copy of the pointer if necessary */
+    if (srepository)
+       free (srepository);
+    repository = NULL;
+
+#ifdef HAVE_PRINT_PTR
+    TRACE (TRACE_FLOW, "Leaving do_recursion (frame=%p)", (void *)frame);
+#else
+    TRACE (TRACE_FLOW, "Leaving do_recursion (frame=%lx)",
+          (unsigned long)frame);
+#endif
+
+    return err;
+}
+
+
+
+/*
+ * Process each of the files in the list with the callback proc
+ *
+ * NOTES
+ *    Fills in FINFO->fullname, and sometimes FINFO->rcs before
+ *    calling the callback proc (FRFILE->frame->fileproc), but frees them
+ *    before return.
+ *
+ * OUTPUTS
+ *    Fills in FINFO->file.
+ *
+ * RETURNS
+ *    0 if we were supposed to find an RCS file but couldn't.
+ *    Otherwise, returns the error code returned by the callback function.
+ */
+static int
+do_file_proc (Node *p, void *closure)
+{
+    struct frame_and_file *frfile = closure;
+    struct file_info *finfo = frfile->finfo;
+    int ret;
+    char *tmp;
+
+    finfo->file = p->key;
+    if (finfo->update_dir[0] != '\0')
+       tmp = Xasprintf ("%s/%s", finfo->update_dir, finfo->file);
+    else
+       tmp = xstrdup (finfo->file);
+
+    if (frfile->frame->dosrcs && repository)
+    {
+       finfo->rcs = RCS_parse (finfo->file, repository);
+
+       /* OK, without W_LOCAL the error handling becomes relatively
+          simple.  The file names came from readdir() on the
+          repository and so we know any ENOENT is an error
+          (e.g. symlink pointing to nothing).  Now, the logic could
+          be simpler - since we got the name from readdir, we could
+          just be calling RCS_parsercsfile.  */
+       if (finfo->rcs == NULL
+           && !(frfile->frame->which & W_LOCAL))
+       {
+           error (0, 0, "could not read RCS file for %s", tmp);
+           free (tmp);
+           cvs_flushout ();
+           return 0;
+       }
+    }
+    else
+        finfo->rcs = NULL;
+    finfo->fullname = tmp;
+    ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);
+
+    freercsnode (&finfo->rcs);
+    free (tmp);
+
+    /* Allow the user to monitor progress with tail -f.  Doing this once
+       per file should be no big deal, but we don't want the performance
+       hit of flushing on every line like previous versions of CVS.  */
+    cvs_flushout ();
+
+    return ret;
+}
+
+
+
+/*
+ * Process each of the directories in the list (recursing as we go)
+ */
+static int
+do_dir_proc (Node *p, void *closure)
+{
+    struct frame_and_entries *frent = (struct frame_and_entries *) closure;
+    struct recursion_frame *frame = frent->frame;
+    struct recursion_frame xframe;
+    char *dir = p->key;
+    char *newrepos;
+    List *sdirlist;
+    char *srepository;
+    Dtype dir_return = R_PROCESS;
+    int stripped_dot = 0;
+    int err = 0;
+    struct saved_cwd cwd;
+    char *saved_update_dir;
+    bool process_this_directory = true;
+
+    if (fncmp (dir, CVSADM) == 0)
+    {
+       /* This seems to most often happen when users (beginning users,
+          generally), try "cvs ci *" or something similar.  On that
+          theory, it is possible that we should just silently skip the
+          CVSADM directories, but on the other hand, using a wildcard
+          like this isn't necessarily a practice to encourage (it operates
+          only on files which exist in the working directory, unlike
+          regular CVS recursion).  */
+
+       /* FIXME-reentrancy: printed_cvs_msg should be in a "command
+          struct" or some such, so that it gets cleared for each new
+          command (this is possible using the remote protocol and a
+          custom-written client).  The struct recursion_frame is not
+          far back enough though, some commands (commit at least)
+          will call start_recursion several times.  An alternate solution
+          would be to take this whole check and move it to a new function
+          validate_arguments or some such that all the commands call
+          and which snips the offending directory from the argc,argv
+          vector.  */
+       static int printed_cvs_msg = 0;
+       if (!printed_cvs_msg)
+       {
+           error (0, 0, "warning: directory %s specified in argument",
+                  dir);
+           error (0, 0, "\
+but CVS uses %s for its own purposes; skipping %s directory",
+                  CVSADM, dir);
+           printed_cvs_msg = 1;
+       }
+       return 0;
+    }
+
+    saved_update_dir = update_dir;
+    update_dir = xmalloc (strlen (saved_update_dir)
+                         + strlen (dir)
+                         + 5);
+    strcpy (update_dir, saved_update_dir);
+
+    /* set up update_dir - skip dots if not at start */
+    if (strcmp (dir, ".") != 0)
+    {
+       if (update_dir[0] != '\0')
+       {
+           (void) strcat (update_dir, "/");
+           (void) strcat (update_dir, dir);
+       }
+       else
+           (void) strcpy (update_dir, dir);
+
+       /*
+        * Here we need a plausible repository name for the sub-directory. We
+        * create one by concatenating the new directory name onto the
+        * previous repository name.  The only case where the name should be
+        * used is in the case where we are creating a new sub-directory for
+        * update -d and in that case the generated name will be correct.
+        */
+       if (repository == NULL)
+           newrepos = xstrdup ("");
+       else
+           newrepos = Xasprintf ("%s/%s", repository, dir);
+    }
+    else
+    {
+       if (update_dir[0] == '\0')
+           (void) strcpy (update_dir, dir);
+
+       if (repository == NULL)
+           newrepos = xstrdup ("");
+       else
+           newrepos = xstrdup (repository);
+    }
+
+    /* Check to see that the CVSADM directory, if it exists, seems to be
+       well-formed.  It can be missing files if the user hit ^C in the
+       middle of a previous run.  We want to (a) make this a nonfatal
+       error, and (b) make sure we print which directory has the
+       problem.
+
+       Do this before the direntproc, so that (1) the direntproc
+       doesn't have to guess/deduce whether we will skip the directory
+       (e.g. send_dirent_proc and whether to send the directory), and
+       (2) so that the warm fuzzy doesn't get printed if we skip the
+       directory.  */
+    if (frame->which & W_LOCAL)
+    {
+       char *cvsadmdir;
+
+       cvsadmdir = xmalloc (strlen (dir)
+                            + sizeof (CVSADM_REP)
+                            + sizeof (CVSADM_ENT)
+                            + 80);
+
+       strcpy (cvsadmdir, dir);
+       strcat (cvsadmdir, "/");
+       strcat (cvsadmdir, CVSADM);
+       if (isdir (cvsadmdir))
+       {
+           strcpy (cvsadmdir, dir);
+           strcat (cvsadmdir, "/");
+           strcat (cvsadmdir, CVSADM_REP);
+           if (!isfile (cvsadmdir))
+           {
+               /* Some commands like update may have printed "? foo" but
+                  if we were planning to recurse, and don't on account of
+                  CVS/Repository, we want to say why.  */
+               error (0, 0, "ignoring %s (%s missing)", update_dir,
+                      CVSADM_REP);
+               dir_return = R_SKIP_ALL;
+           }
+
+           /* Likewise for CVS/Entries.  */
+           if (dir_return != R_SKIP_ALL)
+           {
+               strcpy (cvsadmdir, dir);
+               strcat (cvsadmdir, "/");
+               strcat (cvsadmdir, CVSADM_ENT);
+               if (!isfile (cvsadmdir))
+               {
+                   /* Some commands like update may have printed "? foo" but
+                      if we were planning to recurse, and don't on account of
+                      CVS/Repository, we want to say why.  */
+                   error (0, 0, "ignoring %s (%s missing)", update_dir,
+                          CVSADM_ENT);
+                   dir_return = R_SKIP_ALL;
+               }
+           }
+       }
+       free (cvsadmdir);
+    }
+
+    /* Only process this directory if the root matches.  This nearly
+       duplicates code in do_recursion. */
+
+    /* If -d was specified, it should override CVS/Root.
+
+       In the single-repository case, it is long-standing CVS behavior
+       and makes sense - the user might want another access method,
+       another server (which mounts the same repository), &c.
+
+       In the multiple-repository case, -d overrides all CVS/Root
+       files.  That is the only plausible generalization I can
+       think of.  */
+    if (CVSroot_cmdline == NULL && !server_active)
+    {
+       cvsroot_t *this_root = Name_Root (dir, update_dir);
+       if (this_root != NULL)
+       {
+           if (findnode (root_directories, this_root->original))
+           {
+               process_this_directory =
+                   !strcmp (original_parsed_root->original,
+                            this_root->original);
+           }
+           else
+           {
+               /* Add it to our list. */
+
+               Node *n = getnode ();
+               n->type = NT_UNKNOWN;
+               n->key = xstrdup (this_root->original);
+               n->data = this_root;
+
+               if (addnode (root_directories, n))
+                   error (1, 0, "cannot add new CVSROOT %s",
+                          this_root->original);
+
+               process_this_directory = false;
+           }
+       }
+    }
+
+    /* call-back dir entry proc (if any) */
+    if (dir_return == R_SKIP_ALL)
+       ;
+    else if (frame->direntproc != NULL)
+    {
+       /* If we're doing the actual processing, call direntproc.
+           Otherwise, assume that we need to process this directory
+           and recurse. FIXME. */
+
+       if (process_this_directory)
+           dir_return = frame->direntproc (frame->callerdat, dir, newrepos,
+                                           update_dir, frent->entries);
+       else
+           dir_return = R_PROCESS;
+    }
+    else
+    {
+       /* Generic behavior.  I don't see a reason to make the caller specify
+          a direntproc just to get this.  */
+       if ((frame->which & W_LOCAL) && !isdir (dir))
+           dir_return = R_SKIP_ALL;
+    }
+
+    free (newrepos);
+
+    /* only process the dir if the return code was 0 */
+    if (dir_return != R_SKIP_ALL)
+    {
+       /* save our current directory and static vars */
+        if (save_cwd (&cwd))
+           error (1, errno, "Failed to save current directory.");
+       sdirlist = dirlist;
+       srepository = repository;
+       dirlist = NULL;
+
+       /* cd to the sub-directory */
+       if (CVS_CHDIR (dir) < 0)
+           error (1, errno, "could not chdir to %s", dir);
+
+       /* honor the global SKIP_DIRS (a.k.a. local) */
+       if (frame->flags == R_SKIP_DIRS)
+           dir_return = R_SKIP_DIRS;
+
+       /* remember if the `.' will be stripped for subsequent dirs */
+       if (strcmp (update_dir, ".") == 0)
+       {
+           update_dir[0] = '\0';
+           stripped_dot = 1;
+       }
+
+       /* make the recursive call */
+       xframe = *frame;
+       xframe.flags = dir_return;
+       /* Keep track of repository, really just for r* commands (rtag, rdiff,
+        * co, ...) to tag_check_valid, since all the other commands use
+        * CVS/Repository to figure it out per directory.
+        */
+       if (repository)
+       {
+           if (strcmp (dir, ".") == 0)
+               xframe.repository = xstrdup (repository);
+           else
+               xframe.repository = Xasprintf ("%s/%s", repository, dir);
+       }
+       else
+           xframe.repository = NULL;
+       err += do_recursion (&xframe);
+       if (xframe.repository)
+       {
+           free (xframe.repository);
+           xframe.repository = NULL;
+       }
+
+       /* put the `.' back if necessary */
+       if (stripped_dot)
+           (void) strcpy (update_dir, ".");
+
+       /* call-back dir leave proc (if any) */
+       if (process_this_directory && frame->dirleaveproc != NULL)
+           err = frame->dirleaveproc (frame->callerdat, dir, err, update_dir,
+                                      frent->entries);
+
+       /* get back to where we started and restore state vars */
+       if (restore_cwd (&cwd))
+           error (1, errno, "Failed to restore current directory, `%s'.",
+                  cwd.name);
+       free_cwd (&cwd);
+       dirlist = sdirlist;
+       repository = srepository;
+    }
+
+    free (update_dir);
+    update_dir = saved_update_dir;
+
+    return err;
+}
+
+/*
+ * Add a node to a list allocating the list if necessary.
+ */
+static void
+addlist (List **listp, char *key)
+{
+    Node *p;
+
+    if (*listp == NULL)
+       *listp = getlist ();
+    p = getnode ();
+    p->type = FILES;
+    p->key = xstrdup (key);
+    if (addnode (*listp, p) != 0)
+       freenode (p);
+}
+
+static void
+addfile (List **listp, char *dir, char *file)
+{
+    Node *n;
+    List *fl;
+
+    /* add this dir. */
+    addlist (listp, dir);
+
+    n = findnode (*listp, dir);
+    if (n == NULL)
+    {
+       error (1, 0, "can't find recently added dir node `%s' in 
start_recursion.",
+              dir);
+    }
+
+    n->type = DIRS;
+    fl = n->data;
+    addlist (&fl, file);
+    n->data = fl;
+    return;
+}
+
+static int
+unroll_files_proc (Node *p, void *closure)
+{
+    Node *n;
+    struct recursion_frame *frame = (struct recursion_frame *) closure;
+    int err = 0;
+    List *save_dirlist;
+    char *save_update_dir = NULL;
+    struct saved_cwd cwd;
+
+    /* if this dir was also an explicitly named argument, then skip
+       it.  We'll catch it later when we do dirs. */
+    n = findnode (dirlist, p->key);
+    if (n != NULL)
+       return (0);
+
+    /* otherwise, call dorecusion for this list of files. */
+    filelist = p->data;
+    p->data = NULL;
+    save_dirlist = dirlist;
+    dirlist = NULL;
+
+    if (strcmp(p->key, ".") != 0)
+    {
+        if (save_cwd (&cwd))
+           error (1, errno, "Failed to save current directory.");
+       if ( CVS_CHDIR (p->key) < 0)
+           error (1, errno, "could not chdir to %s", p->key);
+
+       save_update_dir = update_dir;
+       update_dir = xmalloc (strlen (save_update_dir)
+                                 + strlen (p->key)
+                                 + 5);
+       strcpy (update_dir, save_update_dir);
+
+       if (*update_dir != '\0')
+           (void) strcat (update_dir, "/");
+
+       (void) strcat (update_dir, p->key);
+    }
+
+    err += do_recursion (frame);
+
+    if (save_update_dir != NULL)
+    {
+       free (update_dir);
+       update_dir = save_update_dir;
+
+       if (restore_cwd (&cwd))
+           error (1, errno, "Failed to restore current directory, `%s'.",
+                  cwd.name);
+       free_cwd (&cwd);
+    }
+
+    dirlist = save_dirlist;
+    if (filelist)
+       dellist (&filelist);
+    return(err);
+}
+/* vim:tabstop=8:shiftwidth=4
+ */
Index: ccvs/src/recurse.h
diff -u /dev/null ccvs/src/recurse.h:1.1.2.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/recurse.h  Fri Jan  6 19:34:15 2006
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2006 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef RECURSE_H
+#define RECURSE_H
+
+#include "rcs.h"       /* Get struct file_info.  */
+
+/* Flags for find_{names,dirs} routines */
+#define W_LOCAL                        0x01    /* look for files locally */
+#define W_REPOS                        0x02    /* look for files in the 
repository */
+#define W_ATTIC                        0x04    /* look for files in the attic 
*/
+
+/* Flags for return values of direnter procs for the recursion processor */
+enum direnter_type
+{
+    R_PROCESS = 1,                     /* process files and maybe dirs */
+    R_SKIP_FILES,                      /* don't process files in this dir */
+    R_SKIP_DIRS,                       /* don't process sub-dirs */
+    R_SKIP_ALL                         /* don't process files or dirs */
+};
+#ifdef ENUMS_CAN_BE_TROUBLE
+typedef int Dtype;
+#else
+typedef enum direnter_type Dtype;
+#endif
+
+/* Recursion processor lock types */
+#define CVS_LOCK_NONE  0
+#define CVS_LOCK_READ  1
+#define CVS_LOCK_WRITE 2
+
+/* Callback functions.  */
+typedef        int (*FILEPROC) (void *callerdat, struct file_info *finfo);
+typedef        int (*FILESDONEPROC) (void *callerdat, int err,
+                              const char *repository, const char *update_dir,
+                              List *entries);
+typedef        Dtype (*DIRENTPROC) (void *callerdat, const char *dir,
+                             const char *repos, const char *update_dir,
+                             List *entries);
+typedef        int (*DIRLEAVEPROC) (void *callerdat, const char *dir, int err,
+                             const char *update_dir, List *entries);
+
+int start_recursion (FILEPROC fileproc, FILESDONEPROC filesdoneproc,
+                    DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc,
+                    void *callerdat,
+                    int argc, char *argv[], int local, int which,
+                    int aflag, int locktype, char *update_preload,
+                    int dosrcs, char *repository);
+
+#endif /* RECURSE_H */
Index: ccvs/src/release.c
diff -u /dev/null ccvs/src/release.c:1.71.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/release.c  Fri Jan  6 19:34:15 2006
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 1994-2005 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Release: "cancel" a checkout in the history log.
+ * 
+ * - Enter a line in the history log indicating the "release". - If asked to,
+ * delete the local working directory.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* GNULIB headers.  */
+#include "getline.h"
+#include "save-cwd.h"
+#include "yesno.h"
+
+/* CVS headers.  */
+#include "ignore.h"
+
+#include "cvs.h"
+
+
+
+static const char *const release_usage[] =
+{
+    "Usage: %s %s [-d] directories...\n",
+    "\t-d\tDelete the given directory.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+#ifdef SERVER_SUPPORT
+static int release_server (int argc, char **argv);
+
+/* This is the server side of cvs release.  */
+static int
+release_server (int argc, char **argv)
+{
+    int i;
+
+    /* Note that we skip argv[0].  */
+    for (i = 1; i < argc; ++i)
+       history_write ('F', argv[i], "", argv[i], "");
+    return 0;
+}
+
+#endif /* SERVER_SUPPORT */
+
+/* Construct an update command.  Be sure to add authentication and
+* encryption if we are using them currently, else our child process may
+* not be able to communicate with the server.
+*/
+static FILE *
+setup_update_command (pid_t *child_pid)
+{
+    int tofd, fromfd;
+
+    run_setup (program_path);
+   
+#if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
+    /* Be sure to add authentication and encryption if we are using them
+     * currently, else our child process may not be able to communicate with
+     * the server.
+     */
+    if (cvsauthenticate) run_add_arg ("-a");
+    if (cvsencrypt) run_add_arg ("-x");
+#endif
+
+    /* Don't really change anything.  */
+    run_add_arg ("-n");
+
+    /* Don't need full verbosity.  */
+    run_add_arg ("-q");
+
+    /* Propogate our CVSROOT.  */
+    run_add_arg ("-d");
+    run_add_arg (original_parsed_root->original);
+
+    run_add_arg ("update");
+    *child_pid = run_piped (&tofd, &fromfd);
+    if (*child_pid < 0)
+       error (1, 0, "could not fork server process");
+
+    close (tofd);
+
+    return fdopen (fromfd, "r");
+}
+
+
+
+static int
+close_update_command (FILE *fp, pid_t child_pid)
+{
+    int status;
+    pid_t w;
+
+    do
+       w = waitpid (child_pid, &status, 0);
+    while (w == -1 && errno == EINTR);
+    if (w == -1)
+       error (1, errno, "waiting for process %d", child_pid);
+
+    return status;
+}
+
+
+
+/* There are various things to improve about this implementation:
+
+   1.  Using run_popen to run "cvs update" could be replaced by a
+   fairly simple start_recursion/classify_file loop--a win for
+   portability, performance, and cleanliness.  In particular, there is
+   no particularly good way to find the right "cvs".
+
+   2.  The fact that "cvs update" contacts the server slows things down;
+   it undermines the case for using "cvs release" rather than "rm -rf".
+   However, for correctly printing "? foo" and correctly handling
+   CVSROOTADM_IGNORE, we currently need to contact the server.  (One
+   idea for how to fix this is to stash a copy of CVSROOTADM_IGNORE in
+   the working directories; see comment at base_* in entries.c for a
+   few thoughts on that).
+
+   3.  Would be nice to take processing things on the client side one step
+   further, and making it like edit/unedit in terms of working well if
+   disconnected from the network, and then sending a delayed
+   notification.
+
+   4.  Having separate network turnarounds for the "Notify" request
+   which we do as part of unedit, and for the "release" itself, is slow
+   and unnecessary.  */
+
+int
+release (int argc, char **argv)
+{
+    FILE *fp;
+    int i, c;
+    char *line = NULL;
+    size_t line_allocated = 0;
+    char *thisarg;
+    int arg_start_idx;
+    int err = 0;
+    short delete_flag = 0;
+    struct saved_cwd cwd;
+
+#ifdef SERVER_SUPPORT
+    if (server_active)
+       return release_server (argc, argv);
+#endif
+
+    /* Everything from here on is client or local.  */
+    if (argc == -1)
+       usage (release_usage);
+    optind = 0;
+    while ((c = getopt (argc, argv, "+Qdq")) != -1)
+    {
+       switch (c)
+       {
+           case 'Q':
+           case 'q':
+               error (1, 0,
+                      "-q or -Q must be specified before \"%s\"",
+                      cvs_cmd_name);
+               break;
+           case 'd':
+               delete_flag++;
+               break;
+           case '?':
+           default:
+               usage (release_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    /* We're going to run "cvs -n -q update" and check its output; if
+     * the output is sufficiently unalarming, then we release with no
+     * questions asked.  Else we prompt, then maybe release.
+     * (Well, actually we ask no matter what.  Our notion of "sufficiently
+     * unalarming" doesn't take into account "? foo.c" files, so it is
+     * up to the user to take note of them, at least currently
+     * (ignore-193 in testsuite)).
+     */
+
+#ifdef CLIENT_SUPPORT
+    /* Start the server; we'll close it after looping. */
+    if (current_parsed_root->isremote)
+    {
+       start_server ();
+       ign_setup ();
+    }
+#endif /* CLIENT_SUPPORT */
+
+    /* Remember the directory where "cvs release" was invoked because
+       all args are relative to this directory and we chdir around.
+       */
+    if (save_cwd (&cwd))
+       error (1, errno, "Failed to save current directory.");
+
+    arg_start_idx = 0;
+
+    for (i = arg_start_idx; i < argc; i++)
+    {
+       thisarg = argv[i];
+
+        if (isdir (thisarg))
+        {
+           if (CVS_CHDIR (thisarg) < 0)
+           {
+               if (!really_quiet)
+                   error (0, errno, "can't chdir to: %s", thisarg);
+               continue;
+           }
+           if (!isdir (CVSADM))
+           {
+               if (!really_quiet)
+                   error (0, 0, "no repository directory: %s", thisarg);
+               if (restore_cwd (&cwd))
+                   error (1, errno,
+                          "Failed to restore current directory, `%s'.",
+                          cwd.name);
+               continue;
+           }
+       }
+       else
+        {
+           if (!really_quiet)
+               error (0, 0, "no such directory: %s", thisarg);
+           continue;
+       }
+
+       if (!really_quiet)
+       {
+           int line_length, status;
+           pid_t child_pid;
+
+           /* The "release" command piggybacks on "update", which
+              does the real work of finding out if anything is not
+              up-to-date with the repository.  Then "release" prompts
+              the user, telling her how many files have been
+              modified, and asking if she still wants to do the
+              release.  */
+           fp = setup_update_command (&child_pid);
+           if (fp == NULL)
+           {
+               error (0, 0, "cannot run command:");
+               run_print (stderr);
+               error (1, 0, "Exiting due to fatal error referenced above.");
+           }
+
+           c = 0;
+
+           while ((line_length = getline (&line, &line_allocated, fp)) >= 0)
+           {
+               if (strchr ("MARCZ", *line))
+                   c++;
+               (void) fputs (line, stdout);
+           }
+           if (line_length < 0 && !feof (fp))
+               error (0, errno, "cannot read from subprocess");
+
+           /* If the update exited with an error, then we just want to
+              complain and go on to the next arg.  Especially, we do
+              not want to delete the local copy, since it's obviously
+              not what the user thinks it is.  */
+           status = close_update_command (fp, child_pid);
+           if (status != 0)
+           {
+               error (0, 0, "unable to release `%s' (%d)", thisarg, status);
+               if (restore_cwd (&cwd))
+                   error (1, errno,
+                          "Failed to restore current directory, `%s'.",
+                          cwd.name);
+               continue;
+           }
+
+           printf ("You have [%d] altered files in this repository.\n",
+                   c);
+
+           if (!noexec)
+           {
+               printf ("Are you sure you want to release %sdirectory `%s': ",
+                       delete_flag ? "(and delete) " : "", thisarg);
+               fflush (stderr);
+               fflush (stdout);
+               if (!yesno ())                  /* "No" */
+               {
+                   (void) fprintf (stderr,
+                                   "** `%s' aborted by user choice.\n",
+                                   cvs_cmd_name);
+                   if (restore_cwd (&cwd))
+                       error (1, errno,
+                              "Failed to restore current directory, `%s'.",
+                              cwd.name);
+                   continue;
+               }
+           }
+           else
+           {
+               if (restore_cwd (&cwd))
+                   error (1, errno,
+                          "Failed to restore current directory, `%s'.",
+                          cwd.name);
+               continue;
+           }
+       }
+
+        /* Note:  client.c doesn't like to have other code
+           changing the current directory on it.  So a fair amount
+           of effort is needed to make sure it doesn't get confused
+           about the directory and (for example) overwrite
+           CVS/Entries file in the wrong directory.  See release-17
+           through release-23. */
+
+       if (restore_cwd (&cwd))
+           error (1, errno, "Failed to restore current directory, `%s'.",
+                  cwd.name);
+
+#ifdef CLIENT_SUPPORT
+       if (!current_parsed_root->isremote
+           || (supported_request ("noop") && supported_request ("Notify")))
+#endif
+       {
+           int argc = 2;
+           char *argv[3];
+           argv[0] = "dummy";
+           argv[1] = thisarg;
+           argv[2] = NULL;
+           err += unedit (argc, argv);
+            if (restore_cwd (&cwd))
+               error (1, errno, "Failed to restore current directory, `%s'.",
+                      cwd.name);
+       }
+
+#ifdef CLIENT_SUPPORT
+        if (current_parsed_root->isremote)
+        {
+           send_to_server ("Argument ", 0);
+           send_to_server (thisarg, 0);
+           send_to_server ("\012", 1);
+           send_to_server ("release\012", 0);
+       }
+        else
+#endif /* CLIENT_SUPPORT */
+        {
+           history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
+        }
+
+       if (delete_flag)
+       {
+           /* FIXME?  Shouldn't this just delete the CVS-controlled
+              files and, perhaps, the files that would normally be
+              ignored and leave everything else?  */
+
+           if (unlink_file_dir (thisarg) < 0)
+               error (0, errno, "deletion of directory %s failed", thisarg);
+       }
+
+#ifdef CLIENT_SUPPORT
+        if (current_parsed_root->isremote)
+        {
+           /* FIXME:
+            * Is there a good reason why get_server_responses() isn't
+            * responsible for restoring its initial directory itself when
+            * finished?
+            */
+            err += get_server_responses ();
+
+            if (restore_cwd (&cwd))
+               error (1, errno, "Failed to restore current directory, `%s'.",
+                      cwd.name);
+        }
+#endif /* CLIENT_SUPPORT */
+    }
+
+    if (restore_cwd (&cwd))
+       error (1, errno, "Failed to restore current directory, `%s'.",
+              cwd.name);
+    free_cwd (&cwd);
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       /* Unfortunately, client.c doesn't offer a way to close
+          the connection without waiting for responses.  The extra
+          network turnaround here is quite unnecessary other than
+          that....  */
+       send_to_server ("noop\012", 0);
+       err += get_responses_and_close ();
+    }
+#endif /* CLIENT_SUPPORT */
+
+    if (line != NULL)
+       free (line);
+    return err;
+}
Index: ccvs/src/remove.c
diff -u /dev/null ccvs/src/remove.c:1.63.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/remove.c   Fri Jan  6 19:34:15 2006
@@ -0,0 +1,286 @@
+/*
+ * 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.
+ * 
+ * Remove a File
+ * 
+ * Removes entries from the present version. The entries will be removed from
+ * the RCS repository upon the next "commit".
+ * 
+ * "remove" accepts no options, only file names that are to be removed.  The
+ * file must not exist in the current directory for "remove" to work
+ * correctly.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* CVS headers.  */
+#include "ignore.h"
+#include "recurse.h"
+
+#include "cvs.h"
+
+
+
+#ifdef CLIENT_SUPPORT
+static int remove_force_fileproc (void *callerdat,
+                                        struct file_info *finfo);
+#endif
+static int remove_fileproc (void *callerdat, struct file_info *finfo);
+static Dtype remove_dirproc (void *callerdat, const char *dir,
+                             const char *repos, const char *update_dir,
+                             List *entries);
+
+static int force;
+static int local;
+static int removed_files;
+static int existing_files;
+
+static const char *const remove_usage[] =
+{
+    "Usage: %s %s [-flR] [files...]\n",
+    "\t-f\tDelete the file before removing it.\n",
+    "\t-l\tProcess this directory only (not recursive).\n",
+    "\t-R\tProcess directories recursively.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+int
+cvsremove (int argc, char **argv)
+{
+    int c, err;
+
+    if (argc == -1)
+       usage (remove_usage);
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+flR")) != -1)
+    {
+       switch (c)
+       {
+           case 'f':
+               force = 1;
+               break;
+           case 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case '?':
+           default:
+               usage (remove_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote) {
+       /* Call expand_wild so that the local removal of files will
+           work.  It's ok to do it always because we have to send the
+           file names expanded anyway.  */
+       expand_wild (argc, argv, &argc, &argv);
+       
+       if (force)
+       {
+           if (!noexec)
+           {
+               start_recursion (remove_force_fileproc, NULL, NULL, NULL,
+                                NULL, argc, argv, local, W_LOCAL,
+                                0, CVS_LOCK_NONE, NULL, 0, NULL);
+           }
+           /* else FIXME should probably act as if the file doesn't exist
+              in doing the following checks.  */
+       }
+
+       start_server ();
+       ign_setup ();
+       if (local)
+           send_arg("-l");
+       send_arg ("--");
+       /* FIXME: Can't we set SEND_NO_CONTENTS here?  Needs investigation.  */
+       send_files (argc, argv, local, 0, 0);
+       send_file_names (argc, argv, 0);
+       free_names (&argc, argv);
+       send_to_server ("remove\012", 0);
+        return get_responses_and_close ();
+    }
+#endif
+
+    /* start the recursion processor */
+    err = start_recursion (remove_fileproc, NULL, remove_dirproc, NULL,
+                          NULL, argc, argv, local, W_LOCAL, 0,
+                          CVS_LOCK_READ, NULL, 1, NULL);
+
+    if (removed_files && !really_quiet)
+       error (0, 0, "use `%s commit' to remove %s permanently", program_name,
+              (removed_files == 1) ? "this file" : "these files");
+
+    if (existing_files)
+       error (0, 0,
+              ((existing_files == 1) ?
+               "%d file exists; remove it first" :
+               "%d files exist; remove them first"),
+              existing_files);
+
+    return (err);
+}
+
+#ifdef CLIENT_SUPPORT
+
+/*
+ * This is called via start_recursion if we are running as the client
+ * and the -f option was used.  We just physically remove the file.
+ */
+
+/*ARGSUSED*/
+static int
+remove_force_fileproc (void *callerdat, struct file_info *finfo)
+{
+    if (CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
+       error (0, errno, "unable to remove %s", finfo->fullname);
+    return 0;
+}
+
+#endif
+
+/*
+ * remove the file, only if it has already been physically removed
+ */
+/* ARGSUSED */
+static int
+remove_fileproc (void *callerdat, struct file_info *finfo)
+{
+    Vers_TS *vers;
+
+    if (force)
+    {
+       if (!noexec)
+       {
+           if ( CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
+           {
+               error (0, errno, "unable to remove %s", finfo->fullname);
+           }
+       }
+       /* else FIXME should probably act as if the file doesn't exist
+          in doing the following checks.  */
+    }
+
+    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
+
+    if (vers->ts_user != NULL)
+    {
+       existing_files++;
+       if (!quiet)
+           error (0, 0, "file `%s' still in working directory",
+                  finfo->fullname);
+    }
+    else if (vers->vn_user == NULL)
+    {
+       if (!quiet)
+           error (0, 0, "nothing known about `%s'", finfo->fullname);
+    }
+    else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+    {
+       char *fname;
+
+       /*
+        * It's a file that has been added, but not commited yet. So,
+        * remove the ,t file for it and scratch it from the
+        * entries file.  */
+       Scratch_Entry (finfo->entries, finfo->file);
+       fname = Xasprintf ("%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
+       if (unlink_file (fname) < 0
+           && !existence_error (errno))
+           error (0, errno, "cannot remove %s", CVSEXT_LOG);
+       if (!quiet)
+           error (0, 0, "removed `%s'", finfo->fullname);
+
+#ifdef SERVER_SUPPORT
+       if (server_active)
+           server_checked_in (finfo->file, finfo->update_dir, 
finfo->repository);
+#endif
+       free (fname);
+    }
+    else if (vers->vn_user[0] == '-')
+    {
+       if (!quiet)
+           error (0, 0, "file `%s' already scheduled for removal",
+                  finfo->fullname);
+    }
+    else if (vers->tag != NULL && isdigit ((unsigned char) *vers->tag))
+    {
+       /* Commit will just give an error, and so there seems to be
+          little reason to allow the remove.  I mean, conflicts that
+          arise out of parallel development are one thing, but conflicts
+          that arise from sticky tags are quite another.
+
+          I would have thought that non-branch sticky tags should be the
+          same but at least now, removing a file with a non-branch sticky
+          tag means to delete the tag from the file.  I'm not sure that
+          is a good behavior, but until it is changed, we need to allow
+          it.  */
+       error (0, 0, "\
+cannot remove file `%s' which has a numeric sticky tag of `%s'",
+              finfo->fullname, vers->tag);
+    }
+    else if (vers->date != NULL)
+    {
+       /* Commit will just give an error, and so there seems to be
+          little reason to allow the remove.  */
+       error (0, 0, "\
+cannot remove file `%s' which has a sticky date of `%s'",
+              finfo->fullname, vers->date);
+    }
+    else
+    {
+       char *fname;
+
+       /* Re-register it with a negative version number.  */
+       fname = Xasprintf ("-%s", vers->vn_user);
+       Register (finfo->entries, finfo->file, fname, vers->ts_rcs,
+                 vers->options, vers->tag, vers->date, vers->ts_conflict);
+       if (!quiet)
+           error (0, 0, "scheduling `%s' for removal", finfo->fullname);
+       removed_files++;
+
+#ifdef SERVER_SUPPORT
+       if (server_active)
+           server_checked_in (finfo->file, finfo->update_dir, 
finfo->repository);
+#endif
+       free (fname);
+    }
+
+    freevers_ts (&vers);
+    return (0);
+}
+
+
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+remove_dirproc (void *callerdat, const char *dir, const char *repos,
+                const char *update_dir, List *entries)
+{
+    if (!quiet)
+       error (0, 0, "Removing %s", update_dir);
+    return (R_PROCESS);
+}
Index: ccvs/src/tag.c
diff -u ccvs/src/tag.c:1.142.6.1 ccvs/src/tag.c:1.142.6.2
--- ccvs/src/tag.c:1.142.6.1    Wed Dec 21 13:25:10 2005
+++ ccvs/src/tag.c      Fri Jan  6 19:34:15 2006
@@ -17,9 +17,22 @@
  * the modules database, if necessary.
  */
 
-#include "cvs.h"
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* GNULIB headers.  */
 #include "save-cwd.h"
 
+/* CVS headers.  */
+#include "classify.h"
+#include "ignore.h"
+#include "recurse.h"
+
+#include "cvs.h"
+
+
+
 static int rtag_proc (int argc, char **argv, char *xwhere,
                      char *mwhere, char *mfile, int shorten,
                      int local_specified, char *mname, char *msg);
Index: ccvs/src/update.c
diff -u ccvs/src/update.c:1.259.2.1 ccvs/src/update.c:1.259.2.2
--- ccvs/src/update.c:1.259.2.1 Wed Dec 21 13:25:10 2005
+++ ccvs/src/update.c   Fri Jan  6 19:34:15 2006
@@ -50,18 +50,23 @@
 # include "md5.h"
 #endif
 
-/* CVS */
+/* ANSI C headers.  */
+#include <assert.h>
+
+/* CVS headers.  */
 #include "base.h"
+#include "buffer.h"
+#include "classify.h"
+#include "edit.h"
+#include "ignore.h"
+#include "recurse.h"
 
 #include "cvs.h"
 #include "watch.h"
 #include "fileattr.h"
-#include "edit.h"
-#include "buffer.h"
 #include "hardlink.h"
 
-/* C89 */
-#include <assert.h>
+
 
 static int checkout_file (struct file_info *finfo, Vers_TS *vers_ts,
                                 int adding, int merging, int update_server);
Index: ccvs/src/verify.c
diff -u ccvs/src/verify.c:1.1.2.3 ccvs/src/verify.c:1.1.2.4
--- ccvs/src/verify.c:1.1.2.3   Tue Jan  3 18:09:24 2006
+++ ccvs/src/verify.c   Fri Jan  6 19:34:15 2006
@@ -33,6 +33,7 @@
 
 /* CVS headers.  */
 #include "base.h"
+#include "recurse.h"
 #include "stack.h"
 
 /* Get current_parsed_root.  */
Index: ccvs/src/watch.c
diff -u /dev/null ccvs/src/watch.c:1.45.6.1
--- /dev/null   Fri Jan  6 19:34:16 2006
+++ ccvs/src/watch.c    Fri Jan  6 19:34:15 2006
@@ -0,0 +1,550 @@
+/* Implementation for "cvs watch add", "cvs watchers", and related commands
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* CVS headers.  */
+#include "edit.h"
+#include "ignore.h"
+#include "recurse.h"
+
+#include "cvs.h"
+#include "fileattr.h"
+#include "watch.h"
+
+
+
+const char *const watch_usage[] =
+{
+    "Usage: %s %s {on|off|add|remove} [-lR] [-a <action>]... [<path>]...\n",
+    "on/off: Turn on/off read-only checkouts of files.\n",
+    "add/remove: Add or remove notification on actions.\n",
+    "-l (on/off/add/remove): Local directory only, not recursive.\n",
+    "-R (on/off/add/remove): Process directories recursively (default).\n",
+    "-a (add/remove): Specify what actions, one of: `edit', `unedit',\n",
+    "                 `commit', `all', or `none' (defaults to `all').\n",
+    "(Specify the --help global option for a list of other help options.)\n",
+    NULL
+};
+
+static struct addremove_args the_args;
+
+void
+watch_modify_watchers (const char *file, struct addremove_args *what)
+{
+    char *curattr = fileattr_get0 (file, "_watchers");
+    char *p;
+    char *pend;
+    char *nextp;
+    char *who;
+    int who_len;
+    char *mycurattr;
+    char *mynewattr;
+    size_t mynewattr_size;
+
+    int add_edit_pending;
+    int add_unedit_pending;
+    int add_commit_pending;
+    int remove_edit_pending;
+    int remove_unedit_pending;
+    int remove_commit_pending;
+    int add_tedit_pending;
+    int add_tunedit_pending;
+    int add_tcommit_pending;
+
+    TRACE( TRACE_FUNCTION, "modify_watchers ( %s )", file );
+
+    who = getcaller ();
+    who_len = strlen (who);
+
+    /* Look for current watcher types for this user.  */
+    mycurattr = NULL;
+    if (curattr != NULL)
+    {
+       p = curattr;
+       while (1) {
+           if (strncmp (who, p, who_len) == 0
+               && p[who_len] == '>')
+           {
+               /* Found this user.  */
+               mycurattr = p + who_len + 1;
+           }
+           p = strchr (p, ',');
+           if (p == NULL)
+               break;
+           ++p;
+       }
+    }
+    if (mycurattr != NULL)
+    {
+       mycurattr = xstrdup (mycurattr);
+       p = strchr (mycurattr, ',');
+       if (p != NULL)
+           *p = '\0';
+    }
+
+    /* Now copy mycurattr to mynewattr, making the requisite modifications.
+       Note that we add a dummy '+' to the start of mynewattr, to reduce
+       special cases (but then we strip it off when we are done).  */
+
+    mynewattr_size = sizeof "+edit+unedit+commit+tedit+tunedit+tcommit";
+    if (mycurattr != NULL)
+       mynewattr_size += strlen (mycurattr);
+    mynewattr = xmalloc (mynewattr_size);
+    mynewattr[0] = '\0';
+
+    add_edit_pending = what->adding && what->edit;
+    add_unedit_pending = what->adding && what->unedit;
+    add_commit_pending = what->adding && what->commit;
+    remove_edit_pending = !what->adding && what->edit;
+    remove_unedit_pending = !what->adding && what->unedit;
+    remove_commit_pending = !what->adding && what->commit;
+    add_tedit_pending = what->add_tedit;
+    add_tunedit_pending = what->add_tunedit;
+    add_tcommit_pending = what->add_tcommit;
+
+    /* Copy over existing watch types, except those to be removed.  */
+    p = mycurattr;
+    while (p != NULL)
+    {
+       pend = strchr (p, '+');
+       if (pend == NULL)
+       {
+           pend = p + strlen (p);
+           nextp = NULL;
+       }
+       else
+           nextp = pend + 1;
+
+       /* Process this item.  */
+       if (pend - p == 4 && strncmp ("edit", p, 4) == 0)
+       {
+           if (!remove_edit_pending)
+               strcat (mynewattr, "+edit");
+           add_edit_pending = 0;
+       }
+       else if (pend - p == 6 && strncmp ("unedit", p, 6) == 0)
+       {
+           if (!remove_unedit_pending)
+               strcat (mynewattr, "+unedit");
+           add_unedit_pending = 0;
+       }
+       else if (pend - p == 6 && strncmp ("commit", p, 6) == 0)
+       {
+           if (!remove_commit_pending)
+               strcat (mynewattr, "+commit");
+           add_commit_pending = 0;
+       }
+       else if (pend - p == 5 && strncmp ("tedit", p, 5) == 0)
+       {
+           if (!what->remove_temp)
+               strcat (mynewattr, "+tedit");
+           add_tedit_pending = 0;
+       }
+       else if (pend - p == 7 && strncmp ("tunedit", p, 7) == 0)
+       {
+           if (!what->remove_temp)
+               strcat (mynewattr, "+tunedit");
+           add_tunedit_pending = 0;
+       }
+       else if (pend - p == 7 && strncmp ("tcommit", p, 7) == 0)
+       {
+           if (!what->remove_temp)
+               strcat (mynewattr, "+tcommit");
+           add_tcommit_pending = 0;
+       }
+       else
+       {
+           char *mp;
+
+           /* Copy over any unrecognized watch types, for future
+              expansion.  */
+           mp = mynewattr + strlen (mynewattr);
+           *mp++ = '+';
+           strncpy (mp, p, pend - p);
+           *(mp + (pend - p)) = '\0';
+       }
+
+       /* Set up for next item.  */
+       p = nextp;
+    }
+
+    /* Add in new watch types.  */
+    if (add_edit_pending)
+       strcat (mynewattr, "+edit");
+    if (add_unedit_pending)
+       strcat (mynewattr, "+unedit");
+    if (add_commit_pending)
+       strcat (mynewattr, "+commit");
+    if (add_tedit_pending)
+       strcat (mynewattr, "+tedit");
+    if (add_tunedit_pending)
+       strcat (mynewattr, "+tunedit");
+    if (add_tcommit_pending)
+       strcat (mynewattr, "+tcommit");
+
+    {
+       char *curattr_new;
+
+       curattr_new =
+         fileattr_modify (curattr,
+                          who,
+                          mynewattr[0] == '\0' ? NULL : mynewattr + 1,
+                          '>',
+                          ',');
+       /* If the attribute is unchanged, don't rewrite the attribute file.  */
+       if (!((curattr_new == NULL && curattr == NULL)
+             || (curattr_new != NULL
+                 && curattr != NULL
+                 && strcmp (curattr_new, curattr) == 0)))
+           fileattr_set (file,
+                         "_watchers",
+                         curattr_new);
+       if (curattr_new != NULL)
+           free (curattr_new);
+    }
+
+    if (curattr != NULL)
+       free (curattr);
+    if (mycurattr != NULL)
+       free (mycurattr);
+    if (mynewattr != NULL)
+       free (mynewattr);
+}
+
+static int addremove_fileproc (void *callerdat,
+                                     struct file_info *finfo);
+
+static int
+addremove_fileproc (void *callerdat, struct file_info *finfo)
+{
+    watch_modify_watchers (finfo->file, &the_args);
+    return 0;
+}
+
+static int addremove_filesdoneproc (void * callerdat, int err, const char * 
repository,
+                                           const char *update_dir, List * 
entries)
+{
+    int set_default = the_args.setting_default;
+    int dir_check = 0;
+
+    while ( !set_default && dir_check < the_args.num_dirs )
+    {
+       /* If we are recursing, then just see if the first part of update_dir 
+          matches any of the specified directories. Otherwise, it must be an 
exact
+          match. */
+       if ( the_args.local )
+           set_default = strcmp( update_dir, the_args.dirs[ dir_check ] )==0;
+       else 
+           set_default = strncmp( update_dir, the_args.dirs[ dir_check ], 
strlen( the_args.dirs[ dir_check ] ) ) == 0;
+       dir_check++;
+    }
+
+    if (set_default)
+       watch_modify_watchers (NULL, &the_args);
+    return err;
+}
+
+
+static int
+watch_addremove (int argc, char **argv)
+{
+    int c;
+    int err;
+    int a_omitted;
+    int arg_index;
+    int max_dirs;
+
+    a_omitted = 1;
+    the_args.commit = 0;
+    the_args.edit = 0;
+    the_args.unedit = 0;
+    the_args.num_dirs = 0;
+    the_args.dirs = NULL;
+    the_args.local = 0;
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+lRa:")) != -1)
+    {
+       switch (c)
+       {
+           case 'l':
+               the_args.local = 1;
+               break;
+           case 'R':
+               the_args.local = 0;
+               break;
+           case 'a':
+               a_omitted = 0;
+               if (strcmp (optarg, "edit") == 0)
+                   the_args.edit = 1;
+               else if (strcmp (optarg, "unedit") == 0)
+                   the_args.unedit = 1;
+               else if (strcmp (optarg, "commit") == 0)
+                   the_args.commit = 1;
+               else if (strcmp (optarg, "all") == 0)
+               {
+                   the_args.edit = 1;
+                   the_args.unedit = 1;
+                   the_args.commit = 1;
+               }
+               else if (strcmp (optarg, "none") == 0)
+               {
+                   the_args.edit = 0;
+                   the_args.unedit = 0;
+                   the_args.commit = 0;
+               }
+               else
+                   usage (watch_usage);
+               break;
+           case '?':
+           default:
+               usage (watch_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    the_args.num_dirs = 0;
+    max_dirs = 4; /* Arbitrary choice. */
+    the_args.dirs = xmalloc( sizeof( const char * ) * max_dirs );
+
+    TRACE (TRACE_FUNCTION, "watch_addremove (%d)", argc);
+    for ( arg_index=0; arg_index<argc; ++arg_index )
+    {
+       TRACE( TRACE_FUNCTION, "\t%s", argv[ arg_index ]);
+       if ( isdir( argv[ arg_index ] ) )
+       {
+           if ( the_args.num_dirs >= max_dirs )
+           {
+               max_dirs *= 2;
+               the_args.dirs = (const char ** )xrealloc( (void 
*)the_args.dirs, max_dirs );
+           }
+           the_args.dirs[ the_args.num_dirs++ ] = argv[ arg_index ];
+       }
+    }
+
+    if (a_omitted)
+    {
+       the_args.edit = 1;
+       the_args.unedit = 1;
+       the_args.commit = 1;
+    }
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       start_server ();
+       ign_setup ();
+
+       if (the_args.local)
+           send_arg ("-l");
+       /* FIXME: copes poorly with "all" if server is extended to have
+          new watch types and client is still running an old version.  */
+       if (the_args.edit)
+           option_with_arg ("-a", "edit");
+       if (the_args.unedit)
+           option_with_arg ("-a", "unedit");
+       if (the_args.commit)
+           option_with_arg ("-a", "commit");
+       if (!the_args.edit && !the_args.unedit && !the_args.commit)
+           option_with_arg ("-a", "none");
+       send_arg ("--");
+       send_files (argc, argv, the_args.local, 0, SEND_NO_CONTENTS);
+       send_file_names (argc, argv, SEND_EXPAND_WILD);
+       send_to_server (the_args.adding ?
+                        "watch-add\012" : "watch-remove\012",
+                        0);
+       return get_responses_and_close ();
+    }
+#endif /* CLIENT_SUPPORT */
+
+    the_args.setting_default = (argc <= 0);
+
+    lock_tree_promotably (argc, argv, the_args.local, W_LOCAL, 0);
+
+    err = start_recursion
+       (addremove_fileproc, addremove_filesdoneproc, NULL, NULL, NULL,
+        argc, argv, the_args.local, W_LOCAL, 0, CVS_LOCK_WRITE,
+        NULL, 1, NULL);
+
+    Lock_Cleanup ();
+    free( (void *)the_args.dirs );
+    the_args.dirs = NULL;
+
+    return err;
+}
+
+
+
+int
+watch_add (int argc, char **argv)
+{
+    the_args.adding = 1;
+    return watch_addremove (argc, argv);
+}
+
+int
+watch_remove (int argc, char **argv)
+{
+    the_args.adding = 0;
+    return watch_addremove (argc, argv);
+}
+
+int
+watch (int argc, char **argv)
+{
+    if (argc <= 1)
+       usage (watch_usage);
+    if (strcmp (argv[1], "on") == 0)
+    {
+       --argc;
+       ++argv;
+       return watch_on (argc, argv);
+    }
+    else if (strcmp (argv[1], "off") == 0)
+    {
+       --argc;
+       ++argv;
+       return watch_off (argc, argv);
+    }
+    else if (strcmp (argv[1], "add") == 0)
+    {
+       --argc;
+       ++argv;
+       return watch_add (argc, argv);
+    }
+    else if (strcmp (argv[1], "remove") == 0)
+    {
+       --argc;
+       ++argv;
+       return watch_remove (argc, argv);
+    }
+    else
+       usage (watch_usage);
+    return 0;
+}
+
+static const char *const watchers_usage[] =
+{
+    "Usage: %s %s [-lR] [<file>]...\n",
+    "-l\tProcess this directory only (not recursive).\n",
+    "-R\tProcess directories recursively (default).\n",
+    "(Specify the --help global option for a list of other help options.)\n",
+    NULL
+};
+
+static int watchers_fileproc (void *callerdat,
+                                    struct file_info *finfo);
+
+static int
+watchers_fileproc (void *callerdat, struct file_info *finfo)
+{
+    char *them;
+    char *p;
+
+    them = fileattr_get0 (finfo->file, "_watchers");
+    if (them == NULL)
+       return 0;
+
+    cvs_output (finfo->fullname, 0);
+
+    p = them;
+    while (1)
+    {
+       cvs_output ("\t", 1);
+       while (*p != '>' && *p != '\0')
+           cvs_output (p++, 1);
+       if (*p == '\0')
+       {
+           /* Only happens if attribute is misformed.  */
+           cvs_output ("\n", 1);
+           break;
+       }
+       ++p;
+       cvs_output ("\t", 1);
+       while (1)
+       {
+           while (*p != '+' && *p != ',' && *p != '\0')
+               cvs_output (p++, 1);
+           if (*p == '\0')
+           {
+               cvs_output ("\n", 1);
+               goto out;
+           }
+           if (*p == ',')
+           {
+               ++p;
+               break;
+           }
+           ++p;
+           cvs_output ("\t", 1);
+       }
+       cvs_output ("\n", 1);
+    }
+  out:;
+    free (them);
+    return 0;
+}
+
+int
+watchers (int argc, char **argv)
+{
+    int local = 0;
+    int c;
+
+    if (argc == -1)
+       usage (watchers_usage);
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+lR")) != -1)
+    {
+       switch (c)
+       {
+           case 'l':
+               local = 1;
+               break;
+           case 'R':
+               local = 0;
+               break;
+           case '?':
+           default:
+               usage (watchers_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       start_server ();
+       ign_setup ();
+
+       if (local)
+           send_arg ("-l");
+       send_arg ("--");
+       send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+       send_file_names (argc, argv, SEND_EXPAND_WILD);
+       send_to_server ("watchers\012", 0);
+       return get_responses_and_close ();
+    }
+#endif /* CLIENT_SUPPORT */
+
+    return start_recursion (watchers_fileproc, NULL, NULL,
+                           NULL, NULL, argc, argv, local, W_LOCAL, 0,
+                           CVS_LOCK_READ, NULL, 1, NULL);
+}




reply via email to

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