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