cvs-cvs
[Top][All Lists]
Advanced

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

[Cvs-cvs] Changes to ccvs/src/log.c [signed-commits]


From: Derek Robert Price
Subject: [Cvs-cvs] Changes to ccvs/src/log.c [signed-commits]
Date: Tue, 11 Oct 2005 22:46:48 -0400

Index: ccvs/src/log.c
diff -u /dev/null ccvs/src/log.c:1.103.2.1
--- /dev/null   Wed Oct 12 02:46:48 2005
+++ ccvs/src/log.c      Wed Oct 12 02:46:37 2005
@@ -0,0 +1,1783 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ *                                  and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ * 
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ * 
+ * Print Log Information
+ * 
+ * Prints the RCS "log" (rlog) information for the specified files.  With no
+ * argument, prints the log information for all the files in the directory
+ * (recursive by default).
+ */
+
+#include "cvs.h"
+#include <assert.h>
+
+/* This structure holds information parsed from the -r option.  */
+
+struct option_revlist
+{
+    /* The next -r option.  */
+    struct option_revlist *next;
+    /* The first revision to print.  This is NULL if the range is
+       :rev, or if no revision is given.  */
+    char *first;
+    /* The last revision to print.  This is NULL if the range is rev:,
+       or if no revision is given.  If there is no colon, first and
+       last are the same.  */
+    char *last;
+    /* Nonzero if there was a trailing `.', which means to print only
+       the head revision of a branch.  */
+    int branchhead;
+    /* Nonzero if first and last are inclusive.  */
+    int inclusive;
+};
+
+/* This structure holds information derived from option_revlist given
+   a particular RCS file.  */
+
+struct revlist
+{
+    /* The next pair.  */
+    struct revlist *next;
+    /* The first numeric revision to print.  */
+    char *first;
+    /* The last numeric revision to print.  */
+    char *last;
+    /* The number of fields in these revisions (one more than
+       numdots).  */
+    int fields;
+    /* Whether first & last are to be included or excluded.  */
+    int inclusive;
+};
+
+/* This structure holds information parsed from the -d option.  */
+
+struct datelist
+{
+    /* The next date.  */
+    struct datelist *next;
+    /* The starting date.  */
+    char *start;
+    /* The ending date.  */
+    char *end;
+    /* Nonzero if the range is inclusive rather than exclusive.  */
+    int inclusive;
+};
+
+/* This structure is used to pass information through start_recursion.  */
+struct log_data
+{
+    /* Nonzero if the -R option was given, meaning that only the name
+       of the RCS file should be printed.  */
+    int nameonly;
+    /* Nonzero if the -h option was given, meaning that only header
+       information should be printed.  */
+    int header;
+    /* Nonzero if the -t option was given, meaning that only the
+       header and the descriptive text should be printed.  */
+    int long_header;
+    /* Nonzero if the -N option was seen, meaning that tag information
+       should not be printed.  */
+    int notags;
+    /* Nonzero if the -b option was seen, meaning that only revisions
+       on the default branch should be printed.  */
+    int default_branch;
+    /* Nonzero if the -S option was seen, meaning that the header/name
+       should be suppressed if no revisions are selected.  */
+    int sup_header;
+    /* If not NULL, the value given for the -r option, which lists
+       sets of revisions to be printed.  */
+    struct option_revlist *revlist;
+    /* If not NULL, the date pairs given for the -d option, which
+       select date ranges to print.  */
+    struct datelist *datelist;
+    /* If not NULL, the single dates given for the -d option, which
+       select specific revisions to print based on a date.  */
+    struct datelist *singledatelist;
+    /* If not NULL, the list of states given for the -s option, which
+       only prints revisions of given states.  */
+    List *statelist;
+    /* If not NULL, the list of login names given for the -w option,
+       which only prints revisions checked in by given users.  */
+    List *authorlist;
+};
+
+/* This structure is used to pass information through walklist.  */
+struct log_data_and_rcs
+{
+    struct log_data *log_data;
+    struct revlist *revlist;
+    RCSNode *rcs;
+};
+
+static int rlog_proc (int argc, char **argv, char *xwhere,
+                      char *mwhere, char *mfile, int shorten,
+                      int local_specified, char *mname, char *msg);
+static Dtype log_dirproc (void *callerdat, const char *dir,
+                          const char *repository, const char *update_dir,
+                          List *entries);
+static int log_fileproc (void *callerdat, struct file_info *finfo);
+static struct option_revlist *log_parse_revlist (const char *);
+static void log_parse_date (struct log_data *, const char *);
+static void log_parse_list (List **, const char *);
+static struct revlist *log_expand_revlist (RCSNode *, char *,
+                                           struct option_revlist *, int);
+static void log_free_revlist (struct revlist *);
+static int log_version_requested (struct log_data *, struct revlist *,
+                                        RCSNode *, RCSVers *);
+static int log_symbol (Node *, void *);
+static int log_count (Node *, void *);
+static int log_fix_singledate (Node *, void *);
+static int log_count_print (Node *, void *);
+static void log_tree (struct log_data *, struct revlist *,
+                            RCSNode *, const char *);
+static void log_abranch (struct log_data *, struct revlist *,
+                               RCSNode *, const char *);
+static void log_version (struct log_data *, struct revlist *,
+                               RCSNode *, RCSVers *, int);
+static int log_branch (Node *, void *);
+static int version_compare (const char *, const char *, int);
+
+static struct log_data log_data;
+static int is_rlog;
+
+static const char *const log_usage[] =
+{
+    "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
+    "    [-w[logins]] [files...]\n",
+    "\t-l\tLocal directory only, no recursion.\n",
+    "\t-b\tOnly list revisions on the default branch.\n",
+    "\t-h\tOnly print header.\n",
+    "\t-R\tOnly print name of RCS file.\n",
+    "\t-t\tOnly print header and descriptive text.\n",
+    "\t-N\tDo not list tags.\n",
+    "\t-S\tDo not print name/header if no revisions selected.  -d, -r,\n",
+    "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n",
+    "\t\t-t without this option.\n",
+    "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
+    "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
+    "\t   rev1::rev2  Between rev1 and rev2, excluding rev1.\n",
+    "\t   rev:        rev and following revisions on the same branch.\n",
+    "\t   rev::       After rev on the same branch.\n",
+    "\t   :rev        rev and previous revisions on the same branch.\n",
+    "\t   ::rev       rev and previous revisions on the same branch.\n",
+    "\t   rev         Just rev.\n",
+    "\t   branch      All revisions on the branch.\n",
+    "\t   branch.     The last revision on the branch.\n",
+    "\t-d dates\tA semicolon-separated list of dates\n",
+    "\t        \t(D1<D2 for range, D for latest before).\n",
+    "\t-s states\tOnly list revisions with specified states.\n",
+    "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
+    "(Specify the --help global option for a list of other help options)\n",
+    NULL
+};
+
+#ifdef CLIENT_SUPPORT
+
+
+
+/* Helper function for send_arg_list.  */
+static int
+send_one (Node *node, void *closure)
+{
+    char *option = closure;
+
+    send_to_server ("Argument ", 0);
+    send_to_server (option, 0);
+    if (strcmp (node->key, "@@MYSELF") == 0)
+       /* It is a bare -w option.  Note that we must send it as
+          -w rather than messing with getcaller() or something (which on
+          the client will return garbage).  */
+       ;
+    else
+       send_to_server (node->key, 0);
+    send_to_server ("\012", 0);
+    return 0;
+}
+
+
+
+/* For each element in ARG, send an argument consisting of OPTION
+   concatenated with that element.  */
+static void
+send_arg_list (char *option, List *arg)
+{
+    if (arg == NULL)
+       return;
+    walklist (arg, send_one, option);
+}
+
+#endif
+
+
+
+int
+cvslog (int argc, char **argv)
+{
+    int c;
+    int err = 0;
+    int local = 0;
+    struct option_revlist **prl;
+
+    is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
+
+    if (argc == -1)
+       usage (log_usage);
+
+    memset (&log_data, 0, sizeof log_data);
+    prl = &log_data.revlist;
+
+    optind = 0;
+    while ((c = getopt (argc, argv, "+bd:hlNSRr::s:tw::")) != -1)
+    {
+       switch (c)
+       {
+           case 'b':
+               log_data.default_branch = 1;
+               break;
+           case 'd':
+               log_parse_date (&log_data, optarg);
+               break;
+           case 'h':
+               log_data.header = 1;
+               break;
+           case 'l':
+               local = 1;
+               break;
+           case 'N':
+               log_data.notags = 1;
+               break;
+           case 'S':
+               log_data.sup_header = 1;
+               break;
+           case 'R':
+               log_data.nameonly = 1;
+               break;
+           case 'r':
+               *prl = log_parse_revlist (optarg);
+               prl = &(*prl)->next;
+               break;
+           case 's':
+               log_parse_list (&log_data.statelist, optarg);
+               break;
+           case 't':
+               log_data.long_header = 1;
+               break;
+           case 'w':
+               if (optarg != NULL)
+                   log_parse_list (&log_data.authorlist, optarg);
+               else
+                   log_parse_list (&log_data.authorlist, "@@MYSELF");
+               break;
+           case '?':
+           default:
+               usage (log_usage);
+               break;
+       }
+    }
+    argc -= optind;
+    argv += optind;
+
+    wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+    if (current_parsed_root->isremote)
+    {
+       struct datelist *p;
+       struct option_revlist *rp;
+       char datetmp[MAXDATELEN];
+
+       /* We're the local client.  Fire up the remote server.  */
+       start_server ();
+
+       if (is_rlog && !supported_request ("rlog"))
+           error (1, 0, "server does not support rlog");
+
+       ign_setup ();
+
+       if (log_data.default_branch)
+           send_arg ("-b");
+
+       while (log_data.datelist != NULL)
+       {
+           p = log_data.datelist;
+           log_data.datelist = p->next;
+           send_to_server ("Argument -d\012", 0);
+           send_to_server ("Argument ", 0);
+           date_to_internet (datetmp, p->start);
+           send_to_server (datetmp, 0);
+           if (p->inclusive)
+               send_to_server ("<=", 0);
+           else
+               send_to_server ("<", 0);
+           date_to_internet (datetmp, p->end);
+           send_to_server (datetmp, 0);
+           send_to_server ("\012", 0);
+           if (p->start)
+               free (p->start);
+           if (p->end)
+               free (p->end);
+           free (p);
+       }
+       while (log_data.singledatelist != NULL)
+       {
+           p = log_data.singledatelist;
+           log_data.singledatelist = p->next;
+           send_to_server ("Argument -d\012", 0);
+           send_to_server ("Argument ", 0);
+           date_to_internet (datetmp, p->end);
+           send_to_server (datetmp, 0);
+           send_to_server ("\012", 0);
+           if (p->end)
+               free (p->end);
+           free (p);
+       }
+           
+       if (log_data.header)
+           send_arg ("-h");
+       if (local)
+           send_arg("-l");
+       if (log_data.notags)
+           send_arg("-N");
+       if (log_data.sup_header)
+           send_arg("-S");
+       if (log_data.nameonly)
+           send_arg("-R");
+       if (log_data.long_header)
+           send_arg("-t");
+
+       while (log_data.revlist != NULL)
+       {
+           rp = log_data.revlist;
+           log_data.revlist = rp->next;
+           send_to_server ("Argument -r", 0);
+           if (rp->branchhead)
+           {
+               if (rp->first != NULL)
+                   send_to_server (rp->first, 0);
+               send_to_server (".", 1);
+           }
+           else
+           {
+               if (rp->first != NULL)
+                   send_to_server (rp->first, 0);
+               send_to_server (":", 1);
+               if (!rp->inclusive)
+                   send_to_server (":", 1);
+               if (rp->last != NULL)
+                   send_to_server (rp->last, 0);
+           }
+           send_to_server ("\012", 0);
+           if (rp->first)
+               free (rp->first);
+           if (rp->last)
+               free (rp->last);
+           free (rp);
+       }
+       send_arg_list ("-s", log_data.statelist);
+       dellist (&log_data.statelist);
+       send_arg_list ("-w", log_data.authorlist);
+       dellist (&log_data.authorlist);
+       send_arg ("--");
+
+       if (is_rlog)
+       {
+           int i;
+           for (i = 0; i < argc; i++)
+               send_arg (argv[i]);
+           send_to_server ("rlog\012", 0);
+       }
+       else
+       {
+           send_files (argc, argv, local, 0, SEND_NO_CONTENTS,
+                       SIGN_NEVER, NULL, NULL);
+           send_file_names (argc, argv, SEND_EXPAND_WILD);
+           send_to_server ("log\012", 0);
+       }
+        err = get_responses_and_close ();
+       return err;
+    }
+#endif
+
+    /* OK, now that we know we are local/server, we can resolve @@MYSELF
+       into our user name.  */
+    if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
+       log_parse_list (&log_data.authorlist, getcaller ());
+
+    if (is_rlog)
+    {
+       DBM *db;
+       int i;
+       db = open_module ();
+       for (i = 0; i < argc; i++)
+       {
+             err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
+                               NULL, 0, local, 0, 0, NULL);
+       }
+       close_module (db);
+    }
+    else
+    {
+        err = rlog_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, local, NULL,
+                         NULL);
+    }
+
+    while (log_data.revlist)
+    {
+       struct option_revlist *rl = log_data.revlist->next;
+       if (log_data.revlist->first)
+           free (log_data.revlist->first);
+       if (log_data.revlist->last)
+           free (log_data.revlist->last);
+       free (log_data.revlist);
+       log_data.revlist = rl;
+    }
+    while (log_data.datelist)
+    {
+       struct datelist *nd = log_data.datelist->next;
+       if (log_data.datelist->start)
+           free (log_data.datelist->start);
+       if (log_data.datelist->end)
+           free (log_data.datelist->end);
+       free (log_data.datelist);
+       log_data.datelist = nd;
+    }
+    while (log_data.singledatelist)
+    {
+       struct datelist *nd = log_data.singledatelist->next;
+       if (log_data.singledatelist->start)
+           free (log_data.singledatelist->start);
+       if (log_data.singledatelist->end)
+           free (log_data.singledatelist->end);
+       free (log_data.singledatelist);
+       log_data.singledatelist = nd;
+    }
+    dellist (&log_data.statelist);
+    dellist (&log_data.authorlist);
+
+    return err;
+}
+
+
+
+static int
+rlog_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
+           int shorten, int local, char *mname, char *msg)
+{
+    /* Begin section which is identical to patch_proc--should this
+       be abstracted out somehow?  */
+    char *myargv[2];
+    int err = 0;
+    int which;
+    char *repository = NULL;
+    char *where;
+
+    if (is_rlog)
+    {
+       repository = xmalloc (strlen (current_parsed_root->directory)
+                              + strlen (argv[0])
+                             + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
+       (void)sprintf (repository, "%s/%s",
+                       current_parsed_root->directory, argv[0]);
+       where = xmalloc (strlen (argv[0])
+                         + (mfile == NULL ? 0 : strlen (mfile) + 1)
+                        + 1);
+       (void)strcpy (where, argv[0]);
+
+       /* If mfile isn't null, we need to set up to do only part of theu
+         * module.
+         */
+       if (mfile != NULL)
+       {
+           char *cp;
+           char *path;
+
+           /* If the portion of the module is a path, put the dir part on
+             * repos.
+             */
+           if ((cp = strrchr (mfile, '/')) != NULL)
+           {
+               *cp = '\0';
+               (void)strcat (repository, "/");
+               (void)strcat (repository, mfile);
+               (void)strcat (where, "/");
+               (void)strcat (where, mfile);
+               mfile = cp + 1;
+           }
+
+           /* take care of the rest */
+           path = Xasprintf ("%s/%s", repository, mfile);
+           if (isdir (path))
+           {
+               /* directory means repository gets the dir tacked on */
+               (void)strcpy (repository, path);
+               (void)strcat (where, "/");
+               (void)strcat (where, mfile);
+           }
+           else
+           {
+               myargv[0] = argv[0];
+               myargv[1] = mfile;
+               argc = 2;
+               argv = myargv;
+           }
+           free (path);
+       }
+
+       /* cd to the starting repository */
+       if (CVS_CHDIR (repository) < 0)
+       {
+           error (0, errno, "cannot chdir to %s", repository);
+           free (repository);
+           free (where);
+           return 1;
+       }
+       /* End section which is identical to patch_proc.  */
+
+       which = W_REPOS | W_ATTIC;
+    }
+    else
+    {
+        repository = NULL;
+        where = NULL;
+        which = W_LOCAL | W_REPOS | W_ATTIC;
+    }
+
+    err = start_recursion (log_fileproc, NULL, log_dirproc,
+                          NULL, &log_data,
+                          argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
+                          where, 1, repository);
+
+    if (!(which & W_LOCAL)) free (repository);
+    if (where) free (where);
+
+    return err;
+}
+
+
+
+/*
+ * Parse a revision list specification.
+ */
+static struct option_revlist *
+log_parse_revlist (const char *argstring)
+{
+    char *orig_copy, *copy;
+    struct option_revlist *ret, **pr;
+
+    /* Unfortunately, rlog accepts -r without an argument to mean that
+       latest revision on the default branch, so we must support that
+       for compatibility.  */
+    if (argstring == NULL)
+       argstring = "";
+
+    ret = NULL;
+    pr = &ret;
+
+    /* Copy the argument into memory so that we can change it.  We
+       don't want to change the argument because, at least as of this
+       writing, we will use it if we send the arguments to the server.  */
+    orig_copy = copy = xstrdup (argstring);
+    while (copy != NULL)
+    {
+       char *comma;
+       struct option_revlist *r;
+
+       comma = strchr (copy, ',');
+       if (comma != NULL)
+           *comma++ = '\0';
+
+       r = xmalloc (sizeof *r);
+       r->next = NULL;
+       r->first = copy;
+       r->branchhead = 0;
+       r->last = strchr (copy, ':');
+       if (r->last != NULL)
+       {
+           *r->last++ = '\0';
+           r->inclusive = (*r->last != ':');
+           if (!r->inclusive)
+               r->last++;
+       }
+       else
+       {
+           r->last = r->first;
+           r->inclusive = 1;
+           if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
+           {
+               r->branchhead = 1;
+               r->first[strlen (r->first) - 1] = '\0';
+           }
+       }
+
+       if (*r->first == '\0')
+           r->first = NULL;
+       if (*r->last == '\0')
+           r->last = NULL;
+
+       if (r->first != NULL)
+           r->first = xstrdup (r->first);
+       if (r->last != NULL)
+           r->last = xstrdup (r->last);
+
+       *pr = r;
+       pr = &r->next;
+
+       copy = comma;
+    }
+
+    free (orig_copy);
+    return ret;
+}
+
+
+
+/*
+ * Parse a date specification.
+ */
+static void
+log_parse_date (struct log_data *log_data, const char *argstring)
+{
+    char *orig_copy, *copy;
+
+    /* Copy the argument into memory so that we can change it.  We
+       don't want to change the argument because, at least as of this
+       writing, we will use it if we send the arguments to the server.  */
+    orig_copy = copy = xstrdup (argstring);
+    while (copy != NULL)
+    {
+       struct datelist *nd, **pd;
+       char *cpend, *cp, *ds, *de;
+
+       nd = xmalloc (sizeof *nd);
+
+       cpend = strchr (copy, ';');
+       if (cpend != NULL)
+           *cpend++ = '\0';
+
+       pd = &log_data->datelist;
+       nd->inclusive = 0;
+
+       if ((cp = strchr (copy, '>')) != NULL)
+       {
+           *cp++ = '\0';
+           if (*cp == '=')
+           {
+               ++cp;
+               nd->inclusive = 1;
+           }
+           ds = cp;
+           de = copy;
+       }
+       else if ((cp = strchr (copy, '<')) != NULL)
+       {
+           *cp++ = '\0';
+           if (*cp == '=')
+           {
+               ++cp;
+               nd->inclusive = 1;
+           }
+           ds = copy;
+           de = cp;
+       }
+       else
+       {
+           ds = NULL;
+           de = copy;
+           pd = &log_data->singledatelist;
+       }
+
+       if (ds == NULL)
+           nd->start = NULL;
+       else if (*ds != '\0')
+           nd->start = Make_Date (ds);
+       else
+       {
+         /* 1970 was the beginning of time, as far as get_date and
+            Make_Date are concerned.  FIXME: That is true only if time_t
+            is a POSIX-style time and there is nothing in ANSI that
+            mandates that.  It would be cleaner to set a flag saying
+            whether or not there is a start date.  */
+           nd->start = Make_Date ("1/1/1970 UTC");
+       }
+
+       if (*de != '\0')
+           nd->end = Make_Date (de);
+       else
+       {
+           /* We want to set the end date to some time sufficiently far
+              in the future to pick up all revisions that have been
+              created since the specified date and the time `cvs log'
+              completes.  FIXME: The date in question only makes sense
+              if time_t is a POSIX-style time and it is 32 bits
+              and signed.  We should instead be setting a flag saying
+              whether or not there is an end date.  Note that using
+              something like "next week" would break the testsuite (and,
+              perhaps less importantly, loses if the clock is set grossly
+              wrong).  */
+           nd->end = Make_Date ("2038-01-01");
+       }
+
+       nd->next = *pd;
+       *pd = nd;
+
+       copy = cpend;
+    }
+
+    free (orig_copy);
+}
+
+
+
+/*
+ * Parse a comma separated list of items, and add each one to *PLIST.
+ */
+static void
+log_parse_list (List **plist, const char *argstring)
+{
+    while (1)
+    {
+       Node *p;
+       char *cp;
+
+       p = getnode ();
+
+       cp = strchr (argstring, ',');
+       if (cp == NULL)
+           p->key = xstrdup (argstring);
+       else
+       {
+           size_t len;
+
+           len = cp - argstring;
+           p->key = xmalloc (len + 1);
+           strncpy (p->key, argstring, len);
+           p->key[len] = '\0';
+       }
+
+       if (*plist == NULL)
+           *plist = getlist ();
+       if (addnode (*plist, p) != 0)
+           freenode (p);
+
+       if (cp == NULL)
+           break;
+
+       argstring = cp + 1;
+    }
+}
+
+
+
+static int
+printlock_proc (Node *lock, void *foo)
+{
+    cvs_output ("\n\t", 2);
+    cvs_output (lock->data, 0);
+    cvs_output (": ", 2);
+    cvs_output (lock->key, 0);
+    return 0;
+}
+
+
+
+/*
+ * Do an rlog on a file
+ */
+static int
+log_fileproc (void *callerdat, struct file_info *finfo)
+{
+    struct log_data *log_data = callerdat;
+    Node *p;
+    char *baserev;
+    int selrev = -1;
+    RCSNode *rcsfile;
+    char buf[50];
+    struct revlist *revlist = NULL;
+    struct log_data_and_rcs log_data_and_rcs;
+
+    rcsfile = finfo->rcs;
+    p = findnode (finfo->entries, finfo->file);
+    if (p != NULL)
+    {
+       Entnode *e = p->data;
+       baserev = e->version;
+       if (baserev[0] == '-') ++baserev;
+    }
+    else
+       baserev = NULL;
+
+    if (rcsfile == NULL)
+    {
+       /* no rcs file.  What *do* we know about this file? */
+       if (baserev != NULL)
+       {
+           if (baserev[0] == '0' && baserev[1] == '\0')
+           {
+               if (!really_quiet)
+                   error (0, 0, "%s has been added, but not committed",
+                          finfo->file);
+               return 0;
+           }
+       }
+       
+       if (!really_quiet)
+           error (0, 0, "nothing known about %s", finfo->file);
+       
+       return 1;
+    }
+
+    if (log_data->sup_header || !log_data->nameonly)
+    {
+
+       /* We will need all the information in the RCS file.  */
+       RCS_fully_parse (rcsfile);
+
+       /* Turn any symbolic revisions in the revision list into numeric
+          revisions.  */
+       revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist,
+                                     log_data->default_branch);
+       if (log_data->sup_header
+            || (!log_data->header && !log_data->long_header))
+       {
+           log_data_and_rcs.log_data = log_data;
+           log_data_and_rcs.revlist = revlist;
+           log_data_and_rcs.rcs = rcsfile;
+
+           /* If any single dates were specified, we need to identify the
+              revisions they select.  Each one selects the single
+              revision, which is otherwise selected, of that date or
+              earlier.  The log_fix_singledate routine will fill in the
+              start date for each specific revision.  */
+           if (log_data->singledatelist != NULL)
+               walklist (rcsfile->versions, log_fix_singledate,
+                         &log_data_and_rcs);
+
+           selrev = walklist (rcsfile->versions, log_count_print,
+                              &log_data_and_rcs);
+           if (log_data->sup_header && selrev == 0)
+           {
+               log_free_revlist (revlist);
+               return 0;
+           }
+       }
+
+    }
+
+    if (log_data->nameonly)
+    {
+       cvs_output (rcsfile->print_path, 0);
+       cvs_output ("\n", 1);
+       log_free_revlist (revlist);
+       return 0;
+    }
+
+    /* The output here is intended to be exactly compatible with the
+       output of rlog.  I'm not sure whether this code should be here
+       or in rcs.c; I put it here because it is specific to the log
+       function, even though it uses information gathered by the
+       functions in rcs.c.  */
+
+    cvs_output ("\n", 1);
+
+    cvs_output ("RCS file: ", 0);
+    cvs_output (rcsfile->print_path, 0);
+
+    if (!is_rlog)
+    {
+       cvs_output ("\nWorking file: ", 0);
+       if (finfo->update_dir[0] != '\0')
+       {
+           cvs_output (finfo->update_dir, 0);
+           cvs_output ("/", 0);
+       }
+       cvs_output (finfo->file, 0);
+    }
+
+    cvs_output ("\nhead:", 0);
+    if (rcsfile->head != NULL)
+    {
+       cvs_output (" ", 1);
+       cvs_output (rcsfile->head, 0);
+    }
+
+    cvs_output ("\nbranch:", 0);
+    if (rcsfile->branch != NULL)
+    {
+       cvs_output (" ", 1);
+       cvs_output (rcsfile->branch, 0);
+    }
+
+    cvs_output ("\nlocks:", 0);
+    if (rcsfile->strict_locks)
+       cvs_output (" strict", 0);
+    walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
+
+    cvs_output ("\naccess list:", 0);
+    if (rcsfile->access != NULL)
+    {
+       const char *cp;
+
+       cp = rcsfile->access;
+       while (*cp != '\0')
+       {
+               const char *cp2;
+
+               cvs_output ("\n\t", 2);
+               cp2 = cp;
+               while (!isspace ((unsigned char)*cp2) && *cp2 != '\0')
+                   ++cp2;
+               cvs_output (cp, cp2 - cp);
+               cp = cp2;
+               while (isspace ((unsigned char)*cp) && *cp != '\0')
+                   ++cp;
+       }
+    }
+
+    if (!log_data->notags)
+    {
+       List *syms;
+
+       cvs_output ("\nsymbolic names:", 0);
+       syms = RCS_symbols (rcsfile);
+       walklist (syms, log_symbol, NULL);
+    }
+
+    cvs_output ("\nkeyword substitution: ", 0);
+    if (rcsfile->expand == NULL)
+       cvs_output ("kv", 2);
+    else
+       cvs_output (rcsfile->expand, 0);
+
+    cvs_output ("\ntotal revisions: ", 0);
+    sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
+    cvs_output (buf, 0);
+
+    if (selrev >= 0)
+    {
+       cvs_output (";\tselected revisions: ", 0);
+       sprintf (buf, "%d", selrev);
+       cvs_output (buf, 0);
+    }
+
+    cvs_output ("\n", 1);
+
+    if (!log_data->header || log_data->long_header)
+    {
+       cvs_output ("description:\n", 0);
+       if (rcsfile->desc != NULL)
+           cvs_output (rcsfile->desc, 0);
+    }
+
+    if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
+    {
+       p = findnode (rcsfile->versions, rcsfile->head);
+       if (p == NULL)
+           error (1, 0, "can not find head revision in `%s'",
+                  finfo->fullname);
+       while (p != NULL)
+       {
+           RCSVers *vers = p->data;
+
+           log_version (log_data, revlist, rcsfile, vers, 1);
+           if (vers->next == NULL)
+               p = NULL;
+           else
+           {
+               p = findnode (rcsfile->versions, vers->next);
+               if (p == NULL)
+                   error (1, 0, "can not find next revision `%s' in `%s'",
+                          vers->next, finfo->fullname);
+           }
+       }
+
+       log_tree (log_data, revlist, rcsfile, rcsfile->head);
+    }
+
+    cvs_output("\
+=============================================================================\n",
+              0);
+
+    /* Free up the new revlist and restore the old one.  */
+    log_free_revlist (revlist);
+
+    /* If singledatelist is not NULL, free up the start dates we added
+       to it.  */
+    if (log_data->singledatelist != NULL)
+    {
+       struct datelist *d;
+
+       for (d = log_data->singledatelist; d != NULL; d = d->next)
+       {
+           if (d->start != NULL)
+               free (d->start);
+           d->start = NULL;
+       }
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Fix up a revision list in order to compare it against versions.
+ * Expand any symbolic revisions.
+ */
+static struct revlist *
+log_expand_revlist (RCSNode *rcs, char *baserev,
+                    struct option_revlist *revlist, int default_branch)
+{
+    struct option_revlist *r;
+    struct revlist *ret, **pr;
+
+    ret = NULL;
+    pr = &ret;
+    for (r = revlist; r != NULL; r = r->next)
+    {
+       struct revlist *nr;
+
+       nr = xmalloc (sizeof *nr);
+       nr->inclusive = r->inclusive;
+
+       if (r->first == NULL && r->last == NULL)
+       {
+           /* If both first and last are NULL, it means that we want
+              just the head of the default branch, which is RCS_head.  */
+           nr->first = RCS_head (rcs);
+           if (!nr->first)
+           {
+               if (!really_quiet)
+                   error (0, 0, "No head revision in archive `%s'.",
+                          rcs->path);
+               nr->last = NULL;
+               nr->fields = 0;
+           }
+           else
+           {
+               nr->last = xstrdup (nr->first);
+               nr->fields = numdots (nr->first) + 1;
+           }
+       }
+       else if (r->branchhead)
+       {
+           char *branch;
+
+           /* Print just the head of the branch.  */
+           if (isdigit ((unsigned char) r->first[0]))
+               nr->first = RCS_getbranch (rcs, r->first, 1);
+           else
+           {
+               branch = RCS_whatbranch (rcs, r->first);
+               if (branch == NULL)
+                   nr->first = NULL;
+               else
+               {
+                   nr->first = RCS_getbranch (rcs, branch, 1);
+                   free (branch);
+               }
+           }
+           if (!nr->first)
+           {
+               if (!really_quiet)
+                   error (0, 0, "warning: no branch `%s' in `%s'",
+                          r->first, rcs->print_path);
+               nr->last = NULL;
+               nr->fields = 0;
+           }
+           else
+           {
+               nr->last = xstrdup (nr->first);
+               nr->fields = numdots (nr->first) + 1;
+           }
+       }
+       else
+       {
+           if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
+               nr->first = xstrdup (r->first);
+           else
+           {
+               if (baserev && strcmp (r->first, TAG_BASE) == 0)
+                   nr->first = xstrdup (baserev);
+               else if (RCS_nodeisbranch (rcs, r->first))
+                   nr->first = RCS_whatbranch (rcs, r->first);
+               else
+                   nr->first = RCS_gettag (rcs, r->first, 1, NULL);
+               if (nr->first == NULL && !really_quiet)
+               {
+                   error (0, 0, "warning: no revision `%s' in `%s'",
+                          r->first, rcs->print_path);
+               }
+           }
+
+           if (r->last == r->first || (r->last != NULL && r->first != NULL &&
+                                       strcmp (r->last, r->first) == 0))
+               nr->last = xstrdup (nr->first);
+           else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
+               nr->last = xstrdup (r->last);
+           else
+           {
+               if (baserev && strcmp (r->last, TAG_BASE) == 0)
+                   nr->last = xstrdup (baserev);
+               else if (RCS_nodeisbranch (rcs, r->last))
+                   nr->last = RCS_whatbranch (rcs, r->last);
+               else
+                   nr->last = RCS_gettag (rcs, r->last, 1, NULL);
+               if (nr->last == NULL && !really_quiet)
+               {
+                   error (0, 0, "warning: no revision `%s' in `%s'",
+                          r->last, rcs->print_path);
+               }
+           }
+
+           /* Process the revision numbers the same way that rlog
+               does.  This code is a bit cryptic for my tastes, but
+               keeping the same implementation as rlog ensures a
+               certain degree of compatibility.  */
+           if (r->first == NULL && nr->last != NULL)
+           {
+               nr->fields = numdots (nr->last) + 1;
+               if (nr->fields < 2)
+                   nr->first = xstrdup (".0");
+               else
+               {
+                   char *cp;
+
+                   nr->first = xstrdup (nr->last);
+                   cp = strrchr (nr->first, '.');
+                   assert (cp);
+                   strcpy (cp + 1, "0");
+               }
+           }
+           else if (r->last == NULL && nr->first != NULL)
+           {
+               nr->fields = numdots (nr->first) + 1;
+               nr->last = xstrdup (nr->first);
+               if (nr->fields < 2)
+                   nr->last[0] = '\0';
+               else
+               {
+                   char *cp;
+
+                   cp = strrchr (nr->last, '.');
+                   assert (cp);
+                   *cp = '\0';
+               }
+           }
+           else if (nr->first == NULL || nr->last == NULL)
+               nr->fields = 0;
+           else if (strcmp (nr->first, nr->last) == 0)
+               nr->fields = numdots (nr->last) + 1;
+           else
+           {
+               int ord;
+               int dots1 = numdots (nr->first);
+               int dots2 = numdots (nr->last);
+               if (dots1 > dots2 || (dots1 == dots2 &&
+                   version_compare (nr->first, nr->last, dots1 + 1) > 0))
+               {
+                   char *tmp = nr->first;
+                   nr->first = nr->last;
+                   nr->last = tmp;
+                   nr->fields = dots2 + 1;
+                   dots2 = dots1;
+                   dots1 = nr->fields - 1;
+               }
+               else
+                   nr->fields = dots1 + 1;
+               dots1 += (nr->fields & 1);
+               ord = version_compare (nr->first, nr->last, dots1);
+               if (ord > 0 || (nr->fields > 2 && ord < 0))
+               {
+                   error (0, 0,
+                          "invalid branch or revision pair %s:%s in `%s'",
+                          r->first, r->last, rcs->print_path);
+                   free (nr->first);
+                   nr->first = NULL;
+                   free (nr->last);
+                   nr->last = NULL;
+                   nr->fields = 0;
+               }
+               else
+               {
+                   if (nr->fields <= dots2 && (nr->fields & 1))
+                   {
+                       char *p = Xasprintf ("%s.0", nr->first);
+                       free (nr->first);
+                       nr->first = p;
+                       ++nr->fields;
+                   }
+                   while (nr->fields <= dots2)
+                   {
+                       char *p;
+                       int i;
+
+                       nr->next = NULL;
+                       *pr = nr;
+                       nr = xmalloc (sizeof *nr);
+                       nr->inclusive = 1;
+                       nr->first = xstrdup ((*pr)->last);
+                       nr->last = xstrdup ((*pr)->last);
+                       nr->fields = (*pr)->fields;
+                       p = (*pr)->last;
+                       for (i = 0; i < nr->fields; i++)
+                           p = strchr (p, '.') + 1;
+                       p[-1] = '\0';
+                       p = strchr (nr->first + (p - (*pr)->last), '.');
+                       if (p != NULL)
+                       {
+                           *++p = '0';
+                           *++p = '\0';
+                           nr->fields += 2;
+                       }
+                       else
+                           ++nr->fields;
+                       pr = &(*pr)->next;
+                   }
+               }
+           }
+       }
+
+       nr->next = NULL;
+       *pr = nr;
+       pr = &nr->next;
+    }
+
+    /* If the default branch was requested, add a revlist entry for
+       it.  This is how rlog handles this option.  */
+    if (default_branch
+       && (rcs->head != NULL || rcs->branch != NULL))
+    {
+       struct revlist *nr;
+
+       nr = xmalloc (sizeof *nr);
+       if (rcs->branch != NULL)
+           nr->first = xstrdup (rcs->branch);
+       else
+       {
+           char *cp;
+
+           nr->first = xstrdup (rcs->head);
+           assert (nr->first);
+           cp = strrchr (nr->first, '.');
+           assert (cp);
+           *cp = '\0';
+       }
+       nr->last = xstrdup (nr->first);
+       nr->fields = numdots (nr->first) + 1;
+       nr->inclusive = 1;
+
+       nr->next = NULL;
+       *pr = nr;
+    }
+
+    return ret;
+}
+
+
+
+/*
+ * Free a revlist created by log_expand_revlist.
+ */
+static void
+log_free_revlist (struct revlist *revlist)
+{
+    struct revlist *r;
+
+    r = revlist;
+    while (r != NULL)
+    {
+       struct revlist *next;
+
+       if (r->first != NULL)
+           free (r->first);
+       if (r->last != NULL)
+           free (r->last);
+       next = r->next;
+       free (r);
+       r = next;
+    }
+}
+
+
+
+/*
+ * Return nonzero if a revision should be printed, based on the
+ * options provided.
+ */
+static int
+log_version_requested (struct log_data *log_data, struct revlist *revlist,
+                       RCSNode *rcs, RCSVers *vnode)
+{
+    /* Handle the list of states from the -s option.  */
+    if (log_data->statelist != NULL
+       && findnode (log_data->statelist, vnode->state) == NULL)
+    {
+       return 0;
+    }
+
+    /* Handle the list of authors from the -w option.  */
+    if (log_data->authorlist != NULL)
+    {
+       if (vnode->author != NULL
+           && findnode (log_data->authorlist, vnode->author) == NULL)
+       {
+           return 0;
+       }
+    }
+
+    /* rlog considers all the -d options together when it decides
+       whether to print a revision, so we must be compatible.  */
+    if (log_data->datelist != NULL || log_data->singledatelist != NULL)
+    {
+       struct datelist *d;
+
+       for (d = log_data->datelist; d != NULL; d = d->next)
+       {
+           int cmp;
+
+           cmp = RCS_datecmp (vnode->date, d->start);
+           if (cmp > 0 || (cmp == 0 && d->inclusive))
+           {
+               cmp = RCS_datecmp (vnode->date, d->end);
+               if (cmp < 0 || (cmp == 0 && d->inclusive))
+                   break;
+           }
+       }
+
+       if (d == NULL)
+       {
+           /* Look through the list of specific dates.  We want to
+              select the revision with the exact date found in the
+              start field.  The commit code ensures that it is
+              impossible to check in multiple revisions of a single
+              file in a single second, so checking the date this way
+              should never select more than one revision.  */
+           for (d = log_data->singledatelist; d != NULL; d = d->next)
+           {
+               if (d->start != NULL
+                   && RCS_datecmp (vnode->date, d->start) == 0)
+               {
+                   break;
+               }
+           }
+
+           if (d == NULL)
+               return 0;
+       }
+    }
+
+    /* If the -r or -b options were used, REVLIST will be non NULL,
+       and we print the union of the specified revisions.  */
+    if (revlist != NULL)
+    {
+       char *v;
+       int vfields;
+       struct revlist *r;
+
+       /* This code is taken from rlog.  */
+       v = vnode->version;
+       vfields = numdots (v) + 1;
+       for (r = revlist; r != NULL; r = r->next)
+       {
+            if (vfields == r->fields + (r->fields & 1) &&
+                (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
+                                version_compare (v, r->first, r->fields) > 0)
+                && version_compare (v, r->last, r->fields) <= 0)
+           {
+               return 1;
+           }
+       }
+
+       /* If we get here, then the -b and/or the -r option was used,
+           but did not match this revision, so we reject it.  */
+
+       return 0;
+    }
+
+    /* By default, we print all revisions.  */
+    return 1;
+}
+
+
+
+/*
+ * Output a single symbol.  This is called via walklist.
+ */
+/*ARGSUSED*/
+static int
+log_symbol (Node *p, void *closure)
+{
+    cvs_output ("\n\t", 2);
+    cvs_output (p->key, 0);
+    cvs_output (": ", 2);
+    cvs_output (p->data, 0);
+    return 0;
+}
+
+
+
+/*
+ * Count the number of entries on a list.  This is called via walklist.
+ */
+/*ARGSUSED*/
+static int
+log_count (Node *p, void *closure)
+{
+    return 1;
+}
+
+
+
+/*
+ * Sort out a single date specification by narrowing down the date
+ * until we find the specific selected revision.
+ */
+static int
+log_fix_singledate (Node *p, void *closure)
+{
+    struct log_data_and_rcs *data = closure;
+    Node *pv;
+    RCSVers *vnode;
+    struct datelist *holdsingle, *holddate;
+    int requested;
+
+    pv = findnode (data->rcs->versions, p->key);
+    if (pv == NULL)
+       error (1, 0, "missing version `%s' in RCS file `%s'",
+              p->key, data->rcs->print_path);
+    vnode = pv->data;
+
+    /* We are only interested if this revision passes any other tests.
+       Temporarily clear log_data->singledatelist to avoid confusing
+       log_version_requested.  We also clear log_data->datelist,
+       because rlog considers all the -d options together.  We don't
+       want to reject a revision because it does not match a date pair
+       if we are going to select it on the basis of the singledate.  */
+    holdsingle = data->log_data->singledatelist;
+    data->log_data->singledatelist = NULL;
+    holddate = data->log_data->datelist;
+    data->log_data->datelist = NULL;
+    requested = log_version_requested (data->log_data, data->revlist,
+                                      data->rcs, vnode);
+    data->log_data->singledatelist = holdsingle;
+    data->log_data->datelist = holddate;
+
+    if (requested)
+    {
+       struct datelist *d;
+
+       /* For each single date, if this revision is before the
+          specified date, but is closer than the previously selected
+          revision, select it instead.  */
+       for (d = data->log_data->singledatelist; d != NULL; d = d->next)
+       {
+           if (RCS_datecmp (vnode->date, d->end) <= 0
+               && (d->start == NULL
+                   || RCS_datecmp (vnode->date, d->start) > 0))
+           {
+               if (d->start != NULL)
+                   free (d->start);
+               d->start = xstrdup (vnode->date);
+           }
+       }
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Count the number of revisions we are going to print.
+ */
+static int
+log_count_print (Node *p, void *closure)
+{
+    struct log_data_and_rcs *data = closure;
+    Node *pv;
+
+    pv = findnode (data->rcs->versions, p->key);
+    if (pv == NULL)
+       error (1, 0, "missing version `%s' in RCS file `%s'",
+              p->key, data->rcs->print_path);
+    if (log_version_requested (data->log_data, data->revlist, data->rcs,
+                              pv->data))
+       return 1;
+    else
+       return 0;
+}
+
+
+
+/*
+ * Print the list of changes, not including the trunk, in reverse
+ * order for each branch.
+ */
+static void
+log_tree (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
+          const char *ver)
+{
+    Node *p;
+    RCSVers *vnode;
+
+    p = findnode (rcs->versions, ver);
+    if (p == NULL)
+       error (1, 0, "missing version `%s' in RCS file `%s'",
+              ver, rcs->print_path);
+    vnode = p->data;
+    if (vnode->next != NULL)
+       log_tree (log_data, revlist, rcs, vnode->next);
+    if (vnode->branches != NULL)
+    {
+       Node *head, *branch;
+
+       /* We need to do the branches in reverse order.  This breaks
+           the List abstraction, but so does most of the branch
+           manipulation in rcs.c.  */
+       head = vnode->branches->list;
+       for (branch = head->prev; branch != head; branch = branch->prev)
+       {
+           log_abranch (log_data, revlist, rcs, branch->key);
+           log_tree (log_data, revlist, rcs, branch->key);
+       }
+    }
+}
+
+
+
+/*
+ * Log the changes for a branch, in reverse order.
+ */
+static void
+log_abranch (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
+             const char *ver)
+{
+    Node *p;
+    RCSVers *vnode;
+
+    p = findnode (rcs->versions, ver);
+    if (p == NULL)
+       error (1, 0, "missing version `%s' in RCS file `%s'",
+              ver, rcs->print_path);
+    vnode = p->data;
+    if (vnode->next != NULL)
+       log_abranch (log_data, revlist, rcs, vnode->next);
+    log_version (log_data, revlist, rcs, vnode, 0);
+}
+
+
+
+/*
+ * Print the log output for a single version.
+ */
+static void
+log_version (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs, 
+             RCSVers *ver, int trunk)
+{
+    Node *p;
+    int year, mon, mday, hour, min, sec;
+    char buf[100];
+    Node *padd, *pdel;
+
+    if (! log_version_requested (log_data, revlist, rcs, ver))
+       return;
+
+    cvs_output ("----------------------------\nrevision ", 0);
+    cvs_output (ver->version, 0);
+
+    p = findnode (RCS_getlocks (rcs), ver->version);
+    if (p != NULL)
+    {
+       cvs_output ("\tlocked by: ", 0);
+       cvs_output (p->data, 0);
+       cvs_output (";", 1);
+    }
+    cvs_output ("\n", 1);
+
+    cvs_output_tagged ("text", "date: ");
+    (void)sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
+                 &sec);
+    if (year < 1900)
+       year += 1900;
+    sprintf (buf, "%04d-%02d-%02d %02d:%02d:%02d +0000", year, mon, mday,
+            hour, min, sec);
+    cvs_output_tagged ("date", buf);
+
+    cvs_output_tagged ("text", ";  author: ");
+    cvs_output_tagged ("text", ver->author);
+
+    cvs_output_tagged ("text", ";  state: ");
+    cvs_output_tagged ("text", ver->state);
+    cvs_output_tagged ("text", ";");
+
+    if (! trunk)
+    {
+       padd = findnode (ver->other, ";add");
+       pdel = findnode (ver->other, ";delete");
+    }
+    else if (ver->next == NULL)
+    {
+       padd = NULL;
+       pdel = NULL;
+    }
+    else
+    {
+       Node *nextp;
+       RCSVers *nextver;
+
+       nextp = findnode (rcs->versions, ver->next);
+       if (nextp == NULL)
+           error (1, 0, "missing version `%s' in `%s'", ver->next,
+                  rcs->print_path);
+       nextver = nextp->data;
+       pdel = findnode (nextver->other, ";add");
+       padd = findnode (nextver->other, ";delete");
+    }
+
+    if (padd != NULL)
+    {
+       assert (pdel);
+       cvs_output_tagged ("text", "  lines: +");
+       cvs_output_tagged ("text", padd->data);
+       cvs_output_tagged ("text", " -");
+       cvs_output_tagged ("text", pdel->data);
+        cvs_output_tagged ("text", ";");
+    }
+
+    p = findnode(ver->other_delta,"commitid");
+    if(p && p->data)
+    {
+        cvs_output_tagged ("text", "  commitid: ");
+       cvs_output_tagged ("text", p->data);
+       cvs_output_tagged ("text", ";");
+    }
+
+    cvs_output_tagged ("newline", NULL);
+
+    if (ver->branches != NULL)
+    {
+       cvs_output ("branches:", 0);
+       walklist (ver->branches, log_branch, NULL);
+       cvs_output ("\n", 1);
+    }
+
+    p = findnode (ver->other, "log");
+    /* The p->date == NULL case is the normal one for an empty log
+       message (rcs-14 in sanity.sh).  I don't think the case where
+       p->data is "" can happen (getrcskey in rcs.c checks for an
+       empty string and set the value to NULL in that case).  My guess
+       would be the p == NULL case would mean an RCS file which was
+       missing the "log" keyword (which is invalid according to
+       rcsfile.5).  */
+    if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
+       cvs_output ("*** empty log message ***\n", 0);
+    else
+    {
+       /* FIXME: Technically, the log message could contain a null
+           byte.  */
+       cvs_output (p->data, 0);
+       if (((char *)p->data)[strlen (p->data) - 1] != '\n')
+           cvs_output ("\n", 1);
+    }
+}
+
+
+
+/*
+ * Output a branch version.  This is called via walklist.
+ */
+/*ARGSUSED*/
+static int
+log_branch (Node *p, void *closure)
+{
+    cvs_output ("  ", 2);
+    if ((numdots (p->key) & 1) == 0)
+       cvs_output (p->key, 0);
+    else
+    {
+       char *f, *cp;
+
+       f = xstrdup (p->key);
+       cp = strrchr (f, '.');
+       *cp = '\0';
+       cvs_output (f, 0);
+       free (f);
+    }
+    cvs_output (";", 1);
+    return 0;
+}
+
+
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+log_dirproc (void *callerdat, const char *dir, const char *repository,
+             const char *update_dir, List *entries)
+{
+    if (!isdir (dir))
+       return R_SKIP_ALL;
+
+    if (!quiet)
+       error (0, 0, "Logging %s", update_dir);
+    return R_PROCESS;
+}
+
+
+
+/*
+ * Compare versions.  This is taken from RCS compartial.
+ */
+static int
+version_compare (const char *v1, const char *v2, int len)
+{
+    while (1)
+    {
+       int d1, d2, r;
+
+       if (*v1 == '\0')
+           return 1;
+       if (*v2 == '\0')
+           return -1;
+
+       while (*v1 == '0')
+           ++v1;
+       for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
+           ;
+
+       while (*v2 == '0')
+           ++v2;
+       for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
+           ;
+
+       if (d1 != d2)
+           return d1 < d2 ? -1 : 1;
+
+       r = memcmp (v1, v2, d1);
+       if (r != 0)
+           return r;
+
+       --len;
+       if (len == 0)
+           return 0;
+
+       v1 += d1;
+       v2 += d1;
+
+       if (*v1 == '.')
+           ++v1;
+       if (*v2 == '.')
+           ++v2;
+    }
+}




reply via email to

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