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