From 4f40fe8334ad5f056e1d9105f23fe7ac859c39ba Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 15 May 1997 22:46:24 +0000 Subject: Import of cvs-1.9.9-970515 onto vendor branch. Obtained from: cyclic.com --- contrib/cvs/src/rcs.c | 2904 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 2454 insertions(+), 450 deletions(-) (limited to 'contrib/cvs/src/rcs.c') diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c index c68c255..15d303e 100644 --- a/contrib/cvs/src/rcs.c +++ b/contrib/cvs/src/rcs.c @@ -11,13 +11,35 @@ #include #include "cvs.h" +/* 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 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 free_rcsnode_contents PROTO((RCSNode *)); static void rcsvers_delproc PROTO((Node * p)); +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: @@ -49,9 +71,11 @@ static const char spacetab[] = { #define whitespace(c) (spacetab[(unsigned char)c] != 0) -/* - * 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; @@ -59,26 +83,31 @@ RCS_parse (file, repos) { RCSNode *rcs; FILE *fp; - char rcsfile[PATH_MAX]; + 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, FOPEN_BINARY_READ)) != 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 (! existence_error (errno)) { error (0, errno, "cannot open %s", rcsfile); - return NULL; + retval = NULL; + goto out; } (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT); - if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL) + if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL) { rcs = RCS_parsercsfile_i(fp, rcsfile); if (rcs != NULL) @@ -88,15 +117,78 @@ RCS_parse (file, repos) } fclose (fp); - return (rcs); + retval = rcs; + goto out; } 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; } /* @@ -110,7 +202,7 @@ RCS_parsercsfile (rcsfile) RCSNode *rcs; /* open the rcsfile */ - if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) == NULL) + if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL) { error (0, errno, "Couldn't open rcs file `%s'", rcsfile); return (NULL); @@ -145,7 +237,7 @@ 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; @@ -153,7 +245,7 @@ RCS_parsercsfile_i (fp, rcsfile) 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; @@ -196,11 +288,15 @@ l_error: 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, pfp) +RCS_reparsercsfile (rdata, all, pfp) RCSNode *rdata; + int all; FILE **pfp; { FILE *fp; @@ -215,7 +311,7 @@ RCS_reparsercsfile (rdata, pfp) assert (rdata != NULL); rcsfile = rdata->path; - fp = fopen(rcsfile, FOPEN_BINARY_READ); + fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ); if (fp == NULL) error (1, 0, "unable to reopen `%s'", rcsfile); @@ -232,7 +328,7 @@ RCS_reparsercsfile (rdata, pfp) /* 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)) @@ -249,10 +345,8 @@ RCS_reparsercsfile (rdata, pfp) if (strcmp (RCSSYMBOLS, key) == 0) { if (value != NULL) - { rdata->symbols_data = xstrdup(value); - continue; - } + continue; } if (strcmp (RCSEXPAND, key) == 0) @@ -271,6 +365,28 @@ RCS_reparsercsfile (rdata, pfp) 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 */ } @@ -297,7 +413,7 @@ RCS_reparsercsfile (rdata, pfp) vnode->date = xstrdup (valp); /* Get author field. */ - (void) getrcskey (fp, &key, &value); + (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, "\ @@ -305,18 +421,19 @@ unable to parse rcs file; `author' not in the expected place"); vnode->author = xstrdup (value); /* Get state field. */ - (void) getrcskey (fp, &key, &value); + (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; } /* 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; @@ -327,7 +444,7 @@ 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; @@ -339,7 +456,7 @@ unable to parse rcs file; `state' not in the expected place"); * we put the symbolic link stuff??? */ /* FIXME: Does not correctly handle errors, e.g. from stdio. */ - while ((n = getrcskey (fp, &key, &value)) >= 0) + while ((n = getrcskey (fp, &key, &value, NULL)) >= 0) { assert (key != NULL); @@ -356,6 +473,9 @@ unable to parse rcs file; `state' not in the expected place"); if (strcmp(key, RCSDEAD) == 0) { vnode->dead = 1; + if (vnode->state != NULL) + free (vnode->state); + vnode->state = xstrdup ("dead"); continue; } /* if we have a revision, break and do it */ @@ -363,6 +483,26 @@ unable to parse rcs file; `state' not in the expected place"); /* 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); + } + } } /* get the node */ @@ -390,6 +530,28 @@ unable to parse rcs file; `state' not in the expected place"); break; } + 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) @@ -403,6 +565,177 @@ unable to parse rcs file; `state' not in the expected place"); } /* + * 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. + */ + +void +RCS_fully_parse (rcs) + RCSNode *rcs; +{ + 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); +} + +/* * freercsnode - free up the info for an RCSNode */ void @@ -419,22 +752,37 @@ freercsnode (rnodep) 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_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 @@ -451,6 +799,12 @@ rcsvers_delproc (p) free (rnode->date); if (rnode->next != (char *) NULL) free (rnode->next); + if (rnode->author != (char *) NULL) + free (rnode->author); + if (rnode->state != (char *) NULL) + free (rnode->state); + if (rnode->other != (List *) NULL) + dellist (&rnode->other); free ((char *) rnode); } @@ -469,8 +823,10 @@ rcsvers_delproc (p) * 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. + * 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; @@ -478,16 +834,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 @@ -508,9 +867,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; @@ -525,9 +884,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 = '\0'; @@ -556,6 +915,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 (;;) { @@ -588,9 +951,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; @@ -599,9 +962,7 @@ getrcskey (fp, keyp, valp) /* 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) + if (just_string) break; /* compress whitespace down to a single space */ @@ -619,9 +980,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++ = ' '; @@ -633,9 +994,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; @@ -652,24 +1013,26 @@ 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); } -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. */ @@ -699,9 +1062,9 @@ getrcsrev (fp, revp) { 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; @@ -716,9 +1079,9 @@ getrcsrev (fp, revp) 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'; @@ -810,36 +1173,43 @@ do_branches (list, val) * The result is returned; null-string if error. */ char * -RCS_getversion (rcs, tag, date, force_tag_match, return_both) +RCS_getversion (rcs, tag, date, force_tag_match, simple_tag) RCSNode *rcs; char *tag; char *date; int force_tag_match; - int return_both; + int *simple_tag; { + if (simple_tag != NULL) + *simple_tag = 0; + /* make sure we have something to look at... */ 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, 0); - 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_both)); + return (RCS_gettag (rcs, tag, force_tag_match, simple_tag)); else if (date) return (RCS_getdate (rcs, date, force_tag_match)); else @@ -856,21 +1226,24 @@ RCS_getversion (rcs, tag, date, force_tag_match, return_both) * If the matched tag is a branch tag, find the head of the branch. */ char * -RCS_gettag (rcs, symtag, force_tag_match, return_both) +RCS_gettag (rcs, symtag, force_tag_match, simple_tag) RCSNode *rcs; char *symtag; int force_tag_match; - int return_both; + 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... */ assert (rcs != NULL); /* XXX this is probably not necessary, --jtc */ if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + 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')) @@ -883,18 +1256,17 @@ RCS_gettag (rcs, symtag, force_tag_match, return_both) 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 @@ -914,21 +1286,17 @@ RCS_gettag (rcs, symtag, force_tag_match, return_both) (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); } @@ -955,39 +1323,40 @@ RCS_gettag (rcs, symtag, force_tag_match, return_both) 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) { - /* - * 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)); + /* 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 @@ -1104,37 +1473,42 @@ RCS_nodeisbranch (rcs, rev) 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); } @@ -1148,7 +1522,7 @@ RCS_whatbranch (rcs, rev) RCSNode *rcs; const char *rev; { - Node *p; + char *version; int dots; /* assume no branch if you can't find the RCS info */ @@ -1156,34 +1530,35 @@ RCS_whatbranch (rcs, rev) return ((char *) NULL); /* now, look for a match in the symbols list */ - 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); } @@ -1208,7 +1583,7 @@ RCS_getbranch (rcs, tag, force_tag_match) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); /* find out if the tag contains a dot, or is on the trunk */ cp = strrchr (tag, '.'); @@ -1347,7 +1722,7 @@ RCS_getdate (rcs, date, force_tag_match) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); /* if the head is on a branch, try the branch first */ if (rcs->branch != NULL) @@ -1434,7 +1809,7 @@ RCS_getdatebranch (rcs, date, branch) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); p = findnode (rcs->versions, xrev); free (xrev); @@ -1442,9 +1817,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 */ @@ -1455,7 +1834,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); @@ -1475,11 +1858,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); } /* @@ -1495,13 +1875,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; @@ -1519,7 +1903,7 @@ RCS_getrevtime (rcs, rev, date, fudge) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); /* look up the revision */ p = findnode (rcs->versions, rev); @@ -1544,11 +1928,11 @@ RCS_getrevtime (rcs, rev, date, fudge) /* 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 @@ -1581,7 +1965,7 @@ RCS_symbols(rcs) assert(rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); if (rcs->symbols_data) { rcs->symbols = getlist (); @@ -1594,6 +1978,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. @@ -1602,8 +2049,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", @@ -1616,6 +2061,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; @@ -1686,7 +2132,7 @@ RCS_isdead (rcs, tag) RCSVers *version; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); p = findnode (rcs->versions, tag); if (p == NULL) @@ -1707,116 +2153,1313 @@ RCS_getexpand (rcs) { assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, 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 +/* RCS keywords, and a matching enum. */ +struct rcs_keyword { - char *text; - struct allocblock *next; + const char *string; + size_t len; +}; +#define KEYWORD_INIT(s) (s), sizeof (s) - 1 +static const 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 } +}; +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 }; -struct allocblock *blocks; -static void *block_alloc PROTO ((size_t)); +/* Convert an RCS date string into a readable string. This is like + the RCS date2str function. */ -static void * -block_alloc (n) - size_t n; +static char * +printable_date (rcs_date) + const char *rcs_date; { - struct allocblock *blk; - blk = (struct allocblock *) xmalloc (sizeof (struct allocblock)); - blk->text = xmalloc (n); - blk->next = blocks; - blocks = blk; - return blk->text; + 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); } -static void block_free PROTO ((void)); +/* Escape the characters in a string so that it can be included in an + RCS value. */ -static void -block_free () +static char * +escape_keyword_value (value, free_value) + const char *value; + int *free_value; { - struct allocblock *p; - struct allocblock *q; + char *ret, *t; + const char *s; - p = blocks; - while (p != NULL) + for (s = value; *s != '\0'; s++) { - free (p->text); - q = p->next; - free (p); - p = q; + char c; + + c = *s; + if (c == '\t' + || c == '\n' + || c == '\\' + || c == ' ' + || c == '$') + { + break; + } } - 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; -}; + if (*s == '\0') + { + *free_value = 0; + return (char *) value; + } -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; -}; + ret = xmalloc (strlen (value) * 4 + 1); + *free_value = 1; -static void linevector_init PROTO ((struct linevector *)); + 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; + } + } -/* Initialize *VEC to be a linevector with no lines. */ -static void -linevector_init (vec) + *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 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: + { + 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 (srch + sublen == s) + { + 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 = 10; + vec->lines_alloced = 0; vec->nlines = 0; - vec->vector = (struct line **) - xmalloc (vec->lines_alloced * sizeof (*vec->vector)); + vec->vector = NULL; } -static void linevector_add PROTO ((struct linevector *vec, char *text, - RCSVers *vers, unsigned int pos)); +static int 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. Set the - version for each of the new lines to VERS. */ -static void -linevector_add (vec, text, vers, pos) + 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. This + function returns non-zero for success. It returns zero if the line + number is out of range. */ +static int +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; - assert (vec->lines_alloced > 0); + if (len == 0) + return 1; + + textend = text + len; /* Count the number of lines we will need to add. */ nnew = 1; - for (p = text; *p != '\0'; ++p) - if (*p == '\n' && p[1] != '\0') + 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)); @@ -1824,6 +3467,8 @@ linevector_add (vec, text, vers, pos) /* 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, @@ -1835,7 +3480,7 @@ linevector_add (vec, text, vers, pos) vec->vector[i] = vec->vector[i - nnew]; if (pos > vec->nlines) - error (1, 0, "invalid rcs file: line to add out of range"); + return 0; /* Actually add the lines, to LINES and VEC->VECTOR. */ i = pos; @@ -1843,22 +3488,26 @@ linevector_add (vec, text, vers, pos) lines[0].vers = vers; lines[0].has_newline = 0; vec->vector[i++] = &lines[0]; - for (p = text; *p != '\0'; ++p) + for (p = text; p < textend; ++p) if (*p == '\n') { *p = '\0'; lines[i - pos - 1].has_newline = 1; - if (p[1] == '\0') + 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; + + return 1; } static void linevector_delete PROTO ((struct linevector *, unsigned int, @@ -1891,6 +3540,8 @@ linevector_copy (to, 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 **) @@ -1909,7 +3560,8 @@ static void linevector_free (vec) struct linevector *vec; { - free (vec->vector); + if (vec->vector != NULL) + free (vec->vector); } static char *month_printname PROTO ((char *)); @@ -1935,279 +3587,617 @@ month_printname (month) return (char *)months[mnum - 1]; } -static int annotate_fileproc PROTO ((struct file_info *)); +static int +apply_rcs_changes PROTO ((struct linevector *, const char *, size_t, + const char *, RCSVers *, RCSVers *)); + +/* Apply changes to the line vector LINES. DIFFBUF is a buffer of + length DIFFLEN holding the change text from an RCS file (the output + of diff -n). NAME is used in error messages. The VERS field of + any line added is set to ADDVERS. The VERS field of any line + deleted is set to DELVERS, unless DELVERS is NULL, in which case + the VERS field of deleted lines is unchanged. The function returns + non-zero if the change text is applied successfully. It returns + zero if the change text does not appear to apply to LINES (e.g., a + line number is invalid). If the change text is improperly + formatted (e.g., it is not the output of diff -n), the function + calls error with a status of 1, causing the program to exit. */ static int -annotate_fileproc (finfo) - struct file_info *finfo; +apply_rcs_changes (lines, diffbuf, difflen, name, addvers, delvers) + struct linevector *lines; + const char *diffbuf; + size_t difflen; + const char *name; + RCSVers *addvers; + RCSVers *delvers; +{ + const char *p; + const 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 = diffbuf; p != NULL && p < diffbuf + difflen; ) + { + 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, name); + df = (struct deltafrag *) xmalloc (sizeof (struct deltafrag)); + df->next = dfhead; + dfhead = df; + df->pos = strtoul (p, (char **) &q, 10); + + if (p == q) + error (1, 0, "number expected in %s", name); + p = q; + if (*p++ != ' ') + error (1, 0, "space expected in %s", name); + df->nlines = strtoul (p, (char **) &q, 10); + if (p == q) + error (1, 0, "number expected in %s", name); + p = q; + if (*p++ != '\012') + error (1, 0, "linefeed expected in %s", name); + + 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 == diffbuf + difflen) + { + if (i != 1) + error (1, 0, "premature end of change in %s", name); + 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: + if (! linevector_add (lines, df->new_lines, df->len, addvers, + df->pos)) + return 0; + break; + case DELETE: + if (df->pos > lines->nlines + || df->pos + df->nlines > lines->nlines) + return 0; + if (delvers != NULL) + for (ln = df->pos; ln < df->pos + df->nlines; ++ln) + lines->vector[ln]->vers = delvers; + linevector_delete (lines, df->pos, df->nlines); + break; + } + df = df->next; + free (dfhead); + dfhead = df; + } + + return 1; +} + +/* Apply an RCS change text to a buffer. The function name starts + with rcs rather than RCS because this does not take an RCSNode + argument. NAME is used in error messages. TEXTBUF is the text + buffer to change, and TEXTLEN is the size. DIFFBUF and DIFFLEN are + the change buffer and size. The new buffer is returned in *RETBUF + and *RETLEN. The new buffer is allocated by xmalloc. The function + changes the contents of TEXTBUF. This function returns 1 for + success. On failure, it calls error and returns 0. */ + +int +rcs_change_text (name, textbuf, textlen, diffbuf, difflen, retbuf, retlen) + const char *name; + char *textbuf; + size_t textlen; + const char *diffbuf; + size_t difflen; + char **retbuf; + size_t *retlen; { + struct linevector lines; + int ret; + + *retbuf = NULL; + *retlen = 0; + + linevector_init (&lines); + + if (! linevector_add (&lines, textbuf, textlen, NULL, 0)) + error (1, 0, "cannot initialize line vector"); + + if (! apply_rcs_changes (&lines, diffbuf, difflen, name, NULL, NULL)) + { + error (0, 0, "invalid change text in %s", name); + ret = 0; + } + else + { + char *p; + size_t n; + unsigned int ln; + + n = 0; + for (ln = 0; ln < lines.nlines; ++ln) + /* 1 for \n */ + n += lines.vector[ln]->len + 1; + + p = xmalloc (n); + *retbuf = p; + + for (ln = 0; ln < lines.nlines; ++ln) + { + memcpy (p, lines.vector[ln]->text, lines.vector[ln]->len); + p += lines.vector[ln]->len; + if (lines.vector[ln]->has_newline) + *p++ = '\n'; + } + + *retlen = p - *retbuf; + assert (*retlen <= n); + + ret = 1; + } + + linevector_free (&lines); + + /* Note that this assumes that we have not called from anything + else which uses the block vectors. FIXME: We could fix this by + saving and restoring the state of the block allocation code. */ + block_free (); + + return ret; +} + +/* 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; + int ishead, isnext, isversion, onbranch; Node *node; struct linevector headlines; struct linevector curlines; + struct linevector trunklines; + int foundhead; - 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); + 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); - /* Stash the previous version. */ - prev_vers = vers; + 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; - /* 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; + vers = (RCSVers *) node->data; + next = vers->next; - while ((n = getrcskey (fp, &key, &value)) >= 0) + /* 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 (strlen (value) + 1); - strcpy (p, value); + p = block_alloc (vallen); + memcpy (p, value, vallen); + + if (! linevector_add (&curlines, p, vallen, NULL, 0)) + error (1, 0, "invalid rcs file %s", rcs->path); - linevector_init (&headlines); - linevector_init (&curlines); - linevector_add (&headlines, p, NULL, 0); - linevector_copy (&curlines, &headlines); ishead = 0; } - else + 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; - 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; - } + if (! apply_rcs_changes (&curlines, value, vallen, + rcs->path, + onbranch ? vers : NULL, + onbranch ? NULL : prev_vers)) + error (1, 0, "invalid change text in %s", rcs->path); } break; } } if (n < 0) goto l_error; - } while (vers->next != NULL); - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", finfo->rcs->path); + 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; - /* Now print out the data we have just computed. */ - { - unsigned int ln; + /* We need to look up the branch. */ + onbranch = 1; - 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; + 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; + } - prvers = headlines.vector[ln]->vers; - if (prvers == NULL) - prvers = vers; + /* 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); - sprintf (buf, "%-12s (%-8.8s ", - prvers->version, - prvers->author); - cvs_output (buf, 0); + next = p->key; - /* 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); + cpversion = strchr (cpversion + 1, '.'); + if (cpversion != NULL) + *cpversion = '\0'; } - cvs_output ("): ", 0); - cvs_output (headlines.vector[ln]->text, 0); - cvs_output ("\n", 1); } - } + 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); - if (!ishead) + /* Now print out or return the data we have just computed. */ + switch (op) { - linevector_free (&curlines); - linevector_free (&headlines); + 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 0; + return; l_error: if (ferror (fp)) - error (1, errno, "cannot read %s", finfo->rcs->path); + error (1, errno, "cannot read %s", rcs->path); else error (1, 0, "%s does not appear to be a valid rcs file", - finfo->rcs->path); - /* Shut up gcc -Wall. */ + 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 [-l] [files...]\n", + "Usage: %s %s [-lRf] [-r rev|-D date] [files...]\n", "\t-l\tLocal directory only, no recursion.\n", + "\t-R\tProcess directories recursively.\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. 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. */ + file was modified. */ int annotate (argc, argv) @@ -2221,13 +4211,25 @@ annotate (argc, argv) usage (annotate_usage); optind = 0; - while ((c = getopt (argc, argv, "+l")) != -1) + while ((c = getopt (argc, argv, "+lr:D:fR")) != -1) { switch (c) { case 'l': local = 1; break; + case 'R': + local = 0; + 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); @@ -2245,18 +4247,20 @@ annotate (argc, argv) 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); - /* 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_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, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, - 1, 0); + 1); } -- cgit v1.1