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