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