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