Merge from vendor branch CVS:
[dragonfly.git] / contrib / cvs-1.12 / src / log.c
1 /*
2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3  *
4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5  *                                  and others.
6  *
7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8  * Portions Copyright (C) 1989-1992, Brian Berliner
9  * 
10  * You may distribute under the terms of the GNU General Public License as
11  * specified in the README file that comes with the CVS source distribution.
12  * 
13  * Print Log Information
14  * 
15  * Prints the RCS "log" (rlog) information for the specified files.  With no
16  * argument, prints the log information for all the files in the directory
17  * (recursive by default).
18  */
19
20 #include "cvs.h"
21 #include <assert.h>
22
23 /* This structure holds information parsed from the -r option.  */
24
25 struct option_revlist
26 {
27     /* The next -r option.  */
28     struct option_revlist *next;
29     /* The first revision to print.  This is NULL if the range is
30        :rev, or if no revision is given.  */
31     char *first;
32     /* The last revision to print.  This is NULL if the range is rev:,
33        or if no revision is given.  If there is no colon, first and
34        last are the same.  */
35     char *last;
36     /* Nonzero if there was a trailing `.', which means to print only
37        the head revision of a branch.  */
38     int branchhead;
39     /* Nonzero if first and last are inclusive.  */
40     int inclusive;
41 };
42
43 /* This structure holds information derived from option_revlist given
44    a particular RCS file.  */
45
46 struct revlist
47 {
48     /* The next pair.  */
49     struct revlist *next;
50     /* The first numeric revision to print.  */
51     char *first;
52     /* The last numeric revision to print.  */
53     char *last;
54     /* The number of fields in these revisions (one more than
55        numdots).  */
56     int fields;
57     /* Whether first & last are to be included or excluded.  */
58     int inclusive;
59 };
60
61 /* This structure holds information parsed from the -d option.  */
62
63 struct datelist
64 {
65     /* The next date.  */
66     struct datelist *next;
67     /* The starting date.  */
68     char *start;
69     /* The ending date.  */
70     char *end;
71     /* Nonzero if the range is inclusive rather than exclusive.  */
72     int inclusive;
73 };
74
75 /* This structure is used to pass information through start_recursion.  */
76 struct log_data
77 {
78     /* Nonzero if the -R option was given, meaning that only the name
79        of the RCS file should be printed.  */
80     int nameonly;
81     /* Nonzero if the -h option was given, meaning that only header
82        information should be printed.  */
83     int header;
84     /* Nonzero if the -t option was given, meaning that only the
85        header and the descriptive text should be printed.  */
86     int long_header;
87     /* Nonzero if the -N option was seen, meaning that tag information
88        should not be printed.  */
89     int notags;
90     /* Nonzero if the -b option was seen, meaning that only revisions
91        on the default branch should be printed.  */
92     int default_branch;
93     /* Nonzero if the -S option was seen, meaning that the header/name
94        should be suppressed if no revisions are selected.  */
95     int sup_header;
96     /* If not NULL, the value given for the -r option, which lists
97        sets of revisions to be printed.  */
98     struct option_revlist *revlist;
99     /* If not NULL, the date pairs given for the -d option, which
100        select date ranges to print.  */
101     struct datelist *datelist;
102     /* If not NULL, the single dates given for the -d option, which
103        select specific revisions to print based on a date.  */
104     struct datelist *singledatelist;
105     /* If not NULL, the list of states given for the -s option, which
106        only prints revisions of given states.  */
107     List *statelist;
108     /* If not NULL, the list of login names given for the -w option,
109        which only prints revisions checked in by given users.  */
110     List *authorlist;
111 };
112
113 /* This structure is used to pass information through walklist.  */
114 struct log_data_and_rcs
115 {
116     struct log_data *log_data;
117     struct revlist *revlist;
118     RCSNode *rcs;
119 };
120
121 static int rlog_proc (int argc, char **argv, char *xwhere,
122                       char *mwhere, char *mfile, int shorten,
123                       int local_specified, char *mname, char *msg);
124 static Dtype log_dirproc (void *callerdat, const char *dir,
125                           const char *repository, const char *update_dir,
126                           List *entries);
127 static int log_fileproc (void *callerdat, struct file_info *finfo);
128 static struct option_revlist *log_parse_revlist (const char *);
129 static void log_parse_date (struct log_data *, const char *);
130 static void log_parse_list (List **, const char *);
131 static struct revlist *log_expand_revlist (RCSNode *, char *,
132                                            struct option_revlist *, int);
133 static void log_free_revlist (struct revlist *);
134 static int log_version_requested (struct log_data *, struct revlist *,
135                                          RCSNode *, RCSVers *);
136 static int log_symbol (Node *, void *);
137 static int log_count (Node *, void *);
138 static int log_fix_singledate (Node *, void *);
139 static int log_count_print (Node *, void *);
140 static void log_tree (struct log_data *, struct revlist *,
141                              RCSNode *, const char *);
142 static void log_abranch (struct log_data *, struct revlist *,
143                                 RCSNode *, const char *);
144 static void log_version (struct log_data *, struct revlist *,
145                                 RCSNode *, RCSVers *, int);
146 static int log_branch (Node *, void *);
147 static int version_compare (const char *, const char *, int);
148
149 static struct log_data log_data;
150 static int is_rlog;
151
152 static const char *const log_usage[] =
153 {
154     "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
155     "    [-w[logins]] [files...]\n",
156     "\t-l\tLocal directory only, no recursion.\n",
157     "\t-b\tOnly list revisions on the default branch.\n",
158     "\t-h\tOnly print header.\n",
159     "\t-R\tOnly print name of RCS file.\n",
160     "\t-t\tOnly print header and descriptive text.\n",
161     "\t-N\tDo not list tags.\n",
162     "\t-S\tDo not print name/header if no revisions selected.  -d, -r,\n",
163     "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n",
164     "\t\t-t without this option.\n",
165     "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
166     "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
167     "\t   rev1::rev2  Between rev1 and rev2, excluding rev1.\n",
168     "\t   rev:        rev and following revisions on the same branch.\n",
169     "\t   rev::       After rev on the same branch.\n",
170     "\t   :rev        rev and previous revisions on the same branch.\n",
171     "\t   ::rev       rev and previous revisions on the same branch.\n",
172     "\t   rev         Just rev.\n",
173     "\t   branch      All revisions on the branch.\n",
174     "\t   branch.     The last revision on the branch.\n",
175     "\t-d dates\tA semicolon-separated list of dates\n",
176     "\t        \t(D1<D2 for range, D for latest before).\n",
177     "\t-s states\tOnly list revisions with specified states.\n",
178     "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
179     "(Specify the --help global option for a list of other help options)\n",
180     NULL
181 };
182
183 #ifdef CLIENT_SUPPORT
184
185
186
187 /* Helper function for send_arg_list.  */
188 static int
189 send_one (Node *node, void *closure)
190 {
191     char *option = closure;
192
193     send_to_server ("Argument ", 0);
194     send_to_server (option, 0);
195     if (strcmp (node->key, "@@MYSELF") == 0)
196         /* It is a bare -w option.  Note that we must send it as
197            -w rather than messing with getcaller() or something (which on
198            the client will return garbage).  */
199         ;
200     else
201         send_to_server (node->key, 0);
202     send_to_server ("\012", 0);
203     return 0;
204 }
205
206
207
208 /* For each element in ARG, send an argument consisting of OPTION
209    concatenated with that element.  */
210 static void
211 send_arg_list (char *option, List *arg)
212 {
213     if (arg == NULL)
214         return;
215     walklist (arg, send_one, option);
216 }
217
218 #endif
219
220
221
222 int
223 cvslog (int argc, char **argv)
224 {
225     int c;
226     int err = 0;
227     int local = 0;
228     struct option_revlist **prl;
229
230     is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
231
232     if (argc == -1)
233         usage (log_usage);
234
235     memset (&log_data, 0, sizeof log_data);
236     prl = &log_data.revlist;
237
238     optind = 0;
239     while ((c = getopt (argc, argv, "+bd:hlNSRr::s:tw::")) != -1)
240     {
241         switch (c)
242         {
243             case 'b':
244                 log_data.default_branch = 1;
245                 break;
246             case 'd':
247                 log_parse_date (&log_data, optarg);
248                 break;
249             case 'h':
250                 log_data.header = 1;
251                 break;
252             case 'l':
253                 local = 1;
254                 break;
255             case 'N':
256                 log_data.notags = 1;
257                 break;
258             case 'S':
259                 log_data.sup_header = 1;
260                 break;
261             case 'R':
262                 log_data.nameonly = 1;
263                 break;
264             case 'r':
265                 *prl = log_parse_revlist (optarg);
266                 prl = &(*prl)->next;
267                 break;
268             case 's':
269                 log_parse_list (&log_data.statelist, optarg);
270                 break;
271             case 't':
272                 log_data.long_header = 1;
273                 break;
274             case 'w':
275                 if (optarg != NULL)
276                     log_parse_list (&log_data.authorlist, optarg);
277                 else
278                     log_parse_list (&log_data.authorlist, "@@MYSELF");
279                 break;
280             case '?':
281             default:
282                 usage (log_usage);
283                 break;
284         }
285     }
286     argc -= optind;
287     argv += optind;
288
289     wrap_setup ();
290
291 #ifdef CLIENT_SUPPORT
292     if (current_parsed_root->isremote)
293     {
294         struct datelist *p;
295         struct option_revlist *rp;
296         char datetmp[MAXDATELEN];
297
298         /* We're the local client.  Fire up the remote server.  */
299         start_server ();
300
301         if (is_rlog && !supported_request ("rlog"))
302             error (1, 0, "server does not support rlog");
303
304         ign_setup ();
305
306         if (log_data.default_branch)
307             send_arg ("-b");
308
309         while (log_data.datelist != NULL)
310         {
311             p = log_data.datelist;
312             log_data.datelist = p->next;
313             send_to_server ("Argument -d\012", 0);
314             send_to_server ("Argument ", 0);
315             date_to_internet (datetmp, p->start);
316             send_to_server (datetmp, 0);
317             if (p->inclusive)
318                 send_to_server ("<=", 0);
319             else
320                 send_to_server ("<", 0);
321             date_to_internet (datetmp, p->end);
322             send_to_server (datetmp, 0);
323             send_to_server ("\012", 0);
324             if (p->start)
325                 free (p->start);
326             if (p->end)
327                 free (p->end);
328             free (p);
329         }
330         while (log_data.singledatelist != NULL)
331         {
332             p = log_data.singledatelist;
333             log_data.singledatelist = p->next;
334             send_to_server ("Argument -d\012", 0);
335             send_to_server ("Argument ", 0);
336             date_to_internet (datetmp, p->end);
337             send_to_server (datetmp, 0);
338             send_to_server ("\012", 0);
339             if (p->end)
340                 free (p->end);
341             free (p);
342         }
343             
344         if (log_data.header)
345             send_arg ("-h");
346         if (local)
347             send_arg("-l");
348         if (log_data.notags)
349             send_arg("-N");
350         if (log_data.sup_header)
351             send_arg("-S");
352         if (log_data.nameonly)
353             send_arg("-R");
354         if (log_data.long_header)
355             send_arg("-t");
356
357         while (log_data.revlist != NULL)
358         {
359             rp = log_data.revlist;
360             log_data.revlist = rp->next;
361             send_to_server ("Argument -r", 0);
362             if (rp->branchhead)
363             {
364                 if (rp->first != NULL)
365                     send_to_server (rp->first, 0);
366                 send_to_server (".", 1);
367             }
368             else
369             {
370                 if (rp->first != NULL)
371                     send_to_server (rp->first, 0);
372                 send_to_server (":", 1);
373                 if (!rp->inclusive)
374                     send_to_server (":", 1);
375                 if (rp->last != NULL)
376                     send_to_server (rp->last, 0);
377             }
378             send_to_server ("\012", 0);
379             if (rp->first)
380                 free (rp->first);
381             if (rp->last)
382                 free (rp->last);
383             free (rp);
384         }
385         send_arg_list ("-s", log_data.statelist);
386         dellist (&log_data.statelist);
387         send_arg_list ("-w", log_data.authorlist);
388         dellist (&log_data.authorlist);
389         send_arg ("--");
390
391         if (is_rlog)
392         {
393             int i;
394             for (i = 0; i < argc; i++)
395                 send_arg (argv[i]);
396             send_to_server ("rlog\012", 0);
397         }
398         else
399         {
400             send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
401             send_file_names (argc, argv, SEND_EXPAND_WILD);
402             send_to_server ("log\012", 0);
403         }
404         err = get_responses_and_close ();
405         return err;
406     }
407 #endif
408
409     /* OK, now that we know we are local/server, we can resolve @@MYSELF
410        into our user name.  */
411     if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
412         log_parse_list (&log_data.authorlist, getcaller ());
413
414     if (is_rlog)
415     {
416         DBM *db;
417         int i;
418         db = open_module ();
419         for (i = 0; i < argc; i++)
420         {
421              err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
422                                NULL, 0, local, 0, 0, NULL);
423         }
424         close_module (db);
425     }
426     else
427     {
428         err = rlog_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, local, NULL,
429                          NULL);
430     }
431
432     while (log_data.revlist)
433     {
434         struct option_revlist *rl = log_data.revlist->next;
435         if (log_data.revlist->first)
436             free (log_data.revlist->first);
437         if (log_data.revlist->last)
438             free (log_data.revlist->last);
439         free (log_data.revlist);
440         log_data.revlist = rl;
441     }
442     while (log_data.datelist)
443     {
444         struct datelist *nd = log_data.datelist->next;
445         if (log_data.datelist->start)
446             free (log_data.datelist->start);
447         if (log_data.datelist->end)
448             free (log_data.datelist->end);
449         free (log_data.datelist);
450         log_data.datelist = nd;
451     }
452     while (log_data.singledatelist)
453     {
454         struct datelist *nd = log_data.singledatelist->next;
455         if (log_data.singledatelist->start)
456             free (log_data.singledatelist->start);
457         if (log_data.singledatelist->end)
458             free (log_data.singledatelist->end);
459         free (log_data.singledatelist);
460         log_data.singledatelist = nd;
461     }
462     dellist (&log_data.statelist);
463     dellist (&log_data.authorlist);
464
465     return err;
466 }
467
468
469
470 static int
471 rlog_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
472            int shorten, int local, char *mname, char *msg)
473 {
474     /* Begin section which is identical to patch_proc--should this
475        be abstracted out somehow?  */
476     char *myargv[2];
477     int err = 0;
478     int which;
479     char *repository = NULL;
480     char *where;
481
482     if (is_rlog)
483     {
484         repository = xmalloc (strlen (current_parsed_root->directory)
485                               + strlen (argv[0])
486                               + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
487         (void)sprintf (repository, "%s/%s",
488                        current_parsed_root->directory, argv[0]);
489         where = xmalloc (strlen (argv[0])
490                          + (mfile == NULL ? 0 : strlen (mfile) + 1)
491                          + 1);
492         (void)strcpy (where, argv[0]);
493
494         /* If mfile isn't null, we need to set up to do only part of theu
495          * module.
496          */
497         if (mfile != NULL)
498         {
499             char *cp;
500             char *path;
501
502             /* If the portion of the module is a path, put the dir part on
503              * repos.
504              */
505             if ((cp = strrchr (mfile, '/')) != NULL)
506             {
507                 *cp = '\0';
508                 (void)strcat (repository, "/");
509                 (void)strcat (repository, mfile);
510                 (void)strcat (where, "/");
511                 (void)strcat (where, mfile);
512                 mfile = cp + 1;
513             }
514
515             /* take care of the rest */
516             path = Xasprintf ("%s/%s", repository, mfile);
517             if (isdir (path))
518             {
519                 /* directory means repository gets the dir tacked on */
520                 (void)strcpy (repository, path);
521                 (void)strcat (where, "/");
522                 (void)strcat (where, mfile);
523             }
524             else
525             {
526                 myargv[0] = argv[0];
527                 myargv[1] = mfile;
528                 argc = 2;
529                 argv = myargv;
530             }
531             free (path);
532         }
533
534         /* cd to the starting repository */
535         if (CVS_CHDIR (repository) < 0)
536         {
537             error (0, errno, "cannot chdir to %s", repository);
538             free (repository);
539             free (where);
540             return 1;
541         }
542         /* End section which is identical to patch_proc.  */
543
544         which = W_REPOS | W_ATTIC;
545     }
546     else
547     {
548         repository = NULL;
549         where = NULL;
550         which = W_LOCAL | W_REPOS | W_ATTIC;
551     }
552
553     err = start_recursion (log_fileproc, NULL, log_dirproc,
554                            NULL, &log_data,
555                            argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
556                            where, 1, repository);
557
558     if (!(which & W_LOCAL)) free (repository);
559     if (where) free (where);
560
561     return err;
562 }
563
564
565
566 /*
567  * Parse a revision list specification.
568  */
569 static struct option_revlist *
570 log_parse_revlist (const char *argstring)
571 {
572     char *orig_copy, *copy;
573     struct option_revlist *ret, **pr;
574
575     /* Unfortunately, rlog accepts -r without an argument to mean that
576        latest revision on the default branch, so we must support that
577        for compatibility.  */
578     if (argstring == NULL)
579         argstring = "";
580
581     ret = NULL;
582     pr = &ret;
583
584     /* Copy the argument into memory so that we can change it.  We
585        don't want to change the argument because, at least as of this
586        writing, we will use it if we send the arguments to the server.  */
587     orig_copy = copy = xstrdup (argstring);
588     while (copy != NULL)
589     {
590         char *comma;
591         struct option_revlist *r;
592
593         comma = strchr (copy, ',');
594         if (comma != NULL)
595             *comma++ = '\0';
596
597         r = xmalloc (sizeof *r);
598         r->next = NULL;
599         r->first = copy;
600         r->branchhead = 0;
601         r->last = strchr (copy, ':');
602         if (r->last != NULL)
603         {
604             *r->last++ = '\0';
605             r->inclusive = (*r->last != ':');
606             if (!r->inclusive)
607                 r->last++;
608         }
609         else
610         {
611             r->last = r->first;
612             r->inclusive = 1;
613             if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
614             {
615                 r->branchhead = 1;
616                 r->first[strlen (r->first) - 1] = '\0';
617             }
618         }
619
620         if (*r->first == '\0')
621             r->first = NULL;
622         if (*r->last == '\0')
623             r->last = NULL;
624
625         if (r->first != NULL)
626             r->first = xstrdup (r->first);
627         if (r->last != NULL)
628             r->last = xstrdup (r->last);
629
630         *pr = r;
631         pr = &r->next;
632
633         copy = comma;
634     }
635
636     free (orig_copy);
637     return ret;
638 }
639
640
641
642 /*
643  * Parse a date specification.
644  */
645 static void
646 log_parse_date (struct log_data *log_data, const char *argstring)
647 {
648     char *orig_copy, *copy;
649
650     /* Copy the argument into memory so that we can change it.  We
651        don't want to change the argument because, at least as of this
652        writing, we will use it if we send the arguments to the server.  */
653     orig_copy = copy = xstrdup (argstring);
654     while (copy != NULL)
655     {
656         struct datelist *nd, **pd;
657         char *cpend, *cp, *ds, *de;
658
659         nd = xmalloc (sizeof *nd);
660
661         cpend = strchr (copy, ';');
662         if (cpend != NULL)
663             *cpend++ = '\0';
664
665         pd = &log_data->datelist;
666         nd->inclusive = 0;
667
668         if ((cp = strchr (copy, '>')) != NULL)
669         {
670             *cp++ = '\0';
671             if (*cp == '=')
672             {
673                 ++cp;
674                 nd->inclusive = 1;
675             }
676             ds = cp;
677             de = copy;
678         }
679         else if ((cp = strchr (copy, '<')) != NULL)
680         {
681             *cp++ = '\0';
682             if (*cp == '=')
683             {
684                 ++cp;
685                 nd->inclusive = 1;
686             }
687             ds = copy;
688             de = cp;
689         }
690         else
691         {
692             ds = NULL;
693             de = copy;
694             pd = &log_data->singledatelist;
695         }
696
697         if (ds == NULL)
698             nd->start = NULL;
699         else if (*ds != '\0')
700             nd->start = Make_Date (ds);
701         else
702         {
703           /* 1970 was the beginning of time, as far as get_date and
704              Make_Date are concerned.  FIXME: That is true only if time_t
705              is a POSIX-style time and there is nothing in ANSI that
706              mandates that.  It would be cleaner to set a flag saying
707              whether or not there is a start date.  */
708             nd->start = Make_Date ("1/1/1970 UTC");
709         }
710
711         if (*de != '\0')
712             nd->end = Make_Date (de);
713         else
714         {
715             /* We want to set the end date to some time sufficiently far
716                in the future to pick up all revisions that have been
717                created since the specified date and the time `cvs log'
718                completes.  FIXME: The date in question only makes sense
719                if time_t is a POSIX-style time and it is 32 bits
720                and signed.  We should instead be setting a flag saying
721                whether or not there is an end date.  Note that using
722                something like "next week" would break the testsuite (and,
723                perhaps less importantly, loses if the clock is set grossly
724                wrong).  */
725             nd->end = Make_Date ("2038-01-01");
726         }
727
728         nd->next = *pd;
729         *pd = nd;
730
731         copy = cpend;
732     }
733
734     free (orig_copy);
735 }
736
737
738
739 /*
740  * Parse a comma separated list of items, and add each one to *PLIST.
741  */
742 static void
743 log_parse_list (List **plist, const char *argstring)
744 {
745     while (1)
746     {
747         Node *p;
748         char *cp;
749
750         p = getnode ();
751
752         cp = strchr (argstring, ',');
753         if (cp == NULL)
754             p->key = xstrdup (argstring);
755         else
756         {
757             size_t len;
758
759             len = cp - argstring;
760             p->key = xmalloc (len + 1);
761             strncpy (p->key, argstring, len);
762             p->key[len] = '\0';
763         }
764
765         if (*plist == NULL)
766             *plist = getlist ();
767         if (addnode (*plist, p) != 0)
768             freenode (p);
769
770         if (cp == NULL)
771             break;
772
773         argstring = cp + 1;
774     }
775 }
776
777
778
779 static int
780 printlock_proc (Node *lock, void *foo)
781 {
782     cvs_output ("\n\t", 2);
783     cvs_output (lock->data, 0);
784     cvs_output (": ", 2);
785     cvs_output (lock->key, 0);
786     return 0;
787 }
788
789
790
791 /*
792  * Do an rlog on a file
793  */
794 static int
795 log_fileproc (void *callerdat, struct file_info *finfo)
796 {
797     struct log_data *log_data = callerdat;
798     Node *p;
799     char *baserev;
800     int selrev = -1;
801     RCSNode *rcsfile;
802     char buf[50];
803     struct revlist *revlist = NULL;
804     struct log_data_and_rcs log_data_and_rcs;
805
806     rcsfile = finfo->rcs;
807     p = findnode (finfo->entries, finfo->file);
808     if (p != NULL)
809     {
810         Entnode *e = p->data;
811         baserev = e->version;
812         if (baserev[0] == '-') ++baserev;
813     }
814     else
815         baserev = NULL;
816
817     if (rcsfile == NULL)
818     {
819         /* no rcs file.  What *do* we know about this file? */
820         if (baserev != NULL)
821         {
822             if (baserev[0] == '0' && baserev[1] == '\0')
823             {
824                 if (!really_quiet)
825                     error (0, 0, "%s has been added, but not committed",
826                            finfo->file);
827                 return 0;
828             }
829         }
830         
831         if (!really_quiet)
832             error (0, 0, "nothing known about %s", finfo->file);
833         
834         return 1;
835     }
836
837     if (log_data->sup_header || !log_data->nameonly)
838     {
839
840         /* We will need all the information in the RCS file.  */
841         RCS_fully_parse (rcsfile);
842
843         /* Turn any symbolic revisions in the revision list into numeric
844            revisions.  */
845         revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist,
846                                       log_data->default_branch);
847         if (log_data->sup_header
848             || (!log_data->header && !log_data->long_header))
849         {
850             log_data_and_rcs.log_data = log_data;
851             log_data_and_rcs.revlist = revlist;
852             log_data_and_rcs.rcs = rcsfile;
853
854             /* If any single dates were specified, we need to identify the
855                revisions they select.  Each one selects the single
856                revision, which is otherwise selected, of that date or
857                earlier.  The log_fix_singledate routine will fill in the
858                start date for each specific revision.  */
859             if (log_data->singledatelist != NULL)
860                 walklist (rcsfile->versions, log_fix_singledate,
861                           &log_data_and_rcs);
862
863             selrev = walklist (rcsfile->versions, log_count_print,
864                                &log_data_and_rcs);
865             if (log_data->sup_header && selrev == 0)
866             {
867                 log_free_revlist (revlist);
868                 return 0;
869             }
870         }
871
872     }
873
874     if (log_data->nameonly)
875     {
876         cvs_output (rcsfile->print_path, 0);
877         cvs_output ("\n", 1);
878         log_free_revlist (revlist);
879         return 0;
880     }
881
882     /* The output here is intended to be exactly compatible with the
883        output of rlog.  I'm not sure whether this code should be here
884        or in rcs.c; I put it here because it is specific to the log
885        function, even though it uses information gathered by the
886        functions in rcs.c.  */
887
888     cvs_output ("\n", 1);
889
890     cvs_output ("RCS file: ", 0);
891     cvs_output (rcsfile->print_path, 0);
892
893     if (!is_rlog)
894     {
895         cvs_output ("\nWorking file: ", 0);
896         if (finfo->update_dir[0] != '\0')
897         {
898             cvs_output (finfo->update_dir, 0);
899             cvs_output ("/", 0);
900         }
901         cvs_output (finfo->file, 0);
902     }
903
904     cvs_output ("\nhead:", 0);
905     if (rcsfile->head != NULL)
906     {
907         cvs_output (" ", 1);
908         cvs_output (rcsfile->head, 0);
909     }
910
911     cvs_output ("\nbranch:", 0);
912     if (rcsfile->branch != NULL)
913     {
914         cvs_output (" ", 1);
915         cvs_output (rcsfile->branch, 0);
916     }
917
918     cvs_output ("\nlocks:", 0);
919     if (rcsfile->strict_locks)
920         cvs_output (" strict", 0);
921     walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
922
923     cvs_output ("\naccess list:", 0);
924     if (rcsfile->access != NULL)
925     {
926         const char *cp;
927
928         cp = rcsfile->access;
929         while (*cp != '\0')
930         {
931                 const char *cp2;
932
933                 cvs_output ("\n\t", 2);
934                 cp2 = cp;
935                 while (!isspace ((unsigned char)*cp2) && *cp2 != '\0')
936                     ++cp2;
937                 cvs_output (cp, cp2 - cp);
938                 cp = cp2;
939                 while (isspace ((unsigned char)*cp) && *cp != '\0')
940                     ++cp;
941         }
942     }
943
944     if (!log_data->notags)
945     {
946         List *syms;
947
948         cvs_output ("\nsymbolic names:", 0);
949         syms = RCS_symbols (rcsfile);
950         walklist (syms, log_symbol, NULL);
951     }
952
953     cvs_output ("\nkeyword substitution: ", 0);
954     if (rcsfile->expand == NULL)
955         cvs_output ("kv", 2);
956     else
957         cvs_output (rcsfile->expand, 0);
958
959     cvs_output ("\ntotal revisions: ", 0);
960     sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
961     cvs_output (buf, 0);
962
963     if (selrev >= 0)
964     {
965         cvs_output (";\tselected revisions: ", 0);
966         sprintf (buf, "%d", selrev);
967         cvs_output (buf, 0);
968     }
969
970     cvs_output ("\n", 1);
971
972     if (!log_data->header || log_data->long_header)
973     {
974         cvs_output ("description:\n", 0);
975         if (rcsfile->desc != NULL)
976             cvs_output (rcsfile->desc, 0);
977     }
978
979     if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
980     {
981         p = findnode (rcsfile->versions, rcsfile->head);
982         if (p == NULL)
983             error (1, 0, "can not find head revision in `%s'",
984                    finfo->fullname);
985         while (p != NULL)
986         {
987             RCSVers *vers = p->data;
988
989             log_version (log_data, revlist, rcsfile, vers, 1);
990             if (vers->next == NULL)
991                 p = NULL;
992             else
993             {
994                 p = findnode (rcsfile->versions, vers->next);
995                 if (p == NULL)
996                     error (1, 0, "can not find next revision `%s' in `%s'",
997                            vers->next, finfo->fullname);
998             }
999         }
1000
1001         log_tree (log_data, revlist, rcsfile, rcsfile->head);
1002     }
1003
1004     cvs_output("\
1005 =============================================================================\n",
1006                0);
1007
1008     /* Free up the new revlist and restore the old one.  */
1009     log_free_revlist (revlist);
1010
1011     /* If singledatelist is not NULL, free up the start dates we added
1012        to it.  */
1013     if (log_data->singledatelist != NULL)
1014     {
1015         struct datelist *d;
1016
1017         for (d = log_data->singledatelist; d != NULL; d = d->next)
1018         {
1019             if (d->start != NULL)
1020                 free (d->start);
1021             d->start = NULL;
1022         }
1023     }
1024
1025     return 0;
1026 }
1027
1028
1029
1030 /*
1031  * Fix up a revision list in order to compare it against versions.
1032  * Expand any symbolic revisions.
1033  */
1034 static struct revlist *
1035 log_expand_revlist (RCSNode *rcs, char *baserev,
1036                     struct option_revlist *revlist, int default_branch)
1037 {
1038     struct option_revlist *r;
1039     struct revlist *ret, **pr;
1040
1041     ret = NULL;
1042     pr = &ret;
1043     for (r = revlist; r != NULL; r = r->next)
1044     {
1045         struct revlist *nr;
1046
1047         nr = xmalloc (sizeof *nr);
1048         nr->inclusive = r->inclusive;
1049
1050         if (r->first == NULL && r->last == NULL)
1051         {
1052             /* If both first and last are NULL, it means that we want
1053                just the head of the default branch, which is RCS_head.  */
1054             nr->first = RCS_head (rcs);
1055             if (!nr->first)
1056             {
1057                 if (!really_quiet)
1058                     error (0, 0, "No head revision in archive `%s'.",
1059                            rcs->path);
1060                 nr->last = NULL;
1061                 nr->fields = 0;
1062             }
1063             else
1064             {
1065                 nr->last = xstrdup (nr->first);
1066                 nr->fields = numdots (nr->first) + 1;
1067             }
1068         }
1069         else if (r->branchhead)
1070         {
1071             char *branch;
1072
1073             /* Print just the head of the branch.  */
1074             if (isdigit ((unsigned char) r->first[0]))
1075                 nr->first = RCS_getbranch (rcs, r->first, 1);
1076             else
1077             {
1078                 branch = RCS_whatbranch (rcs, r->first);
1079                 if (branch == NULL)
1080                     nr->first = NULL;
1081                 else
1082                 {
1083                     nr->first = RCS_getbranch (rcs, branch, 1);
1084                     free (branch);
1085                 }
1086             }
1087             if (!nr->first)
1088             {
1089                 if (!really_quiet)
1090                     error (0, 0, "warning: no branch `%s' in `%s'",
1091                            r->first, rcs->print_path);
1092                 nr->last = NULL;
1093                 nr->fields = 0;
1094             }
1095             else
1096             {
1097                 nr->last = xstrdup (nr->first);
1098                 nr->fields = numdots (nr->first) + 1;
1099             }
1100         }
1101         else
1102         {
1103             if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
1104                 nr->first = xstrdup (r->first);
1105             else
1106             {
1107                 if (baserev && strcmp (r->first, TAG_BASE) == 0)
1108                     nr->first = xstrdup (baserev);
1109                 else if (RCS_nodeisbranch (rcs, r->first))
1110                     nr->first = RCS_whatbranch (rcs, r->first);
1111                 else
1112                     nr->first = RCS_gettag (rcs, r->first, 1, NULL);
1113                 if (nr->first == NULL && !really_quiet)
1114                 {
1115                     error (0, 0, "warning: no revision `%s' in `%s'",
1116                            r->first, rcs->print_path);
1117                 }
1118             }
1119
1120             if (r->last == r->first || (r->last != NULL && r->first != NULL &&
1121                                         strcmp (r->last, r->first) == 0))
1122                 nr->last = xstrdup (nr->first);
1123             else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
1124                 nr->last = xstrdup (r->last);
1125             else
1126             {
1127                 if (baserev && strcmp (r->last, TAG_BASE) == 0)
1128                     nr->last = xstrdup (baserev);
1129                 else if (RCS_nodeisbranch (rcs, r->last))
1130                     nr->last = RCS_whatbranch (rcs, r->last);
1131                 else
1132                     nr->last = RCS_gettag (rcs, r->last, 1, NULL);
1133                 if (nr->last == NULL && !really_quiet)
1134                 {
1135                     error (0, 0, "warning: no revision `%s' in `%s'",
1136                            r->last, rcs->print_path);
1137                 }
1138             }
1139
1140             /* Process the revision numbers the same way that rlog
1141                does.  This code is a bit cryptic for my tastes, but
1142                keeping the same implementation as rlog ensures a
1143                certain degree of compatibility.  */
1144             if (r->first == NULL && nr->last != NULL)
1145             {
1146                 nr->fields = numdots (nr->last) + 1;
1147                 if (nr->fields < 2)
1148                     nr->first = xstrdup (".0");
1149                 else
1150                 {
1151                     char *cp;
1152
1153                     nr->first = xstrdup (nr->last);
1154                     cp = strrchr (nr->first, '.');
1155                     assert (cp);
1156                     strcpy (cp + 1, "0");
1157                 }
1158             }
1159             else if (r->last == NULL && nr->first != NULL)
1160             {
1161                 nr->fields = numdots (nr->first) + 1;
1162                 nr->last = xstrdup (nr->first);
1163                 if (nr->fields < 2)
1164                     nr->last[0] = '\0';
1165                 else
1166                 {
1167                     char *cp;
1168
1169                     cp = strrchr (nr->last, '.');
1170                     assert (cp);
1171                     *cp = '\0';
1172                 }
1173             }
1174             else if (nr->first == NULL || nr->last == NULL)
1175                 nr->fields = 0;
1176             else if (strcmp (nr->first, nr->last) == 0)
1177                 nr->fields = numdots (nr->last) + 1;
1178             else
1179             {
1180                 int ord;
1181                 int dots1 = numdots (nr->first);
1182                 int dots2 = numdots (nr->last);
1183                 if (dots1 > dots2 || (dots1 == dots2 &&
1184                     version_compare (nr->first, nr->last, dots1 + 1) > 0))
1185                 {
1186                     char *tmp = nr->first;
1187                     nr->first = nr->last;
1188                     nr->last = tmp;
1189                     nr->fields = dots2 + 1;
1190                     dots2 = dots1;
1191                     dots1 = nr->fields - 1;
1192                 }
1193                 else
1194                     nr->fields = dots1 + 1;
1195                 dots1 += (nr->fields & 1);
1196                 ord = version_compare (nr->first, nr->last, dots1);
1197                 if (ord > 0 || (nr->fields > 2 && ord < 0))
1198                 {
1199                     error (0, 0,
1200                            "invalid branch or revision pair %s:%s in `%s'",
1201                            r->first, r->last, rcs->print_path);
1202                     free (nr->first);
1203                     nr->first = NULL;
1204                     free (nr->last);
1205                     nr->last = NULL;
1206                     nr->fields = 0;
1207                 }
1208                 else
1209                 {
1210                     if (nr->fields <= dots2 && (nr->fields & 1))
1211                     {
1212                         char *p = Xasprintf ("%s.0", nr->first);
1213                         free (nr->first);
1214                         nr->first = p;
1215                         ++nr->fields;
1216                     }
1217                     while (nr->fields <= dots2)
1218                     {
1219                         char *p;
1220                         int i;
1221
1222                         nr->next = NULL;
1223                         *pr = nr;
1224                         nr = xmalloc (sizeof *nr);
1225                         nr->inclusive = 1;
1226                         nr->first = xstrdup ((*pr)->last);
1227                         nr->last = xstrdup ((*pr)->last);
1228                         nr->fields = (*pr)->fields;
1229                         p = (*pr)->last;
1230                         for (i = 0; i < nr->fields; i++)
1231                             p = strchr (p, '.') + 1;
1232                         p[-1] = '\0';
1233                         p = strchr (nr->first + (p - (*pr)->last), '.');
1234                         if (p != NULL)
1235                         {
1236                             *++p = '0';
1237                             *++p = '\0';
1238                             nr->fields += 2;
1239                         }
1240                         else
1241                             ++nr->fields;
1242                         pr = &(*pr)->next;
1243                     }
1244                 }
1245             }
1246         }
1247
1248         nr->next = NULL;
1249         *pr = nr;
1250         pr = &nr->next;
1251     }
1252
1253     /* If the default branch was requested, add a revlist entry for
1254        it.  This is how rlog handles this option.  */
1255     if (default_branch
1256         && (rcs->head != NULL || rcs->branch != NULL))
1257     {
1258         struct revlist *nr;
1259
1260         nr = xmalloc (sizeof *nr);
1261         if (rcs->branch != NULL)
1262             nr->first = xstrdup (rcs->branch);
1263         else
1264         {
1265             char *cp;
1266
1267             nr->first = xstrdup (rcs->head);
1268             assert (nr->first);
1269             cp = strrchr (nr->first, '.');
1270             assert (cp);
1271             *cp = '\0';
1272         }
1273         nr->last = xstrdup (nr->first);
1274         nr->fields = numdots (nr->first) + 1;
1275         nr->inclusive = 1;
1276
1277         nr->next = NULL;
1278         *pr = nr;
1279     }
1280
1281     return ret;
1282 }
1283
1284
1285
1286 /*
1287  * Free a revlist created by log_expand_revlist.
1288  */
1289 static void
1290 log_free_revlist (struct revlist *revlist)
1291 {
1292     struct revlist *r;
1293
1294     r = revlist;
1295     while (r != NULL)
1296     {
1297         struct revlist *next;
1298
1299         if (r->first != NULL)
1300             free (r->first);
1301         if (r->last != NULL)
1302             free (r->last);
1303         next = r->next;
1304         free (r);
1305         r = next;
1306     }
1307 }
1308
1309
1310
1311 /*
1312  * Return nonzero if a revision should be printed, based on the
1313  * options provided.
1314  */
1315 static int
1316 log_version_requested (struct log_data *log_data, struct revlist *revlist,
1317                        RCSNode *rcs, RCSVers *vnode)
1318 {
1319     /* Handle the list of states from the -s option.  */
1320     if (log_data->statelist != NULL
1321         && findnode (log_data->statelist, vnode->state) == NULL)
1322     {
1323         return 0;
1324     }
1325
1326     /* Handle the list of authors from the -w option.  */
1327     if (log_data->authorlist != NULL)
1328     {
1329         if (vnode->author != NULL
1330             && findnode (log_data->authorlist, vnode->author) == NULL)
1331         {
1332             return 0;
1333         }
1334     }
1335
1336     /* rlog considers all the -d options together when it decides
1337        whether to print a revision, so we must be compatible.  */
1338     if (log_data->datelist != NULL || log_data->singledatelist != NULL)
1339     {
1340         struct datelist *d;
1341
1342         for (d = log_data->datelist; d != NULL; d = d->next)
1343         {
1344             int cmp;
1345
1346             cmp = RCS_datecmp (vnode->date, d->start);
1347             if (cmp > 0 || (cmp == 0 && d->inclusive))
1348             {
1349                 cmp = RCS_datecmp (vnode->date, d->end);
1350                 if (cmp < 0 || (cmp == 0 && d->inclusive))
1351                     break;
1352             }
1353         }
1354
1355         if (d == NULL)
1356         {
1357             /* Look through the list of specific dates.  We want to
1358                select the revision with the exact date found in the
1359                start field.  The commit code ensures that it is
1360                impossible to check in multiple revisions of a single
1361                file in a single second, so checking the date this way
1362                should never select more than one revision.  */
1363             for (d = log_data->singledatelist; d != NULL; d = d->next)
1364             {
1365                 if (d->start != NULL
1366                     && RCS_datecmp (vnode->date, d->start) == 0)
1367                 {
1368                     break;
1369                 }
1370             }
1371
1372             if (d == NULL)
1373                 return 0;
1374         }
1375     }
1376
1377     /* If the -r or -b options were used, REVLIST will be non NULL,
1378        and we print the union of the specified revisions.  */
1379     if (revlist != NULL)
1380     {
1381         char *v;
1382         int vfields;
1383         struct revlist *r;
1384
1385         /* This code is taken from rlog.  */
1386         v = vnode->version;
1387         vfields = numdots (v) + 1;
1388         for (r = revlist; r != NULL; r = r->next)
1389         {
1390             if (vfields == r->fields + (r->fields & 1) &&
1391                 (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
1392                                 version_compare (v, r->first, r->fields) > 0)
1393                 && version_compare (v, r->last, r->fields) <= 0)
1394             {
1395                 return 1;
1396             }
1397         }
1398
1399         /* If we get here, then the -b and/or the -r option was used,
1400            but did not match this revision, so we reject it.  */
1401
1402         return 0;
1403     }
1404
1405     /* By default, we print all revisions.  */
1406     return 1;
1407 }
1408
1409
1410
1411 /*
1412  * Output a single symbol.  This is called via walklist.
1413  */
1414 /*ARGSUSED*/
1415 static int
1416 log_symbol (Node *p, void *closure)
1417 {
1418     cvs_output ("\n\t", 2);
1419     cvs_output (p->key, 0);
1420     cvs_output (": ", 2);
1421     cvs_output (p->data, 0);
1422     return 0;
1423 }
1424
1425
1426
1427 /*
1428  * Count the number of entries on a list.  This is called via walklist.
1429  */
1430 /*ARGSUSED*/
1431 static int
1432 log_count (Node *p, void *closure)
1433 {
1434     return 1;
1435 }
1436
1437
1438
1439 /*
1440  * Sort out a single date specification by narrowing down the date
1441  * until we find the specific selected revision.
1442  */
1443 static int
1444 log_fix_singledate (Node *p, void *closure)
1445 {
1446     struct log_data_and_rcs *data = closure;
1447     Node *pv;
1448     RCSVers *vnode;
1449     struct datelist *holdsingle, *holddate;
1450     int requested;
1451
1452     pv = findnode (data->rcs->versions, p->key);
1453     if (pv == NULL)
1454         error (1, 0, "missing version `%s' in RCS file `%s'",
1455                p->key, data->rcs->print_path);
1456     vnode = pv->data;
1457
1458     /* We are only interested if this revision passes any other tests.
1459        Temporarily clear log_data->singledatelist to avoid confusing
1460        log_version_requested.  We also clear log_data->datelist,
1461        because rlog considers all the -d options together.  We don't
1462        want to reject a revision because it does not match a date pair
1463        if we are going to select it on the basis of the singledate.  */
1464     holdsingle = data->log_data->singledatelist;
1465     data->log_data->singledatelist = NULL;
1466     holddate = data->log_data->datelist;
1467     data->log_data->datelist = NULL;
1468     requested = log_version_requested (data->log_data, data->revlist,
1469                                        data->rcs, vnode);
1470     data->log_data->singledatelist = holdsingle;
1471     data->log_data->datelist = holddate;
1472
1473     if (requested)
1474     {
1475         struct datelist *d;
1476
1477         /* For each single date, if this revision is before the
1478            specified date, but is closer than the previously selected
1479            revision, select it instead.  */
1480         for (d = data->log_data->singledatelist; d != NULL; d = d->next)
1481         {
1482             if (RCS_datecmp (vnode->date, d->end) <= 0
1483                 && (d->start == NULL
1484                     || RCS_datecmp (vnode->date, d->start) > 0))
1485             {
1486                 if (d->start != NULL)
1487                     free (d->start);
1488                 d->start = xstrdup (vnode->date);
1489             }
1490         }
1491     }
1492
1493     return 0;
1494 }
1495
1496
1497
1498 /*
1499  * Count the number of revisions we are going to print.
1500  */
1501 static int
1502 log_count_print (Node *p, void *closure)
1503 {
1504     struct log_data_and_rcs *data = closure;
1505     Node *pv;
1506
1507     pv = findnode (data->rcs->versions, p->key);
1508     if (pv == NULL)
1509         error (1, 0, "missing version `%s' in RCS file `%s'",
1510                p->key, data->rcs->print_path);
1511     if (log_version_requested (data->log_data, data->revlist, data->rcs,
1512                                pv->data))
1513         return 1;
1514     else
1515         return 0;
1516 }
1517
1518
1519
1520 /*
1521  * Print the list of changes, not including the trunk, in reverse
1522  * order for each branch.
1523  */
1524 static void
1525 log_tree (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
1526           const char *ver)
1527 {
1528     Node *p;
1529     RCSVers *vnode;
1530
1531     p = findnode (rcs->versions, ver);
1532     if (p == NULL)
1533         error (1, 0, "missing version `%s' in RCS file `%s'",
1534                ver, rcs->print_path);
1535     vnode = p->data;
1536     if (vnode->next != NULL)
1537         log_tree (log_data, revlist, rcs, vnode->next);
1538     if (vnode->branches != NULL)
1539     {
1540         Node *head, *branch;
1541
1542         /* We need to do the branches in reverse order.  This breaks
1543            the List abstraction, but so does most of the branch
1544            manipulation in rcs.c.  */
1545         head = vnode->branches->list;
1546         for (branch = head->prev; branch != head; branch = branch->prev)
1547         {
1548             log_abranch (log_data, revlist, rcs, branch->key);
1549             log_tree (log_data, revlist, rcs, branch->key);
1550         }
1551     }
1552 }
1553
1554
1555
1556 /*
1557  * Log the changes for a branch, in reverse order.
1558  */
1559 static void
1560 log_abranch (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
1561              const char *ver)
1562 {
1563     Node *p;
1564     RCSVers *vnode;
1565
1566     p = findnode (rcs->versions, ver);
1567     if (p == NULL)
1568         error (1, 0, "missing version `%s' in RCS file `%s'",
1569                ver, rcs->print_path);
1570     vnode = p->data;
1571     if (vnode->next != NULL)
1572         log_abranch (log_data, revlist, rcs, vnode->next);
1573     log_version (log_data, revlist, rcs, vnode, 0);
1574 }
1575
1576
1577
1578 /*
1579  * Print the log output for a single version.
1580  */
1581 static void
1582 log_version (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs, 
1583              RCSVers *ver, int trunk)
1584 {
1585     Node *p;
1586     int year, mon, mday, hour, min, sec;
1587     char buf[100];
1588     Node *padd, *pdel;
1589
1590     if (! log_version_requested (log_data, revlist, rcs, ver))
1591         return;
1592
1593     cvs_output ("----------------------------\nrevision ", 0);
1594     cvs_output (ver->version, 0);
1595
1596     p = findnode (RCS_getlocks (rcs), ver->version);
1597     if (p != NULL)
1598     {
1599         cvs_output ("\tlocked by: ", 0);
1600         cvs_output (p->data, 0);
1601         cvs_output (";", 1);
1602     }
1603     cvs_output ("\n", 1);
1604
1605     cvs_output_tagged ("text", "date: ");
1606     (void)sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
1607                   &sec);
1608     if (year < 1900)
1609         year += 1900;
1610     sprintf (buf, "%04d-%02d-%02d %02d:%02d:%02d +0000", year, mon, mday,
1611              hour, min, sec);
1612     cvs_output_tagged ("date", buf);
1613
1614     cvs_output_tagged ("text", ";  author: ");
1615     cvs_output_tagged ("text", ver->author);
1616
1617     cvs_output_tagged ("text", ";  state: ");
1618     cvs_output_tagged ("text", ver->state);
1619     cvs_output_tagged ("text", ";");
1620
1621     if (! trunk)
1622     {
1623         padd = findnode (ver->other, ";add");
1624         pdel = findnode (ver->other, ";delete");
1625     }
1626     else if (ver->next == NULL)
1627     {
1628         padd = NULL;
1629         pdel = NULL;
1630     }
1631     else
1632     {
1633         Node *nextp;
1634         RCSVers *nextver;
1635
1636         nextp = findnode (rcs->versions, ver->next);
1637         if (nextp == NULL)
1638             error (1, 0, "missing version `%s' in `%s'", ver->next,
1639                    rcs->print_path);
1640         nextver = nextp->data;
1641         pdel = findnode (nextver->other, ";add");
1642         padd = findnode (nextver->other, ";delete");
1643     }
1644
1645     if (padd != NULL)
1646     {
1647         assert (pdel);
1648         cvs_output_tagged ("text", "  lines: +");
1649         cvs_output_tagged ("text", padd->data);
1650         cvs_output_tagged ("text", " -");
1651         cvs_output_tagged ("text", pdel->data);
1652         cvs_output_tagged ("text", ";");
1653     }
1654
1655     p = findnode(ver->other_delta,"commitid");
1656     if(p && p->data)
1657     {
1658         cvs_output_tagged ("text", "  commitid: ");
1659         cvs_output_tagged ("text", p->data);
1660         cvs_output_tagged ("text", ";");
1661     }
1662
1663     cvs_output_tagged ("newline", NULL);
1664
1665     if (ver->branches != NULL)
1666     {
1667         cvs_output ("branches:", 0);
1668         walklist (ver->branches, log_branch, NULL);
1669         cvs_output ("\n", 1);
1670     }
1671
1672     p = findnode (ver->other, "log");
1673     /* The p->date == NULL case is the normal one for an empty log
1674        message (rcs-14 in sanity.sh).  I don't think the case where
1675        p->data is "" can happen (getrcskey in rcs.c checks for an
1676        empty string and set the value to NULL in that case).  My guess
1677        would be the p == NULL case would mean an RCS file which was
1678        missing the "log" keyword (which is invalid according to
1679        rcsfile.5).  */
1680     if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
1681         cvs_output ("*** empty log message ***\n", 0);
1682     else
1683     {
1684         /* FIXME: Technically, the log message could contain a null
1685            byte.  */
1686         cvs_output (p->data, 0);
1687         if (((char *)p->data)[strlen (p->data) - 1] != '\n')
1688             cvs_output ("\n", 1);
1689     }
1690 }
1691
1692
1693
1694 /*
1695  * Output a branch version.  This is called via walklist.
1696  */
1697 /*ARGSUSED*/
1698 static int
1699 log_branch (Node *p, void *closure)
1700 {
1701     cvs_output ("  ", 2);
1702     if ((numdots (p->key) & 1) == 0)
1703         cvs_output (p->key, 0);
1704     else
1705     {
1706         char *f, *cp;
1707
1708         f = xstrdup (p->key);
1709         cp = strrchr (f, '.');
1710         *cp = '\0';
1711         cvs_output (f, 0);
1712         free (f);
1713     }
1714     cvs_output (";", 1);
1715     return 0;
1716 }
1717
1718
1719
1720 /*
1721  * Print a warm fuzzy message
1722  */
1723 /* ARGSUSED */
1724 static Dtype
1725 log_dirproc (void *callerdat, const char *dir, const char *repository,
1726              const char *update_dir, List *entries)
1727 {
1728     if (!isdir (dir))
1729         return R_SKIP_ALL;
1730
1731     if (!quiet)
1732         error (0, 0, "Logging %s", update_dir);
1733     return R_PROCESS;
1734 }
1735
1736
1737
1738 /*
1739  * Compare versions.  This is taken from RCS compartial.
1740  */
1741 static int
1742 version_compare (const char *v1, const char *v2, int len)
1743 {
1744     while (1)
1745     {
1746         int d1, d2, r;
1747
1748         if (*v1 == '\0')
1749             return 1;
1750         if (*v2 == '\0')
1751             return -1;
1752
1753         while (*v1 == '0')
1754             ++v1;
1755         for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
1756             ;
1757
1758         while (*v2 == '0')
1759             ++v2;
1760         for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
1761             ;
1762
1763         if (d1 != d2)
1764             return d1 < d2 ? -1 : 1;
1765
1766         r = memcmp (v1, v2, d1);
1767         if (r != 0)
1768             return r;
1769
1770         --len;
1771         if (len == 0)
1772             return 0;
1773
1774         v1 += d1;
1775         v2 += d1;
1776
1777         if (*v1 == '.')
1778             ++v1;
1779         if (*v2 == '.')
1780             ++v2;
1781     }
1782 }