2 * Copyright (C) 1986-2008 The Free Software Foundation, Inc.
4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8 * Portions Copyright (C) 1989-1992, Brian Berliner
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.
13 * Print Log Information
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).
25 /* This structure holds information parsed from the -r option. */
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. */
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
38 /* Nonzero if there was a trailing `.', which means to print only
39 the head revision of a branch. */
41 /* Nonzero if first and last are inclusive. */
45 /* This structure holds information derived from option_revlist given
46 a particular RCS file. */
52 /* The first numeric revision to print. */
54 /* The last numeric revision to print. */
56 /* The number of fields in these revisions (one more than
59 /* Whether first & last are to be included or excluded. */
63 /* This structure holds information parsed from the -d option. */
68 struct datelist *next;
69 /* The starting date. */
71 /* The ending date. */
73 /* Nonzero if the range is inclusive rather than exclusive. */
77 /* This structure is used to pass information through start_recursion. */
80 /* Nonzero if the -R option was given, meaning that only the name
81 of the RCS file should be printed. */
83 /* Nonzero if the -h option was given, meaning that only header
84 information should be printed. */
86 /* Nonzero if the -t option was given, meaning that only the
87 header and the descriptive text should be printed. */
89 /* Nonzero if the -N option was seen, meaning that tag information
90 should not be printed. */
92 /* Nonzero if the -b option was seen, meaning that revisions
93 on the default branch should be printed. */
95 /* Nonzero if the -S option was seen, meaning that the header/name
96 should be suppressed if no revisions are selected. */
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. */
110 /* If not NULL, the list of login names given for the -w option,
111 which only prints revisions checked in by given users. */
115 /* This structure is used to pass information through walklist. */
116 struct log_data_and_rcs
118 struct log_data *log_data;
119 struct revlist *revlist;
123 static int rlog_proc PROTO((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 PROTO ((void *callerdat, const char *dir,
127 const char *repository,
128 const char *update_dir,
130 static int log_fileproc PROTO ((void *callerdat, struct file_info *finfo));
131 static struct option_revlist *log_parse_revlist PROTO ((const char *));
132 static void log_parse_date PROTO ((struct log_data *, const char *));
133 static void log_parse_list PROTO ((List **, const char *));
134 static struct revlist *log_expand_revlist PROTO ((RCSNode *, char *,
135 struct option_revlist *,
137 static void log_free_revlist PROTO ((struct revlist *));
138 static int log_version_requested PROTO ((struct log_data *, struct revlist *,
139 RCSNode *, RCSVers *));
140 static int log_symbol PROTO ((Node *, void *));
141 static int log_count PROTO ((Node *, void *));
142 static int log_fix_singledate PROTO ((Node *, void *));
143 static int log_count_print PROTO ((Node *, void *));
144 static void log_tree PROTO ((struct log_data *, struct revlist *,
145 RCSNode *, const char *));
146 static void log_abranch PROTO ((struct log_data *, struct revlist *,
147 RCSNode *, const char *));
148 static void log_version PROTO ((struct log_data *, struct revlist *,
149 RCSNode *, RCSVers *, int));
150 static int log_branch PROTO ((Node *, void *));
151 static int version_compare PROTO ((const char *, const char *, int));
153 static struct log_data log_data;
156 static const char *const log_usage[] =
158 "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
159 " [-w[logins]] [files...]\n",
160 "\t-l\tLocal directory only, no recursion.\n",
161 "\t-b\tList revisions on the default branch.\n",
162 "\t-h\tOnly print header.\n",
163 "\t-R\tOnly print name of RCS file.\n",
164 "\t-t\tOnly print header and descriptive text.\n",
165 "\t-N\tDo not list tags.\n",
166 "\t-n\tList tags (default).\n",
167 "\t-S\tDo not print name/header if no revisions selected. -d, -r,\n",
168 "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n",
169 "\t\t-t without this option.\n",
170 "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
171 "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n",
172 "\t rev1::rev2 Between rev1 and rev2, excluding rev1.\n",
173 "\t rev: rev and following revisions on the same branch.\n",
174 "\t rev:: After rev on the same branch.\n",
175 "\t :rev rev and previous revisions on the same branch.\n",
176 "\t ::rev rev and previous revisions on the same branch.\n",
177 "\t rev Just rev.\n",
178 "\t branch All revisions on the branch.\n",
179 "\t branch. The last revision on the branch.\n",
180 "\t-d dates\tA semicolon-separated list of dates\n",
181 "\t \t(D1<D2 for range, D for latest before).\n",
182 "\t-s states\tOnly list revisions with specified states.\n",
183 "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
184 "(Specify the --help global option for a list of other help options)\n",
188 #ifdef CLIENT_SUPPORT
190 /* Helper function for send_arg_list. */
191 static int send_one PROTO ((Node *, void *));
194 send_one (node, closure)
198 char *option = (char *) closure;
200 send_to_server ("Argument ", 0);
201 send_to_server (option, 0);
202 if (strcmp (node->key, "@@MYSELF") == 0)
203 /* It is a bare -w option. Note that we must send it as
204 -w rather than messing with getcaller() or something (which on
205 the client will return garbage). */
208 send_to_server (node->key, 0);
209 send_to_server ("\012", 0);
213 /* For each element in ARG, send an argument consisting of OPTION
214 concatenated with that element. */
215 static void send_arg_list PROTO ((char *, List *));
218 send_arg_list (option, arg)
224 walklist (arg, send_one, (void *)option);
237 struct option_revlist **prl;
239 is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
244 memset (&log_data, 0, sizeof log_data);
245 prl = &log_data.revlist;
248 while ((c = getopt (argc, argv, "+bd:hlNnSRr::s:tw::")) != -1)
253 log_data.default_branch = 1;
256 log_parse_date (&log_data, optarg);
271 log_data.sup_header = 1;
274 log_data.nameonly = 1;
277 *prl = log_parse_revlist (optarg);
281 log_parse_list (&log_data.statelist, optarg);
284 log_data.long_header = 1;
288 log_parse_list (&log_data.authorlist, optarg);
290 log_parse_list (&log_data.authorlist, "@@MYSELF");
303 #ifdef CLIENT_SUPPORT
304 if (current_parsed_root->isremote)
307 struct option_revlist *rp;
308 char datetmp[MAXDATELEN];
310 /* We're the local client. Fire up the remote server. */
313 if (is_rlog && !supported_request ("rlog"))
314 error (1, 0, "server does not support rlog");
318 if (log_data.default_branch)
321 while (log_data.datelist != NULL)
323 p = log_data.datelist;
324 log_data.datelist = p->next;
325 assert (p->start != NULL && p->end != NULL);
326 send_to_server ("Argument -d\012", 0);
327 send_to_server ("Argument ", 0);
328 date_to_internet (datetmp, p->start);
329 send_to_server (datetmp, 0);
331 send_to_server ("<=", 0);
333 send_to_server ("<", 0);
334 date_to_internet (datetmp, p->end);
335 send_to_server (datetmp, 0);
336 send_to_server ("\012", 0);
341 while (log_data.singledatelist != NULL)
343 p = log_data.singledatelist;
344 log_data.singledatelist = p->next;
345 assert (p->end != NULL);
346 send_to_server ("Argument -d\012", 0);
347 send_to_server ("Argument ", 0);
348 date_to_internet (datetmp, p->end);
349 send_to_server (datetmp, 0);
350 send_to_server ("\012", 0);
361 if (log_data.sup_header)
363 if (log_data.nameonly)
365 if (log_data.long_header)
368 while (log_data.revlist != NULL)
370 rp = log_data.revlist;
371 log_data.revlist = rp->next;
372 send_to_server ("Argument -r", 0);
375 if (rp->first != NULL)
376 send_to_server (rp->first, 0);
377 send_to_server (".", 1);
381 if (rp->first != NULL)
382 send_to_server (rp->first, 0);
383 send_to_server (":", 1);
385 send_to_server (":", 1);
386 if (rp->last != NULL)
387 send_to_server (rp->last, 0);
389 send_to_server ("\012", 0);
396 send_arg_list ("-s", log_data.statelist);
397 dellist (&log_data.statelist);
398 send_arg_list ("-w", log_data.authorlist);
399 dellist (&log_data.authorlist);
405 for (i = 0; i < argc; i++)
407 send_to_server ("rlog\012", 0);
411 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
412 send_file_names (argc, argv, SEND_EXPAND_WILD);
413 send_to_server ("log\012", 0);
415 err = get_responses_and_close ();
420 /* OK, now that we know we are local/server, we can resolve @@MYSELF
421 into our user name. */
422 if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
423 log_parse_list (&log_data.authorlist, getcaller ());
430 for (i = 0; i < argc; i++)
432 err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
433 (char *) NULL, 0, local, 0, 0, (char *) NULL);
439 err = rlog_proc (argc + 1, argv - 1, (char *) NULL,
440 (char *) NULL, (char *) NULL, 0, local, (char *) NULL,
444 while (log_data.revlist)
446 struct option_revlist *rl = log_data.revlist->next;
447 if (log_data.revlist->first)
448 free (log_data.revlist->first);
449 if (log_data.revlist->last)
450 free (log_data.revlist->last);
451 free (log_data.revlist);
452 log_data.revlist = rl;
454 while (log_data.datelist)
456 struct datelist *nd = log_data.datelist->next;
457 if (log_data.datelist->start)
458 free (log_data.datelist->start);
459 if (log_data.datelist->end)
460 free (log_data.datelist->end);
461 free (log_data.datelist);
462 log_data.datelist = nd;
464 while (log_data.singledatelist)
466 struct datelist *nd = log_data.singledatelist->next;
467 if (log_data.singledatelist->start)
468 free (log_data.singledatelist->start);
469 if (log_data.singledatelist->end)
470 free (log_data.singledatelist->end);
471 free (log_data.singledatelist);
472 log_data.singledatelist = nd;
474 dellist (&log_data.statelist);
475 dellist (&log_data.authorlist);
482 rlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
493 /* Begin section which is identical to patch_proc--should this
494 be abstracted out somehow? */
503 repository = xmalloc (strlen (current_parsed_root->directory)
505 + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
506 (void)sprintf (repository, "%s/%s",
507 current_parsed_root->directory, argv[0]);
508 where = xmalloc (strlen (argv[0])
509 + (mfile == NULL ? 0 : strlen (mfile) + 1)
511 (void) strcpy (where, argv[0]);
513 /* If mfile isn't null, we need to set up to do only part of theu
521 /* If the portion of the module is a path, put the dir part on
524 if ((cp = strrchr (mfile, '/')) != NULL)
527 (void)strcat (repository, "/");
528 (void)strcat (repository, mfile);
529 (void)strcat (where, "/");
530 (void)strcat (where, mfile);
534 /* take care of the rest */
535 path = xmalloc (strlen (repository) + strlen (mfile) + 5);
536 (void)sprintf (path, "%s/%s", repository, mfile);
539 /* directory means repository gets the dir tacked on */
540 (void)strcpy (repository, path);
541 (void)strcat (where, "/");
542 (void)strcat (where, mfile);
554 /* cd to the starting repository */
555 if (CVS_CHDIR (repository) < 0)
557 error (0, errno, "cannot chdir to %s", repository);
562 /* End section which is identical to patch_proc. */
564 which = W_REPOS | W_ATTIC;
570 which = W_LOCAL | W_REPOS | W_ATTIC;
573 err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
574 (DIRLEAVEPROC) NULL, (void *) &log_data,
575 argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
576 where, 1, repository);
578 if (!(which & W_LOCAL)) free (repository);
579 if (where) free (where);
587 * Parse a revision list specification.
589 static struct option_revlist *
590 log_parse_revlist (argstring)
591 const char *argstring;
593 char *orig_copy, *copy;
594 struct option_revlist *ret, **pr;
596 /* Unfortunately, rlog accepts -r without an argument to mean that
597 latest revision on the default branch, so we must support that
598 for compatibility. */
599 if (argstring == NULL)
605 /* Copy the argument into memory so that we can change it. We
606 don't want to change the argument because, at least as of this
607 writing, we will use it if we send the arguments to the server. */
608 orig_copy = copy = xstrdup (argstring);
612 struct option_revlist *r;
614 comma = strchr (copy, ',');
618 r = (struct option_revlist *) xmalloc (sizeof *r);
622 r->last = strchr (copy, ':');
626 r->inclusive = (*r->last != ':');
634 if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
637 r->first[strlen (r->first) - 1] = '\0';
641 if (*r->first == '\0')
643 if (*r->last == '\0')
646 if (r->first != NULL)
647 r->first = xstrdup (r->first);
649 r->last = xstrdup (r->last);
662 * Parse a date specification.
665 log_parse_date (log_data, argstring)
666 struct log_data *log_data;
667 const char *argstring;
669 char *orig_copy, *copy;
671 /* Copy the argument into memory so that we can change it. We
672 don't want to change the argument because, at least as of this
673 writing, we will use it if we send the arguments to the server. */
674 orig_copy = copy = xstrdup (argstring);
677 struct datelist *nd, **pd;
678 char *cpend, *cp, *ds, *de;
680 nd = (struct datelist *) xmalloc (sizeof *nd);
682 cpend = strchr (copy, ';');
686 pd = &log_data->datelist;
689 if ((cp = strchr (copy, '>')) != NULL)
700 else if ((cp = strchr (copy, '<')) != NULL)
715 pd = &log_data->singledatelist;
720 else if (*ds != '\0')
721 nd->start = Make_Date (ds);
724 /* 1970 was the beginning of time, as far as get_date and
725 Make_Date are concerned. FIXME: That is true only if time_t
726 is a POSIX-style time and there is nothing in ANSI that
727 mandates that. It would be cleaner to set a flag saying
728 whether or not there is a start date. */
729 nd->start = Make_Date ("1/1/1970 UTC");
733 nd->end = Make_Date (de);
736 /* We want to set the end date to some time sufficiently far
737 in the future to pick up all revisions that have been
738 created since the specified date and the time `cvs log'
739 completes. FIXME: The date in question only makes sense
740 if time_t is a POSIX-style time and it is 32 bits
741 and signed. We should instead be setting a flag saying
742 whether or not there is an end date. Note that using
743 something like "next week" would break the testsuite (and,
744 perhaps less importantly, loses if the clock is set grossly
746 nd->end = Make_Date ("2038-01-01");
759 * Parse a comma separated list of items, and add each one to *PLIST.
762 log_parse_list (plist, argstring)
764 const char *argstring;
773 cp = strchr (argstring, ',');
775 p->key = xstrdup (argstring);
780 len = cp - argstring;
781 p->key = xmalloc (len + 1);
782 strncpy (p->key, argstring, len);
788 if (addnode (*plist, p) != 0)
798 static int printlock_proc PROTO ((Node *, void *));
801 printlock_proc (lock, foo)
805 cvs_output ("\n\t", 2);
806 cvs_output (lock->data, 0);
807 cvs_output (": ", 2);
808 cvs_output (lock->key, 0);
815 * Do an rlog on a file
818 log_fileproc (callerdat, finfo)
820 struct file_info *finfo;
822 struct log_data *log_data = (struct log_data *) callerdat;
828 struct revlist *revlist = NULL;
829 struct log_data_and_rcs log_data_and_rcs;
831 rcsfile = finfo->rcs;
832 p = findnode (finfo->entries, finfo->file);
835 Entnode *e = p->data;
836 baserev = e->version;
837 if (baserev[0] == '-') ++baserev;
844 /* no rcs file. What *do* we know about this file? */
847 if (baserev[0] == '0' && baserev[1] == '\0')
850 error (0, 0, "%s has been added, but not committed",
857 error (0, 0, "nothing known about %s", finfo->file);
862 if (log_data->sup_header || !log_data->nameonly)
865 /* We will need all the information in the RCS file. */
866 RCS_fully_parse (rcsfile);
868 /* Turn any symbolic revisions in the revision list into numeric
870 revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist,
871 log_data->default_branch);
872 if (log_data->sup_header
873 || (!log_data->header && !log_data->long_header))
875 log_data_and_rcs.log_data = log_data;
876 log_data_and_rcs.revlist = revlist;
877 log_data_and_rcs.rcs = rcsfile;
879 /* If any single dates were specified, we need to identify the
880 revisions they select. Each one selects the single
881 revision, which is otherwise selected, of that date or
882 earlier. The log_fix_singledate routine will fill in the
883 start date for each specific revision. */
884 if (log_data->singledatelist != NULL)
885 walklist (rcsfile->versions, log_fix_singledate,
886 (void *)&log_data_and_rcs);
888 selrev = walklist (rcsfile->versions, log_count_print,
889 (void *)&log_data_and_rcs);
890 if (log_data->sup_header && selrev == 0)
892 log_free_revlist (revlist);
899 if (log_data->nameonly)
901 cvs_output (rcsfile->path, 0);
902 cvs_output ("\n", 1);
903 log_free_revlist (revlist);
907 /* The output here is intended to be exactly compatible with the
908 output of rlog. I'm not sure whether this code should be here
909 or in rcs.c; I put it here because it is specific to the log
910 function, even though it uses information gathered by the
911 functions in rcs.c. */
913 cvs_output ("\n", 1);
915 cvs_output ("RCS file: ", 0);
916 cvs_output (rcsfile->path, 0);
920 cvs_output ("\nWorking file: ", 0);
921 if (finfo->update_dir[0] != '\0')
923 cvs_output (finfo->update_dir, 0);
926 cvs_output (finfo->file, 0);
929 cvs_output ("\nhead:", 0);
930 if (rcsfile->head != NULL)
933 cvs_output (rcsfile->head, 0);
936 cvs_output ("\nbranch:", 0);
937 if (rcsfile->branch != NULL)
940 cvs_output (rcsfile->branch, 0);
943 cvs_output ("\nlocks:", 0);
944 if (rcsfile->strict_locks)
945 cvs_output (" strict", 0);
946 walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
948 cvs_output ("\naccess list:", 0);
949 if (rcsfile->access != NULL)
953 cp = rcsfile->access;
958 cvs_output ("\n\t", 2);
960 while (!isspace ((unsigned char) *cp2) && *cp2 != '\0')
962 cvs_output (cp, cp2 - cp);
964 while (isspace ((unsigned char) *cp) && *cp != '\0')
969 if (!log_data->notags)
973 cvs_output ("\nsymbolic names:", 0);
974 syms = RCS_symbols (rcsfile);
975 walklist (syms, log_symbol, NULL);
978 cvs_output ("\nkeyword substitution: ", 0);
979 if (rcsfile->expand == NULL)
980 cvs_output ("kv", 2);
982 cvs_output (rcsfile->expand, 0);
984 cvs_output ("\ntotal revisions: ", 0);
985 sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
990 cvs_output (";\tselected revisions: ", 0);
991 sprintf (buf, "%d", selrev);
995 cvs_output ("\n", 1);
997 if (!log_data->header || log_data->long_header)
999 cvs_output ("description:\n", 0);
1000 if (rcsfile->desc != NULL)
1001 cvs_output (rcsfile->desc, 0);
1004 if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
1006 p = findnode (rcsfile->versions, rcsfile->head);
1008 error (1, 0, "can not find head revision in `%s'",
1012 RCSVers *vers = p->data;
1014 log_version (log_data, revlist, rcsfile, vers, 1);
1015 if (vers->next == NULL)
1019 p = findnode (rcsfile->versions, vers->next);
1021 error (1, 0, "can not find next revision `%s' in `%s'",
1022 vers->next, finfo->fullname);
1026 log_tree (log_data, revlist, rcsfile, rcsfile->head);
1030 =============================================================================\n",
1033 /* Free up the new revlist and restore the old one. */
1034 log_free_revlist (revlist);
1036 /* If singledatelist is not NULL, free up the start dates we added
1038 if (log_data->singledatelist != NULL)
1042 for (d = log_data->singledatelist; d != NULL; d = d->next)
1044 if (d->start != NULL)
1056 * Fix up a revision list in order to compare it against versions.
1057 * Expand any symbolic revisions.
1059 static struct revlist *
1060 log_expand_revlist (rcs, baserev, revlist, default_branch)
1063 struct option_revlist *revlist;
1066 struct option_revlist *r;
1067 struct revlist *ret, **pr;
1071 for (r = revlist; r != NULL; r = r->next)
1075 nr = (struct revlist *) xmalloc (sizeof *nr);
1076 nr->inclusive = r->inclusive;
1078 if (r->first == NULL && r->last == NULL)
1080 /* If both first and last are NULL, it means that we want
1081 just the head of the default branch, which is RCS_head. */
1082 nr->first = RCS_head (rcs);
1086 error (0, 0, "No head revision in archive `%s'.",
1093 nr->last = xstrdup (nr->first);
1094 nr->fields = numdots (nr->first) + 1;
1097 else if (r->branchhead)
1101 assert (r->first != NULL);
1103 /* Print just the head of the branch. */
1104 if (isdigit ((unsigned char) r->first[0]))
1105 nr->first = RCS_getbranch (rcs, r->first, 1);
1108 branch = RCS_whatbranch (rcs, r->first);
1113 nr->first = RCS_getbranch (rcs, branch, 1);
1120 error (0, 0, "warning: no branch `%s' in `%s'",
1121 r->first, rcs->path);
1127 nr->last = xstrdup (nr->first);
1128 nr->fields = numdots (nr->first) + 1;
1133 if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
1134 nr->first = xstrdup (r->first);
1137 if (baserev && strcmp (r->first, TAG_BASE) == 0)
1138 nr->first = xstrdup (baserev);
1139 else if (RCS_nodeisbranch (rcs, r->first))
1140 nr->first = RCS_whatbranch (rcs, r->first);
1142 nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL);
1143 if (nr->first == NULL && !really_quiet)
1145 error (0, 0, "warning: no revision `%s' in `%s'",
1146 r->first, rcs->path);
1150 if (r->last == r->first || (r->last != NULL && r->first != NULL &&
1151 strcmp (r->last, r->first) == 0))
1152 nr->last = xstrdup (nr->first);
1153 else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
1154 nr->last = xstrdup (r->last);
1157 if (baserev && strcmp (r->last, TAG_BASE) == 0)
1158 nr->last = xstrdup (baserev);
1159 else if (RCS_nodeisbranch (rcs, r->last))
1160 nr->last = RCS_whatbranch (rcs, r->last);
1162 nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL);
1163 if (nr->last == NULL && !really_quiet)
1165 error (0, 0, "warning: no revision `%s' in `%s'",
1166 r->last, rcs->path);
1170 /* Process the revision numbers the same way that rlog
1171 does. This code is a bit cryptic for my tastes, but
1172 keeping the same implementation as rlog ensures a
1173 certain degree of compatibility. */
1174 if (r->first == NULL && nr->last != NULL)
1176 nr->fields = numdots (nr->last) + 1;
1178 nr->first = xstrdup (".0");
1183 nr->first = xstrdup (nr->last);
1184 cp = strrchr (nr->first, '.');
1186 strcpy (cp + 1, "0");
1189 else if (r->last == NULL && nr->first != NULL)
1191 nr->fields = numdots (nr->first) + 1;
1192 nr->last = xstrdup (nr->first);
1199 cp = strrchr (nr->last, '.');
1204 else if (nr->first == NULL || nr->last == NULL)
1206 else if (strcmp (nr->first, nr->last) == 0)
1207 nr->fields = numdots (nr->last) + 1;
1211 int dots1 = numdots (nr->first);
1212 int dots2 = numdots (nr->last);
1213 if (dots1 > dots2 || (dots1 == dots2 &&
1214 version_compare (nr->first, nr->last, dots1 + 1) > 0))
1216 char *tmp = nr->first;
1217 nr->first = nr->last;
1219 nr->fields = dots2 + 1;
1221 dots1 = nr->fields - 1;
1224 nr->fields = dots1 + 1;
1225 dots1 += (nr->fields & 1);
1226 ord = version_compare (nr->first, nr->last, dots1);
1227 if (ord > 0 || (nr->fields > 2 && ord < 0))
1230 "invalid branch or revision pair %s:%s in `%s'",
1231 r->first, r->last, rcs->path);
1240 if (nr->fields <= dots2 && (nr->fields & 1))
1242 char *p = xmalloc (strlen (nr->first) + 3);
1243 strcpy (p, nr->first);
1249 while (nr->fields <= dots2)
1256 nr = (struct revlist *) xmalloc (sizeof *nr);
1258 nr->first = xstrdup ((*pr)->last);
1259 nr->last = xstrdup ((*pr)->last);
1260 nr->fields = (*pr)->fields;
1262 for (i = 0; i < nr->fields; i++)
1263 p = strchr (p, '.') + 1;
1265 p = strchr (nr->first + (p - (*pr)->last), '.');
1285 /* If the default branch was requested, add a revlist entry for
1286 it. This is how rlog handles this option. */
1288 && (rcs->head != NULL || rcs->branch != NULL))
1292 nr = (struct revlist *) xmalloc (sizeof *nr);
1293 if (rcs->branch != NULL)
1294 nr->first = xstrdup (rcs->branch);
1299 nr->first = xstrdup (rcs->head);
1301 cp = strrchr (nr->first, '.');
1305 nr->last = xstrdup (nr->first);
1306 nr->fields = numdots (nr->first) + 1;
1317 * Free a revlist created by log_expand_revlist.
1320 log_free_revlist (revlist)
1321 struct revlist *revlist;
1328 struct revlist *next;
1330 if (r->first != NULL)
1332 if (r->last != NULL)
1341 * Return nonzero if a revision should be printed, based on the
1345 log_version_requested (log_data, revlist, rcs, vnode)
1346 struct log_data *log_data;
1347 struct revlist *revlist;
1351 /* Handle the list of states from the -s option. */
1352 if (log_data->statelist != NULL
1353 && findnode (log_data->statelist, vnode->state) == NULL)
1358 /* Handle the list of authors from the -w option. */
1359 if (log_data->authorlist != NULL)
1361 if (vnode->author != NULL
1362 && findnode (log_data->authorlist, vnode->author) == NULL)
1368 /* rlog considers all the -d options together when it decides
1369 whether to print a revision, so we must be compatible. */
1370 if (log_data->datelist != NULL || log_data->singledatelist != NULL)
1374 for (d = log_data->datelist; d != NULL; d = d->next)
1378 cmp = RCS_datecmp (vnode->date, d->start);
1379 if (cmp > 0 || (cmp == 0 && d->inclusive))
1381 cmp = RCS_datecmp (vnode->date, d->end);
1382 if (cmp < 0 || (cmp == 0 && d->inclusive))
1389 /* Look through the list of specific dates. We want to
1390 select the revision with the exact date found in the
1391 start field. The commit code ensures that it is
1392 impossible to check in multiple revisions of a single
1393 file in a single second, so checking the date this way
1394 should never select more than one revision. */
1395 for (d = log_data->singledatelist; d != NULL; d = d->next)
1397 if (d->start != NULL
1398 && RCS_datecmp (vnode->date, d->start) == 0)
1409 /* If the -r or -b options were used, REVLIST will be non NULL,
1410 and we print the union of the specified revisions. */
1411 if (revlist != NULL)
1417 /* This code is taken from rlog. */
1419 vfields = numdots (v) + 1;
1420 for (r = revlist; r != NULL; r = r->next)
1422 if (vfields == r->fields + (r->fields & 1) &&
1423 (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
1424 version_compare (v, r->first, r->fields) > 0)
1425 && version_compare (v, r->last, r->fields) <= 0)
1431 /* If we get here, then the -b and/or the -r option was used,
1432 but did not match this revision, so we reject it. */
1437 /* By default, we print all revisions. */
1444 * Output a single symbol. This is called via walklist.
1448 log_symbol (p, closure)
1452 cvs_output ("\n\t", 2);
1453 cvs_output (p->key, 0);
1454 cvs_output (": ", 2);
1455 cvs_output (p->data, 0);
1462 * Count the number of entries on a list. This is called via walklist.
1466 log_count (p, closure)
1476 * Sort out a single date specification by narrowing down the date
1477 * until we find the specific selected revision.
1480 log_fix_singledate (p, closure)
1484 struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
1487 struct datelist *holdsingle, *holddate;
1490 pv = findnode (data->rcs->versions, p->key);
1492 error (1, 0, "missing version `%s' in RCS file `%s'",
1493 p->key, data->rcs->path);
1496 /* We are only interested if this revision passes any other tests.
1497 Temporarily clear log_data->singledatelist to avoid confusing
1498 log_version_requested. We also clear log_data->datelist,
1499 because rlog considers all the -d options together. We don't
1500 want to reject a revision because it does not match a date pair
1501 if we are going to select it on the basis of the singledate. */
1502 holdsingle = data->log_data->singledatelist;
1503 data->log_data->singledatelist = NULL;
1504 holddate = data->log_data->datelist;
1505 data->log_data->datelist = NULL;
1506 requested = log_version_requested (data->log_data, data->revlist,
1508 data->log_data->singledatelist = holdsingle;
1509 data->log_data->datelist = holddate;
1515 /* For each single date, if this revision is before the
1516 specified date, but is closer than the previously selected
1517 revision, select it instead. */
1518 for (d = data->log_data->singledatelist; d != NULL; d = d->next)
1520 if (RCS_datecmp (vnode->date, d->end) <= 0
1521 && (d->start == NULL
1522 || RCS_datecmp (vnode->date, d->start) > 0))
1524 if (d->start != NULL)
1526 d->start = xstrdup (vnode->date);
1537 * Count the number of revisions we are going to print.
1540 log_count_print (p, closure)
1544 struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
1547 pv = findnode (data->rcs->versions, p->key);
1549 error (1, 0, "missing version `%s' in RCS file `%s'",
1550 p->key, data->rcs->path);
1551 if (log_version_requested (data->log_data, data->revlist, data->rcs,
1559 * Print the list of changes, not including the trunk, in reverse
1560 * order for each branch.
1563 log_tree (log_data, revlist, rcs, ver)
1564 struct log_data *log_data;
1565 struct revlist *revlist;
1572 p = findnode (rcs->versions, ver);
1574 error (1, 0, "missing version `%s' in RCS file `%s'",
1577 if (vnode->next != NULL)
1578 log_tree (log_data, revlist, rcs, vnode->next);
1579 if (vnode->branches != NULL)
1581 Node *head, *branch;
1583 /* We need to do the branches in reverse order. This breaks
1584 the List abstraction, but so does most of the branch
1585 manipulation in rcs.c. */
1586 head = vnode->branches->list;
1587 for (branch = head->prev; branch != head; branch = branch->prev)
1589 log_abranch (log_data, revlist, rcs, branch->key);
1590 log_tree (log_data, revlist, rcs, branch->key);
1596 * Log the changes for a branch, in reverse order.
1599 log_abranch (log_data, revlist, rcs, ver)
1600 struct log_data *log_data;
1601 struct revlist *revlist;
1608 p = findnode (rcs->versions, ver);
1610 error (1, 0, "missing version `%s' in RCS file `%s'",
1613 if (vnode->next != NULL)
1614 log_abranch (log_data, revlist, rcs, vnode->next);
1615 log_version (log_data, revlist, rcs, vnode, 0);
1619 * Print the log output for a single version.
1622 log_version (log_data, revlist, rcs, ver, trunk)
1623 struct log_data *log_data;
1624 struct revlist *revlist;
1630 int year, mon, mday, hour, min, sec;
1634 if (! log_version_requested (log_data, revlist, rcs, ver))
1637 cvs_output ("----------------------------\nrevision ", 0);
1638 cvs_output (ver->version, 0);
1640 p = findnode (RCS_getlocks (rcs), ver->version);
1643 cvs_output ("\tlocked by: ", 0);
1644 cvs_output (p->data, 0);
1645 cvs_output (";", 1);
1648 cvs_output ("\ndate: ", 0);
1649 (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
1653 sprintf (buf, "%04d%c%02d%c%02d %02d:%02d:%02d",
1654 year, datesep, mon, datesep, mday, hour, min, sec);
1655 cvs_output (buf, 0);
1657 cvs_output ("; author: ", 0);
1658 cvs_output (ver->author, 0);
1660 cvs_output ("; state: ", 0);
1661 cvs_output (ver->state, 0);
1662 cvs_output (";", 1);
1666 padd = findnode (ver->other, ";add");
1667 pdel = findnode (ver->other, ";delete");
1669 else if (ver->next == NULL)
1679 nextp = findnode (rcs->versions, ver->next);
1681 error (1, 0, "missing version `%s' in `%s'", ver->next,
1683 nextver = nextp->data;
1684 pdel = findnode (nextver->other, ";add");
1685 padd = findnode (nextver->other, ";delete");
1691 cvs_output (" lines: +", 0);
1692 cvs_output (padd->data, 0);
1693 cvs_output (" -", 2);
1694 cvs_output (pdel->data, 0);
1697 if (ver->branches != NULL)
1699 cvs_output ("\nbranches:", 0);
1700 walklist (ver->branches, log_branch, (void *) NULL);
1703 cvs_output ("\n", 1);
1705 p = findnode (ver->other, "log");
1706 /* The p->date == NULL case is the normal one for an empty log
1707 message (rcs-14 in sanity.sh). I don't think the case where
1708 p->data is "" can happen (getrcskey in rcs.c checks for an
1709 empty string and set the value to NULL in that case). My guess
1710 would be the p == NULL case would mean an RCS file which was
1711 missing the "log" keyword (which is illegal according to
1713 if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
1714 cvs_output ("*** empty log message ***\n", 0);
1717 /* FIXME: Technically, the log message could contain a null
1719 cvs_output (p->data, 0);
1720 if (((char *)p->data)[strlen (p->data) - 1] != '\n')
1721 cvs_output ("\n", 1);
1726 * Output a branch version. This is called via walklist.
1730 log_branch (p, closure)
1734 cvs_output (" ", 2);
1735 if ((numdots (p->key) & 1) == 0)
1736 cvs_output (p->key, 0);
1741 f = xstrdup (p->key);
1742 cp = strrchr (f, '.');
1747 cvs_output (";", 1);
1752 * Print a warm fuzzy message
1756 log_dirproc (callerdat, dir, repository, update_dir, entries)
1759 const char *repository;
1760 const char *update_dir;
1764 return (R_SKIP_ALL);
1767 error (0, 0, "Logging %s", update_dir);
1772 * Compare versions. This is taken from RCS compartial.
1775 version_compare (v1, v2, len)
1791 for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
1796 for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
1800 return d1 < d2 ? -1 : 1;
1802 r = memcmp (v1, v2, d1);