diff options
Diffstat (limited to 'contrib/cvs/src/log.c')
-rw-r--r-- | contrib/cvs/src/log.c | 1818 |
1 files changed, 1818 insertions, 0 deletions
diff --git a/contrib/cvs/src/log.c b/contrib/cvs/src/log.c new file mode 100644 index 0000000..0bf44c7 --- /dev/null +++ b/contrib/cvs/src/log.c @@ -0,0 +1,1818 @@ +/* + * Copyright (C) 1986-2008 The Free Software Foundation, Inc. + * + * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, + * and others. + * + * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk + * Portions Copyright (C) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS source distribution. + * + * Print Log Information + * + * Prints the RCS "log" (rlog) information for the specified files. With no + * argument, prints the log information for all the files in the directory + * (recursive by default). + * + * $FreeBSD$ + */ + +#include "cvs.h" +#include <assert.h> + +/* This structure holds information parsed from the -r option. */ + +struct option_revlist +{ + /* The next -r option. */ + struct option_revlist *next; + /* The first revision to print. This is NULL if the range is + :rev, or if no revision is given. */ + char *first; + /* The last revision to print. This is NULL if the range is rev:, + or if no revision is given. If there is no colon, first and + last are the same. */ + char *last; + /* Nonzero if there was a trailing `.', which means to print only + the head revision of a branch. */ + int branchhead; + /* Nonzero if first and last are inclusive. */ + int inclusive; +}; + +/* This structure holds information derived from option_revlist given + a particular RCS file. */ + +struct revlist +{ + /* The next pair. */ + struct revlist *next; + /* The first numeric revision to print. */ + char *first; + /* The last numeric revision to print. */ + char *last; + /* The number of fields in these revisions (one more than + numdots). */ + int fields; + /* Whether first & last are to be included or excluded. */ + int inclusive; +}; + +/* This structure holds information parsed from the -d option. */ + +struct datelist +{ + /* The next date. */ + struct datelist *next; + /* The starting date. */ + char *start; + /* The ending date. */ + char *end; + /* Nonzero if the range is inclusive rather than exclusive. */ + int inclusive; +}; + +/* This structure is used to pass information through start_recursion. */ +struct log_data +{ + /* Nonzero if the -R option was given, meaning that only the name + of the RCS file should be printed. */ + int nameonly; + /* Nonzero if the -h option was given, meaning that only header + information should be printed. */ + int header; + /* Nonzero if the -t option was given, meaning that only the + header and the descriptive text should be printed. */ + int long_header; + /* Nonzero if the -N option was seen, meaning that tag information + should not be printed. */ + int notags; + /* Nonzero if the -b option was seen, meaning that revisions + on the default branch should be printed. */ + int default_branch; + /* Nonzero if the -S option was seen, meaning that the header/name + should be suppressed if no revisions are selected. */ + int sup_header; + /* If not NULL, the value given for the -r option, which lists + sets of revisions to be printed. */ + struct option_revlist *revlist; + /* If not NULL, the date pairs given for the -d option, which + select date ranges to print. */ + struct datelist *datelist; + /* If not NULL, the single dates given for the -d option, which + select specific revisions to print based on a date. */ + struct datelist *singledatelist; + /* If not NULL, the list of states given for the -s option, which + only prints revisions of given states. */ + List *statelist; + /* If not NULL, the list of login names given for the -w option, + which only prints revisions checked in by given users. */ + List *authorlist; +}; + +/* This structure is used to pass information through walklist. */ +struct log_data_and_rcs +{ + struct log_data *log_data; + struct revlist *revlist; + RCSNode *rcs; +}; + +static int rlog_proc PROTO((int argc, char **argv, char *xwhere, + char *mwhere, char *mfile, int shorten, + int local_specified, char *mname, char *msg)); +static Dtype log_dirproc PROTO ((void *callerdat, const char *dir, + const char *repository, + const char *update_dir, + List *entries)); +static int log_fileproc PROTO ((void *callerdat, struct file_info *finfo)); +static struct option_revlist *log_parse_revlist PROTO ((const char *)); +static void log_parse_date PROTO ((struct log_data *, const char *)); +static void log_parse_list PROTO ((List **, const char *)); +static struct revlist *log_expand_revlist PROTO ((RCSNode *, char *, + struct option_revlist *, + int)); +static void log_free_revlist PROTO ((struct revlist *)); +static int log_version_requested PROTO ((struct log_data *, struct revlist *, + RCSNode *, RCSVers *)); +static int log_symbol PROTO ((Node *, void *)); +static int log_count PROTO ((Node *, void *)); +static int log_fix_singledate PROTO ((Node *, void *)); +static int log_count_print PROTO ((Node *, void *)); +static void log_tree PROTO ((struct log_data *, struct revlist *, + RCSNode *, const char *)); +static void log_abranch PROTO ((struct log_data *, struct revlist *, + RCSNode *, const char *)); +static void log_version PROTO ((struct log_data *, struct revlist *, + RCSNode *, RCSVers *, int)); +static int log_branch PROTO ((Node *, void *)); +static int version_compare PROTO ((const char *, const char *, int)); + +static struct log_data log_data; +static int is_rlog; + +static const char *const log_usage[] = +{ + "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n", + " [-w[logins]] [files...]\n", + "\t-l\tLocal directory only, no recursion.\n", + "\t-b\tList revisions on the default branch.\n", + "\t-h\tOnly print header.\n", + "\t-R\tOnly print name of RCS file.\n", + "\t-t\tOnly print header and descriptive text.\n", + "\t-N\tDo not list tags.\n", + "\t-n\tList tags (default).\n", + "\t-S\tDo not print name/header if no revisions selected. -d, -r,\n", + "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n", + "\t\t-t without this option.\n", + "\t-r[revisions]\tA comma-separated list of revisions to print:\n", + "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n", + "\t rev1::rev2 Between rev1 and rev2, excluding rev1.\n", + "\t rev: rev and following revisions on the same branch.\n", + "\t rev:: After rev on the same branch.\n", + "\t :rev rev and previous revisions on the same branch.\n", + "\t ::rev rev and previous revisions on the same branch.\n", + "\t rev Just rev.\n", + "\t branch All revisions on the branch.\n", + "\t branch. The last revision on the branch.\n", + "\t-d dates\tA semicolon-separated list of dates\n", + "\t \t(D1<D2 for range, D for latest before).\n", + "\t-s states\tOnly list revisions with specified states.\n", + "\t-w[logins]\tOnly list revisions checked in by specified logins.\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + +#ifdef CLIENT_SUPPORT + +/* Helper function for send_arg_list. */ +static int send_one PROTO ((Node *, void *)); + +static int +send_one (node, closure) + Node *node; + void *closure; +{ + char *option = (char *) closure; + + send_to_server ("Argument ", 0); + send_to_server (option, 0); + if (strcmp (node->key, "@@MYSELF") == 0) + /* It is a bare -w option. Note that we must send it as + -w rather than messing with getcaller() or something (which on + the client will return garbage). */ + ; + else + send_to_server (node->key, 0); + send_to_server ("\012", 0); + return 0; +} + +/* For each element in ARG, send an argument consisting of OPTION + concatenated with that element. */ +static void send_arg_list PROTO ((char *, List *)); + +static void +send_arg_list (option, arg) + char *option; + List *arg; +{ + if (arg == NULL) + return; + walklist (arg, send_one, (void *)option); +} + +#endif + +int +cvslog (argc, argv) + int argc; + char **argv; +{ + int c; + int err = 0; + int local = 0; + struct option_revlist **prl; + + is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0); + + if (argc == -1) + usage (log_usage); + + memset (&log_data, 0, sizeof log_data); + prl = &log_data.revlist; + + optind = 0; + while ((c = getopt (argc, argv, "+bd:hlNnSRr::s:tw::")) != -1) + { + switch (c) + { + case 'b': + log_data.default_branch = 1; + break; + case 'd': + log_parse_date (&log_data, optarg); + break; + case 'h': + log_data.header = 1; + break; + case 'l': + local = 1; + break; + case 'N': + log_data.notags = 1; + break; + case 'n': + log_data.notags = 0; + break; + case 'S': + log_data.sup_header = 1; + break; + case 'R': + log_data.nameonly = 1; + break; + case 'r': + *prl = log_parse_revlist (optarg); + prl = &(*prl)->next; + break; + case 's': + log_parse_list (&log_data.statelist, optarg); + break; + case 't': + log_data.long_header = 1; + break; + case 'w': + if (optarg != NULL) + log_parse_list (&log_data.authorlist, optarg); + else + log_parse_list (&log_data.authorlist, "@@MYSELF"); + break; + case '?': + default: + usage (log_usage); + break; + } + } + argc -= optind; + argv += optind; + + wrap_setup (); + +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + { + struct datelist *p; + struct option_revlist *rp; + char datetmp[MAXDATELEN]; + + /* We're the local client. Fire up the remote server. */ + start_server (); + + if (is_rlog && !supported_request ("rlog")) + error (1, 0, "server does not support rlog"); + + ign_setup (); + + if (log_data.default_branch) + send_arg ("-b"); + + while (log_data.datelist != NULL) + { + p = log_data.datelist; + log_data.datelist = p->next; + assert (p->start != NULL && p->end != NULL); + send_to_server ("Argument -d\012", 0); + send_to_server ("Argument ", 0); + date_to_internet (datetmp, p->start); + send_to_server (datetmp, 0); + if (p->inclusive) + send_to_server ("<=", 0); + else + send_to_server ("<", 0); + date_to_internet (datetmp, p->end); + send_to_server (datetmp, 0); + send_to_server ("\012", 0); + free (p->start); + free (p->end); + free (p); + } + while (log_data.singledatelist != NULL) + { + p = log_data.singledatelist; + log_data.singledatelist = p->next; + assert (p->end != NULL); + send_to_server ("Argument -d\012", 0); + send_to_server ("Argument ", 0); + date_to_internet (datetmp, p->end); + send_to_server (datetmp, 0); + send_to_server ("\012", 0); + free (p->end); + free (p); + } + + if (log_data.header) + send_arg ("-h"); + if (local) + send_arg("-l"); + if (log_data.notags) + send_arg("-N"); + if (log_data.sup_header) + send_arg("-S"); + if (log_data.nameonly) + send_arg("-R"); + if (log_data.long_header) + send_arg("-t"); + + while (log_data.revlist != NULL) + { + rp = log_data.revlist; + log_data.revlist = rp->next; + send_to_server ("Argument -r", 0); + if (rp->branchhead) + { + if (rp->first != NULL) + send_to_server (rp->first, 0); + send_to_server (".", 1); + } + else + { + if (rp->first != NULL) + send_to_server (rp->first, 0); + send_to_server (":", 1); + if (!rp->inclusive) + send_to_server (":", 1); + if (rp->last != NULL) + send_to_server (rp->last, 0); + } + send_to_server ("\012", 0); + if (rp->first) + free (rp->first); + if (rp->last) + free (rp->last); + free (rp); + } + send_arg_list ("-s", log_data.statelist); + dellist (&log_data.statelist); + send_arg_list ("-w", log_data.authorlist); + dellist (&log_data.authorlist); + send_arg ("--"); + + if (is_rlog) + { + int i; + for (i = 0; i < argc; i++) + send_arg (argv[i]); + send_to_server ("rlog\012", 0); + } + else + { + send_files (argc, argv, local, 0, SEND_NO_CONTENTS); + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_to_server ("log\012", 0); + } + err = get_responses_and_close (); + return err; + } +#endif + + /* OK, now that we know we are local/server, we can resolve @@MYSELF + into our user name. */ + if (findnode (log_data.authorlist, "@@MYSELF") != NULL) + log_parse_list (&log_data.authorlist, getcaller ()); + + if (is_rlog) + { + DBM *db; + int i; + db = open_module (); + for (i = 0; i < argc; i++) + { + err += do_module (db, argv[i], MISC, "Logging", rlog_proc, + (char *) NULL, 0, local, 0, 0, (char *) NULL); + } + close_module (db); + } + else + { + err = rlog_proc (argc + 1, argv - 1, (char *) NULL, + (char *) NULL, (char *) NULL, 0, local, (char *) NULL, + (char *) NULL); + } + + while (log_data.revlist) + { + struct option_revlist *rl = log_data.revlist->next; + if (log_data.revlist->first) + free (log_data.revlist->first); + if (log_data.revlist->last) + free (log_data.revlist->last); + free (log_data.revlist); + log_data.revlist = rl; + } + while (log_data.datelist) + { + struct datelist *nd = log_data.datelist->next; + if (log_data.datelist->start) + free (log_data.datelist->start); + if (log_data.datelist->end) + free (log_data.datelist->end); + free (log_data.datelist); + log_data.datelist = nd; + } + while (log_data.singledatelist) + { + struct datelist *nd = log_data.singledatelist->next; + if (log_data.singledatelist->start) + free (log_data.singledatelist->start); + if (log_data.singledatelist->end) + free (log_data.singledatelist->end); + free (log_data.singledatelist); + log_data.singledatelist = nd; + } + dellist (&log_data.statelist); + dellist (&log_data.authorlist); + + return (err); +} + + +static int +rlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg) + int argc; + char **argv; + char *xwhere; + char *mwhere; + char *mfile; + int shorten; + int local; + char *mname; + char *msg; +{ + /* Begin section which is identical to patch_proc--should this + be abstracted out somehow? */ + char *myargv[2]; + int err = 0; + int which; + char *repository; + char *where; + + if (is_rlog) + { + repository = xmalloc (strlen (current_parsed_root->directory) + + strlen (argv[0]) + + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2); + (void)sprintf (repository, "%s/%s", + current_parsed_root->directory, argv[0]); + where = xmalloc (strlen (argv[0]) + + (mfile == NULL ? 0 : strlen (mfile) + 1) + + 1); + (void) strcpy (where, argv[0]); + + /* If mfile isn't null, we need to set up to do only part of theu + * module. + */ + if (mfile != NULL) + { + char *cp; + char *path; + + /* If the portion of the module is a path, put the dir part on + * repos. + */ + if ((cp = strrchr (mfile, '/')) != NULL) + { + *cp = '\0'; + (void)strcat (repository, "/"); + (void)strcat (repository, mfile); + (void)strcat (where, "/"); + (void)strcat (where, mfile); + mfile = cp + 1; + } + + /* take care of the rest */ + path = xmalloc (strlen (repository) + strlen (mfile) + 5); + (void)sprintf (path, "%s/%s", repository, mfile); + if (isdir (path)) + { + /* directory means repository gets the dir tacked on */ + (void)strcpy (repository, path); + (void)strcat (where, "/"); + (void)strcat (where, mfile); + } + else + { + myargv[0] = argv[0]; + myargv[1] = mfile; + argc = 2; + argv = myargv; + } + free (path); + } + + /* cd to the starting repository */ + if (CVS_CHDIR (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + free (repository); + free (where); + return 1; + } + /* End section which is identical to patch_proc. */ + + which = W_REPOS | W_ATTIC; + } + else + { + repository = NULL; + where = NULL; + which = W_LOCAL | W_REPOS | W_ATTIC; + } + + err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc, + (DIRLEAVEPROC) NULL, (void *) &log_data, + argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ, + where, 1, repository); + + if (!(which & W_LOCAL)) free (repository); + if (where) free (where); + + return err; +} + + + +/* + * Parse a revision list specification. + */ +static struct option_revlist * +log_parse_revlist (argstring) + const char *argstring; +{ + char *orig_copy, *copy; + struct option_revlist *ret, **pr; + + /* Unfortunately, rlog accepts -r without an argument to mean that + latest revision on the default branch, so we must support that + for compatibility. */ + if (argstring == NULL) + argstring = ""; + + ret = NULL; + pr = &ret; + + /* Copy the argument into memory so that we can change it. We + don't want to change the argument because, at least as of this + writing, we will use it if we send the arguments to the server. */ + orig_copy = copy = xstrdup (argstring); + while (copy != NULL) + { + char *comma; + struct option_revlist *r; + + comma = strchr (copy, ','); + if (comma != NULL) + *comma++ = '\0'; + + r = (struct option_revlist *) xmalloc (sizeof *r); + r->next = NULL; + r->first = copy; + r->branchhead = 0; + r->last = strchr (copy, ':'); + if (r->last != NULL) + { + *r->last++ = '\0'; + r->inclusive = (*r->last != ':'); + if (!r->inclusive) + r->last++; + } + else + { + r->last = r->first; + r->inclusive = 1; + if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.') + { + r->branchhead = 1; + r->first[strlen (r->first) - 1] = '\0'; + } + } + + if (*r->first == '\0') + r->first = NULL; + if (*r->last == '\0') + r->last = NULL; + + if (r->first != NULL) + r->first = xstrdup (r->first); + if (r->last != NULL) + r->last = xstrdup (r->last); + + *pr = r; + pr = &r->next; + + copy = comma; + } + + free (orig_copy); + return ret; +} + +/* + * Parse a date specification. + */ +static void +log_parse_date (log_data, argstring) + struct log_data *log_data; + const char *argstring; +{ + char *orig_copy, *copy; + + /* Copy the argument into memory so that we can change it. We + don't want to change the argument because, at least as of this + writing, we will use it if we send the arguments to the server. */ + orig_copy = copy = xstrdup (argstring); + while (copy != NULL) + { + struct datelist *nd, **pd; + char *cpend, *cp, *ds, *de; + + nd = (struct datelist *) xmalloc (sizeof *nd); + + cpend = strchr (copy, ';'); + if (cpend != NULL) + *cpend++ = '\0'; + + pd = &log_data->datelist; + nd->inclusive = 0; + + if ((cp = strchr (copy, '>')) != NULL) + { + *cp++ = '\0'; + if (*cp == '=') + { + ++cp; + nd->inclusive = 1; + } + ds = cp; + de = copy; + } + else if ((cp = strchr (copy, '<')) != NULL) + { + *cp++ = '\0'; + if (*cp == '=') + { + ++cp; + nd->inclusive = 1; + } + ds = copy; + de = cp; + } + else + { + ds = NULL; + de = copy; + pd = &log_data->singledatelist; + } + + if (ds == NULL) + nd->start = NULL; + else if (*ds != '\0') + nd->start = Make_Date (ds); + else + { + /* 1970 was the beginning of time, as far as get_date and + Make_Date are concerned. FIXME: That is true only if time_t + is a POSIX-style time and there is nothing in ANSI that + mandates that. It would be cleaner to set a flag saying + whether or not there is a start date. */ + nd->start = Make_Date ("1/1/1970 UTC"); + } + + if (*de != '\0') + nd->end = Make_Date (de); + else + { + /* We want to set the end date to some time sufficiently far + in the future to pick up all revisions that have been + created since the specified date and the time `cvs log' + completes. FIXME: The date in question only makes sense + if time_t is a POSIX-style time and it is 32 bits + and signed. We should instead be setting a flag saying + whether or not there is an end date. Note that using + something like "next week" would break the testsuite (and, + perhaps less importantly, loses if the clock is set grossly + wrong). */ + nd->end = Make_Date ("2038-01-01"); + } + + nd->next = *pd; + *pd = nd; + + copy = cpend; + } + + free (orig_copy); +} + +/* + * Parse a comma separated list of items, and add each one to *PLIST. + */ +static void +log_parse_list (plist, argstring) + List **plist; + const char *argstring; +{ + while (1) + { + Node *p; + char *cp; + + p = getnode (); + + cp = strchr (argstring, ','); + if (cp == NULL) + p->key = xstrdup (argstring); + else + { + size_t len; + + len = cp - argstring; + p->key = xmalloc (len + 1); + strncpy (p->key, argstring, len); + p->key[len] = '\0'; + } + + if (*plist == NULL) + *plist = getlist (); + if (addnode (*plist, p) != 0) + freenode (p); + + if (cp == NULL) + break; + + argstring = cp + 1; + } +} + +static int printlock_proc PROTO ((Node *, void *)); + +static int +printlock_proc (lock, foo) + Node *lock; + void *foo; +{ + cvs_output ("\n\t", 2); + cvs_output (lock->data, 0); + cvs_output (": ", 2); + cvs_output (lock->key, 0); + return 0; +} + + + +/* + * Do an rlog on a file + */ +static int +log_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; +{ + struct log_data *log_data = (struct log_data *) callerdat; + Node *p; + char *baserev; + int selrev = -1; + RCSNode *rcsfile; + char buf[50]; + struct revlist *revlist = NULL; + struct log_data_and_rcs log_data_and_rcs; + + rcsfile = finfo->rcs; + p = findnode (finfo->entries, finfo->file); + if (p != NULL) + { + Entnode *e = p->data; + baserev = e->version; + if (baserev[0] == '-') ++baserev; + } + else + baserev = NULL; + + if (rcsfile == NULL) + { + /* no rcs file. What *do* we know about this file? */ + if (baserev != NULL) + { + if (baserev[0] == '0' && baserev[1] == '\0') + { + if (!really_quiet) + error (0, 0, "%s has been added, but not committed", + finfo->file); + return 0; + } + } + + if (!really_quiet) + error (0, 0, "nothing known about %s", finfo->file); + + return 1; + } + + if (log_data->sup_header || !log_data->nameonly) + { + + /* We will need all the information in the RCS file. */ + RCS_fully_parse (rcsfile); + + /* Turn any symbolic revisions in the revision list into numeric + revisions. */ + revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist, + log_data->default_branch); + if (log_data->sup_header + || (!log_data->header && !log_data->long_header)) + { + log_data_and_rcs.log_data = log_data; + log_data_and_rcs.revlist = revlist; + log_data_and_rcs.rcs = rcsfile; + + /* If any single dates were specified, we need to identify the + revisions they select. Each one selects the single + revision, which is otherwise selected, of that date or + earlier. The log_fix_singledate routine will fill in the + start date for each specific revision. */ + if (log_data->singledatelist != NULL) + walklist (rcsfile->versions, log_fix_singledate, + (void *)&log_data_and_rcs); + + selrev = walklist (rcsfile->versions, log_count_print, + (void *)&log_data_and_rcs); + if (log_data->sup_header && selrev == 0) + { + log_free_revlist (revlist); + return 0; + } + } + + } + + if (log_data->nameonly) + { + cvs_output (rcsfile->path, 0); + cvs_output ("\n", 1); + log_free_revlist (revlist); + return 0; + } + + /* The output here is intended to be exactly compatible with the + output of rlog. I'm not sure whether this code should be here + or in rcs.c; I put it here because it is specific to the log + function, even though it uses information gathered by the + functions in rcs.c. */ + + cvs_output ("\n", 1); + + cvs_output ("RCS file: ", 0); + cvs_output (rcsfile->path, 0); + + if (!is_rlog) + { + cvs_output ("\nWorking file: ", 0); + if (finfo->update_dir[0] != '\0') + { + cvs_output (finfo->update_dir, 0); + cvs_output ("/", 0); + } + cvs_output (finfo->file, 0); + } + + cvs_output ("\nhead:", 0); + if (rcsfile->head != NULL) + { + cvs_output (" ", 1); + cvs_output (rcsfile->head, 0); + } + + cvs_output ("\nbranch:", 0); + if (rcsfile->branch != NULL) + { + cvs_output (" ", 1); + cvs_output (rcsfile->branch, 0); + } + + cvs_output ("\nlocks:", 0); + if (rcsfile->strict_locks) + cvs_output (" strict", 0); + walklist (RCS_getlocks (rcsfile), printlock_proc, NULL); + + cvs_output ("\naccess list:", 0); + if (rcsfile->access != NULL) + { + const char *cp; + + cp = rcsfile->access; + while (*cp != '\0') + { + const char *cp2; + + cvs_output ("\n\t", 2); + cp2 = cp; + while (!isspace ((unsigned char) *cp2) && *cp2 != '\0') + ++cp2; + cvs_output (cp, cp2 - cp); + cp = cp2; + while (isspace ((unsigned char) *cp) && *cp != '\0') + ++cp; + } + } + + if (!log_data->notags) + { + List *syms; + + cvs_output ("\nsymbolic names:", 0); + syms = RCS_symbols (rcsfile); + walklist (syms, log_symbol, NULL); + } + + cvs_output ("\nkeyword substitution: ", 0); + if (rcsfile->expand == NULL) + cvs_output ("kv", 2); + else + cvs_output (rcsfile->expand, 0); + + cvs_output ("\ntotal revisions: ", 0); + sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL)); + cvs_output (buf, 0); + + if (selrev >= 0) + { + cvs_output (";\tselected revisions: ", 0); + sprintf (buf, "%d", selrev); + cvs_output (buf, 0); + } + + cvs_output ("\n", 1); + + if (!log_data->header || log_data->long_header) + { + cvs_output ("description:\n", 0); + if (rcsfile->desc != NULL) + cvs_output (rcsfile->desc, 0); + } + + if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL) + { + p = findnode (rcsfile->versions, rcsfile->head); + if (p == NULL) + error (1, 0, "can not find head revision in `%s'", + finfo->fullname); + while (p != NULL) + { + RCSVers *vers = p->data; + + log_version (log_data, revlist, rcsfile, vers, 1); + if (vers->next == NULL) + p = NULL; + else + { + p = findnode (rcsfile->versions, vers->next); + if (p == NULL) + error (1, 0, "can not find next revision `%s' in `%s'", + vers->next, finfo->fullname); + } + } + + log_tree (log_data, revlist, rcsfile, rcsfile->head); + } + + cvs_output("\ +=============================================================================\n", + 0); + + /* Free up the new revlist and restore the old one. */ + log_free_revlist (revlist); + + /* If singledatelist is not NULL, free up the start dates we added + to it. */ + if (log_data->singledatelist != NULL) + { + struct datelist *d; + + for (d = log_data->singledatelist; d != NULL; d = d->next) + { + if (d->start != NULL) + free (d->start); + d->start = NULL; + } + } + + return 0; +} + + + +/* + * Fix up a revision list in order to compare it against versions. + * Expand any symbolic revisions. + */ +static struct revlist * +log_expand_revlist (rcs, baserev, revlist, default_branch) + RCSNode *rcs; + char *baserev; + struct option_revlist *revlist; + int default_branch; +{ + struct option_revlist *r; + struct revlist *ret, **pr; + + ret = NULL; + pr = &ret; + for (r = revlist; r != NULL; r = r->next) + { + struct revlist *nr; + + nr = (struct revlist *) xmalloc (sizeof *nr); + nr->inclusive = r->inclusive; + + if (r->first == NULL && r->last == NULL) + { + /* If both first and last are NULL, it means that we want + just the head of the default branch, which is RCS_head. */ + nr->first = RCS_head (rcs); + if (!nr->first) + { + if (!really_quiet) + error (0, 0, "No head revision in archive `%s'.", + rcs->path); + nr->last = NULL; + nr->fields = 0; + } + else + { + nr->last = xstrdup (nr->first); + nr->fields = numdots (nr->first) + 1; + } + } + else if (r->branchhead) + { + char *branch; + + assert (r->first != NULL); + + /* Print just the head of the branch. */ + if (isdigit ((unsigned char) r->first[0])) + nr->first = RCS_getbranch (rcs, r->first, 1); + else + { + branch = RCS_whatbranch (rcs, r->first); + if (branch == NULL) + nr->first = NULL; + else + { + nr->first = RCS_getbranch (rcs, branch, 1); + free (branch); + } + } + if (!nr->first) + { + if (!really_quiet) + error (0, 0, "warning: no branch `%s' in `%s'", + r->first, rcs->path); + nr->last = NULL; + nr->fields = 0; + } + else + { + nr->last = xstrdup (nr->first); + nr->fields = numdots (nr->first) + 1; + } + } + else + { + if (r->first == NULL || isdigit ((unsigned char) r->first[0])) + nr->first = xstrdup (r->first); + else + { + if (baserev && strcmp (r->first, TAG_BASE) == 0) + nr->first = xstrdup (baserev); + else if (RCS_nodeisbranch (rcs, r->first)) + nr->first = RCS_whatbranch (rcs, r->first); + else + nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL); + if (nr->first == NULL && !really_quiet) + { + error (0, 0, "warning: no revision `%s' in `%s'", + r->first, rcs->path); + } + } + + if (r->last == r->first || (r->last != NULL && r->first != NULL && + strcmp (r->last, r->first) == 0)) + nr->last = xstrdup (nr->first); + else if (r->last == NULL || isdigit ((unsigned char) r->last[0])) + nr->last = xstrdup (r->last); + else + { + if (baserev && strcmp (r->last, TAG_BASE) == 0) + nr->last = xstrdup (baserev); + else if (RCS_nodeisbranch (rcs, r->last)) + nr->last = RCS_whatbranch (rcs, r->last); + else + nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL); + if (nr->last == NULL && !really_quiet) + { + error (0, 0, "warning: no revision `%s' in `%s'", + r->last, rcs->path); + } + } + + /* Process the revision numbers the same way that rlog + does. This code is a bit cryptic for my tastes, but + keeping the same implementation as rlog ensures a + certain degree of compatibility. */ + if (r->first == NULL && nr->last != NULL) + { + nr->fields = numdots (nr->last) + 1; + if (nr->fields < 2) + nr->first = xstrdup (".0"); + else + { + char *cp; + + nr->first = xstrdup (nr->last); + cp = strrchr (nr->first, '.'); + assert (cp); + strcpy (cp + 1, "0"); + } + } + else if (r->last == NULL && nr->first != NULL) + { + nr->fields = numdots (nr->first) + 1; + nr->last = xstrdup (nr->first); + if (nr->fields < 2) + nr->last[0] = '\0'; + else + { + char *cp; + + cp = strrchr (nr->last, '.'); + assert (cp); + *cp = '\0'; + } + } + else if (nr->first == NULL || nr->last == NULL) + nr->fields = 0; + else if (strcmp (nr->first, nr->last) == 0) + nr->fields = numdots (nr->last) + 1; + else + { + int ord; + int dots1 = numdots (nr->first); + int dots2 = numdots (nr->last); + if (dots1 > dots2 || (dots1 == dots2 && + version_compare (nr->first, nr->last, dots1 + 1) > 0)) + { + char *tmp = nr->first; + nr->first = nr->last; + nr->last = tmp; + nr->fields = dots2 + 1; + dots2 = dots1; + dots1 = nr->fields - 1; + } + else + nr->fields = dots1 + 1; + dots1 += (nr->fields & 1); + ord = version_compare (nr->first, nr->last, dots1); + if (ord > 0 || (nr->fields > 2 && ord < 0)) + { + error (0, 0, + "invalid branch or revision pair %s:%s in `%s'", + r->first, r->last, rcs->path); + free (nr->first); + nr->first = NULL; + free (nr->last); + nr->last = NULL; + nr->fields = 0; + } + else + { + if (nr->fields <= dots2 && (nr->fields & 1)) + { + char *p = xmalloc (strlen (nr->first) + 3); + strcpy (p, nr->first); + strcat (p, ".0"); + free (nr->first); + nr->first = p; + ++nr->fields; + } + while (nr->fields <= dots2) + { + char *p; + int i; + + nr->next = NULL; + *pr = nr; + nr = (struct revlist *) xmalloc (sizeof *nr); + nr->inclusive = 1; + nr->first = xstrdup ((*pr)->last); + nr->last = xstrdup ((*pr)->last); + nr->fields = (*pr)->fields; + p = (*pr)->last; + for (i = 0; i < nr->fields; i++) + p = strchr (p, '.') + 1; + p[-1] = '\0'; + p = strchr (nr->first + (p - (*pr)->last), '.'); + if (p != NULL) + { + *++p = '0'; + *++p = '\0'; + nr->fields += 2; + } + else + ++nr->fields; + pr = &(*pr)->next; + } + } + } + } + + nr->next = NULL; + *pr = nr; + pr = &nr->next; + } + + /* If the default branch was requested, add a revlist entry for + it. This is how rlog handles this option. */ + if (default_branch + && (rcs->head != NULL || rcs->branch != NULL)) + { + struct revlist *nr; + + nr = (struct revlist *) xmalloc (sizeof *nr); + if (rcs->branch != NULL) + nr->first = xstrdup (rcs->branch); + else + { + char *cp; + + nr->first = xstrdup (rcs->head); + assert (nr->first); + cp = strrchr (nr->first, '.'); + assert (cp); + *cp = '\0'; + } + nr->last = xstrdup (nr->first); + nr->fields = numdots (nr->first) + 1; + nr->inclusive = 1; + + nr->next = NULL; + *pr = nr; + } + + return ret; +} + +/* + * Free a revlist created by log_expand_revlist. + */ +static void +log_free_revlist (revlist) + struct revlist *revlist; +{ + struct revlist *r; + + r = revlist; + while (r != NULL) + { + struct revlist *next; + + if (r->first != NULL) + free (r->first); + if (r->last != NULL) + free (r->last); + next = r->next; + free (r); + r = next; + } +} + +/* + * Return nonzero if a revision should be printed, based on the + * options provided. + */ +static int +log_version_requested (log_data, revlist, rcs, vnode) + struct log_data *log_data; + struct revlist *revlist; + RCSNode *rcs; + RCSVers *vnode; +{ + /* Handle the list of states from the -s option. */ + if (log_data->statelist != NULL + && findnode (log_data->statelist, vnode->state) == NULL) + { + return 0; + } + + /* Handle the list of authors from the -w option. */ + if (log_data->authorlist != NULL) + { + if (vnode->author != NULL + && findnode (log_data->authorlist, vnode->author) == NULL) + { + return 0; + } + } + + /* rlog considers all the -d options together when it decides + whether to print a revision, so we must be compatible. */ + if (log_data->datelist != NULL || log_data->singledatelist != NULL) + { + struct datelist *d; + + for (d = log_data->datelist; d != NULL; d = d->next) + { + int cmp; + + cmp = RCS_datecmp (vnode->date, d->start); + if (cmp > 0 || (cmp == 0 && d->inclusive)) + { + cmp = RCS_datecmp (vnode->date, d->end); + if (cmp < 0 || (cmp == 0 && d->inclusive)) + break; + } + } + + if (d == NULL) + { + /* Look through the list of specific dates. We want to + select the revision with the exact date found in the + start field. The commit code ensures that it is + impossible to check in multiple revisions of a single + file in a single second, so checking the date this way + should never select more than one revision. */ + for (d = log_data->singledatelist; d != NULL; d = d->next) + { + if (d->start != NULL + && RCS_datecmp (vnode->date, d->start) == 0) + { + break; + } + } + + if (d == NULL) + return 0; + } + } + + /* If the -r or -b options were used, REVLIST will be non NULL, + and we print the union of the specified revisions. */ + if (revlist != NULL) + { + char *v; + int vfields; + struct revlist *r; + + /* This code is taken from rlog. */ + v = vnode->version; + vfields = numdots (v) + 1; + for (r = revlist; r != NULL; r = r->next) + { + if (vfields == r->fields + (r->fields & 1) && + (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 : + version_compare (v, r->first, r->fields) > 0) + && version_compare (v, r->last, r->fields) <= 0) + { + return 1; + } + } + + /* If we get here, then the -b and/or the -r option was used, + but did not match this revision, so we reject it. */ + + return 0; + } + + /* By default, we print all revisions. */ + return 1; +} + + + +/* + * Output a single symbol. This is called via walklist. + */ +/*ARGSUSED*/ +static int +log_symbol (p, closure) + Node *p; + void *closure; +{ + cvs_output ("\n\t", 2); + cvs_output (p->key, 0); + cvs_output (": ", 2); + cvs_output (p->data, 0); + return 0; +} + + + +/* + * Count the number of entries on a list. This is called via walklist. + */ +/*ARGSUSED*/ +static int +log_count (p, closure) + Node *p; + void *closure; +{ + return 1; +} + + + +/* + * Sort out a single date specification by narrowing down the date + * until we find the specific selected revision. + */ +static int +log_fix_singledate (p, closure) + Node *p; + void *closure; +{ + struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure; + Node *pv; + RCSVers *vnode; + struct datelist *holdsingle, *holddate; + int requested; + + pv = findnode (data->rcs->versions, p->key); + if (pv == NULL) + error (1, 0, "missing version `%s' in RCS file `%s'", + p->key, data->rcs->path); + vnode = pv->data; + + /* We are only interested if this revision passes any other tests. + Temporarily clear log_data->singledatelist to avoid confusing + log_version_requested. We also clear log_data->datelist, + because rlog considers all the -d options together. We don't + want to reject a revision because it does not match a date pair + if we are going to select it on the basis of the singledate. */ + holdsingle = data->log_data->singledatelist; + data->log_data->singledatelist = NULL; + holddate = data->log_data->datelist; + data->log_data->datelist = NULL; + requested = log_version_requested (data->log_data, data->revlist, + data->rcs, vnode); + data->log_data->singledatelist = holdsingle; + data->log_data->datelist = holddate; + + if (requested) + { + struct datelist *d; + + /* For each single date, if this revision is before the + specified date, but is closer than the previously selected + revision, select it instead. */ + for (d = data->log_data->singledatelist; d != NULL; d = d->next) + { + if (RCS_datecmp (vnode->date, d->end) <= 0 + && (d->start == NULL + || RCS_datecmp (vnode->date, d->start) > 0)) + { + if (d->start != NULL) + free (d->start); + d->start = xstrdup (vnode->date); + } + } + } + + return 0; +} + + + +/* + * Count the number of revisions we are going to print. + */ +static int +log_count_print (p, closure) + Node *p; + void *closure; +{ + struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure; + Node *pv; + + pv = findnode (data->rcs->versions, p->key); + if (pv == NULL) + error (1, 0, "missing version `%s' in RCS file `%s'", + p->key, data->rcs->path); + if (log_version_requested (data->log_data, data->revlist, data->rcs, + pv->data)) + return 1; + else + return 0; +} + +/* + * Print the list of changes, not including the trunk, in reverse + * order for each branch. + */ +static void +log_tree (log_data, revlist, rcs, ver) + struct log_data *log_data; + struct revlist *revlist; + RCSNode *rcs; + const char *ver; +{ + Node *p; + RCSVers *vnode; + + p = findnode (rcs->versions, ver); + if (p == NULL) + error (1, 0, "missing version `%s' in RCS file `%s'", + ver, rcs->path); + vnode = p->data; + if (vnode->next != NULL) + log_tree (log_data, revlist, rcs, vnode->next); + if (vnode->branches != NULL) + { + Node *head, *branch; + + /* We need to do the branches in reverse order. This breaks + the List abstraction, but so does most of the branch + manipulation in rcs.c. */ + head = vnode->branches->list; + for (branch = head->prev; branch != head; branch = branch->prev) + { + log_abranch (log_data, revlist, rcs, branch->key); + log_tree (log_data, revlist, rcs, branch->key); + } + } +} + +/* + * Log the changes for a branch, in reverse order. + */ +static void +log_abranch (log_data, revlist, rcs, ver) + struct log_data *log_data; + struct revlist *revlist; + RCSNode *rcs; + const char *ver; +{ + Node *p; + RCSVers *vnode; + + p = findnode (rcs->versions, ver); + if (p == NULL) + error (1, 0, "missing version `%s' in RCS file `%s'", + ver, rcs->path); + vnode = p->data; + if (vnode->next != NULL) + log_abranch (log_data, revlist, rcs, vnode->next); + log_version (log_data, revlist, rcs, vnode, 0); +} + +/* + * Print the log output for a single version. + */ +static void +log_version (log_data, revlist, rcs, ver, trunk) + struct log_data *log_data; + struct revlist *revlist; + RCSNode *rcs; + RCSVers *ver; + int trunk; +{ + Node *p; + int year, mon, mday, hour, min, sec; + char buf[100]; + Node *padd, *pdel; + + if (! log_version_requested (log_data, revlist, rcs, ver)) + return; + + cvs_output ("----------------------------\nrevision ", 0); + cvs_output (ver->version, 0); + + p = findnode (RCS_getlocks (rcs), ver->version); + if (p != NULL) + { + cvs_output ("\tlocked by: ", 0); + cvs_output (p->data, 0); + cvs_output (";", 1); + } + + cvs_output ("\ndate: ", 0); + (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min, + &sec); + if (year < 1900) + year += 1900; + sprintf (buf, "%04d%c%02d%c%02d %02d:%02d:%02d", + year, datesep, mon, datesep, mday, hour, min, sec); + cvs_output (buf, 0); + + cvs_output ("; author: ", 0); + cvs_output (ver->author, 0); + + cvs_output ("; state: ", 0); + cvs_output (ver->state, 0); + cvs_output (";", 1); + + if (! trunk) + { + padd = findnode (ver->other, ";add"); + pdel = findnode (ver->other, ";delete"); + } + else if (ver->next == NULL) + { + padd = NULL; + pdel = NULL; + } + else + { + Node *nextp; + RCSVers *nextver; + + nextp = findnode (rcs->versions, ver->next); + if (nextp == NULL) + error (1, 0, "missing version `%s' in `%s'", ver->next, + rcs->path); + nextver = nextp->data; + pdel = findnode (nextver->other, ";add"); + padd = findnode (nextver->other, ";delete"); + } + + if (padd != NULL) + { + assert (pdel); + cvs_output (" lines: +", 0); + cvs_output (padd->data, 0); + cvs_output (" -", 2); + cvs_output (pdel->data, 0); + } + + if (ver->branches != NULL) + { + cvs_output ("\nbranches:", 0); + walklist (ver->branches, log_branch, (void *) NULL); + } + + cvs_output ("\n", 1); + + p = findnode (ver->other, "log"); + /* The p->date == NULL case is the normal one for an empty log + message (rcs-14 in sanity.sh). I don't think the case where + p->data is "" can happen (getrcskey in rcs.c checks for an + empty string and set the value to NULL in that case). My guess + would be the p == NULL case would mean an RCS file which was + missing the "log" keyword (which is illegal according to + rcsfile.5). */ + if (p == NULL || p->data == NULL || *(char *)p->data == '\0') + cvs_output ("*** empty log message ***\n", 0); + else + { + /* FIXME: Technically, the log message could contain a null + byte. */ + cvs_output (p->data, 0); + if (((char *)p->data)[strlen (p->data) - 1] != '\n') + cvs_output ("\n", 1); + } +} + +/* + * Output a branch version. This is called via walklist. + */ +/*ARGSUSED*/ +static int +log_branch (p, closure) + Node *p; + void *closure; +{ + cvs_output (" ", 2); + if ((numdots (p->key) & 1) == 0) + cvs_output (p->key, 0); + else + { + char *f, *cp; + + f = xstrdup (p->key); + cp = strrchr (f, '.'); + *cp = '\0'; + cvs_output (f, 0); + free (f); + } + cvs_output (";", 1); + return 0; +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +log_dirproc (callerdat, dir, repository, update_dir, entries) + void *callerdat; + const char *dir; + const char *repository; + const char *update_dir; + List *entries; +{ + if (!isdir (dir)) + return (R_SKIP_ALL); + + if (!quiet) + error (0, 0, "Logging %s", update_dir); + return (R_PROCESS); +} + +/* + * Compare versions. This is taken from RCS compartial. + */ +static int +version_compare (v1, v2, len) + const char *v1; + const char *v2; + int len; +{ + while (1) + { + int d1, d2, r; + + if (*v1 == '\0') + return 1; + if (*v2 == '\0') + return -1; + + while (*v1 == '0') + ++v1; + for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1) + ; + + while (*v2 == '0') + ++v2; + for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2) + ; + + if (d1 != d2) + return d1 < d2 ? -1 : 1; + + r = memcmp (v1, v2, d1); + if (r != 0) + return r; + + --len; + if (len == 0) + return 0; + + v1 += d1; + v2 += d1; + + if (*v1 == '.') + ++v1; + if (*v2 == '.') + ++v2; + } +} |