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