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