summaryrefslogtreecommitdiff
path: root/contrib/cvs/src/rcs.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/cvs/src/rcs.c')
-rw-r--r--contrib/cvs/src/rcs.c2262
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);
+}