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