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