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).
17 /* This structure holds information parsed from the -r option. */
21 /* The next -r option. */
22 struct option_revlist *next;
23 /* The first revision to print. This is NULL if the range is
24 :rev, or if no revision is given. */
26 /* The last revision to print. This is NULL if the range is rev:,
27 or if no revision is given. If there is no colon, first and
30 /* Nonzero if there was a trailing `.', which means to print only
31 the head revision of a branch. */
33 /* Nonzero if first and last are inclusive. */
37 /* This structure holds information derived from option_revlist given
38 a particular RCS file. */
44 /* The first numeric revision to print. */
46 /* The last numeric revision to print. */
48 /* The number of fields in these revisions (one more than
51 /* Whether first & last are to be included or excluded. */
55 /* This structure holds information parsed from the -d option. */
60 struct datelist *next;
61 /* The starting date. */
63 /* The ending date. */
65 /* Nonzero if the range is inclusive rather than exclusive. */
69 /* This structure is used to pass information through start_recursion. */
72 /* Nonzero if the -R option was given, meaning that only the name
73 of the RCS file should be printed. */
75 /* Nonzero if the -h option was given, meaning that only header
76 information should be printed. */
78 /* Nonzero if the -t option was given, meaning that only the
79 header and the descriptive text should be printed. */
81 /* Nonzero if the -N option was seen, meaning that tag information
82 should not be printed. */
84 /* Nonzero if the -b option was seen, meaning that only revisions
85 on the default branch should be printed. */
87 /* Nonzero if the -S option was seen, meaning that the header/name
88 should be suppressed if no revisions are selected. */
90 /* If not NULL, the value given for the -r option, which lists
91 sets of revisions to be printed. */
92 struct option_revlist *revlist;
93 /* If not NULL, the date pairs given for the -d option, which
94 select date ranges to print. */
95 struct datelist *datelist;
96 /* If not NULL, the single dates given for the -d option, which
97 select specific revisions to print based on a date. */
98 struct datelist *singledatelist;
99 /* If not NULL, the list of states given for the -s option, which
100 only prints revisions of given states. */
102 /* If not NULL, the list of login names given for the -w option,
103 which only prints revisions checked in by given users. */
107 /* This structure is used to pass information through walklist. */
108 struct log_data_and_rcs
110 struct log_data *log_data;
111 struct revlist *revlist;
115 static int rlog_proc (int argc, char **argv, char *xwhere,
116 char *mwhere, char *mfile, int shorten,
117 int local_specified, char *mname, char *msg);
118 static Dtype log_dirproc (void *callerdat, const char *dir,
119 const char *repository, const char *update_dir,
121 static int log_fileproc (void *callerdat, struct file_info *finfo);
122 static struct option_revlist *log_parse_revlist (const char *);
123 static void log_parse_date (struct log_data *, const char *);
124 static void log_parse_list (List **, const char *);
125 static struct revlist *log_expand_revlist (RCSNode *, struct option_revlist *,
127 static void log_free_revlist (struct revlist *);
128 static int log_version_requested (struct log_data *, struct revlist *,
129 RCSNode *, RCSVers *);
130 static int log_symbol (Node *, void *);
131 static int log_count (Node *, void *);
132 static int log_fix_singledate (Node *, void *);
133 static int log_count_print (Node *, void *);
134 static void log_tree (struct log_data *, struct revlist *,
135 RCSNode *, const char *);
136 static void log_abranch (struct log_data *, struct revlist *,
137 RCSNode *, const char *);
138 static void log_version (struct log_data *, struct revlist *,
139 RCSNode *, RCSVers *, int);
140 static int log_branch (Node *, void *);
141 static int version_compare (const char *, const char *, int);
143 static struct log_data log_data;
146 static const char *const log_usage[] =
148 "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
149 " [-w[logins]] [files...]\n",
150 "\t-l\tLocal directory only, no recursion.\n",
151 "\t-R\tOnly print name of RCS file.\n",
152 "\t-h\tOnly print header.\n",
153 "\t-t\tOnly print header and descriptive text.\n",
154 "\t-N\tDo not list tags.\n",
155 "\t-S\tDo not print name/header if no revisions selected.\n",
156 "\t-b\tOnly list revisions on the default branch.\n",
157 "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
158 "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n",
159 "\t rev1::rev2 Between rev1 and rev2, excluding rev1.\n",
160 "\t rev: rev and following revisions on the same branch.\n",
161 "\t rev:: After rev on the same branch.\n",
162 "\t :rev rev and previous revisions on the same branch.\n",
163 "\t ::rev rev and previous revisions on the same branch.\n",
164 "\t rev Just rev.\n",
165 "\t branch All revisions on the branch.\n",
166 "\t branch. The last revision on the branch.\n",
167 "\t-d dates\tA semicolon-separated list of dates\n",
168 "\t \t(D1<D2 for range, D for latest before).\n",
169 "\t-s states\tOnly list revisions with specified states.\n",
170 "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
171 "(Specify the --help global option for a list of other help options)\n",
175 #ifdef CLIENT_SUPPORT
179 /* Helper function for send_arg_list. */
181 send_one (Node *node, void *closure)
183 char *option = closure;
185 send_to_server ("Argument ", 0);
186 send_to_server (option, 0);
187 if (strcmp (node->key, "@@MYSELF") == 0)
188 /* It is a bare -w option. Note that we must send it as
189 -w rather than messing with getcaller() or something (which on
190 the client will return garbage). */
193 send_to_server (node->key, 0);
194 send_to_server ("\012", 0);
200 /* For each element in ARG, send an argument consisting of OPTION
201 concatenated with that element. */
203 send_arg_list (char *option, List *arg)
207 walklist (arg, send_one, option);
215 cvslog (int argc, char **argv)
220 struct option_revlist **prl;
222 is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
227 memset (&log_data, 0, sizeof log_data);
228 prl = &log_data.revlist;
231 while ((c = getopt (argc, argv, "+bd:hlNSRr::s:tw::")) != -1)
236 log_data.default_branch = 1;
239 log_parse_date (&log_data, optarg);
251 log_data.sup_header = 1;
254 log_data.nameonly = 1;
257 *prl = log_parse_revlist (optarg);
261 log_parse_list (&log_data.statelist, optarg);
264 log_data.long_header = 1;
268 log_parse_list (&log_data.authorlist, optarg);
270 log_parse_list (&log_data.authorlist, "@@MYSELF");
283 #ifdef CLIENT_SUPPORT
284 if (current_parsed_root->isremote)
287 struct option_revlist *rp;
288 char datetmp[MAXDATELEN];
290 /* We're the local client. Fire up the remote server. */
293 if (is_rlog && !supported_request ("rlog"))
294 error (1, 0, "server does not support rlog");
298 if (log_data.default_branch)
301 while (log_data.datelist != NULL)
303 p = log_data.datelist;
304 log_data.datelist = p->next;
305 send_to_server ("Argument -d\012", 0);
306 send_to_server ("Argument ", 0);
307 date_to_internet (datetmp, p->start);
308 send_to_server (datetmp, 0);
310 send_to_server ("<=", 0);
312 send_to_server ("<", 0);
313 date_to_internet (datetmp, p->end);
314 send_to_server (datetmp, 0);
315 send_to_server ("\012", 0);
322 while (log_data.singledatelist != NULL)
324 p = log_data.singledatelist;
325 log_data.singledatelist = p->next;
326 send_to_server ("Argument -d\012", 0);
327 send_to_server ("Argument ", 0);
328 date_to_internet (datetmp, p->end);
329 send_to_server (datetmp, 0);
330 send_to_server ("\012", 0);
342 if (log_data.sup_header)
344 if (log_data.nameonly)
346 if (log_data.long_header)
349 while (log_data.revlist != NULL)
351 rp = log_data.revlist;
352 log_data.revlist = rp->next;
353 send_to_server ("Argument -r", 0);
356 if (rp->first != NULL)
357 send_to_server (rp->first, 0);
358 send_to_server (".", 1);
362 if (rp->first != NULL)
363 send_to_server (rp->first, 0);
364 send_to_server (":", 1);
366 send_to_server (":", 1);
367 if (rp->last != NULL)
368 send_to_server (rp->last, 0);
370 send_to_server ("\012", 0);
377 send_arg_list ("-s", log_data.statelist);
378 dellist (&log_data.statelist);
379 send_arg_list ("-w", log_data.authorlist);
380 dellist (&log_data.authorlist);
386 for (i = 0; i < argc; i++)
388 send_to_server ("rlog\012", 0);
392 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
393 send_file_names (argc, argv, SEND_EXPAND_WILD);
394 send_to_server ("log\012", 0);
396 err = get_responses_and_close ();
401 /* OK, now that we know we are local/server, we can resolve @@MYSELF
402 into our user name. */
403 if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
404 log_parse_list (&log_data.authorlist, getcaller ());
411 for (i = 0; i < argc; i++)
413 err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
414 NULL, 0, local, 0, 0, NULL);
420 err = rlog_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, local, NULL,
424 while (log_data.revlist)
426 struct option_revlist *rl = log_data.revlist->next;
427 if (log_data.revlist->first)
428 free (log_data.revlist->first);
429 if (log_data.revlist->last)
430 free (log_data.revlist->last);
431 free (log_data.revlist);
432 log_data.revlist = rl;
434 while (log_data.datelist)
436 struct datelist *nd = log_data.datelist->next;
437 if (log_data.datelist->start)
438 free (log_data.datelist->start);
439 if (log_data.datelist->end)
440 free (log_data.datelist->end);
441 free (log_data.datelist);
442 log_data.datelist = nd;
444 while (log_data.singledatelist)
446 struct datelist *nd = log_data.singledatelist->next;
447 if (log_data.singledatelist->start)
448 free (log_data.singledatelist->start);
449 if (log_data.singledatelist->end)
450 free (log_data.singledatelist->end);
451 free (log_data.singledatelist);
452 log_data.singledatelist = nd;
454 dellist (&log_data.statelist);
455 dellist (&log_data.authorlist);
463 rlog_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
464 int shorten, int local, char *mname, char *msg)
466 /* Begin section which is identical to patch_proc--should this
467 be abstracted out somehow? */
471 char *repository = NULL;
476 repository = xmalloc (strlen (current_parsed_root->directory)
478 + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
479 (void)sprintf (repository, "%s/%s",
480 current_parsed_root->directory, argv[0]);
481 where = xmalloc (strlen (argv[0])
482 + (mfile == NULL ? 0 : strlen (mfile) + 1)
484 (void)strcpy (where, argv[0]);
486 /* If mfile isn't null, we need to set up to do only part of theu
494 /* If the portion of the module is a path, put the dir part on
497 if ((cp = strrchr (mfile, '/')) != NULL)
500 (void)strcat (repository, "/");
501 (void)strcat (repository, mfile);
502 (void)strcat (where, "/");
503 (void)strcat (where, mfile);
507 /* take care of the rest */
508 path = xmalloc (strlen (repository) + strlen (mfile) + 5);
509 (void)sprintf (path, "%s/%s", repository, mfile);
512 /* directory means repository gets the dir tacked on */
513 (void)strcpy (repository, path);
514 (void)strcat (where, "/");
515 (void)strcat (where, mfile);
527 /* cd to the starting repository */
528 if (CVS_CHDIR (repository) < 0)
530 error (0, errno, "cannot chdir to %s", repository);
535 /* End section which is identical to patch_proc. */
537 which = W_REPOS | W_ATTIC;
543 which = W_LOCAL | W_REPOS | W_ATTIC;
546 err = start_recursion (log_fileproc, NULL, log_dirproc,
548 argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
549 where, 1, repository);
551 if (!(which & W_LOCAL)) free (repository);
552 if (where) free (where);
560 * Parse a revision list specification.
562 static struct option_revlist *
563 log_parse_revlist (const char *argstring)
565 char *orig_copy, *copy;
566 struct option_revlist *ret, **pr;
568 /* Unfortunately, rlog accepts -r without an argument to mean that
569 latest revision on the default branch, so we must support that
570 for compatibility. */
571 if (argstring == NULL)
577 /* Copy the argument into memory so that we can change it. We
578 don't want to change the argument because, at least as of this
579 writing, we will use it if we send the arguments to the server. */
580 orig_copy = copy = xstrdup (argstring);
584 struct option_revlist *r;
586 comma = strchr (copy, ',');
590 r = xmalloc (sizeof *r);
594 r->last = strchr (copy, ':');
598 r->inclusive = (*r->last != ':');
606 if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
609 r->first[strlen (r->first) - 1] = '\0';
613 if (*r->first == '\0')
615 if (*r->last == '\0')
618 if (r->first != NULL)
619 r->first = xstrdup (r->first);
621 r->last = xstrdup (r->last);
636 * Parse a date specification.
639 log_parse_date (struct log_data *log_data, const char *argstring)
641 char *orig_copy, *copy;
643 /* Copy the argument into memory so that we can change it. We
644 don't want to change the argument because, at least as of this
645 writing, we will use it if we send the arguments to the server. */
646 orig_copy = copy = xstrdup (argstring);
649 struct datelist *nd, **pd;
650 char *cpend, *cp, *ds, *de;
652 nd = xmalloc (sizeof *nd);
654 cpend = strchr (copy, ';');
658 pd = &log_data->datelist;
661 if ((cp = strchr (copy, '>')) != NULL)
672 else if ((cp = strchr (copy, '<')) != NULL)
687 pd = &log_data->singledatelist;
692 else if (*ds != '\0')
693 nd->start = Make_Date (ds);
696 /* 1970 was the beginning of time, as far as get_date and
697 Make_Date are concerned. FIXME: That is true only if time_t
698 is a POSIX-style time and there is nothing in ANSI that
699 mandates that. It would be cleaner to set a flag saying
700 whether or not there is a start date. */
701 nd->start = Make_Date ("1/1/1970 UTC");
705 nd->end = Make_Date (de);
708 /* We want to set the end date to some time sufficiently far
709 in the future to pick up all revisions that have been
710 created since the specified date and the time `cvs log'
711 completes. FIXME: The date in question only makes sense
712 if time_t is a POSIX-style time and it is 32 bits
713 and signed. We should instead be setting a flag saying
714 whether or not there is an end date. Note that using
715 something like "next week" would break the testsuite (and,
716 perhaps less importantly, loses if the clock is set grossly
718 nd->end = Make_Date ("2038-01-01");
733 * Parse a comma separated list of items, and add each one to *PLIST.
736 log_parse_list (List **plist, const char *argstring)
745 cp = strchr (argstring, ',');
747 p->key = xstrdup (argstring);
752 len = cp - argstring;
753 p->key = xmalloc (len + 1);
754 strncpy (p->key, argstring, len);
760 if (addnode (*plist, p) != 0)
773 printlock_proc (Node *lock, void *foo)
775 cvs_output ("\n\t", 2);
776 cvs_output (lock->data, 0);
777 cvs_output (": ", 2);
778 cvs_output (lock->key, 0);
785 * Do an rlog on a file
788 log_fileproc (void *callerdat, struct file_info *finfo)
790 struct log_data *log_data = callerdat;
795 struct revlist *revlist = NULL;
796 struct log_data_and_rcs log_data_and_rcs;
798 if ((rcsfile = finfo->rcs) == NULL)
800 /* no rcs file. What *do* we know about this file? */
801 p = findnode (finfo->entries, finfo->file);
804 Entnode *e = p->data;
806 if (e->version[0] == '0' && e->version[1] == '\0')
809 error (0, 0, "%s has been added, but not committed",
816 error (0, 0, "nothing known about %s", finfo->file);
821 if (log_data->sup_header || !log_data->nameonly)
824 /* We will need all the information in the RCS file. */
825 RCS_fully_parse (rcsfile);
827 /* Turn any symbolic revisions in the revision list into numeric
829 revlist = log_expand_revlist (rcsfile, log_data->revlist,
830 log_data->default_branch);
831 if (log_data->sup_header
832 || (!log_data->header && !log_data->long_header))
834 log_data_and_rcs.log_data = log_data;
835 log_data_and_rcs.revlist = revlist;
836 log_data_and_rcs.rcs = rcsfile;
838 /* If any single dates were specified, we need to identify the
839 revisions they select. Each one selects the single
840 revision, which is otherwise selected, of that date or
841 earlier. The log_fix_singledate routine will fill in the
842 start date for each specific revision. */
843 if (log_data->singledatelist != NULL)
844 walklist (rcsfile->versions, log_fix_singledate,
847 selrev = walklist (rcsfile->versions, log_count_print,
849 if (log_data->sup_header && selrev == 0)
851 log_free_revlist (revlist);
858 if (log_data->nameonly)
860 cvs_output (rcsfile->path, 0);
861 cvs_output ("\n", 1);
862 log_free_revlist (revlist);
866 /* The output here is intended to be exactly compatible with the
867 output of rlog. I'm not sure whether this code should be here
868 or in rcs.c; I put it here because it is specific to the log
869 function, even though it uses information gathered by the
870 functions in rcs.c. */
872 cvs_output ("\n", 1);
874 cvs_output ("RCS file: ", 0);
875 cvs_output (rcsfile->path, 0);
879 cvs_output ("\nWorking file: ", 0);
880 if (finfo->update_dir[0] != '\0')
882 cvs_output (finfo->update_dir, 0);
885 cvs_output (finfo->file, 0);
888 cvs_output ("\nhead:", 0);
889 if (rcsfile->head != NULL)
892 cvs_output (rcsfile->head, 0);
895 cvs_output ("\nbranch:", 0);
896 if (rcsfile->branch != NULL)
899 cvs_output (rcsfile->branch, 0);
902 cvs_output ("\nlocks:", 0);
903 if (rcsfile->strict_locks)
904 cvs_output (" strict", 0);
905 walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
907 cvs_output ("\naccess list:", 0);
908 if (rcsfile->access != NULL)
912 cp = rcsfile->access;
917 cvs_output ("\n\t", 2);
919 while (!isspace ((unsigned char)*cp2) && *cp2 != '\0')
921 cvs_output (cp, cp2 - cp);
923 while (isspace ((unsigned char)*cp) && *cp != '\0')
928 if (!log_data->notags)
932 cvs_output ("\nsymbolic names:", 0);
933 syms = RCS_symbols (rcsfile);
934 walklist (syms, log_symbol, NULL);
937 cvs_output ("\nkeyword substitution: ", 0);
938 if (rcsfile->expand == NULL)
939 cvs_output ("kv", 2);
941 cvs_output (rcsfile->expand, 0);
943 cvs_output ("\ntotal revisions: ", 0);
944 sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
949 cvs_output (";\tselected revisions: ", 0);
950 sprintf (buf, "%d", selrev);
954 cvs_output ("\n", 1);
956 if (!log_data->header || log_data->long_header)
958 cvs_output ("description:\n", 0);
959 if (rcsfile->desc != NULL)
960 cvs_output (rcsfile->desc, 0);
963 if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
965 p = findnode (rcsfile->versions, rcsfile->head);
967 error (1, 0, "can not find head revision in `%s'",
971 RCSVers *vers = p->data;
973 log_version (log_data, revlist, rcsfile, vers, 1);
974 if (vers->next == NULL)
978 p = findnode (rcsfile->versions, vers->next);
980 error (1, 0, "can not find next revision `%s' in `%s'",
981 vers->next, finfo->fullname);
985 log_tree (log_data, revlist, rcsfile, rcsfile->head);
989 =============================================================================\n",
992 /* Free up the new revlist and restore the old one. */
993 log_free_revlist (revlist);
995 /* If singledatelist is not NULL, free up the start dates we added
997 if (log_data->singledatelist != NULL)
1001 for (d = log_data->singledatelist; d != NULL; d = d->next)
1003 if (d->start != NULL)
1015 * Fix up a revision list in order to compare it against versions.
1016 * Expand any symbolic revisions.
1018 static struct revlist *
1019 log_expand_revlist (RCSNode *rcs, struct option_revlist *revlist,
1022 struct option_revlist *r;
1023 struct revlist *ret, **pr;
1027 for (r = revlist; r != NULL; r = r->next)
1031 nr = xmalloc (sizeof *nr);
1032 nr->inclusive = r->inclusive;
1034 if (r->first == NULL && r->last == NULL)
1036 /* If both first and last are NULL, it means that we want
1037 just the head of the default branch, which is RCS_head. */
1038 nr->first = RCS_head (rcs);
1039 nr->last = xstrdup (nr->first);
1040 nr->fields = numdots (nr->first) + 1;
1042 else if (r->branchhead)
1046 /* Print just the head of the branch. */
1047 if (isdigit ((unsigned char) r->first[0]))
1048 nr->first = RCS_getbranch (rcs, r->first, 1);
1051 branch = RCS_whatbranch (rcs, r->first);
1056 nr->first = RCS_getbranch (rcs, branch, 1);
1060 if (nr->first == NULL && !really_quiet)
1062 error (0, 0, "warning: no branch `%s' in `%s'",
1063 r->first, rcs->path);
1069 nr->last = xstrdup (nr->first);
1070 nr->fields = numdots (nr->first) + 1;
1075 if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
1076 nr->first = xstrdup (r->first);
1079 if (RCS_nodeisbranch (rcs, r->first))
1080 nr->first = RCS_whatbranch (rcs, r->first);
1082 nr->first = RCS_gettag (rcs, r->first, 1, NULL);
1083 if (nr->first == NULL && !really_quiet)
1085 error (0, 0, "warning: no revision `%s' in `%s'",
1086 r->first, rcs->path);
1090 if (r->last == r->first || (r->last != NULL && r->first != NULL &&
1091 strcmp (r->last, r->first) == 0))
1092 nr->last = xstrdup (nr->first);
1093 else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
1094 nr->last = xstrdup (r->last);
1097 if (RCS_nodeisbranch (rcs, r->last))
1098 nr->last = RCS_whatbranch (rcs, r->last);
1100 nr->last = RCS_gettag (rcs, r->last, 1, NULL);
1101 if (nr->last == NULL && !really_quiet)
1103 error (0, 0, "warning: no revision `%s' in `%s'",
1104 r->last, rcs->path);
1108 /* Process the revision numbers the same way that rlog
1109 does. This code is a bit cryptic for my tastes, but
1110 keeping the same implementation as rlog ensures a
1111 certain degree of compatibility. */
1112 if (r->first == NULL && nr->last != NULL)
1114 nr->fields = numdots (nr->last) + 1;
1116 nr->first = xstrdup (".0");
1121 nr->first = xstrdup (nr->last);
1122 cp = strrchr (nr->first, '.');
1123 strcpy (cp + 1, "0");
1126 else if (r->last == NULL && nr->first != NULL)
1128 nr->fields = numdots (nr->first) + 1;
1129 nr->last = xstrdup (nr->first);
1136 cp = strrchr (nr->last, '.');
1140 else if (nr->first == NULL || nr->last == NULL)
1142 else if (strcmp (nr->first, nr->last) == 0)
1143 nr->fields = numdots (nr->last) + 1;
1147 int dots1 = numdots (nr->first);
1148 int dots2 = numdots (nr->last);
1149 if (dots1 > dots2 || (dots1 == dots2 &&
1150 version_compare (nr->first, nr->last, dots1 + 1) > 0))
1152 char *tmp = nr->first;
1153 nr->first = nr->last;
1155 nr->fields = dots2 + 1;
1157 dots1 = nr->fields - 1;
1160 nr->fields = dots1 + 1;
1161 dots1 += (nr->fields & 1);
1162 ord = version_compare (nr->first, nr->last, dots1);
1163 if (ord > 0 || (nr->fields > 2 && ord < 0))
1166 "invalid branch or revision pair %s:%s in `%s'",
1167 r->first, r->last, rcs->path);
1176 if (nr->fields <= dots2 && (nr->fields & 1))
1178 char *p = xmalloc (strlen (nr->first) + 3);
1179 strcpy (p, nr->first);
1185 while (nr->fields <= dots2)
1192 nr = xmalloc (sizeof *nr);
1194 nr->first = xstrdup ((*pr)->last);
1195 nr->last = xstrdup ((*pr)->last);
1196 nr->fields = (*pr)->fields;
1198 for (i = 0; i < nr->fields; i++)
1199 p = strchr (p, '.') + 1;
1201 p = strchr (nr->first + (p - (*pr)->last), '.');
1221 /* If the default branch was requested, add a revlist entry for
1222 it. This is how rlog handles this option. */
1224 && (rcs->head != NULL || rcs->branch != NULL))
1228 nr = xmalloc (sizeof *nr);
1229 if (rcs->branch != NULL)
1230 nr->first = xstrdup (rcs->branch);
1235 nr->first = xstrdup (rcs->head);
1236 cp = strrchr (nr->first, '.');
1239 nr->last = xstrdup (nr->first);
1240 nr->fields = numdots (nr->first) + 1;
1253 * Free a revlist created by log_expand_revlist.
1256 log_free_revlist (struct revlist *revlist)
1263 struct revlist *next;
1265 if (r->first != NULL)
1267 if (r->last != NULL)
1278 * Return nonzero if a revision should be printed, based on the
1282 log_version_requested (struct log_data *log_data, struct revlist *revlist,
1283 RCSNode *rcs, RCSVers *vnode)
1285 /* Handle the list of states from the -s option. */
1286 if (log_data->statelist != NULL
1287 && findnode (log_data->statelist, vnode->state) == NULL)
1292 /* Handle the list of authors from the -w option. */
1293 if (log_data->authorlist != NULL)
1295 if (vnode->author != NULL
1296 && findnode (log_data->authorlist, vnode->author) == NULL)
1302 /* rlog considers all the -d options together when it decides
1303 whether to print a revision, so we must be compatible. */
1304 if (log_data->datelist != NULL || log_data->singledatelist != NULL)
1308 for (d = log_data->datelist; d != NULL; d = d->next)
1312 cmp = RCS_datecmp (vnode->date, d->start);
1313 if (cmp > 0 || (cmp == 0 && d->inclusive))
1315 cmp = RCS_datecmp (vnode->date, d->end);
1316 if (cmp < 0 || (cmp == 0 && d->inclusive))
1323 /* Look through the list of specific dates. We want to
1324 select the revision with the exact date found in the
1325 start field. The commit code ensures that it is
1326 impossible to check in multiple revisions of a single
1327 file in a single second, so checking the date this way
1328 should never select more than one revision. */
1329 for (d = log_data->singledatelist; d != NULL; d = d->next)
1331 if (d->start != NULL
1332 && RCS_datecmp (vnode->date, d->start) == 0)
1343 /* If the -r or -b options were used, REVLIST will be non NULL,
1344 and we print the union of the specified revisions. */
1345 if (revlist != NULL)
1351 /* This code is taken from rlog. */
1353 vfields = numdots (v) + 1;
1354 for (r = revlist; r != NULL; r = r->next)
1356 if (vfields == r->fields + (r->fields & 1) &&
1357 (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
1358 version_compare (v, r->first, r->fields) > 0)
1359 && version_compare (v, r->last, r->fields) <= 0)
1365 /* If we get here, then the -b and/or the -r option was used,
1366 but did not match this revision, so we reject it. */
1371 /* By default, we print all revisions. */
1378 * Output a single symbol. This is called via walklist.
1382 log_symbol (Node *p, void *closure)
1384 cvs_output ("\n\t", 2);
1385 cvs_output (p->key, 0);
1386 cvs_output (": ", 2);
1387 cvs_output (p->data, 0);
1394 * Count the number of entries on a list. This is called via walklist.
1398 log_count (Node *p, void *closure)
1406 * Sort out a single date specification by narrowing down the date
1407 * until we find the specific selected revision.
1410 log_fix_singledate (Node *p, void *closure)
1412 struct log_data_and_rcs *data = closure;
1415 struct datelist *holdsingle, *holddate;
1418 pv = findnode (data->rcs->versions, p->key);
1420 error (1, 0, "missing version `%s' in RCS file `%s'",
1421 p->key, data->rcs->path);
1424 /* We are only interested if this revision passes any other tests.
1425 Temporarily clear log_data->singledatelist to avoid confusing
1426 log_version_requested. We also clear log_data->datelist,
1427 because rlog considers all the -d options together. We don't
1428 want to reject a revision because it does not match a date pair
1429 if we are going to select it on the basis of the singledate. */
1430 holdsingle = data->log_data->singledatelist;
1431 data->log_data->singledatelist = NULL;
1432 holddate = data->log_data->datelist;
1433 data->log_data->datelist = NULL;
1434 requested = log_version_requested (data->log_data, data->revlist,
1436 data->log_data->singledatelist = holdsingle;
1437 data->log_data->datelist = holddate;
1443 /* For each single date, if this revision is before the
1444 specified date, but is closer than the previously selected
1445 revision, select it instead. */
1446 for (d = data->log_data->singledatelist; d != NULL; d = d->next)
1448 if (RCS_datecmp (vnode->date, d->end) <= 0
1449 && (d->start == NULL
1450 || RCS_datecmp (vnode->date, d->start) > 0))
1452 if (d->start != NULL)
1454 d->start = xstrdup (vnode->date);
1465 * Count the number of revisions we are going to print.
1468 log_count_print (Node *p, void *closure)
1470 struct log_data_and_rcs *data = closure;
1473 pv = findnode (data->rcs->versions, p->key);
1475 error (1, 0, "missing version `%s' in RCS file `%s'",
1476 p->key, data->rcs->path);
1477 if (log_version_requested (data->log_data, data->revlist, data->rcs,
1487 * Print the list of changes, not including the trunk, in reverse
1488 * order for each branch.
1491 log_tree (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
1497 p = findnode (rcs->versions, ver);
1499 error (1, 0, "missing version `%s' in RCS file `%s'",
1502 if (vnode->next != NULL)
1503 log_tree (log_data, revlist, rcs, vnode->next);
1504 if (vnode->branches != NULL)
1506 Node *head, *branch;
1508 /* We need to do the branches in reverse order. This breaks
1509 the List abstraction, but so does most of the branch
1510 manipulation in rcs.c. */
1511 head = vnode->branches->list;
1512 for (branch = head->prev; branch != head; branch = branch->prev)
1514 log_abranch (log_data, revlist, rcs, branch->key);
1515 log_tree (log_data, revlist, rcs, branch->key);
1523 * Log the changes for a branch, in reverse order.
1526 log_abranch (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
1532 p = findnode (rcs->versions, ver);
1534 error (1, 0, "missing version `%s' in RCS file `%s'",
1537 if (vnode->next != NULL)
1538 log_abranch (log_data, revlist, rcs, vnode->next);
1539 log_version (log_data, revlist, rcs, vnode, 0);
1545 * Print the log output for a single version.
1548 log_version (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
1549 RCSVers *ver, int trunk)
1552 int year, mon, mday, hour, min, sec;
1556 if (! log_version_requested (log_data, revlist, rcs, ver))
1559 cvs_output ("----------------------------\nrevision ", 0);
1560 cvs_output (ver->version, 0);
1562 p = findnode (RCS_getlocks (rcs), ver->version);
1565 cvs_output ("\tlocked by: ", 0);
1566 cvs_output (p->data, 0);
1567 cvs_output (";", 1);
1569 cvs_output ("\n", 1);
1571 cvs_output_tagged ("text", "date: ");
1572 (void)sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
1576 sprintf (buf, "%04d-%02d-%02d %02d:%02d:%02d +0000", year, mon, mday,
1578 cvs_output_tagged ("date", buf);
1580 cvs_output_tagged ("text", "; author: ");
1581 cvs_output_tagged ("text", ver->author);
1583 cvs_output_tagged ("text", "; state: ");
1584 cvs_output_tagged ("text", ver->state);
1585 cvs_output_tagged ("text", ";");
1589 padd = findnode (ver->other, ";add");
1590 pdel = findnode (ver->other, ";delete");
1592 else if (ver->next == NULL)
1602 nextp = findnode (rcs->versions, ver->next);
1604 error (1, 0, "missing version `%s' in `%s'", ver->next,
1606 nextver = nextp->data;
1607 pdel = findnode (nextver->other, ";add");
1608 padd = findnode (nextver->other, ";delete");
1613 cvs_output_tagged ("text", " lines: +");
1614 cvs_output_tagged ("text", padd->data);
1615 cvs_output_tagged ("text", " -");
1616 cvs_output_tagged ("text", pdel->data);
1618 cvs_output_tagged ("newline", NULL);
1620 if (ver->branches != NULL)
1622 cvs_output ("branches:", 0);
1623 walklist (ver->branches, log_branch, NULL);
1624 cvs_output ("\n", 1);
1627 p = findnode (ver->other, "log");
1628 /* The p->date == NULL case is the normal one for an empty log
1629 message (rcs-14 in sanity.sh). I don't think the case where
1630 p->data is "" can happen (getrcskey in rcs.c checks for an
1631 empty string and set the value to NULL in that case). My guess
1632 would be the p == NULL case would mean an RCS file which was
1633 missing the "log" keyword (which is invalid according to
1635 if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
1636 cvs_output ("*** empty log message ***\n", 0);
1639 /* FIXME: Technically, the log message could contain a null
1641 cvs_output (p->data, 0);
1642 if (((char *)p->data)[strlen (p->data) - 1] != '\n')
1643 cvs_output ("\n", 1);
1650 * Output a branch version. This is called via walklist.
1654 log_branch (Node *p, void *closure)
1656 cvs_output (" ", 2);
1657 if ((numdots (p->key) & 1) == 0)
1658 cvs_output (p->key, 0);
1663 f = xstrdup (p->key);
1664 cp = strrchr (f, '.');
1669 cvs_output (";", 1);
1676 * Print a warm fuzzy message
1680 log_dirproc (void *callerdat, const char *dir, const char *repository,
1681 const char *update_dir, List *entries)
1687 error (0, 0, "Logging %s", update_dir);
1694 * Compare versions. This is taken from RCS compartial.
1697 version_compare (const char *v1, const char *v2, int len)
1710 for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
1715 for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
1719 return d1 < d2 ? -1 : 1;
1721 r = memcmp (v1, v2, d1);