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