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