diff options
Diffstat (limited to 'contrib/cvs/src/rcs.c')
-rw-r--r-- | contrib/cvs/src/rcs.c | 2262 |
1 files changed, 2262 insertions, 0 deletions
diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c new file mode 100644 index 0000000000000..c68c25524a7c5 --- /dev/null +++ b/contrib/cvs/src/rcs.c @@ -0,0 +1,2262 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * The routines contained in this file do all the rcs file parsing and + * manipulation + */ + +#include <assert.h> +#include "cvs.h" + +static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile)); +static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch)); +static int getrcskey PROTO((FILE * fp, char **keyp, char **valp)); +static int checkmagic_proc PROTO((Node *p, void *closure)); +static void do_branches PROTO((List * list, char *val)); +static void do_symbols PROTO((List * list, char *val)); +static void rcsvers_delproc PROTO((Node * p)); + +/* + * We don't want to use isspace() from the C library because: + * + * 1. The definition of "whitespace" in RCS files includes ASCII + * backspace, but the C locale doesn't. + * 2. isspace is an very expensive function call in some implementations + * due to the addition of wide character support. + */ +static const char spacetab[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, /* 0x00 - 0x0f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x8f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 0xf0 - 0xff */ +}; + +#define whitespace(c) (spacetab[(unsigned char)c] != 0) + + +/* + * Parse an rcsfile given a user file name and a repository + */ +RCSNode * +RCS_parse (file, repos) + const char *file; + const char *repos; +{ + RCSNode *rcs; + FILE *fp; + char rcsfile[PATH_MAX]; + + (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT); + if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL) + { + rcs = RCS_parsercsfile_i(fp, rcsfile); + if (rcs != NULL) + rcs->flags |= VALID; + + fclose (fp); + return (rcs); + } + else if (! existence_error (errno)) + { + error (0, errno, "cannot open %s", rcsfile); + return NULL; + } + + (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT); + if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL) + { + rcs = RCS_parsercsfile_i(fp, rcsfile); + if (rcs != NULL) + { + rcs->flags |= INATTIC; + rcs->flags |= VALID; + } + + fclose (fp); + return (rcs); + } + else if (! existence_error (errno)) + { + error (0, errno, "cannot open %s", rcsfile); + return NULL; + } + + return (NULL); +} + +/* + * Parse a specific rcsfile. + */ +RCSNode * +RCS_parsercsfile (rcsfile) + char *rcsfile; +{ + FILE *fp; + RCSNode *rcs; + + /* open the rcsfile */ + if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) == NULL) + { + error (0, errno, "Couldn't open rcs file `%s'", rcsfile); + return (NULL); + } + + rcs = RCS_parsercsfile_i (fp, rcsfile); + + fclose (fp); + return (rcs); +} + + +/* + */ +static RCSNode * +RCS_parsercsfile_i (fp, rcsfile) + FILE *fp; + const char *rcsfile; +{ + RCSNode *rdata; + char *key, *value; + + /* make a node */ + rdata = (RCSNode *) xmalloc (sizeof (RCSNode)); + memset ((char *) rdata, 0, sizeof (RCSNode)); + rdata->refcount = 1; + rdata->path = xstrdup (rcsfile); + + /* Process HEAD and BRANCH keywords from the RCS header. + * + * Most cvs operatations on the main branch don't need any more + * information. Those that do call XXX to completely parse the + * RCS file. */ + + if (getrcskey (fp, &key, &value) == -1 || key == NULL) + goto l_error; + if (strcmp (key, RCSDESC) == 0) + goto l_error; + + if (strcmp (RCSHEAD, key) == 0 && value != NULL) + rdata->head = xstrdup (value); + + if (getrcskey (fp, &key, &value) == -1 || key == NULL) + goto l_error; + if (strcmp (key, RCSDESC) == 0) + goto l_error; + + if (strcmp (RCSBRANCH, key) == 0 && value != NULL) + { + char *cp; + + rdata->branch = xstrdup (value); + if ((numdots (rdata->branch) & 1) != 0) + { + /* turn it into a branch if it's a revision */ + cp = strrchr (rdata->branch, '.'); + *cp = '\0'; + } + } + + rdata->flags |= PARTIAL; + return rdata; + +l_error: + if (!really_quiet) + { + if (ferror(fp)) + { + error (1, 0, "error reading `%s'", rcsfile); + } + else + { + error (0, 0, "`%s' does not appear to be a valid rcs file", + rcsfile); + } + } + freercsnode (&rdata); + return (NULL); +} + + +/* Do the real work of parsing an RCS file. + + On error, die with a fatal error; if it returns at all it was successful. + + If PFP is NULL, close the file when done. Otherwise, leave it open + and store the FILE * in *PFP. */ +static void +RCS_reparsercsfile (rdata, pfp) + RCSNode *rdata; + FILE **pfp; +{ + FILE *fp; + char *rcsfile; + + Node *q; + RCSVers *vnode; + int n; + char *cp; + char *key, *value; + + assert (rdata != NULL); + rcsfile = rdata->path; + + fp = fopen(rcsfile, FOPEN_BINARY_READ); + if (fp == NULL) + error (1, 0, "unable to reopen `%s'", rcsfile); + + /* make a node */ + rdata->versions = getlist (); + + /* + * process all the special header information, break out when we get to + * the first revision delta + */ + for (;;) + { + /* get the next key/value pair */ + + /* if key is NULL here, then the file is missing some headers + or we had trouble reading the file. */ + if (getrcskey (fp, &key, &value) == -1 || key == NULL + || strcmp (key, RCSDESC) == 0) + { + if (ferror(fp)) + { + error (1, 0, "error reading `%s'", rcsfile); + } + else + { + error (1, 0, "`%s' does not appear to be a valid rcs file", + rcsfile); + } + } + + if (strcmp (RCSSYMBOLS, key) == 0) + { + if (value != NULL) + { + rdata->symbols_data = xstrdup(value); + continue; + } + } + + if (strcmp (RCSEXPAND, key) == 0) + { + rdata->expand = xstrdup (value); + continue; + } + + /* + * check key for '.''s and digits (probably a rev) if it is a + * revision, we are done with the headers and are down to the + * revision deltas, so we break out of the loop + */ + for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) + /* do nothing */ ; + if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) + break; + + /* if we haven't grabbed it yet, we didn't want it */ + } + + /* + * we got out of the loop, so we have the first part of the first + * revision delta in our hand key=the revision and value=the date key and + * its value + */ + for (;;) + { + char *valp; + + vnode = (RCSVers *) xmalloc (sizeof (RCSVers)); + memset (vnode, 0, sizeof (RCSVers)); + + /* fill in the version before we forget it */ + vnode->version = xstrdup (key); + + /* grab the value of the date from value */ + valp = value + strlen (RCSDATE);/* skip the "date" keyword */ + while (whitespace (*valp)) /* take space off front of value */ + valp++; + + vnode->date = xstrdup (valp); + + /* Get author field. */ + (void) getrcskey (fp, &key, &value); + /* FIXME: should be using errno in case of ferror. */ + if (key == NULL || strcmp (key, "author") != 0) + error (1, 0, "\ +unable to parse rcs file; `author' not in the expected place"); + vnode->author = xstrdup (value); + + /* Get state field. */ + (void) getrcskey (fp, &key, &value); + /* FIXME: should be using errno in case of ferror. */ + if (key == NULL || strcmp (key, "state") != 0) + error (1, 0, "\ +unable to parse rcs file; `state' not in the expected place"); + if (strcmp (value, "dead") == 0) + { + vnode->dead = 1; + } + + /* fill in the branch list (if any branches exist) */ + (void) getrcskey (fp, &key, &value); + /* FIXME: should be handling various error conditions better. */ + if (key != NULL && strcmp (key, RCSDESC) == 0) + value = NULL; + if (value != (char *) NULL) + { + vnode->branches = getlist (); + do_branches (vnode->branches, value); + } + + /* fill in the next field if there is a next revision */ + (void) getrcskey (fp, &key, &value); + /* FIXME: should be handling various error conditions better. */ + if (key != NULL && strcmp (key, RCSDESC) == 0) + value = NULL; + if (value != (char *) NULL) + vnode->next = xstrdup (value); + + /* + * at this point, we skip any user defined fields XXX - this is where + * we put the symbolic link stuff??? + */ + /* FIXME: Does not correctly handle errors, e.g. from stdio. */ + while ((n = getrcskey (fp, &key, &value)) >= 0) + { + assert (key != NULL); + + if (strcmp (key, RCSDESC) == 0) + { + n = -1; + break; + } + + /* Enable use of repositories created by certain obsolete + versions of CVS. This code should remain indefinately; + there is no procedure for converting old repositories, and + checking for it is harmless. */ + if (strcmp(key, RCSDEAD) == 0) + { + vnode->dead = 1; + continue; + } + /* if we have a revision, break and do it */ + for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) + /* do nothing */ ; + if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) + break; + } + + /* get the node */ + q = getnode (); + q->type = RCSVERS; + q->delproc = rcsvers_delproc; + q->data = (char *) vnode; + q->key = vnode->version; + + /* add the nodes to the list */ + if (addnode (rdata->versions, q) != 0) + { +#if 0 + purify_printf("WARNING: Adding duplicate version: %s (%s)\n", + q->key, rcsfile); + freenode (q); +#endif + } + + /* + * if we left the loop because there were no more keys, we break out + * of the revision processing loop + */ + if (n < 0) + break; + } + + if (pfp == NULL) + { + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", rcsfile); + } + else + { + *pfp = fp; + } + rdata->flags &= ~PARTIAL; +} + +/* + * freercsnode - free up the info for an RCSNode + */ +void +freercsnode (rnodep) + RCSNode **rnodep; +{ + if (rnodep == NULL || *rnodep == NULL) + return; + + ((*rnodep)->refcount)--; + if ((*rnodep)->refcount != 0) + { + *rnodep = (RCSNode *) NULL; + return; + } + free ((*rnodep)->path); + dellist (&(*rnodep)->versions); + if ((*rnodep)->symbols != (List *) NULL) + dellist (&(*rnodep)->symbols); + if ((*rnodep)->symbols_data != (char *) NULL) + free ((*rnodep)->symbols_data); + if ((*rnodep)->expand != NULL) + free ((*rnodep)->expand); + if ((*rnodep)->head != (char *) NULL) + free ((*rnodep)->head); + if ((*rnodep)->branch != (char *) NULL) + free ((*rnodep)->branch); + free ((char *) *rnodep); + *rnodep = (RCSNode *) NULL; +} + +/* + * rcsvers_delproc - free up an RCSVers type node + */ +static void +rcsvers_delproc (p) + Node *p; +{ + RCSVers *rnode; + + rnode = (RCSVers *) p->data; + + if (rnode->branches != (List *) NULL) + dellist (&rnode->branches); + if (rnode->date != (char *) NULL) + free (rnode->date); + if (rnode->next != (char *) NULL) + free (rnode->next); + free ((char *) rnode); +} + +/* + * getrcskey - fill in the key and value from the rcs file the algorithm is + * as follows + * + * o skip whitespace o fill in key with everything up to next white + * space or semicolon + * o if key == "desc" then key and data are NULL and return -1 + * o if key wasn't terminated by a semicolon, skip white space and fill + * in value with everything up to a semicolon + * o compress all whitespace down to a single space + * o if a word starts with @, do funky rcs processing + * o strip whitespace off end of value or set value to NULL if it empty + * o return 0 since we found something besides "desc" + * + * Sets *KEYP and *VALUEP to point to storage managed by the getrcskey + * function; the contents are only valid until the next call to getrcskey + * or getrcsrev. + */ + +static char *key = NULL; +static char *value = NULL; +static size_t keysize = 0; +static size_t valsize = 0; + +#define ALLOCINCR 1024 + +static int +getrcskey (fp, keyp, valp) + FILE *fp; + char **keyp; + char **valp; +{ + char *cur, *max; + int c; + + /* skip leading whitespace */ + do + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } while (whitespace (c)); + + /* fill in key */ + cur = key; + max = key + keysize; + while (!whitespace (c) && c != ';') + { + if (cur >= max) + { + key = xrealloc (key, keysize + ALLOCINCR); + cur = key + keysize; + keysize += ALLOCINCR; + max = key + keysize; + } + *cur++ = c; + + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } + if (cur >= max) + { + key = xrealloc (key, keysize + ALLOCINCR); + cur = key + keysize; + keysize += ALLOCINCR; + max = key + keysize; + } + *cur = '\0'; + + /* skip whitespace between key and val */ + while (whitespace (c)) + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } + + /* if we ended key with a semicolon, there is no value */ + if (c == ';') + { + *keyp = key; + *valp = (char *) NULL; + return (0); + } + + /* otherwise, there might be a value, so fill it in */ + cur = value; + max = value + valsize; + + /* process the value */ + for (;;) + { + /* handle RCS "strings" */ + if (c == '@') + { + for (;;) + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + + if (c == '@') + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + + if (c != '@') + break; + } + + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur++ = c; + } + } + + /* The syntax for some key-value pairs is different; they + don't end with a semicolon. */ + if (strcmp (key, RCSDESC) == 0 + || strcmp (key, "text") == 0 + || strcmp (key, "log") == 0) + break; + + /* compress whitespace down to a single space */ + if (whitespace (c)) + { + do { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } while (whitespace (c)); + + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur++ = ' '; + } + + /* if we got a semi-colon we are done with the entire value */ + if (c == ';') + break; + + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur++ = c; + + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } + + /* terminate the string */ + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur = '\0'; + + /* if the string is empty, make it null */ + if (value && *value != '\0') + *valp = value; + else + *valp = NULL; + *keyp = key; + return (0); +} + +static void getrcsrev PROTO ((FILE *fp, char **revp)); + +/* Read an RCS revision number from FP. Put a pointer to it in *REVP; + it points to space managed by getrcsrev which is only good until + the next call to getrcskey or getrcsrev. */ +static void +getrcsrev (fp, revp) + FILE *fp; + char **revp; +{ + char *cur; + char *max; + int c; + + do { + c = getc (fp); + if (c == EOF) + /* FIXME: should be including filename in error message. */ + error (1, errno, "cannot read rcs file"); + } while (whitespace (c)); + + if (!(isdigit (c) || c == '.')) + /* FIXME: should be including filename in error message. */ + error (1, 0, "error reading rcs file; revision number expected"); + + cur = key; + max = key + keysize; + while (isdigit (c) || c == '.') + { + if (cur >= max) + { + key = xrealloc (key, keysize + ALLOCINCR); + cur = key + keysize; + keysize += ALLOCINCR; + max = key + keysize; + } + *cur++ = c; + + c = getc (fp); + if (c == EOF) + { + /* FIXME: should be including filename in error message. */ + error (1, errno, "cannot read rcs file"); + } + } + + if (cur >= max) + { + key = xrealloc (key, keysize + ALLOCINCR); + cur = key + keysize; + keysize += ALLOCINCR; + max = key + keysize; + } + *cur = '\0'; + *revp = key; +} + +/* + * process the symbols list of the rcs file + */ +static void +do_symbols (list, val) + List *list; + char *val; +{ + Node *p; + char *cp = val; + char *tag, *rev; + + for (;;) + { + /* skip leading whitespace */ + while (whitespace (*cp)) + cp++; + + /* if we got to the end, we are done */ + if (*cp == '\0') + break; + + /* split it up into tag and rev */ + tag = cp; + cp = strchr (cp, ':'); + *cp++ = '\0'; + rev = cp; + while (!whitespace (*cp) && *cp != '\0') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + + /* make a new node and add it to the list */ + p = getnode (); + p->key = xstrdup (tag); + p->data = xstrdup (rev); + (void) addnode (list, p); + } +} + +/* + * process the branches list of a revision delta + */ +static void +do_branches (list, val) + List *list; + char *val; +{ + Node *p; + char *cp = val; + char *branch; + + for (;;) + { + /* skip leading whitespace */ + while (whitespace (*cp)) + cp++; + + /* if we got to the end, we are done */ + if (*cp == '\0') + break; + + /* find the end of this branch */ + branch = cp; + while (!whitespace (*cp) && *cp != '\0') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + + /* make a new node and add it to the list */ + p = getnode (); + p->key = xstrdup (branch); + (void) addnode (list, p); + } +} + +/* + * Version Number + * + * Returns the requested version number of the RCS file, satisfying tags and/or + * dates, and walking branches, if necessary. + * + * The result is returned; null-string if error. + */ +char * +RCS_getversion (rcs, tag, date, force_tag_match, return_both) + RCSNode *rcs; + char *tag; + char *date; + int force_tag_match; + int return_both; +{ + /* make sure we have something to look at... */ + assert (rcs != NULL); + + if (tag && date) + { + char *cp, *rev, *tagrev; + + /* + * first lookup the tag; if that works, turn the revision into + * a branch and lookup the date. + */ + tagrev = RCS_gettag (rcs, tag, force_tag_match, 0); + if (tagrev == NULL) + return ((char *) NULL); + + if ((cp = strrchr (tagrev, '.')) != NULL) + *cp = '\0'; + rev = RCS_getdatebranch (rcs, date, tagrev); + free (tagrev); + return (rev); + } + else if (tag) + return (RCS_gettag (rcs, tag, force_tag_match, return_both)); + else if (date) + return (RCS_getdate (rcs, date, force_tag_match)); + else + return (RCS_head (rcs)); + +} + +/* + * Find the revision for a specific tag. + * If force_tag_match is set, return NULL if an exact match is not + * possible otherwise return RCS_head (). We are careful to look for + * and handle "magic" revisions specially. + * + * If the matched tag is a branch tag, find the head of the branch. + */ +char * +RCS_gettag (rcs, symtag, force_tag_match, return_both) + RCSNode *rcs; + char *symtag; + int force_tag_match; + int return_both; +{ + Node *p; + char *tag = symtag; + + /* make sure we have something to look at... */ + assert (rcs != NULL); + + /* XXX this is probably not necessary, --jtc */ + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* If tag is "HEAD", special case to get head RCS revision */ + if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0')) +#if 0 /* This #if 0 is only in the Cygnus code. Why? Death support? */ + if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC)) + return ((char *) NULL); /* head request for removed file */ + else +#endif + return (RCS_head (rcs)); + + if (!isdigit (tag[0])) + { + /* If we got a symbolic tag, resolve it to a numeric */ + if (rcs == NULL) + p = NULL; + else { + p = findnode (RCS_symbols(rcs), tag); + } + if (p != NULL) + { + int dots; + char *magic, *branch, *cp; + + tag = p->data; + + /* + * If this is a magic revision, we turn it into either its + * physical branch equivalent (if one exists) or into + * its base revision, which we assume exists. + */ + dots = numdots (tag); + if (dots > 2 && (dots & 1) != 0) + { + branch = strrchr (tag, '.'); + cp = branch++ - 1; + while (*cp != '.') + cp--; + + /* see if we have .magic-branch. (".0.") */ + magic = xmalloc (strlen (tag) + 1); + (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); + if (strncmp (magic, cp, strlen (magic)) == 0) + { + char *xtag; + + /* it's magic. See if the branch exists */ + *cp = '\0'; /* turn it into a revision */ + xtag = xstrdup (tag); + *cp = '.'; /* and back again */ + (void) sprintf (magic, "%s.%s", xtag, branch); + branch = RCS_getbranch (rcs, magic, 1); + free (magic); + if (branch != NULL) + { + free (xtag); + return (branch); + } + return (xtag); + } + free (magic); + } + } + else + { + /* The tag wasn't there, so return the head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + } + + /* + * numeric tag processing: + * 1) revision number - just return it + * 2) branch number - find head of branch + */ + + /* strip trailing dots */ + while (tag[strlen (tag) - 1] == '.') + tag[strlen (tag) - 1] = '\0'; + + if ((numdots (tag) & 1) == 0) + { + /* we have a branch tag, so we need to walk the branch */ + return (RCS_getbranch (rcs, tag, force_tag_match)); + } + else + { + /* we have a revision tag, so make sure it exists */ + if (rcs == NULL) + p = NULL; + else + p = findnode (rcs->versions, tag); + if (p != NULL) + { + /* + * we have found a numeric revision for the revision tag. + * To support expanding the RCS keyword Name, return both + * the numeric tag and the supplied tag (which might be + * symbolic). They are separated with a ':' which is not + * a valid tag char. The variable return_both is only set + * if this function is called through Version_TS -> + * RCS_getversion. + */ + if (return_both) + { + char *both = xmalloc(strlen(tag) + 2 + strlen(symtag)); + sprintf(both, "%s:%s", tag, symtag); + return both; + } + else + return (xstrdup (tag)); + } + else + { + /* The revision wasn't there, so return the head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + } +} + +/* + * Return a "magic" revision as a virtual branch off of REV for the RCS file. + * A "magic" revision is one which is unique in the RCS file. By unique, I + * mean we return a revision which: + * - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH) + * - has a revision component which is not an existing branch off REV + * - has a revision component which is not an existing magic revision + * - is an even-numbered revision, to avoid conflicts with vendor branches + * The first point is what makes it "magic". + * + * As an example, if we pass in 1.37 as REV, we will look for an existing + * branch called 1.37.2. If it did not exist, we would look for an + * existing symbolic tag with a numeric part equal to 1.37.0.2. If that + * didn't exist, then we know that the 1.37.2 branch can be reserved by + * creating a symbolic tag with 1.37.0.2 as the numeric part. + * + * This allows us to fork development with very little overhead -- just a + * symbolic tag is used in the RCS file. When a commit is done, a physical + * branch is dynamically created to hold the new revision. + * + * Note: We assume that REV is an RCS revision and not a branch number. + */ +static char *check_rev; +char * +RCS_magicrev (rcs, rev) + RCSNode *rcs; + char *rev; +{ + int rev_num; + char *xrev, *test_branch; + + xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */ + check_rev = xrev; + + /* only look at even numbered branches */ + for (rev_num = 2; ; rev_num += 2) + { + /* see if the physical branch exists */ + (void) sprintf (xrev, "%s.%d", rev, rev_num); + test_branch = RCS_getbranch (rcs, xrev, 1); + if (test_branch != NULL) /* it did, so keep looking */ + { + free (test_branch); + continue; + } + + /* now, create a "magic" revision */ + (void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num); + + /* walk the symbols list to see if a magic one already exists */ + if (walklist (RCS_symbols(rcs), checkmagic_proc, NULL) != 0) + continue; + + /* we found a free magic branch. Claim it as ours */ + return (xrev); + } +} + +/* + * walklist proc to look for a match in the symbols list. + * Returns 0 if the symbol does not match, 1 if it does. + */ +static int +checkmagic_proc (p, closure) + Node *p; + void *closure; +{ + if (strcmp (check_rev, p->data) == 0) + return (1); + else + return (0); +} + +/* + * Given an RCSNode, returns non-zero if the specified revision number + * or symbolic tag resolves to a "branch" within the rcs file. + * + * FIXME: this is the same as RCS_nodeisbranch except for the special + * case for handling a null rcsnode. + */ +int +RCS_isbranch (rcs, rev) + RCSNode *rcs; + const char *rev; +{ + /* numeric revisions are easy -- even number of dots is a branch */ + if (isdigit (*rev)) + return ((numdots (rev) & 1) == 0); + + /* assume a revision if you can't find the RCS info */ + if (rcs == NULL) + return (0); + + /* now, look for a match in the symbols list */ + return (RCS_nodeisbranch (rcs, rev)); +} + +/* + * Given an RCSNode, returns non-zero if the specified revision number + * or symbolic tag resolves to a "branch" within the rcs file. We do + * take into account any magic branches as well. + */ +int +RCS_nodeisbranch (rcs, rev) + RCSNode *rcs; + const char *rev; +{ + int dots; + Node *p; + + /* numeric revisions are easy -- even number of dots is a branch */ + if (isdigit (*rev)) + return ((numdots (rev) & 1) == 0); + + p = findnode (RCS_symbols(rcs), rev); + if (p == NULL) + return (0); + dots = numdots (p->data); + if ((dots & 1) == 0) + return (1); + + /* got a symbolic tag match, but it's not a branch; see if it's magic */ + if (dots > 2) + { + char *magic; + char *branch = strrchr (p->data, '.'); + char *cp = branch - 1; + while (*cp != '.') + cp--; + + /* see if we have .magic-branch. (".0.") */ + magic = xmalloc (strlen (p->data) + 1); + (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); + if (strncmp (magic, cp, strlen (magic)) == 0) + { + free (magic); + return (1); + } + free (magic); + } + return (0); +} + +/* + * Returns a pointer to malloc'ed memory which contains the branch + * for the specified *symbolic* tag. Magic branches are handled correctly. + */ +char * +RCS_whatbranch (rcs, rev) + RCSNode *rcs; + const char *rev; +{ + Node *p; + int dots; + + /* assume no branch if you can't find the RCS info */ + if (rcs == NULL) + return ((char *) NULL); + + /* now, look for a match in the symbols list */ + p = findnode (RCS_symbols(rcs), rev); + if (p == NULL) + return ((char *) NULL); + dots = numdots (p->data); + if ((dots & 1) == 0) + return (xstrdup (p->data)); + + /* got a symbolic tag match, but it's not a branch; see if it's magic */ + if (dots > 2) + { + char *magic; + char *branch = strrchr (p->data, '.'); + char *cp = branch++ - 1; + while (*cp != '.') + cp--; + + /* see if we have .magic-branch. (".0.") */ + magic = xmalloc (strlen (p->data) + 1); + (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); + if (strncmp (magic, cp, strlen (magic)) == 0) + { + /* yep. it's magic. now, construct the real branch */ + *cp = '\0'; /* turn it into a revision */ + (void) sprintf (magic, "%s.%s", p->data, branch); + *cp = '.'; /* and turn it back */ + return (magic); + } + free (magic); + } + return ((char *) NULL); +} + +/* + * Get the head of the specified branch. If the branch does not exist, + * return NULL or RCS_head depending on force_tag_match + */ +char * +RCS_getbranch (rcs, tag, force_tag_match) + RCSNode *rcs; + char *tag; + int force_tag_match; +{ + Node *p, *head; + RCSVers *vn; + char *xtag; + char *nextvers; + char *cp; + + /* make sure we have something to look at... */ + assert (rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* find out if the tag contains a dot, or is on the trunk */ + cp = strrchr (tag, '.'); + + /* trunk processing is the special case */ + if (cp == NULL) + { + xtag = xmalloc (strlen (tag) + 1 + 1); /* +1 for an extra . */ + (void) strcpy (xtag, tag); + (void) strcat (xtag, "."); + for (cp = rcs->head; cp != NULL;) + { + if (strncmp (xtag, cp, strlen (xtag)) == 0) + break; + p = findnode (rcs->versions, cp); + if (p == NULL) + { + free (xtag); + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + vn = (RCSVers *) p->data; + cp = vn->next; + } + free (xtag); + if (cp == NULL) + { + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + return (xstrdup (cp)); + } + + /* if it had a `.', terminate the string so we have the base revision */ + *cp = '\0'; + + /* look up the revision this branch is based on */ + p = findnode (rcs->versions, tag); + + /* put the . back so we have the branch again */ + *cp = '.'; + + if (p == NULL) + { + /* if the base revision didn't exist, return head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + + /* find the first element of the branch we are looking for */ + vn = (RCSVers *) p->data; + if (vn->branches == NULL) + return (NULL); + xtag = xmalloc (strlen (tag) + 1 + 1); /* 1 for the extra '.' */ + (void) strcpy (xtag, tag); + (void) strcat (xtag, "."); + head = vn->branches->list; + for (p = head->next; p != head; p = p->next) + if (strncmp (p->key, xtag, strlen (xtag)) == 0) + break; + free (xtag); + + if (p == head) + { + /* we didn't find a match so return head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + + /* now walk the next pointers of the branch */ + nextvers = p->key; + do + { + p = findnode (rcs->versions, nextvers); + if (p == NULL) + { + /* a link in the chain is missing - return head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + vn = (RCSVers *) p->data; + nextvers = vn->next; + } while (nextvers != NULL); + + /* we have the version in our hand, so go for it */ + return (xstrdup (vn->version)); +} + +/* + * Get the head of the RCS file. If branch is set, this is the head of the + * branch, otherwise the real head + */ +char * +RCS_head (rcs) + RCSNode *rcs; +{ + /* make sure we have something to look at... */ + assert (rcs != NULL); + + /* + * NOTE: we call getbranch with force_tag_match set to avoid any + * possibility of recursion + */ + if (rcs->branch) + return (RCS_getbranch (rcs, rcs->branch, 1)); + else + return (xstrdup (rcs->head)); +} + +/* + * Get the most recent revision, based on the supplied date, but use some + * funky stuff and follow the vendor branch maybe + */ +char * +RCS_getdate (rcs, date, force_tag_match) + RCSNode *rcs; + char *date; + int force_tag_match; +{ + char *cur_rev = NULL; + char *retval = NULL; + Node *p; + RCSVers *vers = NULL; + + /* make sure we have something to look at... */ + assert (rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* if the head is on a branch, try the branch first */ + if (rcs->branch != NULL) + retval = RCS_getdatebranch (rcs, date, rcs->branch); + + /* if we found a match, we are done */ + if (retval != NULL) + return (retval); + + /* otherwise if we have a trunk, try it */ + if (rcs->head) + { + p = findnode (rcs->versions, rcs->head); + while (p != NULL) + { + /* if the date of this one is before date, take it */ + vers = (RCSVers *) p->data; + if (RCS_datecmp (vers->date, date) <= 0) + { + cur_rev = vers->version; + break; + } + + /* if there is a next version, find the node */ + if (vers->next != NULL) + p = findnode (rcs->versions, vers->next); + else + p = (Node *) NULL; + } + } + + /* + * at this point, either we have the revision we want, or we have the + * first revision on the trunk (1.1?) in our hands + */ + + /* if we found what we're looking for, and it's not 1.1 return it */ + if (cur_rev != NULL && strcmp (cur_rev, "1.1") != 0) + return (xstrdup (cur_rev)); + + /* look on the vendor branch */ + retval = RCS_getdatebranch (rcs, date, CVSBRANCH); + + /* + * if we found a match, return it; otherwise, we return the first + * revision on the trunk or NULL depending on force_tag_match and the + * date of the first rev + */ + if (retval != NULL) + return (retval); + + if (!force_tag_match || RCS_datecmp (vers->date, date) <= 0) + return (xstrdup (vers->version)); + else + return (NULL); +} + +/* + * Look up the last element on a branch that was put in before the specified + * date (return the rev or NULL) + */ +static char * +RCS_getdatebranch (rcs, date, branch) + RCSNode *rcs; + char *date; + char *branch; +{ + char *cur_rev = NULL; + char *cp; + char *xbranch, *xrev; + Node *p; + RCSVers *vers; + + /* look up the first revision on the branch */ + xrev = xstrdup (branch); + cp = strrchr (xrev, '.'); + if (cp == NULL) + { + free (xrev); + return (NULL); + } + *cp = '\0'; /* turn it into a revision */ + + assert (rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + p = findnode (rcs->versions, xrev); + free (xrev); + if (p == NULL) + return (NULL); + vers = (RCSVers *) p->data; + + /* if no branches list, return NULL */ + if (vers->branches == NULL) + return (NULL); + + /* walk the branches list looking for the branch number */ + xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */ + (void) strcpy (xbranch, branch); + (void) strcat (xbranch, "."); + for (p = vers->branches->list->next; p != vers->branches->list; p = p->next) + if (strncmp (p->key, xbranch, strlen (xbranch)) == 0) + break; + free (xbranch); + if (p == vers->branches->list) + return (NULL); + + p = findnode (rcs->versions, p->key); + + /* walk the next pointers until you find the end, or the date is too late */ + while (p != NULL) + { + vers = (RCSVers *) p->data; + if (RCS_datecmp (vers->date, date) <= 0) + cur_rev = vers->version; + else + break; + + /* if there is a next version, find the node */ + if (vers->next != NULL) + p = findnode (rcs->versions, vers->next); + else + p = (Node *) NULL; + } + + /* if we found something acceptable, return it - otherwise NULL */ + if (cur_rev != NULL) + return (xstrdup (cur_rev)); + else + return (NULL); +} + +/* + * Compare two dates in RCS format. Beware the change in format on January 1, + * 2000, when years go from 2-digit to full format. + */ +int +RCS_datecmp (date1, date2) + char *date1, *date2; +{ + int length_diff = strlen (date1) - strlen (date2); + + return (length_diff ? length_diff : strcmp (date1, date2)); +} + +/* + * Lookup the specified revision in the ,v file and return, in the date + * argument, the date specified for the revision *minus one second*, so that + * the logically previous revision will be found later. + * + * Returns zero on failure, RCS revision time as a Unix "time_t" on success. + */ +time_t +RCS_getrevtime (rcs, rev, date, fudge) + RCSNode *rcs; + char *rev; + char *date; + int fudge; +{ + char tdate[MAXDATELEN]; + struct tm xtm, *ftm; + time_t revdate = 0; + Node *p; + RCSVers *vers; + + /* make sure we have something to look at... */ + assert (rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* look up the revision */ + p = findnode (rcs->versions, rev); + if (p == NULL) + return (-1); + vers = (RCSVers *) p->data; + + /* split up the date */ + ftm = &xtm; + (void) sscanf (vers->date, SDATEFORM, &ftm->tm_year, &ftm->tm_mon, + &ftm->tm_mday, &ftm->tm_hour, &ftm->tm_min, + &ftm->tm_sec); + + /* If the year is from 1900 to 1999, RCS files contain only two + digits, and sscanf gives us a year from 0-99. If the year is + 2000+, RCS files contain all four digits and we subtract 1900, + because the tm_year field should contain years since 1900. */ + + if (ftm->tm_year > 1900) + ftm->tm_year -= 1900; + + /* put the date in a form getdate can grok */ +#ifdef HAVE_RCS5 + (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", ftm->tm_mon, + ftm->tm_mday, ftm->tm_year, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); +#else + (void) sprintf (tdate, "%d/%d/%d %d:%d:%d", ftm->tm_mon, + ftm->tm_mday, ftm->tm_year, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); +#endif + + /* turn it into seconds since the epoch */ + revdate = get_date (tdate, (struct timeb *) NULL); + if (revdate != (time_t) -1) + { + revdate -= fudge; /* remove "fudge" seconds */ + if (date) + { + /* put an appropriate string into ``date'' if we were given one */ +#ifdef HAVE_RCS5 + ftm = gmtime (&revdate); +#else + ftm = localtime (&revdate); +#endif + (void) sprintf (date, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); + } + } + return (revdate); +} + +List * +RCS_symbols(rcs) + RCSNode *rcs; +{ + assert(rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + if (rcs->symbols_data) { + rcs->symbols = getlist (); + do_symbols (rcs->symbols, rcs->symbols_data); + free(rcs->symbols_data); + rcs->symbols_data = NULL; + } + + return rcs->symbols; +} + +/* + * The argument ARG is the getopt remainder of the -k option specified on the + * command line. This function returns malloc'ed space that can be used + * directly in calls to RCS V5, with the -k flag munged correctly. + */ +char * +RCS_check_kflag (arg) + const char *arg; +{ + static const char *const kflags[] = + {"kv", "kvl", "k", "v", "o", "b", (char *) NULL}; + static const char *const keyword_usage[] = + { + "%s %s: invalid RCS keyword expansion mode\n", + "Valid expansion modes include:\n", + " -kkv\tGenerate keywords using the default form.\n", + " -kkvl\tLike -kkv, except locker's name inserted.\n", + " -kk\tGenerate only keyword names in keyword strings.\n", + " -kv\tGenerate only keyword values in keyword strings.\n", + " -ko\tGenerate the old keyword string (no changes from checked in file).\n", + " -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n", + NULL, + }; + char karg[10]; + char const *const *cpp = NULL; + +#ifndef HAVE_RCS5 + error (1, 0, "%s %s: your version of RCS does not support the -k option", + program_name, command_name); +#endif + + if (arg) + { + for (cpp = kflags; *cpp != NULL; cpp++) + { + if (strcmp (arg, *cpp) == 0) + break; + } + } + + if (arg == NULL || *cpp == NULL) + { + usage (keyword_usage); + } + + (void) sprintf (karg, "-k%s", *cpp); + return (xstrdup (karg)); +} + +/* + * Do some consistency checks on the symbolic tag... These should equate + * pretty close to what RCS checks, though I don't know for certain. + */ +void +RCS_check_tag (tag) + const char *tag; +{ + char *invalid = "$,.:;@"; /* invalid RCS tag characters */ + const char *cp; + + /* + * The first character must be an alphabetic letter. The remaining + * characters cannot be non-visible graphic characters, and must not be + * in the set of "invalid" RCS identifier characters. + */ + if (isalpha (*tag)) + { + for (cp = tag; *cp; cp++) + { + if (!isgraph (*cp)) + error (1, 0, "tag `%s' has non-visible graphic characters", + tag); + if (strchr (invalid, *cp)) + error (1, 0, "tag `%s' must not contain the characters `%s'", + tag, invalid); + } + } + else + error (1, 0, "tag `%s' must start with a letter", tag); +} + +/* + * Return true if RCS revision with TAG is a dead revision. + */ +int +RCS_isdead (rcs, tag) + RCSNode *rcs; + const char *tag; +{ + Node *p; + RCSVers *version; + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + p = findnode (rcs->versions, tag); + if (p == NULL) + return (0); + + version = (RCSVers *) p->data; + return (version->dead); +} + +/* Return the RCS keyword expansion mode. For example "b" for binary. + Returns a pointer into storage which is allocated and freed along with + the rest of the RCS information; the caller should not modify this + storage. Returns NULL if the RCS file does not specify a keyword + expansion mode; for all other errors, die with a fatal error. */ +char * +RCS_getexpand (rcs) + RCSNode *rcs; +{ + assert (rcs != NULL); + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + return rcs->expand; +} + +/* Stuff related to annotate command. This should perhaps be split + into the stuff which knows about the guts of RCS files, and the + command parsing type stuff. */ + +/* Linked list of allocated blocks. Seems kind of silly to + reinvent the obstack wheel, and this isn't as nice as obstacks + in some ways, but obstacks are pretty baroque. */ +struct allocblock +{ + char *text; + struct allocblock *next; +}; +struct allocblock *blocks; + +static void *block_alloc PROTO ((size_t)); + +static void * +block_alloc (n) + size_t n; +{ + struct allocblock *blk; + blk = (struct allocblock *) xmalloc (sizeof (struct allocblock)); + blk->text = xmalloc (n); + blk->next = blocks; + blocks = blk; + return blk->text; +} + +static void block_free PROTO ((void)); + +static void +block_free () +{ + struct allocblock *p; + struct allocblock *q; + + p = blocks; + while (p != NULL) + { + free (p->text); + q = p->next; + free (p); + p = q; + } + blocks = NULL; +} + +struct line +{ + /* Text of this line, terminated by \n or \0. */ + char *text; + /* Version in which it was introduced. */ + RCSVers *vers; + /* Nonzero if this line ends with \n. This will always be true + except possibly for the last line. */ + int has_newline; +}; + +struct linevector +{ + /* How many lines in use for this linevector? */ + unsigned int nlines; + /* How many lines allocated for this linevector? */ + unsigned int lines_alloced; + /* Pointer to array containing a pointer to each line. */ + struct line **vector; +}; + +static void linevector_init PROTO ((struct linevector *)); + +/* Initialize *VEC to be a linevector with no lines. */ +static void +linevector_init (vec) + struct linevector *vec; +{ + vec->lines_alloced = 10; + vec->nlines = 0; + vec->vector = (struct line **) + xmalloc (vec->lines_alloced * sizeof (*vec->vector)); +} + +static void linevector_add PROTO ((struct linevector *vec, char *text, + RCSVers *vers, unsigned int pos)); + +/* Given some text TEXT, add each of its lines to VEC before line POS + (where line 0 is the first line). The last line in TEXT may or may + not be \n terminated. All \n in TEXT are changed to \0. Set the + version for each of the new lines to VERS. */ +static void +linevector_add (vec, text, vers, pos) + struct linevector *vec; + char *text; + RCSVers *vers; + unsigned int pos; +{ + unsigned int i; + unsigned int nnew; + char *p; + struct line *lines; + + assert (vec->lines_alloced > 0); + + /* Count the number of lines we will need to add. */ + nnew = 1; + for (p = text; *p != '\0'; ++p) + if (*p == '\n' && p[1] != '\0') + ++nnew; + /* Allocate the struct line's. */ + lines = block_alloc (nnew * sizeof (struct line)); + + /* Expand VEC->VECTOR if needed. */ + if (vec->nlines + nnew >= vec->lines_alloced) + { + while (vec->nlines + nnew >= vec->lines_alloced) + vec->lines_alloced *= 2; + vec->vector = xrealloc (vec->vector, + vec->lines_alloced * sizeof (*vec->vector)); + } + + /* Make room for the new lines in VEC->VECTOR. */ + for (i = vec->nlines + nnew - 1; i >= pos + nnew; --i) + vec->vector[i] = vec->vector[i - nnew]; + + if (pos > vec->nlines) + error (1, 0, "invalid rcs file: line to add out of range"); + + /* Actually add the lines, to LINES and VEC->VECTOR. */ + i = pos; + lines[0].text = text; + lines[0].vers = vers; + lines[0].has_newline = 0; + vec->vector[i++] = &lines[0]; + for (p = text; *p != '\0'; ++p) + if (*p == '\n') + { + *p = '\0'; + lines[i - pos - 1].has_newline = 1; + if (p[1] == '\0') + /* If there are no characters beyond the last newline, we + don't consider it another line. */ + break; + lines[i - pos].text = p + 1; + lines[i - pos].vers = vers; + lines[i - pos].has_newline = 0; + vec->vector[i] = &lines[i - pos]; + ++i; + } + vec->nlines += nnew; +} + +static void linevector_delete PROTO ((struct linevector *, unsigned int, + unsigned int)); + +/* Remove NLINES lines from VEC at position POS (where line 0 is the + first line). */ +static void +linevector_delete (vec, pos, nlines) + struct linevector *vec; + unsigned int pos; + unsigned int nlines; +{ + unsigned int i; + unsigned int last; + + last = vec->nlines - nlines; + for (i = pos; i < last; ++i) + vec->vector[i] = vec->vector[i + nlines]; + vec->nlines -= nlines; +} + +static void linevector_copy PROTO ((struct linevector *, struct linevector *)); + +/* Copy FROM to TO, copying the vectors but not the lines pointed to. */ +static void +linevector_copy (to, from) + struct linevector *to; + struct linevector *from; +{ + if (from->nlines > to->lines_alloced) + { + while (from->nlines > to->lines_alloced) + to->lines_alloced *= 2; + to->vector = (struct line **) + xrealloc (to->vector, to->lines_alloced * sizeof (*to->vector)); + } + memcpy (to->vector, from->vector, + from->nlines * sizeof (*to->vector)); + to->nlines = from->nlines; +} + +static void linevector_free PROTO ((struct linevector *)); + +/* Free storage associated with linevector (that is, the vector but + not the lines pointed to). */ +static void +linevector_free (vec) + struct linevector *vec; +{ + free (vec->vector); +} + +static char *month_printname PROTO ((char *)); + +/* Given a textual string giving the month (1-12), terminated with any + character not recognized by atoi, return the 3 character name to + print it with. I do not think it is a good idea to change these + strings based on the locale; they are standard abbreviations (for + example in rfc822 mail messages) which should be widely understood. + Returns a pointer into static readonly storage. */ +static char * +month_printname (month) + char *month; +{ + static const char *const months[] = + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + int mnum; + + mnum = atoi (month); + if (mnum < 1 || mnum > 12) + return "???"; + return (char *)months[mnum - 1]; +} + +static int annotate_fileproc PROTO ((struct file_info *)); + +static int +annotate_fileproc (finfo) + struct file_info *finfo; +{ + FILE *fp; + char *key; + char *value; + RCSVers *vers; + RCSVers *prev_vers; + int n; + int ishead; + Node *node; + struct linevector headlines; + struct linevector curlines; + + if (finfo->rcs == NULL) + return (1); + + /* Distinguish output for various files if we are processing + several files. */ + cvs_outerr ("Annotations for ", 0); + cvs_outerr (finfo->fullname, 0); + cvs_outerr ("\n***************\n", 0); + + if (!(finfo->rcs->flags & PARTIAL)) + /* We are leaking memory by calling RCS_reparsefile again. */ + error (0, 0, "internal warning: non-partial rcs in annotate_fileproc"); + RCS_reparsercsfile (finfo->rcs, &fp); + + ishead = 1; + vers = NULL; + + do { + getrcsrev (fp, &key); + + /* Stash the previous version. */ + prev_vers = vers; + + /* look up the revision */ + node = findnode (finfo->rcs->versions, key); + if (node == NULL) + error (1, 0, "mismatch in rcs file %s between deltas and deltatexts", + finfo->rcs->path); + vers = (RCSVers *) node->data; + + while ((n = getrcskey (fp, &key, &value)) >= 0) + { + if (strcmp (key, "text") == 0) + { + if (ishead) + { + char *p; + + p = block_alloc (strlen (value) + 1); + strcpy (p, value); + + linevector_init (&headlines); + linevector_init (&curlines); + linevector_add (&headlines, p, NULL, 0); + linevector_copy (&curlines, &headlines); + ishead = 0; + } + else + { + char *p; + char *q; + int op; + /* The RCS format throws us for a loop in that the + deltafrags (if we define a deltafrag as an + add or a delete) need to be applied in reverse + order. So we stick them into a linked list. */ + struct deltafrag { + enum {ADD, DELETE} type; + unsigned long pos; + unsigned long nlines; + char *new_lines; + struct deltafrag *next; + }; + struct deltafrag *dfhead; + struct deltafrag *df; + + dfhead = NULL; + for (p = value; p != NULL && *p != '\0'; ) + { + op = *p++; + if (op != 'a' && op != 'd') + /* Can't just skip over the deltafrag, because + the value of op determines the syntax. */ + error (1, 0, "unrecognized operation '%c' in %s", + op, finfo->rcs->path); + df = (struct deltafrag *) + xmalloc (sizeof (struct deltafrag)); + df->next = dfhead; + dfhead = df; + df->pos = strtoul (p, &q, 10); + + if (p == q) + error (1, 0, "number expected in %s", + finfo->rcs->path); + p = q; + if (*p++ != ' ') + error (1, 0, "space expected in %s", + finfo->rcs->path); + df->nlines = strtoul (p, &q, 10); + if (p == q) + error (1, 0, "number expected in %s", + finfo->rcs->path); + p = q; + if (*p++ != '\012') + error (1, 0, "linefeed expected in %s", + finfo->rcs->path); + + if (op == 'a') + { + unsigned int i; + + df->type = ADD; + i = df->nlines; + /* The text we want is the number of lines + specified, or until the end of the value, + whichever comes first (it will be the former + except in the case where we are adding a line + which does not end in newline). */ + for (q = p; i != 0; ++q) + if (*q == '\n') + --i; + else if (*q == '\0') + { + if (i != 1) + error (1, 0, "\ +invalid rcs file %s: premature end of value", + finfo->rcs->path); + else + break; + } + + /* Copy the text we are adding into allocated + space. */ + df->new_lines = block_alloc (q - p + 1); + strncpy (df->new_lines, p, q - p); + df->new_lines[q - p] = '\0'; + + p = q; + } + else + { + /* Correct for the fact that line numbers in RCS + files start with 1. */ + --df->pos; + + assert (op == 'd'); + df->type = DELETE; + } + } + for (df = dfhead; df != NULL;) + { + unsigned int ln; + + switch (df->type) + { + case ADD: + linevector_add (&curlines, df->new_lines, + NULL, df->pos); + break; + case DELETE: + if (df->pos > curlines.nlines + || df->pos + df->nlines > curlines.nlines) + error (1, 0, "\ +invalid rcs file %s (`d' operand out of range)", + finfo->rcs->path); + for (ln = df->pos; ln < df->pos + df->nlines; ++ln) + curlines.vector[ln]->vers = prev_vers; + linevector_delete (&curlines, df->pos, df->nlines); + break; + } + df = df->next; + free (dfhead); + dfhead = df; + } + } + break; + } + } + if (n < 0) + goto l_error; + } while (vers->next != NULL); + + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", finfo->rcs->path); + + /* Now print out the data we have just computed. */ + { + unsigned int ln; + + for (ln = 0; ln < headlines.nlines; ++ln) + { + char buf[80]; + /* Period which separates year from month in date. */ + char *ym; + /* Period which separates month from day in date. */ + char *md; + RCSVers *prvers; + + prvers = headlines.vector[ln]->vers; + if (prvers == NULL) + prvers = vers; + + sprintf (buf, "%-12s (%-8.8s ", + prvers->version, + prvers->author); + cvs_output (buf, 0); + + /* Now output the date. */ + ym = strchr (prvers->date, '.'); + if (ym == NULL) + cvs_output ("??-???-??", 0); + else + { + md = strchr (ym + 1, '.'); + if (md == NULL) + cvs_output ("??", 0); + else + cvs_output (md + 1, 2); + + cvs_output ("-", 1); + cvs_output (month_printname (ym + 1), 0); + cvs_output ("-", 1); + /* Only output the last two digits of the year. Our output + lines are long enough as it is without printing the + century. */ + cvs_output (ym - 2, 2); + } + cvs_output ("): ", 0); + cvs_output (headlines.vector[ln]->text, 0); + cvs_output ("\n", 1); + } + } + + if (!ishead) + { + linevector_free (&curlines); + linevector_free (&headlines); + } + block_free (); + return 0; + + l_error: + if (ferror (fp)) + error (1, errno, "cannot read %s", finfo->rcs->path); + else + error (1, 0, "%s does not appear to be a valid rcs file", + finfo->rcs->path); + /* Shut up gcc -Wall. */ + return 0; +} + +static const char *const annotate_usage[] = +{ + "Usage: %s %s [-l] [files...]\n", + "\t-l\tLocal directory only, no recursion.\n", + NULL +}; + +/* Command to show the revision, date, and author where each line of a + file was modified. Currently it will only show the trunk, all the + way to the head, but it would be useful to enhance it to (a) allow + one to specify a revision, and display only as far as that (easy; + just have annotate_fileproc set all the ->vers fields to NULL when + you hit that revision), and (b) handle branches (not as easy, but + doable). The user interface for both (a) and (b) could be a -r + option. */ + +int +annotate (argc, argv) + int argc; + char **argv; +{ + int local = 0; + int c; + + if (argc == -1) + usage (annotate_usage); + + optind = 0; + while ((c = getopt (argc, argv, "+l")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case '?': + default: + usage (annotate_usage); + break; + } + } + argc -= optind; + argv += optind; + +#ifdef CLIENT_SUPPORT + if (client_active) + { + start_server (); + ign_setup (); + + if (local) + send_arg ("-l"); + send_file_names (argc, argv, SEND_EXPAND_WILD); + /* FIXME: We shouldn't have to send current files, but I'm not sure + whether it works. So send the files -- + it's slower but it works. */ + send_files (argc, argv, local, 0); + send_to_server ("annotate\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + return start_recursion (annotate_fileproc, (FILESDONEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, + 1, 0); +} |