summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gnu/usr.bin/cvs/src/cvs.h1
-rw-r--r--gnu/usr.bin/cvs/src/main.c16
-rw-r--r--gnu/usr.bin/cvs/src/rcs.c3140
3 files changed, 2817 insertions, 340 deletions
diff --git a/gnu/usr.bin/cvs/src/cvs.h b/gnu/usr.bin/cvs/src/cvs.h
index 60a8e8aefa9..d8a3ca3f9cb 100644
--- a/gnu/usr.bin/cvs/src/cvs.h
+++ b/gnu/usr.bin/cvs/src/cvs.h
@@ -363,6 +363,7 @@ extern int really_quiet, quiet;
extern int use_editor;
extern int cvswrite;
extern mode_t cvsumask;
+extern char *RCS_citag;
/* Access method specified in CVSroot. */
typedef enum {
diff --git a/gnu/usr.bin/cvs/src/main.c b/gnu/usr.bin/cvs/src/main.c
index be606cefda2..8b5a7bb81b7 100644
--- a/gnu/usr.bin/cvs/src/main.c
+++ b/gnu/usr.bin/cvs/src/main.c
@@ -43,6 +43,7 @@ int noexec = FALSE;
int readonlyfs = FALSE;
int logoff = FALSE;
mode_t cvsumask = UMASK_DFLT;
+char *RCS_citag = NULL;
char *CurDir;
@@ -956,9 +957,18 @@ parseopts(root)
*q = '\0';
if (!strncmp(buf, "tag=", 4)) {
- char *RCS_citag = strdup(buf+4);
- char *what = malloc(sizeof("RCSLOCALID")+1+strlen(RCS_citag)+1);
-
+ char *what;
+
+ RCS_citag = strdup(buf+4);
+ if (RCS_citag == NULL) {
+ printf("no memory for local tag\n");
+ return;
+ }
+ what = malloc(sizeof("RCSLOCALID")+1+strlen(RCS_citag)+1);
+ if (what == NULL) {
+ printf("no memory for local tag\n");
+ return;
+ }
sprintf(what, "RCSLOCALID=%s", RCS_citag);
putenv(what);
} else if (!strncmp(buf, "umask=", 6)) {
diff --git a/gnu/usr.bin/cvs/src/rcs.c b/gnu/usr.bin/cvs/src/rcs.c
index 9722b338bfb..a56f312c498 100644
--- a/gnu/usr.bin/cvs/src/rcs.c
+++ b/gnu/usr.bin/cvs/src/rcs.c
@@ -11,24 +11,35 @@
#include <assert.h>
#include "cvs.h"
-#ifndef lint
-static const char rcsid[] = "$CVSid: @(#)rcs.c 1.40 94/10/07 $";
-USE(rcsid);
-#endif
+/* The RCS -k options, and a set of enums that must match the array.
+ These come first so that we can use enum kflag in function
+ prototypes. */
+static const char *const kflags[] =
+ {"kv", "kvl", "k", "v", "o", "b", (char *) NULL};
+enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B };
static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile));
+static void RCS_reparsercsfile PROTO((RCSNode *, int, FILE **));
static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch));
-static int getrcskey PROTO((FILE * fp, char **keyp, char **valp));
-static int parse_rcs_proc PROTO((Node * file, void *closure));
+static int getrcskey PROTO((FILE * fp, char **keyp, char **valp,
+ size_t *lenp));
+static void getrcsrev PROTO ((FILE *fp, char **revp));
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 null_delproc PROTO((Node * p));
-static void rcsnode_delproc PROTO((Node * p));
+static void free_rcsnode_contents PROTO((RCSNode *));
static void rcsvers_delproc PROTO((Node * p));
-
-static List *rcslist;
-static char *repository;
+static char *translate_symtag PROTO((RCSNode *, const char *));
+static char *printable_date PROTO((const char *));
+static char *escape_keyword_value PROTO ((const char *, int *));
+static void expand_keywords PROTO((RCSNode *, RCSVers *, const char *,
+ const char *, size_t, enum kflag, char *,
+ size_t, char **, size_t *));
+static void cmp_file_buffer PROTO((void *, const char *, size_t));
+
+enum rcs_delta_op {RCS_ANNOTATE, RCS_FETCH};
+static void RCS_deltas PROTO ((RCSNode *, FILE *, char *, enum rcs_delta_op,
+ char **, size_t *, char **, size_t *));
/*
* We don't want to use isspace() from the C library because:
@@ -59,74 +70,12 @@ static const char spacetab[] = {
#define whitespace(c) (spacetab[(unsigned char)c] != 0)
-/*
- * Parse all the rcs files specified and return a list
- */
-List *
-RCS_parsefiles (files, xrepos)
- List *files;
- char *xrepos;
-{
- /* initialize */
- repository = xrepos;
- rcslist = getlist ();
-
- /* walk the list parsing files */
- if (walklist (files, parse_rcs_proc, NULL) != 0)
- {
- /* free the list and return NULL on error */
- dellist (&rcslist);
- return ((List *) NULL);
- }
- else
- /* return the list we built */
- return (rcslist);
-}
-
-/*
- * Parse an rcs file into a node on the rcs list
- */
-static int
-parse_rcs_proc (file, closure)
- Node *file;
- void *closure;
-{
- RCSNode *rdata;
-
- /* parse the rcs file into rdata */
- rdata = RCS_parse (file->key, repository);
-
- /* if we got a valid RCSNode back, put it on the list */
- if (rdata != (RCSNode *) NULL)
- RCS_addnode (file->key, rdata, rcslist);
-
- return (0);
-}
-
-/*
- * Add an RCSNode to a list of them.
- */
-
-void
-RCS_addnode (file, rcs, list)
- const char *file;
- RCSNode *rcs;
- List *list;
-{
- Node *p;
-
- p = getnode ();
- p->key = xstrdup (file);
- p->delproc = rcsnode_delproc;
- p->type = RCSNODE;
- p->data = (char *) rcs;
- (void) addnode (list, p);
-}
-
-/*
- * Parse an rcsfile given a user file name and a repository
- */
+/* Parse an rcsfile given a user file name and a repository. If there is
+ an error, we print an error message and return NULL. If the file
+ does not exist, we return NULL without printing anything (I'm not
+ sure this allows the caller to do anything reasonable, but it is
+ the current behavior). */
RCSNode *
RCS_parse (file, repos)
const char *file;
@@ -134,38 +83,31 @@ RCS_parse (file, repos)
{
RCSNode *rcs;
FILE *fp;
- char rcsfile[PATH_MAX];
-
-#ifdef LINES_CRLF_TERMINATED
- /* Some ports of RCS to Windows NT write RCS files with newline-
- delimited lines. We would need to pass fopen a "binary" flag. */
- abort ();
-#endif
+ RCSNode *retval;
+ char *rcsfile;
+ rcsfile = xmalloc (strlen (repos) + strlen (file)
+ + sizeof (RCSEXT) + sizeof (CVSATTIC) + 10);
(void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
- if ((fp = fopen (rcsfile, "r")) != NULL)
+ if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL)
{
rcs = RCS_parsercsfile_i(fp, rcsfile);
if (rcs != NULL)
rcs->flags |= VALID;
fclose (fp);
- return (rcs);
+ retval = rcs;
+ goto out;
}
- else if (errno != ENOENT)
+ else if (! existence_error (errno))
{
error (0, errno, "cannot open %s", rcsfile);
- return NULL;
+ retval = NULL;
+ goto out;
}
-#ifdef LINES_CRLF_TERMINATED
- /* Some ports of RCS to Windows NT write RCS files with newline-
- delimited lines. We would need to pass fopen a "binary" flag. */
- abort ();
-#endif
-
(void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT);
- if ((fp = fopen (rcsfile, "r")) != NULL)
+ if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL)
{
rcs = RCS_parsercsfile_i(fp, rcsfile);
if (rcs != NULL)
@@ -175,15 +117,78 @@ RCS_parse (file, repos)
}
fclose (fp);
- return (rcs);
+ retval = rcs;
+ goto out;
}
- else if (errno != ENOENT)
+ else if (! existence_error (errno))
{
error (0, errno, "cannot open %s", rcsfile);
- return NULL;
+ retval = NULL;
+ goto out;
}
+#if defined (SERVER_SUPPORT) && !defined (FILENAMES_CASE_INSENSITIVE)
+ else if (ign_case)
+ {
+ int status;
+ char *found_path;
+
+ /* The client might be asking for a file which we do have
+ (which the client doesn't know about), but for which the
+ filename case differs. We only consider this case if the
+ regular CVS_FOPENs fail, because fopen_case is such an
+ expensive call. */
+ (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
+ status = fopen_case (rcsfile, "rb", &fp, &found_path);
+ if (status == 0)
+ {
+ rcs = RCS_parsercsfile_i (fp, rcsfile);
+ if (rcs != NULL)
+ rcs->flags |= VALID;
+
+ fclose (fp);
+ free (rcs->path);
+ rcs->path = found_path;
+ retval = rcs;
+ goto out;
+ }
+ else if (! existence_error (status))
+ {
+ error (0, status, "cannot open %s", rcsfile);
+ retval = NULL;
+ goto out;
+ }
- return (NULL);
+ (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT);
+ status = fopen_case (rcsfile, "rb", &fp, &found_path);
+ if (status == 0)
+ {
+ rcs = RCS_parsercsfile_i (fp, rcsfile);
+ if (rcs != NULL)
+ {
+ rcs->flags |= INATTIC;
+ rcs->flags |= VALID;
+ }
+
+ fclose (fp);
+ free (rcs->path);
+ rcs->path = found_path;
+ retval = rcs;
+ goto out;
+ }
+ else if (! existence_error (status))
+ {
+ error (0, status, "cannot open %s", rcsfile);
+ retval = NULL;
+ goto out;
+ }
+ }
+#endif
+ retval = NULL;
+
+ out:
+ free (rcsfile);
+
+ return retval;
}
/*
@@ -196,14 +201,8 @@ RCS_parsercsfile (rcsfile)
FILE *fp;
RCSNode *rcs;
-#ifdef LINES_CRLF_TERMINATED
- /* Some ports of RCS to Windows NT write RCS files with newline-
- delimited lines. We would need to pass fopen a "binary" flag. */
- abort ();
-#endif
-
/* open the rcsfile */
- if ((fp = fopen (rcsfile, "r")) == NULL)
+ if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL)
{
error (0, errno, "Couldn't open rcs file `%s'", rcsfile);
return (NULL);
@@ -238,13 +237,17 @@ RCS_parsercsfile_i (fp, rcsfile)
* information. Those that do call XXX to completely parse the
* RCS file. */
- if (getrcskey (fp, &key, &value) == -1 || key == NULL)
+ if (getrcskey (fp, &key, &value, NULL) == -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)
+ if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL)
+ goto l_error;
+ if (strcmp (key, RCSDESC) == 0)
goto l_error;
if (strcmp (RCSBRANCH, key) == 0 && value != NULL)
@@ -281,39 +284,39 @@ l_error:
}
-/*
- * Do the real work of parsing an RCS file
- *
- * There are no allowances for error here.
- */
-void
-RCS_reparsercsfile (rdata)
+/* 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 ALL is nonzero, remember all keywords and values. Otherwise
+ only keep the ones we will need.
+
+ If PFP is NULL, close the file when done. Otherwise, leave it open
+ and store the FILE * in *PFP. */
+static void
+RCS_reparsercsfile (rdata, all, pfp)
RCSNode *rdata;
+ int all;
+ FILE **pfp;
{
FILE *fp;
char *rcsfile;
- Node *q, *r;
+ Node *q;
RCSVers *vnode;
int n;
char *cp;
char *key, *value;
+ assert (rdata != NULL);
rcsfile = rdata->path;
-#ifdef LINES_CRLF_TERMINATED
- /* Some ports of RCS to Windows NT write RCS files with newline-
- delimited lines. We would need to pass fopen a "binary" flag. */
- abort ();
-#endif
-
- fp = fopen(rcsfile, "r");
+ fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ);
if (fp == NULL)
error (1, 0, "unable to reopen `%s'", rcsfile);
/* make a node */
rdata->versions = getlist ();
- rdata->dates = getlist ();
/*
* process all the special header information, break out when we get to
@@ -325,7 +328,8 @@ RCS_reparsercsfile (rdata)
/* 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)
+ if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL
+ || strcmp (key, RCSDESC) == 0)
{
if (ferror(fp))
{
@@ -341,10 +345,14 @@ RCS_reparsercsfile (rdata)
if (strcmp (RCSSYMBOLS, key) == 0)
{
if (value != NULL)
- {
rdata->symbols_data = xstrdup(value);
- continue;
- }
+ continue;
+ }
+
+ if (strcmp (RCSEXPAND, key) == 0)
+ {
+ rdata->expand = xstrdup (value);
+ continue;
}
/*
@@ -357,6 +365,28 @@ RCS_reparsercsfile (rdata)
if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
break;
+ /* We always save lock information, so that we can handle
+ -kkvl correctly when checking out a file. We don't use a
+ special field for this information, since it will normally
+ not be set for a CVS file. */
+ if (all || strcmp (key, "locks") == 0)
+ {
+ Node *kv;
+
+ if (rdata->other == NULL)
+ rdata->other = getlist ();
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (key);
+ kv->data = xstrdup (value);
+ if (addnode (rdata->other, kv) != 0)
+ {
+ error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
+ key, rcsfile);
+ freenode (kv);
+ }
+ }
+
/* if we haven't grabbed it yet, we didn't want it */
}
@@ -368,50 +398,45 @@ RCS_reparsercsfile (rdata)
for (;;)
{
char *valp;
- char date[MAXDATELEN];
+
+ 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++;
- (void) strcpy (date, valp);
- /* get the nodes (q is by version, r is by date) */
- q = getnode ();
- r = getnode ();
- q->type = RCSVERS;
- r->type = RCSVERS;
- q->delproc = rcsvers_delproc;
- r->delproc = null_delproc;
- q->data = r->data = xmalloc (sizeof (RCSVers));
- memset (q->data, 0, sizeof (RCSVers));
- vnode = (RCSVers *) q->data;
-
- /* fill in the version before we forget it */
- q->key = vnode->version = xstrdup (key);
+ vnode->date = xstrdup (valp);
- /* throw away the author field */
- (void) getrcskey (fp, &key, &value);
+ /* Get author field. */
+ (void) getrcskey (fp, &key, &value, NULL);
+ /* 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);
- /* throw away the state field */
- (void) getrcskey (fp, &key, &value);
-#ifdef DEATH_SUPPORT
- /* Accept this regardless of DEATH_STATE, so that we can read
- repositories created with different versions of CVS. */
- if (strcmp (key, "state") != 0)
+ /* Get state field. */
+ (void) getrcskey (fp, &key, &value, NULL);
+ /* 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");
+ vnode->state = xstrdup (value);
if (strcmp (value, "dead") == 0)
{
vnode->dead = 1;
}
-#endif
-
- /* fill in the date field */
- r->key = vnode->date = xstrdup (date);
/* fill in the branch list (if any branches exist) */
- (void) getrcskey (fp, &key, &value);
+ (void) getrcskey (fp, &key, &value, NULL);
+ /* FIXME: should be handling various error conditions better. */
+ if (key != NULL && strcmp (key, RCSDESC) == 0)
+ value = NULL;
if (value != (char *) NULL)
{
vnode->branches = getlist ();
@@ -419,7 +444,10 @@ unable to parse rcs file; `state' not in the expected place");
}
/* fill in the next field if there is a next revision */
- (void) getrcskey (fp, &key, &value);
+ (void) getrcskey (fp, &key, &value, NULL);
+ /* 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);
@@ -427,27 +455,72 @@ unable to parse rcs file; `state' not in the expected place");
* at this point, we skip any user defined fields XXX - this is where
* we put the symbolic link stuff???
*/
- while ((n = getrcskey (fp, &key, &value)) >= 0)
+ /* FIXME: Does not correctly handle errors, e.g. from stdio. */
+ while ((n = getrcskey (fp, &key, &value, NULL)) >= 0)
{
-#ifdef DEATH_SUPPORT
- /* Enable use of repositories created with a CVS which defines
- DEATH_SUPPORT and not DEATH_STATE. */
+ 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;
+ if (vnode->state != NULL)
+ free (vnode->state);
+ vnode->state = xstrdup ("dead");
continue;
}
-#endif
/* 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;
+
+ if (all)
+ {
+ Node *kv;
+
+ if (vnode->other == NULL)
+ vnode->other = getlist ();
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (key);
+ kv->data = xstrdup (value);
+ if (addnode (vnode->other, kv) != 0)
+ {
+ error (0, 0,
+ "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+ key, vnode->version, rcsfile);
+ freenode (kv);
+ }
+ }
}
- /* add the nodes to the lists */
- (void) addnode (rdata->versions, q);
- (void) addnode (rdata->dates, r);
+ /* 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
@@ -457,18 +530,209 @@ unable to parse rcs file; `state' not in the expected place");
break;
}
- fclose (fp);
+ if (all && key != NULL && strcmp (key, RCSDESC) == 0)
+ {
+ Node *kv;
+
+ if (rdata->other == NULL)
+ rdata->other = getlist ();
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (key);
+ kv->data = xstrdup (value);
+ if (addnode (rdata->other, kv) != 0)
+ {
+ error (0, 0,
+ "warning: duplicate key `%s' in RCS file `%s'",
+ key, rcsfile);
+ freenode (kv);
+ }
+ }
+
+ rdata->delta_pos = ftell (fp);
+ rdata->flags &= ~NODELTA;
+
+ if (pfp == NULL)
+ {
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", rcsfile);
+ }
+ else
+ {
+ *pfp = fp;
+ }
rdata->flags &= ~PARTIAL;
}
/*
- * rcsnode_delproc - free up an RCS type node
+ * Fully parse the RCS file. Store all keyword/value pairs, fetch the
+ * log messages for each revision, and fetch add and delete counts for
+ * each revision (we could fetch the entire text for each revision,
+ * but the only caller, log_fileproc, doesn't need that information,
+ * so we don't waste the memory required to store it). The add and
+ * delete counts are stored on the OTHER field of the RCSVERSNODE
+ * structure, under the names ";add" and ";delete", so that we don't
+ * waste the memory space of extra fields in RCSVERSNODE for code
+ * which doesn't need this information.
*/
-static void
-rcsnode_delproc (p)
- Node *p;
+
+void
+RCS_fully_parse (rcs)
+ RCSNode *rcs;
{
- freercsnode ((RCSNode **) & p->data);
+ FILE *fp;
+
+ RCS_reparsercsfile (rcs, 1, &fp);
+
+ while (1)
+ {
+ int c;
+ char *key, *value;
+ size_t vallen;
+ Node *vers;
+ RCSVers *vnode;
+
+ /* Rather than try to keep track of how much information we
+ have read, just read to the end of the file. */
+ do
+ {
+ c = getc (fp);
+ if (c == EOF)
+ break;
+ } while (whitespace (c));
+ if (c == EOF)
+ break;
+ if (ungetc (c, fp) == EOF)
+ error (1, errno, "ungetc failed");
+
+ getrcsrev (fp, &key);
+ vers = findnode (rcs->versions, key);
+ if (vers == NULL)
+ error (1, 0,
+ "mismatch in rcs file %s between deltas and deltatexts",
+ rcs->path);
+
+ vnode = (RCSVers *) vers->data;
+
+ while (getrcskey (fp, &key, &value, &vallen) >= 0)
+ {
+ if (strcmp (key, "text") != 0)
+ {
+ Node *kv;
+
+ if (vnode->other == NULL)
+ vnode->other = getlist ();
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (key);
+ kv->data = xstrdup (value);
+ if (addnode (vnode->other, kv) != 0)
+ {
+ error (0, 0,
+ "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+ key, vnode->version, rcs->path);
+ freenode (kv);
+ }
+
+ continue;
+ }
+
+ if (strcmp (vnode->version, rcs->head) != 0)
+ {
+ unsigned long add, del;
+ char buf[50];
+ Node *kv;
+
+ /* This is a change text. Store the add and delete
+ counts. */
+ add = 0;
+ del = 0;
+ if (value != NULL)
+ {
+ const char *cp;
+
+ cp = value;
+ while (cp < value + vallen)
+ {
+ char op;
+ unsigned long count;
+
+ op = *cp++;
+ if (op != 'a' && op != 'd')
+ error (1, 0, "unrecognized operation '%c' in %s",
+ op, rcs->path);
+ (void) strtoul (cp, (char **) &cp, 10);
+ if (*cp++ != ' ')
+ error (1, 0, "space expected in %s",
+ rcs->path);
+ count = strtoul (cp, (char **) &cp, 10);
+ if (*cp++ != '\012')
+ error (1, 0, "linefeed expected in %s",
+ rcs->path);
+
+ if (op == 'd')
+ del += count;
+ else
+ {
+ add += count;
+ while (count != 0)
+ {
+ if (*cp == '\012')
+ --count;
+ else if (cp == value + vallen)
+ {
+ if (count != 1)
+ error (1, 0, "\
+invalid rcs file %s: premature end of value",
+ rcs->path);
+ else
+ break;
+ }
+ ++cp;
+ }
+ }
+ }
+ }
+
+ sprintf (buf, "%lu", add);
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (";add");
+ kv->data = xstrdup (buf);
+ if (addnode (vnode->other, kv) != 0)
+ {
+ error (0, 0,
+ "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+ key, vnode->version, rcs->path);
+ freenode (kv);
+ }
+
+ sprintf (buf, "%lu", del);
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (";delete");
+ kv->data = xstrdup (buf);
+ if (addnode (vnode->other, kv) != 0)
+ {
+ error (0, 0,
+ "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+ key, vnode->version, rcs->path);
+ freenode (kv);
+ }
+ }
+
+ /* We have found the "text" key which ends the data for
+ this revision. Break out of the loop and go on to the
+ next revision. */
+ break;
+ }
+ }
+
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", rcs->path);
}
/*
@@ -488,21 +752,37 @@ freercsnode (rnodep)
return;
}
free ((*rnodep)->path);
- dellist (&(*rnodep)->versions);
- dellist (&(*rnodep)->dates);
- if ((*rnodep)->symbols != (List *) NULL)
- dellist (&(*rnodep)->symbols);
- if ((*rnodep)->symbols_data != (char *) NULL)
- free ((*rnodep)->symbols_data);
if ((*rnodep)->head != (char *) NULL)
free ((*rnodep)->head);
if ((*rnodep)->branch != (char *) NULL)
free ((*rnodep)->branch);
+ free_rcsnode_contents (*rnodep);
free ((char *) *rnodep);
*rnodep = (RCSNode *) NULL;
}
/*
+ * free_rcsnode_contents - free up the contents of an RCSNode without
+ * freeing the node itself, or the file name, or the head, or the
+ * path. This returns the RCSNode to the state it is in immediately
+ * after a call to RCS_parse.
+ */
+static void
+free_rcsnode_contents (rnode)
+ RCSNode *rnode;
+{
+ dellist (&rnode->versions);
+ if (rnode->symbols != (List *) NULL)
+ dellist (&rnode->symbols);
+ if (rnode->symbols_data != (char *) NULL)
+ free (rnode->symbols_data);
+ if (rnode->expand != NULL)
+ free (rnode->expand);
+ if (rnode->other != (List *) NULL)
+ dellist (&rnode->other);
+}
+
+/*
* rcsvers_delproc - free up an RCSVers type node
*/
static void
@@ -515,23 +795,18 @@ rcsvers_delproc (p)
if (rnode->branches != (List *) NULL)
dellist (&rnode->branches);
+ if (rnode->date != (char *) NULL)
+ free (rnode->date);
if (rnode->next != (char *) NULL)
free (rnode->next);
+ if (rnode->author != (char *) NULL)
+ free (rnode->author);
+ if (rnode->other != (List *) NULL)
+ dellist (&rnode->other);
free ((char *) rnode);
}
/*
- * null_delproc - don't free anything since it will be free'd by someone else
- */
-/* ARGSUSED */
-static void
-null_delproc (p)
- Node *p;
-{
- /* don't do anything */
-}
-
-/*
* getrcskey - fill in the key and value from the rcs file the algorithm is
* as follows
*
@@ -544,6 +819,12 @@ null_delproc (p)
* 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. If LENP is not NULL, this sets *LENP to
+ * the length of *VALUEP; this is needed if the string might contain
+ * binary data.
*/
static char *key = NULL;
@@ -551,16 +832,19 @@ static char *value = NULL;
static size_t keysize = 0;
static size_t valsize = 0;
-#define ALLOCINCR 1024
-
static int
-getrcskey (fp, keyp, valp)
+getrcskey (fp, keyp, valp, lenp)
FILE *fp;
char **keyp;
char **valp;
+ size_t *lenp;
{
char *cur, *max;
int c;
+ int just_string;
+
+ if (lenp != NULL)
+ *lenp = 0;
/* skip leading whitespace */
do
@@ -581,9 +865,9 @@ getrcskey (fp, keyp, valp)
{
if (cur >= max)
{
- key = xrealloc (key, keysize + ALLOCINCR);
- cur = key + keysize;
- keysize += ALLOCINCR;
+ size_t curoff = cur - key;
+ expand_string (&key, &keysize, keysize + 1);
+ cur = key + curoff;
max = key + keysize;
}
*cur++ = c;
@@ -598,21 +882,13 @@ getrcskey (fp, keyp, valp)
}
if (cur >= max)
{
- key = xrealloc (key, keysize + ALLOCINCR);
- cur = key + keysize;
- keysize += ALLOCINCR;
+ size_t curoff = cur - key;
+ expand_string (&key, &keysize, keysize + 1);
+ cur = key + curoff;
max = key + keysize;
}
*cur = '\0';
- /* if we got "desc", we are done with the file */
- if (strcmp (RCSDESC, key) == 0)
- {
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
- }
-
/* skip whitespace between key and val */
while (whitespace (c))
{
@@ -637,6 +913,10 @@ getrcskey (fp, keyp, valp)
cur = value;
max = value + valsize;
+ just_string = (strcmp (key, RCSDESC) == 0
+ || strcmp (key, "text") == 0
+ || strcmp (key, "log") == 0);
+
/* process the value */
for (;;)
{
@@ -669,15 +949,20 @@ getrcskey (fp, keyp, valp)
if (cur >= max)
{
- value = xrealloc (value, valsize + ALLOCINCR);
- cur = value + valsize;
- valsize += ALLOCINCR;
+ size_t curoff = cur - value;
+ expand_string (&value, &valsize, valsize + 1);
+ cur = value + curoff;
max = value + valsize;
}
*cur++ = c;
}
}
+ /* The syntax for some key-value pairs is different; they
+ don't end with a semicolon. */
+ if (just_string)
+ break;
+
/* compress whitespace down to a single space */
if (whitespace (c))
{
@@ -693,9 +978,9 @@ getrcskey (fp, keyp, valp)
if (cur >= max)
{
- value = xrealloc (value, valsize + ALLOCINCR);
- cur = value + valsize;
- valsize += ALLOCINCR;
+ size_t curoff = cur - value;
+ expand_string (&value, &valsize, valsize + 1);
+ cur = value + curoff;
max = value + valsize;
}
*cur++ = ' ';
@@ -707,9 +992,9 @@ getrcskey (fp, keyp, valp)
if (cur >= max)
{
- value = xrealloc (value, valsize + ALLOCINCR);
- cur = value + valsize;
- valsize += ALLOCINCR;
+ size_t curoff = cur - value;
+ expand_string (&value, &valsize, valsize + 1);
+ cur = value + curoff;
max = value + valsize;
}
*cur++ = c;
@@ -726,22 +1011,81 @@ getrcskey (fp, keyp, valp)
/* terminate the string */
if (cur >= max)
{
- value = xrealloc (value, valsize + ALLOCINCR);
- cur = value + valsize;
- valsize += ALLOCINCR;
+ size_t curoff = cur - value;
+ expand_string (&value, &valsize, valsize + 1);
+ cur = value + curoff;
max = value + valsize;
}
*cur = '\0';
/* if the string is empty, make it null */
- if (value && *value != '\0')
+ if (value && cur != value)
+ {
*valp = value;
+ if (lenp != NULL)
+ *lenp = cur - value;
+ }
else
*valp = NULL;
*keyp = key;
return (0);
}
+/* 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)
+ {
+ size_t curoff = cur - key;
+ expand_string (&key, &keysize, keysize + 1);
+ cur = key + curoff;
+ 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)
+ {
+ size_t curoff = cur - key;
+ expand_string (&key, &keysize, keysize + 1);
+ cur = key + curoff;
+ max = key + keysize;
+ }
+ *cur = '\0';
+ *revp = key;
+}
+
/*
* process the symbols list of the rcs file
*/
@@ -827,36 +1171,43 @@ do_branches (list, val)
* The result is returned; null-string if error.
*/
char *
-RCS_getversion (rcs, tag, date, force_tag_match)
+RCS_getversion (rcs, tag, date, force_tag_match, simple_tag)
RCSNode *rcs;
char *tag;
char *date;
int force_tag_match;
+ int *simple_tag;
{
+ if (simple_tag != NULL)
+ *simple_tag = 0;
+
/* make sure we have something to look at... */
- if (rcs == NULL)
- return ((char *) NULL);
+ assert (rcs != NULL);
if (tag && date)
{
- char *cp, *rev, *tagrev;
+ char *branch, *rev;
- /*
- * 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);
- if (tagrev == NULL)
- return ((char *) NULL);
+ if (! RCS_isbranch (rcs, tag))
+ {
+ /* We can't get a particular date if the tag is not a
+ branch. */
+ return NULL;
+ }
- if ((cp = strrchr (tagrev, '.')) != NULL)
- *cp = '\0';
- rev = RCS_getdatebranch (rcs, date, tagrev);
- free (tagrev);
+ /* Work out the branch. */
+ if (! isdigit (tag[0]))
+ branch = RCS_whatbranch (rcs, tag);
+ else
+ branch = xstrdup (tag);
+
+ /* Fetch the revision of branch as of date. */
+ rev = RCS_getdatebranch (rcs, date, branch);
+ free (branch);
return (rev);
}
else if (tag)
- return (RCS_gettag (rcs, tag, force_tag_match));
+ return (RCS_gettag (rcs, tag, force_tag_match, simple_tag));
else if (date)
return (RCS_getdate (rcs, date, force_tag_match));
else
@@ -873,20 +1224,24 @@ RCS_getversion (rcs, tag, date, force_tag_match)
* If the matched tag is a branch tag, find the head of the branch.
*/
char *
-RCS_gettag (rcs, tag, force_tag_match)
+RCS_gettag (rcs, symtag, force_tag_match, simple_tag)
RCSNode *rcs;
- char *tag;
+ char *symtag;
int force_tag_match;
+ int *simple_tag;
{
- Node *p;
+ char *tag = symtag;
+ int tag_allocated = 0;
+
+ if (simple_tag != NULL)
+ *simple_tag = 0;
/* make sure we have something to look at... */
- if (rcs == NULL)
- return ((char *) NULL);
+ assert (rcs != NULL);
/* XXX this is probably not necessary, --jtc */
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs);
+ RCS_reparsercsfile (rcs, 0, NULL);
/* If tag is "HEAD", special case to get head RCS revision */
if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0'))
@@ -899,18 +1254,17 @@ RCS_gettag (rcs, tag, force_tag_match)
if (!isdigit (tag[0]))
{
+ char *version;
+
/* 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)
+ version = translate_symtag (rcs, tag);
+ if (version != NULL)
{
int dots;
char *magic, *branch, *cp;
- tag = p->data;
+ tag = version;
+ tag_allocated = 1;
/*
* If this is a magic revision, we turn it into either its
@@ -930,21 +1284,17 @@ RCS_gettag (rcs, tag, force_tag_match)
(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);
+ (void) sprintf (magic, "%s.%s", tag, branch);
branch = RCS_getbranch (rcs, magic, 1);
free (magic);
if (branch != NULL)
{
- free (xtag);
+ free (tag);
return (branch);
}
- return (xtag);
+ return (tag);
}
free (magic);
}
@@ -971,21 +1321,40 @@ RCS_gettag (rcs, tag, force_tag_match)
if ((numdots (tag) & 1) == 0)
{
+ char *branch;
+
/* we have a branch tag, so we need to walk the branch */
- return (RCS_getbranch (rcs, tag, force_tag_match));
+ branch = RCS_getbranch (rcs, tag, force_tag_match);
+ if (tag_allocated)
+ free (tag);
+ return branch;
}
else
{
+ Node *p;
+
/* we have a revision tag, so make sure it exists */
- if (rcs == NULL)
- p = NULL;
- else
- p = findnode (rcs->versions, tag);
+ p = findnode (rcs->versions, tag);
if (p != NULL)
- return (xstrdup (tag));
+ {
+ /* We have found a numeric revision for the revision tag.
+ To support expanding the RCS keyword Name, if
+ SIMPLE_TAG is not NULL, tell the the caller that this
+ is a simple tag which co will recognize. FIXME: Are
+ there other cases in which we should set this? In
+ particular, what if we expand RCS keywords internally
+ without calling co? */
+ if (simple_tag != NULL)
+ *simple_tag = 1;
+ if (! tag_allocated)
+ tag = xstrdup (tag);
+ return (tag);
+ }
else
{
/* The revision wasn't there, so return the head or NULL */
+ if (tag_allocated)
+ free (tag);
if (force_tag_match)
return (NULL);
else
@@ -1068,31 +1437,27 @@ checkmagic_proc (p, closure)
}
/*
- * Given a list of RCSNodes, returns non-zero if the specified
- * revision number or symbolic tag resolves to a "branch" within the
- * rcs file.
+ * 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 (file, rev, srcfiles)
- char *file;
- char *rev;
- List *srcfiles;
-{
- Node *p;
+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 */
- p = findnode (srcfiles, file);
- if (p == NULL)
+ if (rcs == NULL)
return (0);
/* now, look for a match in the symbols list */
- rcs = (RCSNode *) p->data;
- return (RCS_nodeisbranch (rev, rcs));
+ return (RCS_nodeisbranch (rcs, rev));
}
/*
@@ -1101,42 +1466,47 @@ RCS_isbranch (file, rev, srcfiles)
* take into account any magic branches as well.
*/
int
-RCS_nodeisbranch (rev, rcs)
- char *rev;
+RCS_nodeisbranch (rcs, rev)
RCSNode *rcs;
+ const char *rev;
{
int dots;
- Node *p;
+ char *version;
/* 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)
+ version = translate_symtag (rcs, rev);
+ if (version == NULL)
return (0);
- dots = numdots (p->data);
+ dots = numdots (version);
if ((dots & 1) == 0)
+ {
+ free (version);
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 *branch = strrchr (version, '.');
char *cp = branch - 1;
while (*cp != '.')
cp--;
/* see if we have .magic-branch. (".0.") */
- magic = xmalloc (strlen (p->data) + 1);
+ magic = xmalloc (strlen (version) + 1);
(void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
if (strncmp (magic, cp, strlen (magic)) == 0)
{
free (magic);
+ free (version);
return (1);
}
free (magic);
+ free (version);
}
return (0);
}
@@ -1146,50 +1516,47 @@ RCS_nodeisbranch (rev, rcs)
* for the specified *symbolic* tag. Magic branches are handled correctly.
*/
char *
-RCS_whatbranch (file, rev, srcfiles)
- char *file;
- char *rev;
- List *srcfiles;
+RCS_whatbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
{
+ char *version;
int dots;
- Node *p;
- RCSNode *rcs;
/* assume no branch if you can't find the RCS info */
- p = findnode (srcfiles, file);
- if (p == NULL)
+ if (rcs == NULL)
return ((char *) NULL);
/* now, look for a match in the symbols list */
- rcs = (RCSNode *) p->data;
- p = findnode (RCS_symbols(rcs), rev);
- if (p == NULL)
+ version = translate_symtag (rcs, rev);
+ if (version == NULL)
return ((char *) NULL);
- dots = numdots (p->data);
+ dots = numdots (version);
if ((dots & 1) == 0)
- return (xstrdup (p->data));
+ return (version);
/* 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 *branch = strrchr (version, '.');
char *cp = branch++ - 1;
while (*cp != '.')
cp--;
/* see if we have .magic-branch. (".0.") */
- magic = xmalloc (strlen (p->data) + 1);
+ magic = xmalloc (strlen (version) + 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 */
+ (void) sprintf (magic, "%s.%s", version, branch);
+ free (version);
return (magic);
}
free (magic);
+ free (version);
}
return ((char *) NULL);
}
@@ -1211,11 +1578,10 @@ RCS_getbranch (rcs, tag, force_tag_match)
char *cp;
/* make sure we have something to look at... */
- if (rcs == NULL)
- return ((char *) NULL);
+ assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs);
+ RCS_reparsercsfile (rcs, 0, NULL);
/* find out if the tag contains a dot, or is on the trunk */
cp = strrchr (tag, '.');
@@ -1323,16 +1689,14 @@ RCS_head (rcs)
RCSNode *rcs;
{
/* make sure we have something to look at... */
- if (rcs == NULL)
- return ((char *) NULL);
-
- if (rcs->branch)
- return (RCS_getbranch (rcs, rcs->branch, 1));
+ 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));
}
@@ -1353,11 +1717,10 @@ RCS_getdate (rcs, date, force_tag_match)
RCSVers *vers = NULL;
/* make sure we have something to look at... */
- if (rcs == NULL)
- return ((char *) NULL);
+ assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs);
+ RCS_reparsercsfile (rcs, 0, NULL);
/* if the head is on a branch, try the branch first */
if (rcs->branch != NULL)
@@ -1444,7 +1807,7 @@ RCS_getdatebranch (rcs, date, branch)
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs);
+ RCS_reparsercsfile (rcs, 0, NULL);
p = findnode (rcs->versions, xrev);
free (xrev);
@@ -1452,9 +1815,13 @@ RCS_getdatebranch (rcs, date, branch)
return (NULL);
vers = (RCSVers *) p->data;
- /* if no branches list, return NULL */
+ /* Tentatively use this revision, if it is early enough. */
+ if (RCS_datecmp (vers->date, date) <= 0)
+ cur_rev = vers->version;
+
+ /* if no branches list, return now */
if (vers->branches == NULL)
- return (NULL);
+ return xstrdup (cur_rev);
/* walk the branches list looking for the branch number */
xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */
@@ -1465,7 +1832,11 @@ RCS_getdatebranch (rcs, date, branch)
break;
free (xbranch);
if (p == vers->branches->list)
+ {
+ /* FIXME: This case would seem to imply that the RCS file is
+ somehow invalid. Should we give an error message? */
return (NULL);
+ }
p = findnode (rcs->versions, p->key);
@@ -1485,11 +1856,8 @@ RCS_getdatebranch (rcs, date, branch)
p = (Node *) NULL;
}
- /* if we found something acceptable, return it - otherwise NULL */
- if (cur_rev != NULL)
- return (xstrdup (cur_rev));
- else
- return (NULL);
+ /* Return whatever we found, which may be NULL. */
+ return xstrdup (cur_rev);
}
/*
@@ -1505,13 +1873,17 @@ RCS_datecmp (date1, 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.
- */
+/* Look up revision REV in RCS and return the date specified for the
+ revision minus FUDGE seconds (FUDGE will generally be one, so that the
+ logically previous revision will be found later, or zero, if we want
+ the exact date).
+
+ The return value is the date being returned as a time_t, or (time_t)-1
+ on error (previously was documented as zero on error; I haven't checked
+ the callers to make sure that they really check for (time_t)-1, but
+ the latter is what this function really returns). If DATE is non-NULL,
+ then it must point to MAXDATELEN characters, and we store the same
+ return value there in DATEFORM format. */
time_t
RCS_getrevtime (rcs, rev, date, fudge)
RCSNode *rcs;
@@ -1526,11 +1898,10 @@ RCS_getrevtime (rcs, rev, date, fudge)
RCSVers *vers;
/* make sure we have something to look at... */
- if (rcs == NULL)
- return (revdate);
+ assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs);
+ RCS_reparsercsfile (rcs, 0, NULL);
/* look up the revision */
p = findnode (rcs->versions, rev);
@@ -1543,17 +1914,23 @@ RCS_getrevtime (rcs, rev, date, fudge)
(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_mday, ftm->tm_year + 1900, 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_mday, ftm->tm_year + 1900, ftm->tm_hour,
ftm->tm_min, ftm->tm_sec);
#endif
@@ -1586,7 +1963,7 @@ RCS_symbols(rcs)
assert(rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs);
+ RCS_reparsercsfile (rcs, 0, NULL);
if (rcs->symbols_data) {
rcs->symbols = getlist ();
@@ -1599,6 +1976,69 @@ RCS_symbols(rcs)
}
/*
+ * Return the version associated with a particular symbolic tag.
+ */
+static char *
+translate_symtag (rcs, tag)
+ RCSNode *rcs;
+ const char *tag;
+{
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, 0, NULL);
+
+ if (rcs->symbols != NULL)
+ {
+ Node *p;
+
+ /* The symbols have already been converted into a list. */
+ p = findnode (rcs->symbols, tag);
+ if (p == NULL)
+ return NULL;
+
+ return xstrdup (p->data);
+ }
+
+ if (rcs->symbols_data != NULL)
+ {
+ size_t len;
+ char *cp;
+
+ /* Look through the RCS symbols information. This is like
+ do_symbols, but we don't add the information to a list. In
+ most cases, we will only be called once for this file, so
+ generating the list is unnecessary overhead. */
+
+ len = strlen (tag);
+ cp = rcs->symbols_data;
+ while ((cp = strchr (cp, tag[0])) != NULL)
+ {
+ if ((cp == rcs->symbols_data || whitespace (cp[-1]))
+ && strncmp (cp, tag, len) == 0
+ && cp[len] == ':')
+ {
+ char *v, *r;
+
+ /* We found the tag. Return the version number. */
+
+ cp += len + 1;
+ v = cp;
+ while (! whitespace (*cp) && *cp != '\0')
+ ++cp;
+ r = xmalloc (cp - v + 1);
+ strncpy (r, v, cp - v);
+ r[cp - v] = '\0';
+ return r;
+ }
+
+ while (! whitespace (*cp) && *cp != '\0')
+ ++cp;
+ }
+ }
+
+ return NULL;
+}
+
+/*
* 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.
@@ -1607,8 +2047,6 @@ 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",
@@ -1621,6 +2059,7 @@ RCS_check_kflag (arg)
" -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n",
NULL,
};
+ /* Big enough to hold any of the strings from kflags. */
char karg[10];
char const *const *cpp = NULL;
@@ -1679,7 +2118,6 @@ RCS_check_tag (tag)
error (1, 0, "tag `%s' must start with a letter", tag);
}
-#ifdef DEATH_SUPPORT
/*
* Return true if RCS revision with TAG is a dead revision.
*/
@@ -1692,7 +2130,7 @@ RCS_isdead (rcs, tag)
RCSVers *version;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs);
+ RCS_reparsercsfile (rcs, 0, NULL);
p = findnode (rcs->versions, tag);
if (p == NULL)
@@ -1701,4 +2139,2032 @@ RCS_isdead (rcs, tag)
version = (RCSVers *) p->data;
return (version->dead);
}
-#endif /* DEATH_SUPPORT */
+
+/* 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, 0, NULL);
+ return rcs->expand;
+}
+
+/* RCS keywords, and a matching enum. */
+struct rcs_keyword
+{
+ const char *string;
+ size_t len;
+};
+#define KEYWORD_INIT(s) (s), sizeof (s) - 1
+static struct rcs_keyword keywords[] =
+{
+ { KEYWORD_INIT ("Author") },
+ { KEYWORD_INIT ("Date") },
+ { KEYWORD_INIT ("Header") },
+ { KEYWORD_INIT ("Id") },
+ { KEYWORD_INIT ("Locker") },
+ { KEYWORD_INIT ("Log") },
+ { KEYWORD_INIT ("Name") },
+ { KEYWORD_INIT ("RCSfile") },
+ { KEYWORD_INIT ("Revision") },
+ { KEYWORD_INIT ("Source") },
+ { KEYWORD_INIT ("State") },
+ { NULL, 0 },
+ { NULL, 0 }
+};
+enum keyword
+{
+ KEYWORD_AUTHOR = 0,
+ KEYWORD_DATE,
+ KEYWORD_HEADER,
+ KEYWORD_ID,
+ KEYWORD_LOCKER,
+ KEYWORD_LOG,
+ KEYWORD_NAME,
+ KEYWORD_RCSFILE,
+ KEYWORD_REVISION,
+ KEYWORD_SOURCE,
+ KEYWORD_STATE,
+ KEYWORD_LOCALID
+};
+
+/* Convert an RCS date string into a readable string. This is like
+ the RCS date2str function. */
+
+static char *
+printable_date (rcs_date)
+ const char *rcs_date;
+{
+ int year, mon, mday, hour, min, sec;
+ char buf[100];
+
+ (void) sscanf (rcs_date, SDATEFORM, &year, &mon, &mday, &hour, &min,
+ &sec);
+ if (year < 1900)
+ year += 1900;
+ sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
+ hour, min, sec);
+ return xstrdup (buf);
+}
+
+/* Escape the characters in a string so that it can be included in an
+ RCS value. */
+
+static char *
+escape_keyword_value (value, free_value)
+ const char *value;
+ int *free_value;
+{
+ char *ret, *t;
+ const char *s;
+
+ for (s = value; *s != '\0'; s++)
+ {
+ char c;
+
+ c = *s;
+ if (c == '\t'
+ || c == '\n'
+ || c == '\\'
+ || c == ' '
+ || c == '$')
+ {
+ break;
+ }
+ }
+
+ if (*s == '\0')
+ {
+ *free_value = 0;
+ return (char *) value;
+ }
+
+ ret = xmalloc (strlen (value) * 4 + 1);
+ *free_value = 1;
+
+ for (s = value, t = ret; *s != '\0'; s++, t++)
+ {
+ switch (*s)
+ {
+ default:
+ *t = *s;
+ break;
+ case '\t':
+ *t++ = '\\';
+ *t = 't';
+ break;
+ case '\n':
+ *t++ = '\\';
+ *t = 'n';
+ break;
+ case '\\':
+ *t++ = '\\';
+ *t = '\\';
+ break;
+ case ' ':
+ *t++ = '\\';
+ *t++ = '0';
+ *t++ = '4';
+ *t = '0';
+ break;
+ case '$':
+ *t++ = '\\';
+ *t++ = '0';
+ *t++ = '4';
+ *t = '4';
+ break;
+ }
+ }
+
+ *t = '\0';
+
+ return ret;
+}
+
+/* Expand RCS keywords in the memory buffer BUF of length LEN. This
+ applies to file RCS and version VERS. If NAME is not NULL, and is
+ not a numeric revision, then it is the symbolic tag used for the
+ checkout. EXPAND indicates how to expand the keywords. This
+ function sets *RETBUF and *RETLEN to the new buffer and length.
+ This function may modify the buffer BUF. If BUF != *RETBUF, then
+ RETBUF is a newly allocated buffer. */
+
+static void
+expand_keywords (rcs, ver, name, log, loglen, expand, buf, len, retbuf, retlen)
+ RCSNode *rcs;
+ RCSVers *ver;
+ const char *name;
+ const char *log;
+ size_t loglen;
+ enum kflag expand;
+ char *buf;
+ size_t len;
+ char **retbuf;
+ size_t *retlen;
+{
+ struct expand_buffer
+ {
+ struct expand_buffer *next;
+ char *data;
+ size_t len;
+ int free_data;
+ } *ebufs = NULL;
+ struct expand_buffer *ebuf_last = NULL;
+ size_t ebuf_len = 0;
+ char *locker;
+ char *srch, *srch_next;
+ size_t srch_len;
+
+ if (expand == KFLAG_O || expand == KFLAG_B)
+ {
+ *retbuf = buf;
+ *retlen = len;
+ return;
+ }
+
+ if (RCS_citag != NULL && keywords[KEYWORD_LOCALID].string == NULL) {
+ keywords[KEYWORD_LOCALID].string = RCS_citag;
+ keywords[KEYWORD_LOCALID].len = strlen(RCS_citag);
+ }
+
+ /* If we are using -kkvl, dig out the locker information if any. */
+ locker = NULL;
+ if (expand == KFLAG_KVL && rcs->other != NULL)
+ {
+ Node *p;
+
+ p = findnode (rcs->other, "locks");
+ if (p != NULL)
+ {
+ char *cp;
+ size_t verlen;
+
+ /* The format of the locking information is
+ USER:VERSION USER:VERSION ...
+ If we find our version on the list, we set LOCKER to
+ the corresponding user name. */
+
+ verlen = strlen (ver->version);
+ cp = p->data;
+ while ((cp = strstr (cp, ver->version)) != NULL)
+ {
+ if (cp > p->data
+ && cp[-1] == ':'
+ && (cp[verlen] == '\0'
+ || whitespace (cp[verlen])))
+ {
+ char *cpend;
+
+ --cp;
+ cpend = cp;
+ while (cp > p->data && ! whitespace (*cp))
+ --cp;
+ locker = xmalloc (cpend - cp + 1);
+ memcpy (locker, cp, cpend - cp);
+ locker[cpend - cp] = '\0';
+ break;
+ }
+
+ ++cp;
+ }
+ }
+ }
+
+ /* RCS keywords look like $STRING$ or $STRING: VALUE$. */
+ srch = buf;
+ srch_len = len;
+ while ((srch_next = memchr (srch, '$', srch_len)) != NULL)
+ {
+ char *s, *send;
+ size_t slen;
+ const struct rcs_keyword *keyword;
+ enum keyword kw;
+ char *value;
+ int free_value;
+ char *sub;
+ size_t sublen;
+
+ srch_len -= (srch_next + 1) - srch;
+ srch = srch_next + 1;
+
+ /* Look for the first non alphabetic character after the '$'. */
+ send = srch + srch_len;
+ for (s = srch; s < send; s++)
+ if (! isalpha (*s))
+ break;
+
+ /* If the first non alphabetic character is not '$' or ':',
+ then this is not an RCS keyword. */
+ if (s == send || (*s != '$' && *s != ':'))
+ continue;
+
+ /* See if this is one of the keywords. */
+ slen = s - srch;
+ for (keyword = keywords; keyword->string != NULL; keyword++)
+ {
+ if (keyword->len == slen
+ && strncmp (keyword->string, srch, slen) == 0)
+ {
+ break;
+ }
+ }
+ if (keyword->string == NULL)
+ continue;
+
+ kw = (enum keyword) (keyword - keywords);
+
+ /* If the keyword ends with a ':', then the old value consists
+ of the characters up to the next '$'. If there is no '$'
+ before the end of the line, though, then this wasn't an RCS
+ keyword after all. */
+ if (*s == ':')
+ {
+ for (; s < send; s++)
+ if (*s == '$' || *s == '\n')
+ break;
+ if (s == send || *s != '$')
+ continue;
+ }
+
+ /* At this point we must replace the string from SRCH to S
+ with the expansion of the keyword KW. */
+
+ /* Get the value to use. */
+ free_value = 0;
+ if (expand == KFLAG_K)
+ value = NULL;
+ else
+ {
+ switch (kw)
+ {
+ default:
+ abort ();
+
+ case KEYWORD_AUTHOR:
+ value = ver->author;
+ break;
+
+ case KEYWORD_DATE:
+ value = printable_date (ver->date);
+ free_value = 1;
+ break;
+
+ case KEYWORD_HEADER:
+ case KEYWORD_ID:
+ case KEYWORD_LOCALID:
+ {
+ char *path;
+ int free_path;
+ char *date;
+
+ if (kw == KEYWORD_HEADER)
+ path = rcs->path;
+ else
+ path = last_component (rcs->path);
+ path = escape_keyword_value (path, &free_path);
+ date = printable_date (ver->date);
+ value = xmalloc (strlen (path)
+ + strlen (ver->version)
+ + strlen (date)
+ + strlen (ver->author)
+ + strlen (ver->state)
+ + (locker == NULL ? 0 : strlen (locker))
+ + 20);
+
+ sprintf (value, "%s %s %s %s %s%s%s",
+ path, ver->version, date, ver->author,
+ ver->state,
+ locker != NULL ? " " : "",
+ locker != NULL ? locker : "");
+ if (free_path)
+ free (path);
+ free (date);
+ free_value = 1;
+ }
+ break;
+
+ case KEYWORD_LOCKER:
+ value = locker;
+ break;
+
+ case KEYWORD_LOG:
+ case KEYWORD_RCSFILE:
+ value = escape_keyword_value (last_component (rcs->path),
+ &free_value);
+ break;
+
+ case KEYWORD_NAME:
+ if (name != NULL && ! isdigit (*name))
+ value = (char *) name;
+ else
+ value = NULL;
+ break;
+
+ case KEYWORD_REVISION:
+ value = ver->version;
+ break;
+
+ case KEYWORD_SOURCE:
+ value = escape_keyword_value (rcs->path, &free_value);
+ break;
+
+ case KEYWORD_STATE:
+ value = ver->state;
+ break;
+ }
+ }
+
+ sub = xmalloc (keyword->len
+ + (value == NULL ? 0 : strlen (value))
+ + 10);
+ if (expand == KFLAG_V)
+ {
+ /* Decrement SRCH and increment S to remove the $
+ characters. */
+ --srch;
+ ++srch_len;
+ ++s;
+ sublen = 0;
+ }
+ else
+ {
+ strcpy (sub, keyword->string);
+ sublen = strlen (keyword->string);
+ if (expand != KFLAG_K)
+ {
+ sub[sublen] = ':';
+ sub[sublen + 1] = ' ';
+ sublen += 2;
+ }
+ }
+ if (value != NULL)
+ {
+ strcpy (sub + sublen, value);
+ sublen += strlen (value);
+ }
+ if (expand != KFLAG_V && expand != KFLAG_K)
+ {
+ sub[sublen] = ' ';
+ ++sublen;
+ sub[sublen] = '\0';
+ }
+
+ if (free_value)
+ free (value);
+
+ /* The Log keyword requires special handling. This behaviour
+ is taken from RCS 5.7. The special log message is what RCS
+ uses for ci -k. */
+ if (kw == KEYWORD_LOG
+ && (sizeof "checked in with -k by " <= loglen
+ || strncmp (log, "checked in with -k by ",
+ sizeof "checked in with -k by " - 1) != 0))
+ {
+ char *start;
+ char *leader;
+ size_t leader_len, leader_sp_len;
+ const char *logend;
+ const char *snl;
+ int cnl;
+ char *date;
+ const char *sl;
+
+ /* We are going to insert the trailing $ ourselves, before
+ the log message, so we must remove it from S, if we
+ haven't done so already. */
+ if (expand != KFLAG_V)
+ ++s;
+
+ /* Find the start of the line. */
+ start = srch;
+ while (start > buf && start[-1] != '\n')
+ --start;
+
+ /* Copy the start of the line to use as a comment leader. */
+ leader_len = srch - start;
+ if (expand != KFLAG_V)
+ --leader_len;
+ leader = xmalloc (leader_len);
+ memcpy (leader, start, leader_len);
+ leader_sp_len = leader_len;
+ while (leader_sp_len > 0 && leader[leader_sp_len - 1] == ' ')
+ --leader_sp_len;
+
+ /* RCS does some checking for an old style of Log here,
+ but we don't bother. RCS issues a warning if it
+ changes anything. */
+
+ /* Count the number of newlines in the log message so that
+ we know how many copies of the leader we will need. */
+ cnl = 0;
+ logend = log + loglen;
+ for (snl = log; snl < logend; snl++)
+ if (*snl == '\n')
+ ++cnl;
+
+ date = printable_date (ver->date);
+ sub = xrealloc (sub,
+ (sublen
+ + sizeof "Revision"
+ + strlen (ver->version)
+ + strlen (date)
+ + strlen (ver->author)
+ + loglen
+ + (cnl + 2) * leader_len
+ + 20));
+ if (expand != KFLAG_V)
+ {
+ sub[sublen] = '$';
+ ++sublen;
+ }
+ sub[sublen] = '\n';
+ ++sublen;
+ memcpy (sub + sublen, leader, leader_len);
+ sublen += leader_len;
+ sprintf (sub + sublen, "Revision %s %s %s\n",
+ ver->version, date, ver->author);
+ sublen += strlen (sub + sublen);
+ free (date);
+
+ sl = log;
+ while (sl < logend)
+ {
+ if (*sl == '\n')
+ {
+ memcpy (sub + sublen, leader, leader_sp_len);
+ sublen += leader_sp_len;
+ sub[sublen] = '\n';
+ ++sublen;
+ ++sl;
+ }
+ else
+ {
+ const char *slnl;
+
+ memcpy (sub + sublen, leader, leader_len);
+ sublen += leader_len;
+ for (slnl = sl; slnl < logend && *slnl != '\n'; ++slnl)
+ ;
+ if (slnl < logend)
+ ++slnl;
+ memcpy (sub + sublen, sl, slnl - sl);
+ sublen += slnl - sl;
+ sl = slnl;
+ }
+ }
+
+ memcpy (sub + sublen, leader, leader_sp_len);
+ sublen += leader_sp_len;
+
+ free (leader);
+ }
+
+ /* Now SUB contains a string which is to replace the string
+ from SRCH to S. SUBLEN is the length of SUB. */
+
+ if (sublen == s - srch)
+ {
+ memcpy (srch, sub, sublen);
+ free (sub);
+ }
+ else
+ {
+ struct expand_buffer *ebuf;
+
+ /* We need to change the size of the buffer. We build a
+ list of expand_buffer structures. Each expand_buffer
+ structure represents a portion of the final output. We
+ concatenate them back into a single buffer when we are
+ done. This minimizes the number of potentially large
+ buffer copies we must do. */
+
+ if (ebufs == NULL)
+ {
+ ebufs = (struct expand_buffer *) xmalloc (sizeof *ebuf);
+ ebufs->next = NULL;
+ ebufs->data = buf;
+ ebufs->free_data = 0;
+ ebuf_len = srch - buf;
+ ebufs->len = ebuf_len;
+ ebuf_last = ebufs;
+ }
+ else
+ {
+ assert (srch >= ebuf_last->data);
+ assert (srch - ebuf_last->data <= ebuf_last->len);
+ ebuf_len -= ebuf_last->len - (srch - ebuf_last->data);
+ ebuf_last->len = srch - ebuf_last->data;
+ }
+
+ ebuf = (struct expand_buffer *) xmalloc (sizeof *ebuf);
+ ebuf->data = sub;
+ ebuf->len = sublen;
+ ebuf->free_data = 1;
+ ebuf->next = NULL;
+ ebuf_last->next = ebuf;
+ ebuf_last = ebuf;
+ ebuf_len += sublen;
+
+ ebuf = (struct expand_buffer *) xmalloc (sizeof *ebuf);
+ ebuf->data = s;
+ ebuf->len = srch_len - (s - srch);
+ ebuf->free_data = 0;
+ ebuf->next = NULL;
+ ebuf_last->next = ebuf;
+ ebuf_last = ebuf;
+ ebuf_len += srch_len - (s - srch);
+ }
+
+ srch_len -= (s - srch);
+ srch = s;
+ }
+
+ if (locker != NULL)
+ free (locker);
+
+ if (ebufs == NULL)
+ {
+ *retbuf = buf;
+ *retlen = len;
+ }
+ else
+ {
+ char *ret;
+
+ ret = xmalloc (ebuf_len);
+ *retbuf = ret;
+ *retlen = ebuf_len;
+ while (ebufs != NULL)
+ {
+ struct expand_buffer *next;
+
+ memcpy (ret, ebufs->data, ebufs->len);
+ ret += ebufs->len;
+ if (ebufs->free_data)
+ free (ebufs->data);
+ next = ebufs->next;
+ free (ebufs);
+ ebufs = next;
+ }
+ }
+}
+
+/* Check out a revision from an RCS file.
+
+ If PFN is not NULL, then ignore WORKFILE and SOUT. Call PFN zero
+ or more times with the contents of the file. CALLERDAT is passed,
+ uninterpreted, to PFN. (The current code will always call PFN
+ exactly once for a non empty file; however, the current code
+ assumes that it can hold the entire file contents in memory, which
+ is not a good assumption, and might change in the future).
+
+ Otherwise, if WORKFILE is not NULL, check out the revision to
+ WORKFILE. However, if WORKFILE is not NULL, and noexec is set,
+ then don't do anything.
+
+ Otherwise, if WORKFILE is NULL, check out the revision to SOUT. If
+ SOUT is RUN_TTY, then write the contents of the revision to
+ standard output. When using SOUT, the output is generally a
+ temporary file; don't bother to get the file modes correct.
+
+ REV is the numeric revision to check out. It may be NULL, which
+ means to check out the head of the default branch.
+
+ If NAMETAG is not NULL, and is not a numeric revision, then it is
+ the tag that should be used when expanding the RCS Name keyword.
+
+ OPTIONS is a string such as "-kb" or "-kv" for keyword expansion
+ options. It may be NULL to use the default expansion mode of the
+ file, typically "-kkv". */
+
+int
+RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
+ RCSNode *rcs;
+ char *workfile;
+ char *rev;
+ char *nametag;
+ char *options;
+ char *sout;
+ RCSCHECKOUTPROC pfn;
+ void *callerdat;
+{
+ int free_rev = 0;
+ enum kflag expand;
+ FILE *fp;
+ struct stat sb;
+ char *key;
+ char *value;
+ size_t len;
+ int free_value = 0;
+ char *log = NULL;
+ size_t loglen;
+ FILE *ofp;
+
+ if (trace)
+ {
+ (void) fprintf (stderr, "%s-> checkout (%s, %s, %s, %s)\n",
+#ifdef SERVER_SUPPORT
+ server_active ? "S" : " ",
+#else
+ "",
+#endif
+ rcs->path,
+ rev != NULL ? rev : "",
+ options != NULL ? options : "",
+ (pfn != NULL ? "(function)"
+ : (workfile != NULL
+ ? workfile
+ : (sout != RUN_TTY ? sout : "(stdout)"))));
+ }
+
+ assert (rev == NULL || isdigit (*rev));
+
+ if (noexec && workfile != NULL)
+ return 0;
+
+ assert (sout == RUN_TTY || workfile == NULL);
+ assert (pfn == NULL || (sout == RUN_TTY && workfile == NULL));
+
+ /* Some callers, such as Checkin or remove_file, will pass us a
+ branch. */
+ if (rev != NULL && (numdots (rev) & 1) == 0)
+ {
+ rev = RCS_getbranch (rcs, rev, 1);
+ if (rev == NULL)
+ error (1, 0, "internal error: bad branch tag in checkout");
+ free_rev = 1;
+ }
+
+ if (rev == NULL || strcmp (rev, rcs->head) == 0)
+ {
+ int gothead;
+
+ /* We want the head revision. Try to read it directly. */
+
+ if (rcs->flags & NODELTA)
+ {
+ free_rcsnode_contents (rcs);
+ rcs->flags |= PARTIAL;
+ }
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, 0, &fp);
+ else
+ {
+ fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
+ if (fp == NULL)
+ error (1, 0, "unable to reopen `%s'", rcs->path);
+ if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0)
+ error (1, 0, "cannot fseek RCS file");
+ }
+
+ gothead = 0;
+ getrcsrev (fp, &key);
+ while (getrcskey (fp, &key, &value, &len) >= 0)
+ {
+ if (strcmp (key, "log") == 0)
+ {
+ log = xmalloc (len);
+ memcpy (log, value, len);
+ loglen = len;
+ }
+ if (strcmp (key, "text") == 0)
+ {
+ gothead = 1;
+ break;
+ }
+ }
+
+ if (! gothead)
+ {
+ error (0, 0, "internal error: cannot find head text");
+ if (free_rev)
+ free (rev);
+ return 1;
+ }
+
+ if (fstat (fileno (fp), &sb) < 0)
+ error (1, errno, "cannot fstat %s", rcs->path);
+
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", rcs->path);
+ }
+ else
+ {
+ /* It isn't the head revision of the trunk. We'll need to
+ walk through the deltas. */
+
+ fp = NULL;
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, 0, &fp);
+
+ if (fp == NULL)
+ {
+ /* If RCS_deltas didn't close the file, we could use fstat
+ here too. Probably should change it thusly.... */
+ if (stat (rcs->path, &sb) < 0)
+ error (1, errno, "cannot stat %s", rcs->path);
+ }
+ else
+ {
+ if (fstat (fileno (fp), &sb) < 0)
+ error (1, errno, "cannot fstat %s", rcs->path);
+ }
+
+ RCS_deltas (rcs, fp, rev, RCS_FETCH, &value, &len, &log, &loglen);
+ free_value = 1;
+ }
+
+ /* If OPTIONS is NULL or the empty string, then the old code would
+ invoke the RCS co program with no -k option, which means that
+ co would use the string we have stored in rcs->expand. */
+ if ((options == NULL || options[0] == '\0') && rcs->expand == NULL)
+ expand = KFLAG_KV;
+ else
+ {
+ const char *ouroptions;
+ const char * const *cpp;
+
+ if (options != NULL && options[0] != '\0')
+ {
+ assert (options[0] == '-' && options[1] == 'k');
+ ouroptions = options + 2;
+ }
+ else
+ ouroptions = rcs->expand;
+
+ for (cpp = kflags; *cpp != NULL; cpp++)
+ if (strcmp (*cpp, ouroptions) == 0)
+ break;
+
+ if (*cpp != NULL)
+ expand = (enum kflag) (cpp - kflags);
+ else
+ {
+ error (0, 0,
+ "internal error: unsupported substitution string -k%s",
+ ouroptions);
+ expand = KFLAG_KV;
+ }
+ }
+
+ if (expand != KFLAG_O && expand != KFLAG_B)
+ {
+ Node *p;
+ char *newvalue;
+
+ p = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
+ if (p == NULL)
+ error (1, 0, "internal error: no revision information for %s",
+ rev == NULL ? rcs->head : rev);
+
+ expand_keywords (rcs, (RCSVers *) p->data, nametag, log, loglen,
+ expand, value, len, &newvalue, &len);
+
+ if (newvalue != value)
+ {
+ if (free_value)
+ free (value);
+ value = newvalue;
+ free_value = 1;
+ }
+ }
+
+ if (log != NULL)
+ {
+ free (log);
+ log = NULL;
+ }
+
+ if (pfn != NULL)
+ {
+ /* The PFN interface is very simple to implement right now, as
+ we always have the entire file in memory. */
+ if (len != 0)
+ pfn (callerdat, value, len);
+ }
+ else
+ {
+ if (workfile == NULL)
+ {
+ if (sout == RUN_TTY)
+ ofp = stdout;
+ else
+ {
+ ofp = CVS_FOPEN (sout, expand == KFLAG_B ? "wb" : "w");
+ if (ofp == NULL)
+ error (1, errno, "cannot open %s", sout);
+ }
+ }
+ else
+ {
+ ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
+ if (ofp == NULL)
+ error (1, errno, "cannot open %s", workfile);
+ }
+
+ if (workfile == NULL && sout == RUN_TTY)
+ {
+ if (len > 0)
+ cvs_output (value, len);
+ }
+ else
+ {
+ if (fwrite (value, 1, len, ofp) != len)
+ error (1, errno, "cannot write %s",
+ (workfile != NULL
+ ? workfile
+ : (sout != RUN_TTY ? sout : "stdout")));
+ }
+
+ if (workfile != NULL)
+ {
+ if (fclose (ofp) < 0)
+ error (1, errno, "cannot close %s", workfile);
+ if (chmod (workfile,
+ sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)) < 0)
+ error (0, errno, "cannot change mode of file %s",
+ workfile);
+ }
+ else if (sout != RUN_TTY)
+ {
+ if (fclose (ofp) < 0)
+ error (1, errno, "cannot close %s", sout);
+ }
+ }
+
+ if (free_value)
+ free (value);
+ if (free_rev)
+ free (rev);
+
+ return 0;
+}
+
+/* This structure is passed between RCS_cmp_file and cmp_file_buffer. */
+
+struct cmp_file_data
+{
+ const char *filename;
+ FILE *fp;
+ int different;
+};
+
+/* Compare the contents of revision REV of RCS file RCS with the
+ contents of the file FILENAME. OPTIONS is a string for the keyword
+ expansion options. Return 0 if the contents of the revision are
+ the same as the contents of the file, 1 if they are different. */
+
+int
+RCS_cmp_file (rcs, rev, options, filename)
+ RCSNode *rcs;
+ char *rev;
+ char *options;
+ const char *filename;
+{
+ int binary;
+ FILE *fp;
+ struct cmp_file_data data;
+ int retcode;
+
+ if (options != NULL && options[0] != '\0')
+ binary = (strcmp (options, "-kb") == 0);
+ else
+ {
+ char *expand;
+
+ expand = RCS_getexpand (rcs);
+ if (expand != NULL && strcmp (expand, "b") == 0)
+ binary = 1;
+ else
+ binary = 0;
+ }
+
+ fp = CVS_FOPEN (filename, binary ? FOPEN_BINARY_READ : "r");
+
+ data.filename = filename;
+ data.fp = fp;
+ data.different = 0;
+
+ retcode = RCS_checkout (rcs, (char *) NULL, rev, (char *) NULL,
+ options, RUN_TTY, cmp_file_buffer,
+ (void *) &data);
+
+ /* If we have not yet found a difference, make sure that we are at
+ the end of the file. */
+ if (! data.different)
+ {
+ if (getc (fp) != EOF)
+ data.different = 1;
+ }
+
+ fclose (fp);
+
+ if (retcode != 0)
+ return 1;
+
+ return data.different;
+}
+
+/* This is a subroutine of RCS_cmp_file. It is passed to
+ RCS_checkout. */
+
+#define CMP_BUF_SIZE (8 * 1024)
+
+static void
+cmp_file_buffer (callerdat, buffer, len)
+ void *callerdat;
+ const char *buffer;
+ size_t len;
+{
+ struct cmp_file_data *data = (struct cmp_file_data *) callerdat;
+ char *filebuf;
+
+ /* If we've already found a difference, we don't need to check
+ further. */
+ if (data->different)
+ return;
+
+ filebuf = xmalloc (len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len);
+
+ while (len > 0)
+ {
+ size_t checklen;
+
+ checklen = len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len;
+ if (fread (filebuf, 1, checklen, data->fp) != checklen)
+ {
+ if (ferror (data->fp))
+ error (1, errno, "cannot read file %s for comparing",
+ data->filename);
+ data->different = 1;
+ free (filebuf);
+ return;
+ }
+
+ if (memcmp (filebuf, buffer, checklen) != 0)
+ {
+ data->different = 1;
+ free (filebuf);
+ return;
+ }
+
+ buffer += checklen;
+ len -= checklen;
+ }
+
+ free (filebuf);
+}
+
+/* For RCS file RCS, make symbolic tag TAG point to revision REV.
+ This validates that TAG is OK for a user to use. Return value is
+ -1 for error (and errno is set to indicate the error), positive for
+ error (and an error message has been printed), or zero for success. */
+
+int
+RCS_settag (rcs, tag, rev)
+ RCSNode *rcs;
+ const char *tag;
+ const char *rev;
+{
+ int ret;
+
+ /* FIXME: This check should be moved to RCS_check_tag. There is no
+ reason for it to be here. */
+ if (strcmp (tag, TAG_BASE) == 0
+ || strcmp (tag, TAG_HEAD) == 0)
+ {
+ /* Print the name of the tag might be considered redundant
+ with the caller, which also prints it. Perhaps this helps
+ clarify why the tag name is considered reserved, I don't
+ know. */
+ error (0, 0, "Attempt to add reserved tag name %s", tag);
+ return 1;
+ }
+
+ ret = RCS_exec_settag (rcs->path, tag, rev);
+ if (ret != 0)
+ return ret;
+
+ /* If we have already parsed the RCS file, update the tag
+ information. If we have not yet parsed it (i.e., the PARTIAL
+ flag is set), the new tag information will be read when and if
+ we do parse it. */
+ if ((rcs->flags & PARTIAL) == 0)
+ {
+ List *symbols;
+ Node *node;
+
+ /* At this point rcs->symbol_data may not have been parsed.
+ Calling RCS_symbols will force it to be parsed into a list
+ which we can easily manipulate. */
+ symbols = RCS_symbols (rcs);
+ if (symbols == NULL)
+ {
+ symbols = getlist ();
+ rcs->symbols = symbols;
+ }
+ node = findnode (symbols, tag);
+ if (node != NULL)
+ {
+ free (node->data);
+ node->data = xstrdup (rev);
+ }
+ else
+ {
+ node = getnode ();
+ node->key = xstrdup (tag);
+ node->data = xstrdup (rev);
+ (void) addnode (symbols, node);
+ }
+ }
+
+ /* Setting the tag will most likely have invalidated delta_pos. */
+ rcs->flags |= NODELTA;
+
+ return 0;
+}
+
+/* Delete the symbolic tag TAG from the RCS file RCS. NOERR is 1 to
+ suppress errors--FIXME it would be better to avoid the errors or
+ some cleaner solution. */
+
+int
+RCS_deltag (rcs, tag, noerr)
+ RCSNode *rcs;
+ const char *tag;
+ int noerr;
+{
+ int ret;
+
+ ret = RCS_exec_deltag (rcs->path, tag, noerr);
+ if (ret != 0)
+ return ret;
+
+ /* If we have already parsed the RCS file, update the tag
+ information. If we have not yet parsed it (i.e., the PARTIAL
+ flag is set), the new tag information will be read when and if
+ we do parse it. */
+ if ((rcs->flags & PARTIAL) == 0)
+ {
+ List *symbols;
+
+ /* At this point rcs->symbol_data may not have been parsed.
+ Calling RCS_symbols will force it to be parsed into a list
+ which we can easily manipulate. */
+ symbols = RCS_symbols (rcs);
+ if (symbols != NULL)
+ {
+ Node *node;
+
+ node = findnode (symbols, tag);
+ if (node != NULL)
+ delnode (node);
+ }
+ }
+
+ /* Deleting the tag will most likely have invalidated delta_pos. */
+ rcs->flags |= NODELTA;
+
+ return 0;
+}
+
+/* Set the default branch of RCS to REV. */
+
+int
+RCS_setbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
+{
+ int ret;
+
+ if (rev == NULL && rcs->branch == NULL)
+ return 0;
+ if (rev != NULL && rcs->branch != NULL && strcmp (rev, rcs->branch) == 0)
+ return 0;
+
+ ret = RCS_exec_setbranch (rcs->path, rev);
+ if (ret != 0)
+ return ret;
+
+ if (rcs->branch != NULL)
+ free (rcs->branch);
+ rcs->branch = xstrdup (rev);
+
+ /* Changing the branch will have changed the data in the file, so
+ delta_pos will no longer be correct. */
+ rcs->flags |= NODELTA;
+
+ return 0;
+}
+
+/* Lock revision REV. NOERR is 1 to suppress errors--FIXME it would
+ be better to avoid the errors or some cleaner solution. FIXME:
+ This is only required because the RCS ci program requires a lock.
+ If we eventually do the checkin ourselves, this can become a no-op. */
+
+int
+RCS_lock (rcs, rev, noerr)
+ RCSNode *rcs;
+ const char *rev;
+ int noerr;
+{
+ int ret;
+
+ ret = RCS_exec_lock (rcs->path, rev, noerr);
+ if (ret != 0)
+ return ret;
+
+ /* Setting a lock will have changed the data in the file, so
+ delta_pos will no longer be correct. */
+ rcs->flags |= NODELTA;
+
+ return 0;
+}
+
+/* Unlock revision REV. NOERR is 1 to suppress errors--FIXME it would
+ be better to avoid the errors or some cleaner solution. FIXME:
+ Like RCS_lock, this can become a no-op if we do the checkin
+ ourselves. */
+
+int
+RCS_unlock (rcs, rev, noerr)
+ RCSNode *rcs;
+ const char *rev;
+ int noerr;
+{
+ int ret;
+
+ ret = RCS_exec_unlock (rcs->path, rev, noerr);
+ if (ret != 0)
+ return ret;
+
+ /* Setting a lock will have changed the data in the file, so
+ delta_pos will no longer be correct. */
+ rcs->flags |= NODELTA;
+
+ return 0;
+}
+
+/* RCS_deltas and friends. Processing of the deltas in RCS files. */
+
+/* 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. */
+ char *text;
+ /* Length of this line, not counting \n if has_newline is true. */
+ size_t len;
+ /* 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 = 0;
+ vec->nlines = 0;
+ vec->vector = NULL;
+}
+
+static void linevector_add PROTO ((struct linevector *vec, char *text,
+ size_t len, 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 (FIXME: I
+ don't think this is needed, or used, now that we have the ->len
+ field). Set the version for each of the new lines to VERS. */
+static void
+linevector_add (vec, text, len, vers, pos)
+ struct linevector *vec;
+ char *text;
+ size_t len;
+ RCSVers *vers;
+ unsigned int pos;
+{
+ char *textend;
+ unsigned int i;
+ unsigned int nnew;
+ char *p;
+ struct line *lines;
+
+ if (len == 0)
+ return;
+
+ textend = text + len;
+
+ /* Count the number of lines we will need to add. */
+ nnew = 1;
+ for (p = text; p < textend; ++p)
+ if (*p == '\n' && p + 1 < textend)
+ ++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)
+ {
+ if (vec->lines_alloced == 0)
+ vec->lines_alloced = 10;
+ 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 < textend; ++p)
+ if (*p == '\n')
+ {
+ *p = '\0';
+ lines[i - pos - 1].has_newline = 1;
+ if (p + 1 == textend)
+ /* If there are no characters beyond the last newline, we
+ don't consider it another line. */
+ break;
+ lines[i - pos - 1].len = p - lines[i - pos - 1].text;
+ lines[i - pos].text = p + 1;
+ lines[i - pos].vers = vers;
+ lines[i - pos].has_newline = 0;
+ vec->vector[i] = &lines[i - pos];
+ ++i;
+ }
+ lines[i - pos - 1].len = p - lines[i - pos - 1].text;
+ 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)
+ {
+ if (to->lines_alloced == 0)
+ to->lines_alloced = 10;
+ 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;
+{
+ if (vec->vector != NULL)
+ 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];
+}
+
+/* Walk the deltas in RCS to get to revision VERSION.
+
+ If OP is RCS_ANNOTATE, then write annotations using cvs_output.
+
+ If OP is RCS_FETCH, then put the contents of VERSION into a
+ newly-malloc'd array and put a pointer to it in *TEXT. Each line
+ is \n terminated; the caller is responsible for converting text
+ files if desired. The total length is put in *LEN.
+
+ If FP is non-NULL, it should be a file descriptor open to the file
+ RCS with file position pointing to the deltas. We close the file
+ when we are done.
+
+ If LOG is non-NULL, then *LOG is set to the log message of VERSION,
+ and *LOGLEN is set to the length of the log message.
+
+ On error, give a fatal error. */
+
+static void
+RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
+ RCSNode *rcs;
+ FILE *fp;
+ char *version;
+ enum rcs_delta_op op;
+ char **text;
+ size_t *len;
+ char **log;
+ size_t *loglen;
+{
+ char *branchversion;
+ char *cpversion;
+ char *key;
+ char *value;
+ size_t vallen;
+ RCSVers *vers;
+ RCSVers *prev_vers;
+ RCSVers *trunk_vers;
+ char *next;
+ int n;
+ int ishead, isnext, isversion, onbranch;
+ Node *node;
+ struct linevector headlines;
+ struct linevector curlines;
+ struct linevector trunklines;
+ int foundhead;
+
+ if (fp == NULL)
+ {
+ if (rcs->flags & NODELTA)
+ {
+ free_rcsnode_contents (rcs);
+ RCS_reparsercsfile (rcs, 0, &fp);
+ }
+ else
+ {
+ fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
+ if (fp == NULL)
+ error (1, 0, "unable to reopen `%s'", rcs->path);
+ if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0)
+ error (1, 0, "cannot fseek RCS file");
+ }
+ }
+
+ ishead = 1;
+ vers = NULL;
+ prev_vers = NULL;
+ trunk_vers = NULL;
+ next = NULL;
+ onbranch = 0;
+ foundhead = 0;
+
+ linevector_init (&curlines);
+ linevector_init (&headlines);
+ linevector_init (&trunklines);
+
+ /* We set BRANCHVERSION to the version we are currently looking
+ for. Initially, this is the version on the trunk from which
+ VERSION branches off. If VERSION is not a branch, then
+ BRANCHVERSION is just VERSION. */
+ branchversion = xstrdup (version);
+ cpversion = strchr (branchversion, '.');
+ if (cpversion != NULL)
+ cpversion = strchr (cpversion + 1, '.');
+ if (cpversion != NULL)
+ *cpversion = '\0';
+
+ do {
+ getrcsrev (fp, &key);
+
+ if (next != NULL && strcmp (next, key) != 0)
+ {
+ /* This is not the next version we need. It is a branch
+ version which we want to ignore. */
+ isnext = 0;
+ isversion = 0;
+ }
+ else
+ {
+ isnext = 1;
+
+ /* look up the revision */
+ node = findnode (rcs->versions, key);
+ if (node == NULL)
+ error (1, 0,
+ "mismatch in rcs file %s between deltas and deltatexts",
+ rcs->path);
+
+ /* Stash the previous version. */
+ prev_vers = vers;
+
+ vers = (RCSVers *) node->data;
+ next = vers->next;
+
+ /* Compare key and trunkversion now, because key points to
+ storage controlled by getrcskey. */
+ if (strcmp (branchversion, key) == 0)
+ isversion = 1;
+ else
+ isversion = 0;
+ }
+
+ while ((n = getrcskey (fp, &key, &value, &vallen)) >= 0)
+ {
+ if (log != NULL
+ && isversion
+ && strcmp (key, "log") == 0
+ && strcmp (branchversion, version) == 0)
+ {
+ *log = xmalloc (vallen);
+ memcpy (*log, value, vallen);
+ *loglen = vallen;
+ }
+
+ if (strcmp (key, "text") == 0)
+ {
+ if (ishead)
+ {
+ char *p;
+
+ p = block_alloc (vallen);
+ memcpy (p, value, vallen);
+
+ linevector_add (&curlines, p, vallen, NULL, 0);
+ ishead = 0;
+ }
+ else if (isnext)
+ {
+ 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;
+ size_t len;
+ struct deltafrag *next;
+ };
+ struct deltafrag *dfhead;
+ struct deltafrag *df;
+
+ dfhead = NULL;
+ for (p = value; p != NULL && p < value + vallen; )
+ {
+ 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, 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",
+ rcs->path);
+ p = q;
+ if (*p++ != ' ')
+ error (1, 0, "space expected in %s",
+ rcs->path);
+ df->nlines = strtoul (p, &q, 10);
+ if (p == q)
+ error (1, 0, "number expected in %s",
+ rcs->path);
+ p = q;
+ if (*p++ != '\012')
+ error (1, 0, "linefeed expected in %s",
+ 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 == value + vallen)
+ {
+ if (i != 1)
+ error (1, 0, "\
+invalid rcs file %s: premature end of value",
+ rcs->path);
+ else
+ break;
+ }
+
+ /* Copy the text we are adding into allocated
+ space. */
+ df->new_lines = block_alloc (q - p);
+ memcpy (df->new_lines, p, q - p);
+ df->len = q - p;
+
+ 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,
+ df->len,
+ onbranch ? vers : 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)",
+ rcs->path);
+ if (! onbranch)
+ 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;
+
+ if (isversion)
+ {
+ /* This is either the version we want, or it is the
+ branchpoint to the version we want. */
+ if (strcmp (branchversion, version) == 0)
+ {
+ /* This is the version we want. */
+ linevector_copy (&headlines, &curlines);
+ foundhead = 1;
+ if (onbranch)
+ {
+ /* We have found this version by tracking up a
+ branch. Restore back to the lines we saved
+ when we left the trunk, and continue tracking
+ down the trunk. */
+ onbranch = 0;
+ vers = trunk_vers;
+ next = vers->next;
+ linevector_copy (&curlines, &trunklines);
+ }
+ }
+ else
+ {
+ Node *p;
+
+ /* We need to look up the branch. */
+ onbranch = 1;
+
+ if (numdots (branchversion) < 2)
+ {
+ unsigned int ln;
+
+ /* We are leaving the trunk; save the current
+ lines so that we can restore them when we
+ continue tracking down the trunk. */
+ trunk_vers = vers;
+ linevector_copy (&trunklines, &curlines);
+
+ /* Reset the version information we have
+ accumulated so far. It only applies to the
+ changes from the head to this version. */
+ for (ln = 0; ln < curlines.nlines; ++ln)
+ curlines.vector[ln]->vers = NULL;
+ }
+
+ /* The next version we want is the entry on
+ VERS->branches which matches this branch. For
+ example, suppose VERSION is 1.21.4.3 and
+ BRANCHVERSION was 1.21. Then we look for an entry
+ starting with "1.21.4" and we'll put it (probably
+ 1.21.4.1) in NEXT. We'll advance BRANCHVERSION by
+ two dots (in this example, to 1.21.4.3). */
+
+ if (vers->branches == NULL)
+ error (1, 0, "missing expected branches in %s",
+ rcs->path);
+ *cpversion = '.';
+ ++cpversion;
+ cpversion = strchr (cpversion, '.');
+ if (cpversion == NULL)
+ error (1, 0, "version number confusion in %s",
+ rcs->path);
+ for (p = vers->branches->list->next;
+ p != vers->branches->list;
+ p = p->next)
+ if (strncmp (p->key, branchversion,
+ cpversion - branchversion) == 0)
+ break;
+ if (p == vers->branches->list)
+ error (1, 0, "missing expected branch in %s",
+ rcs->path);
+
+ next = p->key;
+
+ cpversion = strchr (cpversion + 1, '.');
+ if (cpversion != NULL)
+ *cpversion = '\0';
+ }
+ }
+ if (op == RCS_FETCH && foundhead)
+ break;
+ } while (next != NULL);
+
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", rcs->path);
+
+ if (! foundhead)
+ error (1, 0, "could not find desired version %s in %s",
+ version, rcs->path);
+
+ /* Now print out or return the data we have just computed. */
+ switch (op)
+ {
+ case RCS_ANNOTATE:
+ {
+ 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,
+ headlines.vector[ln]->len);
+ cvs_output ("\n", 1);
+ }
+ }
+ break;
+ case RCS_FETCH:
+ {
+ char *p;
+ size_t n;
+ unsigned int ln;
+
+ assert (text != NULL);
+ assert (len != NULL);
+
+ n = 0;
+ for (ln = 0; ln < headlines.nlines; ++ln)
+ /* 1 for \n */
+ n += headlines.vector[ln]->len + 1;
+ p = xmalloc (n);
+ *text = p;
+ for (ln = 0; ln < headlines.nlines; ++ln)
+ {
+ memcpy (p, headlines.vector[ln]->text,
+ headlines.vector[ln]->len);
+ p += headlines.vector[ln]->len;
+ if (headlines.vector[ln]->has_newline)
+ *p++ = '\n';
+ }
+ *len = p - *text;
+ assert (*len <= n);
+ }
+ break;
+ }
+
+ linevector_free (&curlines);
+ linevector_free (&headlines);
+ linevector_free (&trunklines);
+
+ block_free ();
+ return;
+
+ l_error:
+ if (ferror (fp))
+ error (1, errno, "cannot read %s", rcs->path);
+ else
+ error (1, 0, "%s does not appear to be a valid rcs file",
+ rcs->path);
+}
+
+
+/* Annotate command. In rcs.c for historical reasons (from back when
+ what is now RCS_deltas was part of annotate_fileproc). */
+
+/* Options from the command line. */
+
+static int force_tag_match = 1;
+static char *tag = NULL;
+static char *date = NULL;
+
+static int annotate_fileproc PROTO ((void *callerdat, struct file_info *));
+
+static int
+annotate_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ FILE *fp = NULL;
+ char *version;
+
+ if (finfo->rcs == NULL)
+ return (1);
+
+ if (finfo->rcs->flags & PARTIAL)
+ RCS_reparsercsfile (finfo->rcs, 0, &fp);
+
+ version = RCS_getversion (finfo->rcs, tag, date, force_tag_match,
+ (int *) NULL);
+ if (version == NULL)
+ return 0;
+
+ /* 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);
+
+ RCS_deltas (finfo->rcs, fp, version, RCS_ANNOTATE, (char **) NULL,
+ (size_t) NULL, (char **) NULL, (size_t *) NULL);
+ free (version);
+ return 0;
+}
+
+static const char *const annotate_usage[] =
+{
+ "Usage: %s %s [-lf] [-r rev|-D date] [files...]\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ "\t-f\tUse head revision if tag/date not found.\n",
+ "\t-r rev\tAnnotate file as of specified revision/tag.\n",
+ "\t-D date\tAnnotate file as of specified date.\n",
+ NULL
+};
+
+/* Command to show the revision, date, and author where each line of a
+ file was modified. */
+
+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, "+lr:D:f")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'r':
+ tag = optarg;
+ break;
+ case 'D':
+ date = Make_Date (optarg);
+ break;
+ case 'f':
+ force_tag_match = 0;
+ 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");
+ if (!force_tag_match)
+ send_arg ("-f");
+ option_with_arg ("-r", tag);
+ if (date)
+ client_senddate (date);
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+ 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, NULL,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 1);
+}