[Top][All Lists]
[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);
+}
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Cvs-cvs] ccvs/src add.c admin.c annotate.c base.c buffer... [signed-commits3],
Derek Robert Price <=