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