2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
5 * You may distribute under the terms of the GNU General Public License as
6 * specified in the README file that comes with the CVS source distribution.
8 * Print Log Information
10 * Prints the RCS "log" (rlog) information for the specified files. With no
11 * argument, prints the log information for all the files in the directory
12 * (recursive by default).
19 /* This structure holds information parsed from the -r option. */
23 /* The next -r option. */
24 struct option_revlist *next;
25 /* The first revision to print. This is NULL if the range is
26 :rev, or if no revision is given. */
28 /* The last revision to print. This is NULL if the range is rev:,
29 or if no revision is given. If there is no colon, first and
32 /* Nonzero if there was a trailing `.', which means to print only
33 the head revision of a branch. */
35 /* Nonzero if first and last are inclusive. */
39 /* This structure holds information derived from option_revlist given
40 a particular RCS file. */
46 /* The first numeric revision to print. */
48 /* The last numeric revision to print. */
50 /* The number of fields in these revisions (one more than
53 /* Whether first & last are to be included or excluded. */
57 /* This structure holds information parsed from the -d option. */
62 struct datelist *next;
63 /* The starting date. */
65 /* The ending date. */
67 /* Nonzero if the range is inclusive rather than exclusive. */
71 /* This structure is used to pass information through start_recursion. */
74 /* Nonzero if the -R option was given, meaning that only the name
75 of the RCS file should be printed. */
77 /* Nonzero if the -h option was given, meaning that only header
78 information should be printed. */
80 /* Nonzero if the -t option was given, meaning that only the
81 header and the descriptive text should be printed. */
83 /* Nonzero if the -N option was seen, meaning that tag information
84 should not be printed. */
86 /* Nonzero if the -b option was seen, meaning that only revisions
87 on the default branch should be printed. */
89 /* Nonzero if the -S option was seen, meaning that the header/name
90 should be suppressed if no revisions are selected. */
92 /* If not NULL, the value given for the -r option, which lists
93 sets of revisions to be printed. */
94 struct option_revlist *revlist;
95 /* If not NULL, the date pairs given for the -d option, which
96 select date ranges to print. */
97 struct datelist *datelist;
98 /* If not NULL, the single dates given for the -d option, which
99 select specific revisions to print based on a date. */
100 struct datelist *singledatelist;
101 /* If not NULL, the list of states given for the -s option, which
102 only prints revisions of given states. */
104 /* If not NULL, the list of login names given for the -w option,
105 which only prints revisions checked in by given users. */
109 /* This structure is used to pass information through walklist. */
110 struct log_data_and_rcs
112 struct log_data *log_data;
113 struct revlist *revlist;
117 static int rlog_proc PROTO((int argc, char **argv, char *xwhere,
118 char *mwhere, char *mfile, int shorten,
119 int local_specified, char *mname, char *msg));
120 static Dtype log_dirproc PROTO ((void *callerdat, const char *dir,
121 const char *repository,
122 const char *update_dir,
124 static int log_fileproc PROTO ((void *callerdat, struct file_info *finfo));
125 static struct option_revlist *log_parse_revlist PROTO ((const char *));
126 static void log_parse_date PROTO ((struct log_data *, const char *));
127 static void log_parse_list PROTO ((List **, const char *));
128 static struct revlist *log_expand_revlist PROTO ((RCSNode *,
129 struct option_revlist *,
131 static void log_free_revlist PROTO ((struct revlist *));
132 static int log_version_requested PROTO ((struct log_data *, struct revlist *,
133 RCSNode *, RCSVers *));
134 static int log_symbol PROTO ((Node *, void *));
135 static int log_count PROTO ((Node *, void *));
136 static int log_fix_singledate PROTO ((Node *, void *));
137 static int log_count_print PROTO ((Node *, void *));
138 static void log_tree PROTO ((struct log_data *, struct revlist *,
139 RCSNode *, const char *));
140 static void log_abranch PROTO ((struct log_data *, struct revlist *,
141 RCSNode *, const char *));
142 static void log_version PROTO ((struct log_data *, struct revlist *,
143 RCSNode *, RCSVers *, int));
144 static int log_branch PROTO ((Node *, void *));
145 static int version_compare PROTO ((const char *, const char *, int));
147 static struct log_data log_data;
150 static const char *const log_usage[] =
152 "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
153 " [-w[logins]] [files...]\n",
154 "\t-l\tLocal directory only, no recursion.\n",
155 "\t-R\tOnly print name of RCS file.\n",
156 "\t-h\tOnly print header.\n",
157 "\t-t\tOnly print header and descriptive text.\n",
158 "\t-N\tDo not list tags.\n",
159 "\t-S\tDo not print name/header if no revisions selected.\n",
160 "\t-b\tOnly list revisions on the default branch.\n",
161 "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
162 "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n",
163 "\t rev1::rev2 Between rev1 and rev2, excluding rev1.\n",
164 "\t rev: rev and following revisions on the same branch.\n",
165 "\t rev:: After rev on the same branch.\n",
166 "\t :rev rev and previous revisions on the same branch.\n",
167 "\t ::rev rev and previous revisions on the same branch.\n",
168 "\t rev Just rev.\n",
169 "\t branch All revisions on the branch.\n",
170 "\t branch. The last revision on the branch.\n",
171 "\t-d dates\tA semicolon-separated list of dates\n",
172 "\t \t(D1<D2 for range, D for latest before).\n",
173 "\t-s states\tOnly list revisions with specified states.\n",
174 "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
175 "(Specify the --help global option for a list of other help options)\n",
179 #ifdef CLIENT_SUPPORT
181 /* Helper function for send_arg_list. */
182 static int send_one PROTO ((Node *, void *));
185 send_one (node, closure)
189 char *option = (char *) closure;
191 send_to_server ("Argument ", 0);
192 send_to_server (option, 0);
193 if (strcmp (node->key, "@@MYSELF") == 0)
194 /* It is a bare -w option. Note that we must send it as
195 -w rather than messing with getcaller() or something (which on
196 the client will return garbage). */
199 send_to_server (node->key, 0);
200 send_to_server ("\012", 0);
204 /* For each element in ARG, send an argument consisting of OPTION
205 concatenated with that element. */
206 static void send_arg_list PROTO ((char *, List *));
209 send_arg_list (option, arg)
215 walklist (arg, send_one, (void *)option);
228 struct option_revlist **prl;
230 is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
235 memset (&log_data, 0, sizeof log_data);
236 prl = &log_data.revlist;
239 while ((c = getopt (argc, argv, "+bd:hlNnSRr::s:tw::")) != -1)
244 log_data.default_branch = 1;
247 log_parse_date (&log_data, optarg);
262 log_data.sup_header = 1;
265 log_data.nameonly = 1;
268 *prl = log_parse_revlist (optarg);
272 log_parse_list (&log_data.statelist, optarg);
275 log_data.long_header = 1;
279 log_parse_list (&log_data.authorlist, optarg);
281 log_parse_list (&log_data.authorlist, "@@MYSELF");
294 #ifdef CLIENT_SUPPORT
295 if (current_parsed_root->isremote)
298 struct option_revlist *rp;
299 char datetmp[MAXDATELEN];
301 /* We're the local client. Fire up the remote server. */
304 if (is_rlog && !supported_request ("rlog"))
305 error (1, 0, "server does not support rlog");
309 if (log_data.default_branch)
312 while (log_data.datelist != NULL)
314 p = log_data.datelist;
315 log_data.datelist = p->next;
316 send_to_server ("Argument -d\012", 0);
317 send_to_server ("Argument ", 0);
318 date_to_internet (datetmp, p->start);
319 send_to_server (datetmp, 0);
321 send_to_server ("<=", 0);
323 send_to_server ("<", 0);
324 date_to_internet (datetmp, p->end);
325 send_to_server (datetmp, 0);
326 send_to_server ("\012", 0);
333 while (log_data.singledatelist != NULL)
335 p = log_data.singledatelist;
336 log_data.singledatelist = p->next;
337 send_to_server ("Argument -d\012", 0);
338 send_to_server ("Argument ", 0);
339 date_to_internet (datetmp, p->end);
340 send_to_server (datetmp, 0);
341 send_to_server ("\012", 0);
353 if (log_data.sup_header)
355 if (log_data.nameonly)
357 if (log_data.long_header)
360 while (log_data.revlist != NULL)
362 rp = log_data.revlist;
363 log_data.revlist = rp->next;
364 send_to_server ("Argument -r", 0);
367 if (rp->first != NULL)
368 send_to_server (rp->first, 0);
369 send_to_server (".", 1);
373 if (rp->first != NULL)
374 send_to_server (rp->first, 0);
375 send_to_server (":", 1);
377 send_to_server (":", 1);
378 if (rp->last != NULL)
379 send_to_server (rp->last, 0);
381 send_to_server ("\012", 0);
388 send_arg_list ("-s", log_data.statelist);
389 dellist (&log_data.statelist);
390 send_arg_list ("-w", log_data.authorlist);
391 dellist (&log_data.authorlist);
397 for (i = 0; i < argc; i++)
399 send_to_server ("rlog\012", 0);
403 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
404 send_file_names (argc, argv, SEND_EXPAND_WILD);
405 send_to_server ("log\012", 0);
407 err = get_responses_and_close ();
412 /* OK, now that we know we are local/server, we can resolve @@MYSELF
413 into our user name. */
414 if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
415 log_parse_list (&log_data.authorlist, getcaller ());
422 for (i = 0; i < argc; i++)
424 err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
425 (char *) NULL, 0, local, 0, 0, (char *) NULL);
431 err = rlog_proc (argc + 1, argv - 1, (char *) NULL,
432 (char *) NULL, (char *) NULL, 0, local, (char *) NULL,
436 while (log_data.revlist)
438 struct option_revlist *rl = log_data.revlist->next;
439 if (log_data.revlist->first)
440 free (log_data.revlist->first);
441 if (log_data.revlist->last)
442 free (log_data.revlist->last);
443 free (log_data.revlist);
444 log_data.revlist = rl;
446 while (log_data.datelist)
448 struct datelist *nd = log_data.datelist->next;
449 if (log_data.datelist->start)
450 free (log_data.datelist->start);
451 if (log_data.datelist->end)
452 free (log_data.datelist->end);
453 free (log_data.datelist);
454 log_data.datelist = nd;
456 while (log_data.singledatelist)
458 struct datelist *nd = log_data.singledatelist->next;
459 if (log_data.singledatelist->start)
460 free (log_data.singledatelist->start);
461 if (log_data.singledatelist->end)
462 free (log_data.singledatelist->end);
463 free (log_data.singledatelist);
464 log_data.singledatelist = nd;
466 dellist (&log_data.statelist);
467 dellist (&log_data.authorlist);
474 rlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
485 /* Begin section which is identical to patch_proc--should this
486 be abstracted out somehow? */
495 repository = xmalloc (strlen (current_parsed_root->directory)
497 + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
498 (void)sprintf (repository, "%s/%s",
499 current_parsed_root->directory, argv[0]);
500 where = xmalloc (strlen (argv[0])
501 + (mfile == NULL ? 0 : strlen (mfile) + 1)
503 (void) strcpy (where, argv[0]);
505 /* If mfile isn't null, we need to set up to do only part of theu
513 /* If the portion of the module is a path, put the dir part on
516 if ((cp = strrchr (mfile, '/')) != NULL)
519 (void)strcat (repository, "/");
520 (void)strcat (repository, mfile);
521 (void)strcat (where, "/");
522 (void)strcat (where, mfile);
526 /* take care of the rest */
527 path = xmalloc (strlen (repository) + strlen (mfile) + 5);
528 (void)sprintf (path, "%s/%s", repository, mfile);
531 /* directory means repository gets the dir tacked on */
532 (void)strcpy (repository, path);
533 (void)strcat (where, "/");
534 (void)strcat (where, mfile);
546 /* cd to the starting repository */
547 if (CVS_CHDIR (repository) < 0)
549 error (0, errno, "cannot chdir to %s", repository);
554 /* End section which is identical to patch_proc. */
556 which = W_REPOS | W_ATTIC;
562 which = W_LOCAL | W_REPOS | W_ATTIC;
565 err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
566 (DIRLEAVEPROC) NULL, (void *) &log_data,
567 argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
568 where, 1, repository);
570 if (!(which & W_LOCAL)) free (repository);
571 if (where) free (where);
579 * Parse a revision list specification.
581 static struct option_revlist *
582 log_parse_revlist (argstring)
583 const char *argstring;
585 char *orig_copy, *copy;
586 struct option_revlist *ret, **pr;
588 /* Unfortunately, rlog accepts -r without an argument to mean that
589 latest revision on the default branch, so we must support that
590 for compatibility. */
591 if (argstring == NULL)
597 /* Copy the argument into memory so that we can change it. We
598 don't want to change the argument because, at least as of this
599 writing, we will use it if we send the arguments to the server. */
600 orig_copy = copy = xstrdup (argstring);
604 struct option_revlist *r;
606 comma = strchr (copy, ',');
610 r = (struct option_revlist *) xmalloc (sizeof *r);
614 r->last = strchr (copy, ':');
618 r->inclusive = (*r->last != ':');
626 if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
629 r->first[strlen (r->first) - 1] = '\0';
633 if (*r->first == '\0')
635 if (*r->last == '\0')
638 if (r->first != NULL)
639 r->first = xstrdup (r->first);
641 r->last = xstrdup (r->last);
654 * Parse a date specification.
657 log_parse_date (log_data, argstring)
658 struct log_data *log_data;
659 const char *argstring;
661 char *orig_copy, *copy;
663 /* Copy the argument into memory so that we can change it. We
664 don't want to change the argument because, at least as of this
665 writing, we will use it if we send the arguments to the server. */
666 orig_copy = copy = xstrdup (argstring);
669 struct datelist *nd, **pd;
670 char *cpend, *cp, *ds, *de;
672 nd = (struct datelist *) xmalloc (sizeof *nd);
674 cpend = strchr (copy, ';');
678 pd = &log_data->datelist;
681 if ((cp = strchr (copy, '>')) != NULL)
692 else if ((cp = strchr (copy, '<')) != NULL)
707 pd = &log_data->singledatelist;
712 else if (*ds != '\0')
713 nd->start = Make_Date (ds);
716 /* 1970 was the beginning of time, as far as get_date and
717 Make_Date are concerned. FIXME: That is true only if time_t
718 is a POSIX-style time and there is nothing in ANSI that
719 mandates that. It would be cleaner to set a flag saying
720 whether or not there is a start date. */
721 nd->start = Make_Date ("1/1/1970 UTC");
725 nd->end = Make_Date (de);
728 /* We want to set the end date to some time sufficiently far
729 in the future to pick up all revisions that have been
730 created since the specified date and the time `cvs log'
731 completes. FIXME: The date in question only makes sense
732 if time_t is a POSIX-style time and it is 32 bits
733 and signed. We should instead be setting a flag saying
734 whether or not there is an end date. Note that using
735 something like "next week" would break the testsuite (and,
736 perhaps less importantly, loses if the clock is set grossly
738 nd->end = Make_Date ("2038-01-01");
751 * Parse a comma separated list of items, and add each one to *PLIST.
754 log_parse_list (plist, argstring)
756 const char *argstring;
765 cp = strchr (argstring, ',');
767 p->key = xstrdup (argstring);
772 len = cp - argstring;
773 p->key = xmalloc (len + 1);
774 strncpy (p->key, argstring, len);
780 if (addnode (*plist, p) != 0)
790 static int printlock_proc PROTO ((Node *, void *));
793 printlock_proc (lock, foo)
797 cvs_output ("\n\t", 2);
798 cvs_output (lock->data, 0);
799 cvs_output (": ", 2);
800 cvs_output (lock->key, 0);
807 * Do an rlog on a file
810 log_fileproc (callerdat, finfo)
812 struct file_info *finfo;
814 struct log_data *log_data = (struct log_data *) callerdat;
819 struct revlist *revlist = NULL;
820 struct log_data_and_rcs log_data_and_rcs;
822 if ((rcsfile = finfo->rcs) == NULL)
824 /* no rcs file. What *do* we know about this file? */
825 p = findnode (finfo->entries, finfo->file);
828 Entnode *e = p->data;
830 if (e->version[0] == '0' && e->version[1] == '\0')
833 error (0, 0, "%s has been added, but not committed",
840 error (0, 0, "nothing known about %s", finfo->file);
845 if (log_data->sup_header || !log_data->nameonly)
848 /* We will need all the information in the RCS file. */
849 RCS_fully_parse (rcsfile);
851 /* Turn any symbolic revisions in the revision list into numeric
853 revlist = log_expand_revlist (rcsfile, log_data->revlist,
854 log_data->default_branch);
855 if (log_data->sup_header
856 || (!log_data->header && !log_data->long_header))
858 log_data_and_rcs.log_data = log_data;
859 log_data_and_rcs.revlist = revlist;
860 log_data_and_rcs.rcs = rcsfile;
862 /* If any single dates were specified, we need to identify the
863 revisions they select. Each one selects the single
864 revision, which is otherwise selected, of that date or
865 earlier. The log_fix_singledate routine will fill in the
866 start date for each specific revision. */
867 if (log_data->singledatelist != NULL)
868 walklist (rcsfile->versions, log_fix_singledate,
869 (void *)&log_data_and_rcs);
871 selrev = walklist (rcsfile->versions, log_count_print,
872 (void *)&log_data_and_rcs);
873 if (log_data->sup_header && selrev == 0)
875 log_free_revlist (revlist);
882 if (log_data->nameonly)
884 cvs_output (rcsfile->path, 0);
885 cvs_output ("\n", 1);
886 log_free_revlist (revlist);
890 /* The output here is intended to be exactly compatible with the
891 output of rlog. I'm not sure whether this code should be here
892 or in rcs.c; I put it here because it is specific to the log
893 function, even though it uses information gathered by the
894 functions in rcs.c. */
896 cvs_output ("\n", 1);
898 cvs_output ("RCS file: ", 0);
899 cvs_output (rcsfile->path, 0);
903 cvs_output ("\nWorking file: ", 0);
904 if (finfo->update_dir[0] != '\0')
906 cvs_output (finfo->update_dir, 0);
909 cvs_output (finfo->file, 0);
912 cvs_output ("\nhead:", 0);
913 if (rcsfile->head != NULL)
916 cvs_output (rcsfile->head, 0);
919 cvs_output ("\nbranch:", 0);
920 if (rcsfile->branch != NULL)
923 cvs_output (rcsfile->branch, 0);
926 cvs_output ("\nlocks:", 0);
927 if (rcsfile->strict_locks)
928 cvs_output (" strict", 0);
929 walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
931 cvs_output ("\naccess list:", 0);
932 if (rcsfile->access != NULL)
936 cp = rcsfile->access;
941 cvs_output ("\n\t", 2);
943 while (!isspace ((unsigned char) *cp2) && *cp2 != '\0')
945 cvs_output (cp, cp2 - cp);
947 while (isspace ((unsigned char) *cp) && *cp != '\0')
952 if (!log_data->notags)
956 cvs_output ("\nsymbolic names:", 0);
957 syms = RCS_symbols (rcsfile);
958 walklist (syms, log_symbol, NULL);
961 cvs_output ("\nkeyword substitution: ", 0);
962 if (rcsfile->expand == NULL)
963 cvs_output ("kv", 2);
965 cvs_output (rcsfile->expand, 0);
967 cvs_output ("\ntotal revisions: ", 0);
968 sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
973 cvs_output (";\tselected revisions: ", 0);
974 sprintf (buf, "%d", selrev);
978 cvs_output ("\n", 1);
980 if (!log_data->header || log_data->long_header)
982 cvs_output ("description:\n", 0);
983 if (rcsfile->desc != NULL)
984 cvs_output (rcsfile->desc, 0);
987 if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
989 p = findnode (rcsfile->versions, rcsfile->head);
991 error (1, 0, "can not find head revision in `%s'",
995 RCSVers *vers = p->data;
997 log_version (log_data, revlist, rcsfile, vers, 1);
998 if (vers->next == NULL)
1002 p = findnode (rcsfile->versions, vers->next);
1004 error (1, 0, "can not find next revision `%s' in `%s'",
1005 vers->next, finfo->fullname);
1009 log_tree (log_data, revlist, rcsfile, rcsfile->head);
1013 =============================================================================\n",
1016 /* Free up the new revlist and restore the old one. */
1017 log_free_revlist (revlist);
1019 /* If singledatelist is not NULL, free up the start dates we added
1021 if (log_data->singledatelist != NULL)
1025 for (d = log_data->singledatelist; d != NULL; d = d->next)
1027 if (d->start != NULL)
1039 * Fix up a revision list in order to compare it against versions.
1040 * Expand any symbolic revisions.
1042 static struct revlist *
1043 log_expand_revlist (rcs, revlist, default_branch)
1045 struct option_revlist *revlist;
1048 struct option_revlist *r;
1049 struct revlist *ret, **pr;
1053 for (r = revlist; r != NULL; r = r->next)
1057 nr = (struct revlist *) xmalloc (sizeof *nr);
1058 nr->inclusive = r->inclusive;
1060 if (r->first == NULL && r->last == NULL)
1062 /* If both first and last are NULL, it means that we want
1063 just the head of the default branch, which is RCS_head. */
1064 nr->first = RCS_head (rcs);
1065 nr->last = xstrdup (nr->first);
1066 nr->fields = numdots (nr->first) + 1;
1068 else if (r->branchhead)
1072 /* Print just the head of the branch. */
1073 if (isdigit ((unsigned char) r->first[0]))
1074 nr->first = RCS_getbranch (rcs, r->first, 1);
1077 branch = RCS_whatbranch (rcs, r->first);
1082 nr->first = RCS_getbranch (rcs, branch, 1);
1086 if (nr->first == NULL && !really_quiet)
1088 error (0, 0, "warning: no branch `%s' in `%s'",
1089 r->first, rcs->path);
1095 nr->last = xstrdup (nr->first);
1096 nr->fields = numdots (nr->first) + 1;
1101 if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
1102 nr->first = xstrdup (r->first);
1105 if (RCS_nodeisbranch (rcs, r->first))
1106 nr->first = RCS_whatbranch (rcs, r->first);
1108 nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL);
1109 if (nr->first == NULL && !really_quiet)
1111 error (0, 0, "warning: no revision `%s' in `%s'",
1112 r->first, rcs->path);
1116 if (r->last == r->first || (r->last != NULL && r->first != NULL &&
1117 strcmp (r->last, r->first) == 0))
1118 nr->last = xstrdup (nr->first);
1119 else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
1120 nr->last = xstrdup (r->last);
1123 if (RCS_nodeisbranch (rcs, r->last))
1124 nr->last = RCS_whatbranch (rcs, r->last);
1126 nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL);
1127 if (nr->last == NULL && !really_quiet)
1129 error (0, 0, "warning: no revision `%s' in `%s'",
1130 r->last, rcs->path);
1134 /* Process the revision numbers the same way that rlog
1135 does. This code is a bit cryptic for my tastes, but
1136 keeping the same implementation as rlog ensures a
1137 certain degree of compatibility. */
1138 if (r->first == NULL && nr->last != NULL)
1140 nr->fields = numdots (nr->last) + 1;
1142 nr->first = xstrdup (".0");
1147 nr->first = xstrdup (nr->last);
1148 cp = strrchr (nr->first, '.');
1149 strcpy (cp + 1, "0");
1152 else if (r->last == NULL && nr->first != NULL)
1154 nr->fields = numdots (nr->first) + 1;
1155 nr->last = xstrdup (nr->first);
1162 cp = strrchr (nr->last, '.');
1166 else if (nr->first == NULL || nr->last == NULL)
1168 else if (strcmp (nr->first, nr->last) == 0)
1169 nr->fields = numdots (nr->last) + 1;
1173 int dots1 = numdots (nr->first);
1174 int dots2 = numdots (nr->last);
1175 if (dots1 > dots2 || (dots1 == dots2 &&
1176 version_compare (nr->first, nr->last, dots1 + 1) > 0))
1178 char *tmp = nr->first;
1179 nr->first = nr->last;
1181 nr->fields = dots2 + 1;
1183 dots1 = nr->fields - 1;
1186 nr->fields = dots1 + 1;
1187 dots1 += (nr->fields & 1);
1188 ord = version_compare (nr->first, nr->last, dots1);
1189 if (ord > 0 || (nr->fields > 2 && ord < 0))
1192 "invalid branch or revision pair %s:%s in `%s'",
1193 r->first, r->last, rcs->path);
1202 if (nr->fields <= dots2 && (nr->fields & 1))
1204 char *p = xmalloc (strlen (nr->first) + 3);
1205 strcpy (p, nr->first);
1211 while (nr->fields <= dots2)
1218 nr = (struct revlist *) xmalloc (sizeof *nr);
1220 nr->first = xstrdup ((*pr)->last);
1221 nr->last = xstrdup ((*pr)->last);
1222 nr->fields = (*pr)->fields;
1224 for (i = 0; i < nr->fields; i++)
1225 p = strchr (p, '.') + 1;
1227 p = strchr (nr->first + (p - (*pr)->last), '.');
1247 /* If the default branch was requested, add a revlist entry for
1248 it. This is how rlog handles this option. */
1250 && (rcs->head != NULL || rcs->branch != NULL))
1254 nr = (struct revlist *) xmalloc (sizeof *nr);
1255 if (rcs->branch != NULL)
1256 nr->first = xstrdup (rcs->branch);
1261 nr->first = xstrdup (rcs->head);
1262 cp = strrchr (nr->first, '.');
1265 nr->last = xstrdup (nr->first);
1266 nr->fields = numdots (nr->first) + 1;
1277 * Free a revlist created by log_expand_revlist.
1280 log_free_revlist (revlist)
1281 struct revlist *revlist;
1288 struct revlist *next;
1290 if (r->first != NULL)
1292 if (r->last != NULL)
1301 * Return nonzero if a revision should be printed, based on the
1305 log_version_requested (log_data, revlist, rcs, vnode)
1306 struct log_data *log_data;
1307 struct revlist *revlist;
1311 /* Handle the list of states from the -s option. */
1312 if (log_data->statelist != NULL
1313 && findnode (log_data->statelist, vnode->state) == NULL)
1318 /* Handle the list of authors from the -w option. */
1319 if (log_data->authorlist != NULL)
1321 if (vnode->author != NULL
1322 && findnode (log_data->authorlist, vnode->author) == NULL)
1328 /* rlog considers all the -d options together when it decides
1329 whether to print a revision, so we must be compatible. */
1330 if (log_data->datelist != NULL || log_data->singledatelist != NULL)
1334 for (d = log_data->datelist; d != NULL; d = d->next)
1338 cmp = RCS_datecmp (vnode->date, d->start);
1339 if (cmp > 0 || (cmp == 0 && d->inclusive))
1341 cmp = RCS_datecmp (vnode->date, d->end);
1342 if (cmp < 0 || (cmp == 0 && d->inclusive))
1349 /* Look through the list of specific dates. We want to
1350 select the revision with the exact date found in the
1351 start field. The commit code ensures that it is
1352 impossible to check in multiple revisions of a single
1353 file in a single second, so checking the date this way
1354 should never select more than one revision. */
1355 for (d = log_data->singledatelist; d != NULL; d = d->next)
1357 if (d->start != NULL
1358 && RCS_datecmp (vnode->date, d->start) == 0)
1369 /* If the -r or -b options were used, REVLIST will be non NULL,
1370 and we print the union of the specified revisions. */
1371 if (revlist != NULL)
1377 /* This code is taken from rlog. */
1379 vfields = numdots (v) + 1;
1380 for (r = revlist; r != NULL; r = r->next)
1382 if (vfields == r->fields + (r->fields & 1) &&
1383 (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
1384 version_compare (v, r->first, r->fields) > 0)
1385 && version_compare (v, r->last, r->fields) <= 0)
1391 /* If we get here, then the -b and/or the -r option was used,
1392 but did not match this revision, so we reject it. */
1397 /* By default, we print all revisions. */
1404 * Output a single symbol. This is called via walklist.
1408 log_symbol (p, closure)
1412 cvs_output ("\n\t", 2);
1413 cvs_output (p->key, 0);
1414 cvs_output (": ", 2);
1415 cvs_output (p->data, 0);
1422 * Count the number of entries on a list. This is called via walklist.
1426 log_count (p, closure)
1436 * Sort out a single date specification by narrowing down the date
1437 * until we find the specific selected revision.
1440 log_fix_singledate (p, closure)
1444 struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
1447 struct datelist *holdsingle, *holddate;
1450 pv = findnode (data->rcs->versions, p->key);
1452 error (1, 0, "missing version `%s' in RCS file `%s'",
1453 p->key, data->rcs->path);
1456 /* We are only interested if this revision passes any other tests.
1457 Temporarily clear log_data->singledatelist to avoid confusing
1458 log_version_requested. We also clear log_data->datelist,
1459 because rlog considers all the -d options together. We don't
1460 want to reject a revision because it does not match a date pair
1461 if we are going to select it on the basis of the singledate. */
1462 holdsingle = data->log_data->singledatelist;
1463 data->log_data->singledatelist = NULL;
1464 holddate = data->log_data->datelist;
1465 data->log_data->datelist = NULL;
1466 requested = log_version_requested (data->log_data, data->revlist,
1468 data->log_data->singledatelist = holdsingle;
1469 data->log_data->datelist = holddate;
1475 /* For each single date, if this revision is before the
1476 specified date, but is closer than the previously selected
1477 revision, select it instead. */
1478 for (d = data->log_data->singledatelist; d != NULL; d = d->next)
1480 if (RCS_datecmp (vnode->date, d->end) <= 0
1481 && (d->start == NULL
1482 || RCS_datecmp (vnode->date, d->start) > 0))
1484 if (d->start != NULL)
1486 d->start = xstrdup (vnode->date);
1497 * Count the number of revisions we are going to print.
1500 log_count_print (p, closure)
1504 struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
1507 pv = findnode (data->rcs->versions, p->key);
1509 error (1, 0, "missing version `%s' in RCS file `%s'",
1510 p->key, data->rcs->path);
1511 if (log_version_requested (data->log_data, data->revlist, data->rcs,
1519 * Print the list of changes, not including the trunk, in reverse
1520 * order for each branch.
1523 log_tree (log_data, revlist, rcs, ver)
1524 struct log_data *log_data;
1525 struct revlist *revlist;
1532 p = findnode (rcs->versions, ver);
1534 error (1, 0, "missing version `%s' in RCS file `%s'",
1537 if (vnode->next != NULL)
1538 log_tree (log_data, revlist, rcs, vnode->next);
1539 if (vnode->branches != NULL)
1541 Node *head, *branch;
1543 /* We need to do the branches in reverse order. This breaks
1544 the List abstraction, but so does most of the branch
1545 manipulation in rcs.c. */
1546 head = vnode->branches->list;
1547 for (branch = head->prev; branch != head; branch = branch->prev)
1549 log_abranch (log_data, revlist, rcs, branch->key);
1550 log_tree (log_data, revlist, rcs, branch->key);
1556 * Log the changes for a branch, in reverse order.
1559 log_abranch (log_data, revlist, rcs, ver)
1560 struct log_data *log_data;
1561 struct revlist *revlist;
1568 p = findnode (rcs->versions, ver);
1570 error (1, 0, "missing version `%s' in RCS file `%s'",
1573 if (vnode->next != NULL)
1574 log_abranch (log_data, revlist, rcs, vnode->next);
1575 log_version (log_data, revlist, rcs, vnode, 0);
1579 * Print the log output for a single version.
1582 log_version (log_data, revlist, rcs, ver, trunk)
1583 struct log_data *log_data;
1584 struct revlist *revlist;
1590 int year, mon, mday, hour, min, sec;
1594 if (! log_version_requested (log_data, revlist, rcs, ver))
1597 cvs_output ("----------------------------\nrevision ", 0);
1598 cvs_output (ver->version, 0);
1600 p = findnode (RCS_getlocks (rcs), ver->version);
1603 cvs_output ("\tlocked by: ", 0);
1604 cvs_output (p->data, 0);
1605 cvs_output (";", 1);
1608 cvs_output ("\ndate: ", 0);
1609 (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
1613 sprintf (buf, "%04d%c%02d%c%02d %02d:%02d:%02d",
1614 year, datesep, mon, datesep, mday, hour, min, sec);
1615 cvs_output (buf, 0);
1617 cvs_output ("; author: ", 0);
1618 cvs_output (ver->author, 0);
1620 cvs_output ("; state: ", 0);
1621 cvs_output (ver->state, 0);
1622 cvs_output (";", 1);
1626 padd = findnode (ver->other, ";add");
1627 pdel = findnode (ver->other, ";delete");
1629 else if (ver->next == NULL)
1639 nextp = findnode (rcs->versions, ver->next);
1641 error (1, 0, "missing version `%s' in `%s'", ver->next,
1643 nextver = nextp->data;
1644 pdel = findnode (nextver->other, ";add");
1645 padd = findnode (nextver->other, ";delete");
1650 cvs_output (" lines: +", 0);
1651 cvs_output (padd->data, 0);
1652 cvs_output (" -", 2);
1653 cvs_output (pdel->data, 0);
1656 if (ver->branches != NULL)
1658 cvs_output ("\nbranches:", 0);
1659 walklist (ver->branches, log_branch, (void *) NULL);
1662 cvs_output ("\n", 1);
1664 p = findnode (ver->other, "log");
1665 /* The p->date == NULL case is the normal one for an empty log
1666 message (rcs-14 in sanity.sh). I don't think the case where
1667 p->data is "" can happen (getrcskey in rcs.c checks for an
1668 empty string and set the value to NULL in that case). My guess
1669 would be the p == NULL case would mean an RCS file which was
1670 missing the "log" keyword (which is illegal according to
1672 if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
1673 cvs_output ("*** empty log message ***\n", 0);
1676 /* FIXME: Technically, the log message could contain a null
1678 cvs_output (p->data, 0);
1679 if (((char *)p->data)[strlen (p->data) - 1] != '\n')
1680 cvs_output ("\n", 1);
1685 * Output a branch version. This is called via walklist.
1689 log_branch (p, closure)
1693 cvs_output (" ", 2);
1694 if ((numdots (p->key) & 1) == 0)
1695 cvs_output (p->key, 0);
1700 f = xstrdup (p->key);
1701 cp = strrchr (f, '.');
1706 cvs_output (";", 1);
1711 * Print a warm fuzzy message
1715 log_dirproc (callerdat, dir, repository, update_dir, entries)
1718 const char *repository;
1719 const char *update_dir;
1723 return (R_SKIP_ALL);
1726 error (0, 0, "Logging %s", update_dir);
1731 * Compare versions. This is taken from RCS compartial.
1734 version_compare (v1, v2, len)
1750 for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
1755 for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
1759 return d1 < d2 ? -1 : 1;
1761 r = memcmp (v1, v2, d1);