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