diff options
Diffstat (limited to 'contrib/cvs/src/rcs.c')
-rw-r--r-- | contrib/cvs/src/rcs.c | 3345 |
1 files changed, 2880 insertions, 465 deletions
diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c index a49c449..c134d67 100644 --- a/contrib/cvs/src/rcs.c +++ b/contrib/cvs/src/rcs.c @@ -2,7 +2,7 @@ * Copyright (c) 1992, Brian Berliner and Jeff Polk * * You may distribute under the terms of the GNU General Public License as - * specified in the README file that comes with the CVS 1.4 kit. + * specified in the README file that comes with the CVS source distribution. * * The routines contained in this file do all the rcs file parsing and * manipulation @@ -10,6 +10,7 @@ #include <assert.h> #include "cvs.h" +#include "edit.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 @@ -19,7 +20,6 @@ static const char *const kflags[] = 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, size_t *lenp)); @@ -27,7 +27,9 @@ 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 do_locks PROTO((List * list, char *val)); static void free_rcsnode_contents PROTO((RCSNode *)); +static void free_rcsvers_contents PROTO((RCSVers *)); static void rcsvers_delproc PROTO((Node * p)); static char *translate_symtag PROTO((RCSNode *, const char *)); static char *printable_date PROTO((const char *)); @@ -41,6 +43,24 @@ 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 *)); +/* Routines for reading, parsing and writing RCS files. */ +static RCSVers *getdelta PROTO ((FILE *, char *)); +static Deltatext *RCS_getdeltatext PROTO ((RCSNode *, FILE *)); +static void freedeltatext PROTO ((Deltatext *)); + +static void RCS_putadmin PROTO ((RCSNode *, FILE *)); +static void RCS_putdtree PROTO ((RCSNode *, char *, FILE *)); +static void RCS_putdesc PROTO ((RCSNode *, FILE *)); +static void putdelta PROTO ((RCSVers *, FILE *)); +static int putrcsfield_proc PROTO ((Node *, void *)); +static int putsymbol_proc PROTO ((Node *, void *)); +static void RCS_copydeltas PROTO ((RCSNode *, FILE *, FILE *, Deltatext *, char *)); +static void putdeltatext PROTO ((FILE *, Deltatext *)); + +static FILE *rcs_internal_lockfile PROTO ((char *)); +static void rcs_internal_unlockfile PROTO ((FILE *, char *)); +static char *rcs_lockfilename PROTO ((char *)); + /* * We don't want to use isspace() from the C library because: * @@ -232,10 +252,20 @@ RCS_parsercsfile_i (fp, rcsfile) rdata->path = xstrdup (rcsfile); /* Process HEAD and BRANCH keywords from the RCS header. - * - * Most cvs operatations on the main branch don't need any more - * information. Those that do call XXX to completely parse the - * RCS file. */ + + Most cvs operations on the main branch don't need any more + information. Those that do call RCS_reparsercsfile to parse + the rest of the header and the deltas. + + People often wonder whether this is inefficient, to open the + file once here and once in RCS_reparsercsfile. Well, it might + help a little bit if we kept the file open (I haven't tried + timing this myself), but basically the common case, which we + want to optimize, is the one in which we call + RCS_parsercsfile_i and not RCS_reparsercsfile (for example, + "cvs update" on a lot of files most of which are unmodified). + So making the case in which we call RCS_reparsercsfile fast is + not as important. */ if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL) goto l_error; @@ -288,23 +318,19 @@ 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, all, pfp) +void +RCS_reparsercsfile (rdata, pfp) RCSNode *rdata; - int all; FILE **pfp; { FILE *fp; char *rcsfile; - Node *q; + Node *q, *kv; RCSVers *vnode; - int n; + long fpos; char *cp; char *key, *value; @@ -316,6 +342,9 @@ RCS_reparsercsfile (rdata, all, pfp) error (1, 0, "unable to reopen `%s'", rcsfile); /* make a node */ + /* This probably shouldn't be done until later: if a file has an + empty revision tree (which is permissible), rdata->versions + should be NULL. -twp */ rdata->versions = getlist (); /* @@ -324,12 +353,13 @@ RCS_reparsercsfile (rdata, all, pfp) */ for (;;) { + fpos = ftell (fp); + /* get the next key/value pair */ /* if key is NULL here, then the file is missing some headers or we had trouble reading the file. */ - if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL - || strcmp (key, RCSDESC) == 0) + if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL) { if (ferror(fp)) { @@ -342,6 +372,35 @@ RCS_reparsercsfile (rdata, all, pfp) } } + /* Skip head and branch tags; we already have them. */ + if (strcmp (key, RCSHEAD) == 0 || strcmp (key, RCSBRANCH) == 0) + continue; + + if (strcmp (key, "access") == 0) + { + if (value != NULL) + rdata->access = xstrdup (value); + continue; + } + + /* We always save lock information, so that we can handle + -kkvl correctly when checking out a file. */ + if (strcmp (key, "locks") == 0) + { + if (value != NULL) + rdata->locks_data = xstrdup (value); + fpos = ftell (fp); + if (getrcskey (fp, &key, &value, NULL) >= 0 && + strcmp (key, "strict") == 0 && + value == NULL) + { + rdata->strict_locks = 1; + } + else + (void) fseek (fp, fpos, SEEK_SET); + continue; + } + if (strcmp (RCSSYMBOLS, key) == 0) { if (value != NULL) @@ -357,7 +416,7 @@ RCS_reparsercsfile (rdata, all, pfp) /* * check key for '.''s and digits (probably a rev) if it is a - * revision, we are done with the headers and are down to the + * revision or `desc', we are done with the headers and are down to the * revision deltas, so we break out of the loop */ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) @@ -365,26 +424,25 @@ RCS_reparsercsfile (rdata, all, 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 (strcmp (key, RCSDESC) == 0) + break; + + if (strcmp (key, "comment") == 0) + { + rdata->comment = xstrdup (value); + continue; + } + 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 */ @@ -395,116 +453,11 @@ RCS_reparsercsfile (rdata, all, pfp) * revision delta in our hand key=the revision and value=the date key and * its value */ - for (;;) - { - char *valp; - - vnode = (RCSVers *) xmalloc (sizeof (RCSVers)); - memset (vnode, 0, sizeof (RCSVers)); - - /* fill in the version before we forget it */ - vnode->version = xstrdup (key); - - /* grab the value of the date from value */ - valp = value + strlen (RCSDATE);/* skip the "date" keyword */ - while (whitespace (*valp)) /* take space off front of value */ - valp++; - - vnode->date = xstrdup (valp); - - /* Get author field. */ - (void) getrcskey (fp, &key, &value, 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); - - /* 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; - } - - /* fill in the branch list (if any branches exist) */ - (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 (); - do_branches (vnode->branches, value); - } - - /* fill in the next field if there is a next revision */ - (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); - - /* - * at this point, we skip any user defined fields XXX - this is where - * we put the symbolic link stuff??? - */ - /* FIXME: Does not correctly handle errors, e.g. from stdio. */ - while ((n = getrcskey (fp, &key, &value, NULL)) >= 0) - { - assert (key != NULL); - - if (strcmp (key, RCSDESC) == 0) - { - n = -1; - break; - } - - /* Enable use of repositories created by certain obsolete - versions of CVS. This code should remain indefinately; - there is no procedure for converting old repositories, and - checking for it is harmless. */ - if (strcmp(key, RCSDEAD) == 0) - { - vnode->dead = 1; - if (vnode->state != NULL) - free (vnode->state); - vnode->state = xstrdup ("dead"); - continue; - } - /* if we have a revision, break and do it */ - for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) - /* do nothing */ ; - if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) - break; - - 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); - } - } - } + /* First, seek back to the start of the delta block. */ + (void) fseek (fp, fpos, SEEK_SET); + while ((vnode = getdelta (fp, rcsfile)) != NULL) + { /* get the node */ q = getnode (); q->type = RCSVERS; @@ -521,36 +474,22 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'", freenode (q); #endif } - - /* - * if we left the loop because there were no more keys, we break out - * of the revision processing loop - */ - if (n < 0) - break; } - if (all && key != NULL && strcmp (key, RCSDESC) == 0) + (void) getrcskey (fp, &key, &value, NULL); + if (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) + if (rdata->desc != NULL) { error (0, 0, "warning: duplicate key `%s' in RCS file `%s'", key, rcsfile); - freenode (kv); + free (rdata->desc); } + rdata->desc = xstrdup (value); } rdata->delta_pos = ftell (fp); - rdata->flags &= ~NODELTA; if (pfp == NULL) { @@ -582,7 +521,7 @@ RCS_fully_parse (rcs) { FILE *fp; - RCS_reparsercsfile (rcs, 1, &fp); + RCS_reparsercsfile (rcs, &fp); while (1) { @@ -780,19 +719,25 @@ free_rcsnode_contents (rnode) free (rnode->expand); if (rnode->other != (List *) NULL) dellist (&rnode->other); + if (rnode->access != NULL) + free (rnode->access); + if (rnode->locks_data != NULL) + free (rnode->locks_data); + if (rnode->locks != (List *) NULL) + dellist (&rnode->locks); + if (rnode->comment != NULL) + free (rnode->comment); + if (rnode->desc != NULL) + free (rnode->desc); } -/* - * rcsvers_delproc - free up an RCSVers type node - */ +/* free_rcsvers_contents -- free up the contents of an RCSVers node, + but also free the pointer to the node itself. */ + static void -rcsvers_delproc (p) - Node *p; -{ +free_rcsvers_contents (rnode) RCSVers *rnode; - - rnode = (RCSVers *) p->data; - +{ if (rnode->branches != (List *) NULL) dellist (&rnode->branches); if (rnode->date != (char *) NULL) @@ -805,14 +750,29 @@ rcsvers_delproc (p) free (rnode->state); if (rnode->other != (List *) NULL) dellist (&rnode->other); + if (rnode->other_delta != NULL) + dellist (&rnode->other_delta); + if (rnode->text != NULL) + freedeltatext (rnode->text); free ((char *) rnode); } /* + * rcsvers_delproc - free up an RCSVers type node + */ +static void +rcsvers_delproc (p) + Node *p; +{ + free_rcsvers_contents ((RCSVers *) p->data); +} + +/* * getrcskey - fill in the key and value from the rcs file the algorithm is * as follows * - * o skip whitespace o fill in key with everything up to next white + * o skip whitespace + * o fill in key with everything up to next white * space or semicolon * o if key == "desc" then key and data are NULL and return -1 * o if key wasn't terminated by a semicolon, skip white space and fill @@ -978,14 +938,18 @@ getrcskey (fp, keyp, valp, lenp) } } while (whitespace (c)); - if (cur >= max) + /* Do not include any trailing whitespace in the value. */ + if (c != ';') { - size_t curoff = cur - value; - expand_string (&value, &valsize, valsize + 1); - cur = value + curoff; - max = value + valsize; + if (cur >= max) + { + size_t curoff = cur - value; + expand_string (&value, &valsize, valsize + 1); + cur = value + curoff; + max = value + valsize; + } + *cur++ = ' '; } - *cur++ = ' '; } /* if we got a semi-colon we are done with the entire value */ @@ -1096,6 +1060,59 @@ getrcsrev (fp, revp) *revp = key; } +/* Like getrcsrev, but don't die on error. Return the last character + read (last call to getc, which may be EOF). TODO: implement getrcsrev + in terms of this function. */ +static int +getrevnum (fp, revp) + FILE *fp; + char **revp; +{ + char *cur; + char *max; + int c; + + *revp = NULL; + do { + c = getc (fp); + if (c == EOF) + return c; + } while (whitespace (c)); + + if (!(isdigit (c) || c == '.')) + return c; + + 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) + break; + cur++; + } + + if (cur >= max) + { + size_t curoff = cur - key; + expand_string (&key, &keysize, keysize + 1); + cur = key + curoff; + max = key + keysize; + } + *cur = '\0'; + *revp = key; + return c; +} + /* * process the symbols list of the rcs file */ @@ -1137,6 +1154,48 @@ do_symbols (list, val) } /* + * process the locks list of the rcs file + * Like do_symbols, but hash entries are keyed backwards: i.e. + * an entry like `user:rev' is keyed on REV rather than on USER. + */ +static void +do_locks (list, val) + List *list; + char *val; +{ + Node *p; + char *cp = val; + char *user, *rev; + + for (;;) + { + /* skip leading whitespace */ + while (whitespace (*cp)) + cp++; + + /* if we got to the end, we are done */ + if (*cp == '\0') + break; + + /* split it up into user and rev */ + user = cp; + cp = strchr (cp, ':'); + *cp++ = '\0'; + rev = cp; + while (!whitespace (*cp) && *cp != '\0') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + + /* make a new node and add it to the list */ + p = getnode (); + p->key = xstrdup (rev); + p->data = xstrdup (user); + (void) addnode (list, p); + } +} + +/* * process the branches list of a revision delta */ static void @@ -1198,7 +1257,7 @@ RCS_getversion (rcs, tag, date, force_tag_match, simple_tag) { char *branch, *rev; - if (! RCS_isbranch (rcs, tag)) + if (! RCS_nodeisbranch (rcs, tag)) { /* We can't get a particular date if the tag is not a branch. */ @@ -1232,6 +1291,8 @@ RCS_getversion (rcs, tag, date, force_tag_match, simple_tag) * and handle "magic" revisions specially. * * If the matched tag is a branch tag, find the head of the branch. + * + * Returns pointer to newly malloc'd string, or NULL. */ char * RCS_gettag (rcs, symtag, force_tag_match, simple_tag) @@ -1251,7 +1312,7 @@ RCS_gettag (rcs, symtag, force_tag_match, simple_tag) /* XXX this is probably not necessary, --jtc */ if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, NULL); + RCS_reparsercsfile (rcs, NULL); /* If tag is "HEAD", special case to get head RCS revision */ if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0')) @@ -1483,6 +1544,8 @@ RCS_nodeisbranch (rcs, rev) int dots; char *version; + assert (rcs != NULL); + /* numeric revisions are easy -- even number of dots is a branch */ if (isdigit (*rev)) return ((numdots (rev) & 1) == 0); @@ -1573,7 +1636,8 @@ RCS_whatbranch (rcs, rev) /* * Get the head of the specified branch. If the branch does not exist, - * return NULL or RCS_head depending on force_tag_match + * return NULL or RCS_head depending on force_tag_match. + * Returns NULL or a newly malloc'd string. */ char * RCS_getbranch (rcs, tag, force_tag_match) @@ -1591,7 +1655,7 @@ RCS_getbranch (rcs, tag, force_tag_match) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, NULL); + RCS_reparsercsfile (rcs, NULL); /* find out if the tag contains a dot, or is on the trunk */ cp = strrchr (tag, '.'); @@ -1690,9 +1754,88 @@ RCS_getbranch (rcs, tag, force_tag_match) return (xstrdup (vn->version)); } +/* Get the branch point for a particular branch, that is the first + revision on that branch. For example, RCS_getbranchpoint (rcs, + "1.3.2") will normally return "1.3.2.1". TARGET may be either a + branch number or a revision number; if a revnum, find the + branchpoint of the branch to which TARGET belongs. + + Return RCS_head if TARGET is on the trunk or if the root node could + not be found (this is sort of backwards from our behavior on a branch; + the rationale is that the return value is a revision from which you + can start walking the next fields and end up at TARGET). + Return NULL on error. */ + +static char * +RCS_getbranchpoint (rcs, target) + RCSNode *rcs; + char *target; +{ + char *branch, *bp; + Node *vp; + RCSVers *rev; + int dots, isrevnum, brlen; + + dots = numdots (target); + isrevnum = dots & 1; + + if (dots == 1) + /* TARGET is a trunk revision; return rcs->head. */ + return (RCS_head (rcs)); + + /* Get the revision number of the node at which TARGET's branch is + rooted. If TARGET is a branch number, lop off the last field; + if it's a revision number, lop off the last *two* fields. */ + branch = xstrdup (target); + bp = strrchr (branch, '.'); + if (bp == NULL) + error (1, 0, "%s: confused revision number %s", + rcs->path, target); + if (isrevnum) + while (*--bp != '.') + ; + *bp = '\0'; + + vp = findnode (rcs->versions, branch); + if (vp == NULL) + { + error (0, 0, "%s: can't find branch point %s", rcs->path, target); + return NULL; + } + rev = (RCSVers *) vp->data; + + *bp++ = '.'; + while (*bp && *bp != '.') + ++bp; + brlen = bp - branch; + + vp = rev->branches->list->next; + while (vp != rev->branches->list) + { + /* BRANCH may be a genuine branch number, e.g. `1.1.3', or + maybe a full revision number, e.g. `1.1.3.6'. We have + found our branch point if the first BRANCHLEN characters + of the revision number match, *and* if the following + character is a dot. */ + if (strncmp (vp->key, branch, brlen) == 0 && vp->key[brlen] == '.') + break; + vp = vp->next; + } + + free (branch); + if (vp == rev->branches->list) + { + error (0, 0, "%s: can't find branch point %s", rcs->path, target); + return NULL; + } + else + return (xstrdup (vp->key)); +} + /* * Get the head of the RCS file. If branch is set, this is the head of the - * branch, otherwise the real head + * branch, otherwise the real head. + * Returns NULL or a newly malloc'd string. */ char * RCS_head (rcs) @@ -1730,7 +1873,7 @@ RCS_getdate (rcs, date, force_tag_match) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, NULL); + RCS_reparsercsfile (rcs, NULL); /* if the head is on a branch, try the branch first */ if (rcs->branch != NULL) @@ -1817,7 +1960,7 @@ RCS_getdatebranch (rcs, date, branch) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, NULL); + RCS_reparsercsfile (rcs, NULL); p = findnode (rcs->versions, xrev); free (xrev); @@ -1829,7 +1972,8 @@ RCS_getdatebranch (rcs, date, branch) if (RCS_datecmp (vers->date, date) <= 0) cur_rev = vers->version; - /* if no branches list, return now */ + /* If no branches list, return now. This is what happens if the branch + is a (magic) branch with no revisions yet. */ if (vers->branches == NULL) return xstrdup (cur_rev); @@ -1843,9 +1987,11 @@ RCS_getdatebranch (rcs, date, branch) 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); + /* This is what happens if the branch is a (magic) branch with + no revisions yet. Similar to the case where vers->branches == + NULL, except here there was a another branch off the same + branchpoint. */ + return xstrdup (cur_rev); } p = findnode (rcs->versions, p->key); @@ -1911,7 +2057,7 @@ RCS_getrevtime (rcs, rev, date, fudge) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, NULL); + RCS_reparsercsfile (rcs, NULL); /* look up the revision */ p = findnode (rcs->versions, rev); @@ -1934,15 +2080,9 @@ RCS_getrevtime (rcs, rev, date, fudge) 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 + 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 + 1900, ftm->tm_hour, - ftm->tm_min, ftm->tm_sec); -#endif /* turn it into seconds since the epoch */ revdate = get_date (tdate, (struct timeb *) NULL); @@ -1952,11 +2092,7 @@ RCS_getrevtime (rcs, rev, date, fudge) if (date) { /* put an appropriate string into ``date'' if we were given one */ -#ifdef HAVE_RCS5 ftm = gmtime (&revdate); -#else - ftm = localtime (&revdate); -#endif (void) sprintf (date, DATEFORM, ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, @@ -1967,13 +2103,32 @@ RCS_getrevtime (rcs, rev, date, fudge) } List * +RCS_getlocks (rcs) + RCSNode *rcs; +{ + assert(rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + if (rcs->locks_data) { + rcs->locks = getlist (); + do_locks (rcs->locks, rcs->locks_data); + free(rcs->locks_data); + rcs->locks_data = NULL; + } + + return rcs->locks; +} + +List * RCS_symbols(rcs) RCSNode *rcs; { assert(rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, NULL); + RCS_reparsercsfile (rcs, NULL); if (rcs->symbols_data) { rcs->symbols = getlist (); @@ -1987,6 +2142,7 @@ RCS_symbols(rcs) /* * Return the version associated with a particular symbolic tag. + * Returns NULL or a newly malloc'd string. */ static char * translate_symtag (rcs, tag) @@ -1994,7 +2150,7 @@ translate_symtag (rcs, tag) const char *tag; { if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, NULL); + RCS_reparsercsfile (rcs, NULL); if (rcs->symbols != NULL) { @@ -2067,17 +2223,13 @@ RCS_check_kflag (arg) " -kv\tGenerate only keyword values in keyword strings.\n", " -ko\tGenerate the old keyword string (no changes from checked in file).\n", " -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n", + "(Specify the --help global option for a list of other help options)\n", NULL, }; /* Big enough to hold any of the strings from kflags. */ char karg[10]; char const *const *cpp = NULL; -#ifndef HAVE_RCS5 - error (1, 0, "%s %s: your version of RCS does not support the -k option", - program_name, command_name); -#endif - if (arg) { for (cpp = kflags; *cpp != NULL; cpp++) @@ -2140,7 +2292,7 @@ RCS_isdead (rcs, tag) RCSVers *version; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, NULL); + RCS_reparsercsfile (rcs, NULL); p = findnode (rcs->versions, tag); if (p == NULL) @@ -2161,7 +2313,7 @@ RCS_getexpand (rcs) { assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, NULL); + RCS_reparsercsfile (rcs, NULL); return rcs->expand; } @@ -2338,45 +2490,12 @@ expand_keywords (rcs, ver, name, log, loglen, expand, buf, len, retbuf, retlen) /* If we are using -kkvl, dig out the locker information if any. */ locker = NULL; - if (expand == KFLAG_KVL && rcs->other != NULL) + if (expand == KFLAG_KVL) { - 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; - } - } + Node *lock; + lock = findnode (RCS_getlocks(rcs), ver->version); + if (lock != NULL) + locker = xstrdup (lock->data); } /* RCS keywords look like $STRING$ or $STRING: VALUE$. */ @@ -2849,14 +2968,8 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) /* 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); + RCS_reparsercsfile (rcs, &fp); else { fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ); @@ -2904,7 +3017,7 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) fp = NULL; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, 0, &fp); + RCS_reparsercsfile (rcs, &fp); if (fp == NULL) { @@ -3013,16 +3126,42 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) if (workfile == NULL && sout == RUN_TTY) { - if (len > 0) - cvs_output (value, len); + if (expand == KFLAG_B) + cvs_output_binary (value, len); + else + { + /* cvs_output requires the caller to check for zero + length. */ + 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"))); + /* NT 4.0 is said to have trouble writing 2099999 bytes + (for example) in a single fwrite. So break it down + (there is no need to be writing that much at once + anyway; it is possible that LARGEST_FWRITE should be + somewhat larger for good performance, but for testing I + want to start with a small value until/unless a bigger + one proves useful). */ +#define LARGEST_FWRITE 8192 + size_t nleft = len; + size_t nstep = (len < LARGEST_FWRITE ? len : LARGEST_FWRITE); + char *p = value; + + while (nleft > 0) + { + if (fwrite (p, 1, nstep, ofp) != nstep) + error (1, errno, "cannot write %s", + (workfile != NULL + ? workfile + : (sout != RUN_TTY ? sout : "stdout"))); + p += nstep; + nleft -= nstep; + if (nleft < nstep) + nstep = nleft; + } } if (workfile != NULL) @@ -3049,6 +3188,794 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) return 0; } +static RCSVers *RCS_findlock_or_tip PROTO ((RCSNode *rcs)); + +/* Find the delta currently locked by the user. From the `ci' man page: + + "If rev is omitted, ci tries to derive the new revision + number from the caller's last lock. If the caller has + locked the tip revision of a branch, the new revision is + appended to that branch. The new revision number is + obtained by incrementing the tip revision number. If the + caller locked a non-tip revision, a new branch is started + at that revision by incrementing the highest branch number + at that revision. The default initial branch and level + numbers are 1. + + If rev is omitted and the caller has no lock, but owns the + file and locking is not set to strict, then the revision + is appended to the default branch (normally the trunk; see + the -b option of rcs(1))." + + RCS_findlock_or_tip finds the unique revision locked by the caller + and returns its delta node. If the caller has not locked any + revisions (and is permitted to commit to an unlocked delta, as + described above), return the tip of the default branch. */ + +static RCSVers * +RCS_findlock_or_tip (rcs) + RCSNode *rcs; +{ + char *user = getcaller(); + Node *lock, *p; + List *locklist; + + /* Find unique delta locked by caller. This code is very similar + to the code in RCS_unlock -- perhaps it could be abstracted + into a RCS_findlock function. */ + locklist = RCS_getlocks (rcs); + lock = NULL; + for (p = locklist->list->next; p != locklist->list; p = p->next) + { + if (strcmp (p->data, user) == 0) + { + if (lock != NULL) + { + error (0, 0, "\ +%s: multiple revisions locked by %s; please specify one", rcs->path, user); + return NULL; + } + lock = p; + } + } + + if (lock != NULL) + { + /* Found an old lock, but check that the revision still exists. */ + p = findnode (rcs->versions, lock->key); + if (p == NULL) + { + error (0, 0, "%s: can't unlock nonexistent revision %s", + rcs->path, + lock->key); + return NULL; + } + return (RCSVers *) p->data; + } + + /* No existing lock. The RCS rule is that this is an error unless + locking is nonstrict AND the file is owned by the current + user. Trying to determine the latter is a portability nightmare + in the face of NT, VMS, AFS, and other systems with non-unix-like + ideas of users and owners. In the case of CVS, we should never get + here (as long as the traditional behavior of making sure to call + RCS_lock persists). Anyway, we skip the RCS error checks + and just return the default branch or head. The reasoning is that + those error checks are to make users lock before a checkin, and we do + that in other ways if at all anyway (e.g. rcslock.pl). */ + + p = findnode (rcs->versions, RCS_getbranch (rcs, rcs->branch, 0)); + return (RCSVers *) p->data; +} + +/* Revision number string, R, must contain a `.'. + Return a newly-malloc'd copy of the prefix of R up + to but not including the final `.'. */ + +static char * +truncate_revnum (r) + const char *r; +{ + size_t len; + char *new_r; + char *dot = strrchr (r, '.'); + + assert (dot); + len = dot - r; + new_r = xmalloc (len + 1); + memcpy (new_r, r, len); + *(new_r + len) = '\0'; + return new_r; +} + +/* Revision number string, R, must contain a `.'. + R must be writable. Replace the rightmost `.' in R with + the NUL byte and return a pointer to that NUL byte. */ + +static char * +truncate_revnum_in_place (r) + char *r; +{ + char *dot = strrchr (r, '.'); + assert (dot); + *dot = '\0'; + return dot; +} + +/* Revision number strings, R and S, must each contain a `.'. + R and S must be writable and must have the same number of dots. + Truncate R and S for the comparison, then restored them to their + original state. + Return the result (see compare_revnums) of comparing R and S + ignoring differences in any component after the rightmost `.'. */ + +static int +compare_truncated_revnums (r, s) + char *r; + char *s; +{ + char *r_dot = truncate_revnum_in_place (r); + char *s_dot = truncate_revnum_in_place (s); + int cmp; + + assert (numdots (r) == numdots (s)); + + cmp = compare_revnums (r, s); + + *r_dot = '.'; + *s_dot = '.'; + + return cmp; +} + +/* Return a malloc'd copy of the string representing the highest branch + number on BRANCHNODE. If there are no branches on BRANCHNODE, return NULL. + FIXME: isn't the max rev always the last one? + If so, we don't even need a loop. */ + +static char * +max_rev (const RCSVers *branchnode) +{ + Node *head; + Node *bp; + char *max; + + if (branchnode->branches == NULL) + { + return NULL; + } + + max = NULL; + head = branchnode->branches->list; + for (bp = head->next; bp != head; bp = bp->next) + { + if (max == NULL || compare_truncated_revnums (max, bp->key) < 0) + { + max = bp->key; + } + } + assert (max); + + return truncate_revnum (max); +} + +/* Create BRANCH in RCS's delta tree. BRANCH may be either a branch + number or a revision number. In the former case, create the branch + with the specified number; in the latter case, create a new branch + rooted at node BRANCH with a higher branch number than any others. + Return the number of the tip node on the new branch. */ + +static char * +RCS_addbranch (rcs, branch) + RCSNode *rcs; + const char *branch; +{ + char *branchpoint, *newrevnum; + Node *nodep, *bp; + Node *marker; + RCSVers *branchnode; + + /* Append to end by default. */ + marker = NULL; + + branchpoint = xstrdup (branch); + if ((numdots (branchpoint) & 1) == 0) + { + truncate_revnum_in_place (branchpoint); + } + + /* Find the branch rooted at BRANCHPOINT. */ + nodep = findnode (rcs->versions, branchpoint); + if (nodep == NULL) + { + error (0, 0, "%s: can't find branch point %s", rcs->path, branchpoint); + return NULL; + } + branchnode = (RCSVers *) nodep->data; + + /* If BRANCH was a full branch number, make sure it is higher than MAX. */ + if ((numdots (branch) & 1) == 1) + { + if (branchnode->branches == NULL) + { + /* We have to create the first branch on this node, which means + appending ".2" to the revision number. */ + newrevnum = (char *) xmalloc (strlen (branch) + 3); + strcpy (newrevnum, branch); + strcat (newrevnum, ".2"); + } + else + { + char *max = max_rev (branchnode); + assert (max); + newrevnum = increment_revnum (max); + free (max); + } + } + else + { + newrevnum = xstrdup (branch); + + if (branchnode->branches != NULL) + { + Node *head; + Node *bp; + + /* Find the position of this new branch in the sorted list + of branches. */ + head = branchnode->branches->list; + for (bp = head->next; bp != head; bp = bp->next) + { + char *dot; + int found_pos; + + /* The existing list must be sorted on increasing revnum. */ + assert (bp->next == head + || compare_truncated_revnums (bp->key, + bp->next->key) < 0); + dot = truncate_revnum_in_place (bp->key); + found_pos = (compare_revnums (branch, bp->key) < 0); + *dot = '.'; + + if (found_pos) + { + break; + } + } + marker = bp; + } + } + + newrevnum = (char *) xrealloc (newrevnum, strlen (newrevnum) + 3); + strcat (newrevnum, ".1"); + + /* Add this new revision number to BRANCHPOINT's branches list. */ + if (branchnode->branches == NULL) + branchnode->branches = getlist(); + bp = getnode(); + bp->key = xstrdup (newrevnum); + + /* Append to the end of the list by default, that is, just before + the header node, `list'. */ + if (marker == NULL) + marker = branchnode->branches->list; + + { + int fail; + fail = insert_before (branchnode->branches, marker, bp); + assert (!fail); + } + + return newrevnum; +} + +/* Check in to RCSFILE with revision REV (which must be greater than the + largest revision) and message MESSAGE (which is checked for legality). + If FLAGS & RCS_FLAGS_DEAD, check in a dead revision. If FLAGS & + RCS_FLAGS_QUIET, tell ci to be quiet. If FLAGS & RCS_FLAGS_MODTIME, + use the working file's modification time for the checkin time. + WORKFILE is the working file to check in from, or NULL to use the usual + RCS rules for deriving it from the RCSFILE. + + 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. */ + +/* TODO: RCS_checkin always unlinks the working file after checkin -- + then RCS_checkout checks it out again. The logic should probably + be reversed here. */ + +int +RCS_checkin (rcs, workfile, message, rev, flags) + RCSNode *rcs; + char *workfile; + char *message; + char *rev; + int flags; +{ + RCSVers *delta, *commitpt; + Deltatext *dtext; + Node *nodep; + char *tmpfile, *changefile, *chtext; + char *diffopts; + size_t bufsize; + int buflen, chtextlen; + int status, checkin_quiet, allocated_workfile; + struct tm *ftm; + time_t modtime; + + commitpt = NULL; + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* Get basename of working file. Is there a library function to + do this? I couldn't find one. -twp */ + allocated_workfile = 0; + if (workfile == NULL) + { + char *p; + int extlen = strlen (RCSEXT); + workfile = xstrdup (last_component (rcs->path)); + p = workfile + (strlen (workfile) - extlen); + assert (strncmp (p, RCSEXT, extlen) == 0); + *p = '\0'; + allocated_workfile = 1; + } + + checkin_quiet = flags & RCS_FLAGS_QUIET; + if (!checkin_quiet) + { + cvs_output (rcs->path, 0); + cvs_output (" <-- ", 7); + cvs_output (workfile, 0); + cvs_output ("\n", 1); + } + + /* Create new delta node. */ + delta = (RCSVers *) xmalloc (sizeof (RCSVers)); + memset (delta, 0, sizeof (RCSVers)); + delta->author = xstrdup (getcaller ()); + if (flags & RCS_FLAGS_MODTIME) + { + struct stat ws; + if (stat (workfile, &ws) < 0) + { + error (1, errno, "cannot stat %s", workfile); + } + modtime = ws.st_mtime; + } + else + (void) time (&modtime); + ftm = gmtime (&modtime); + delta->date = (char *) xmalloc (MAXDATELEN); + (void) sprintf (delta->date, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); + if (flags & RCS_FLAGS_DEAD) + { + delta->state = xstrdup (RCSDEAD); + delta->dead = 1; + } + else + delta->state = xstrdup ("Exp"); + + /* Create a new deltatext node. */ + dtext = (Deltatext *) xmalloc (sizeof (Deltatext)); + memset (dtext, 0, sizeof (Deltatext)); + + dtext->log = make_message_rcslegal (message); + + /* If the delta tree is empty, then there's nothing to link the + new delta into. So make a new delta tree, snarf the working + file contents, and just write the new RCS file. */ + if (rcs->head == NULL) + { + char *newrev; + FILE *fout; + + /* Figure out what the first revision number should be. */ + if (rev == NULL || *rev == '\0') + newrev = xstrdup ("1.1"); + else if (numdots (rev) == 0) + { + newrev = (char *) xmalloc (strlen (rev) + 3); + strcpy (newrev, rev); + strcat (newrev, ".1"); + } + else + newrev = xstrdup (rev); + + /* Don't need to xstrdup NEWREV because it's already dynamic, and + not used for anything else. (Don't need to free it, either.) */ + rcs->head = newrev; + delta->version = xstrdup (newrev); + nodep = getnode(); + nodep->type = RCSVERS; + nodep->key = xstrdup (newrev); + nodep->data = (char *) delta; + (void) addnode (rcs->versions, nodep); + + dtext->version = xstrdup (newrev); + bufsize = 0; + get_file(workfile, workfile, "r", &dtext->text, &bufsize, &dtext->len); + + if (!checkin_quiet) + { + cvs_output ("initial revision: ", 0); + cvs_output (rcs->head, 0); + cvs_output ("\n", 1); + } + + fout = rcs_internal_lockfile (rcs->path); + RCS_putadmin (rcs, fout); + RCS_putdtree (rcs, rcs->head, fout); + RCS_putdesc (rcs, fout); + rcs->delta_pos = ftell (fout); + if (rcs->delta_pos == -1) + error (1, errno, "cannot ftell for %s", rcs->path); + putdeltatext (fout, dtext); + rcs_internal_unlockfile (fout, rcs->path); + freedeltatext (dtext); + + /* Removing the file here is an RCS user-visible behavior which + we almost surely do not need in the CVS case. In fact, getting + rid of it should clean up link_file and friends in import.c. */ + if (unlink_file (workfile) < 0) + /* FIXME-update-dir: message does not include update_dir. */ + error (0, errno, "cannot remove %s", workfile); + + if (!checkin_quiet) + cvs_output ("done\n", 5); + + return 0; + } + + /* Derive a new revision number. From the `ci' man page: + + "If rev is a revision number, it must be higher than the + latest one on the branch to which rev belongs, or must + start a new branch. + + If rev is a branch rather than a revision number, the new + revision is appended to that branch. The level number is + obtained by incrementing the tip revision number of that + branch. If rev indicates a non-existing branch, that + branch is created with the initial revision numbered + rev.1." + + RCS_findlock_or_tip handles the case where REV is omitted. + RCS 5.7 also permits REV to be "$" or to begin with a dot, but + we do not address those cases -- every routine that calls + RCS_checkin passes it a numeric revision. */ + + if (rev == NULL || *rev == '\0') + { + /* Figure out where the commit point is by looking for locks. + If the commit point is at the tip of a branch (or is the + head of the delta tree), then increment its revision number + to obtain the new revnum. Otherwise, start a new + branch. */ + commitpt = RCS_findlock_or_tip (rcs); + if (commitpt == NULL) + { + status = 1; + goto checkin_done; + } + else if (commitpt->next == NULL + || strcmp (commitpt->version, rcs->head) == 0) + delta->version = increment_revnum (commitpt->version); + else + delta->version = RCS_addbranch (rcs, commitpt->version); + } + else + { + /* REV is either a revision number or a branch number. Find the + tip of the target branch. */ + char *branch, *tip, *newrev, *p; + int dots, isrevnum; + + assert (isdigit(*rev)); + + newrev = xstrdup (rev); + dots = numdots (newrev); + isrevnum = dots & 1; + + branch = xstrdup (rev); + if (isrevnum) + { + p = strrchr (branch, '.'); + *p = '\0'; + } + + /* Find the tip of the target branch. If we got a one- or two-digit + revision number, this will be the head of the tree. Exception: + if rev is a single-field revision equal to the branch number of + the trunk (usually "1") then we want to treat it like an ordinary + branch revision. */ + if (dots == 0) + { + tip = xstrdup (rcs->head); + if (atoi (tip) != atoi (branch)) + { + newrev = (char *) xrealloc (newrev, strlen (newrev) + 3); + strcat (newrev, ".1"); + dots = isrevnum = 1; + } + } + else if (dots == 1) + tip = xstrdup (rcs->head); + else + tip = RCS_getbranch (rcs, branch, 1); + + /* If the branch does not exist, and we were supplied an exact + revision number, signal an error. Otherwise, if we were + given only a branch number, create it and set COMMITPT to + the branch point. */ + if (tip == NULL) + { + if (isrevnum) + { + error (0, 0, "%s: can't find branch point %s", + rcs->path, branch); + free (branch); + free (newrev); + status = 1; + goto checkin_done; + } + delta->version = RCS_addbranch (rcs, branch); + p = strrchr (branch, '.'); + *p = '\0'; + tip = xstrdup (branch); + } + else + { + if (isrevnum) + { + /* NEWREV must be higher than TIP. */ + if (compare_revnums (tip, newrev) >= 0) + { + error (0, 0, + "%s: revision %s too low; must be higher than %s", + rcs->path, + newrev, tip); + free (branch); + free (newrev); + free (tip); + status = 1; + goto checkin_done; + } + delta->version = xstrdup (newrev); + } + else + /* Just increment the tip number to get the new revision. */ + delta->version = increment_revnum (tip); + } + + nodep = findnode (rcs->versions, tip); + commitpt = (RCSVers *) nodep->data; + + free (branch); + free (newrev); + free (tip); + } + + assert (delta->version != NULL); + + /* If COMMITPT is locked by us, break the lock. If it's locked + by someone else, signal an error. */ + nodep = findnode (RCS_getlocks (rcs), commitpt->version); + if (nodep != NULL) + { + if (strcmp (nodep->data, delta->author) != 0) + { + error (0, 0, "%s: revision %s locked by %s", + rcs->path, + nodep->key, nodep->data); + status = 1; + goto checkin_done; + } + delnode (nodep); + } + + dtext->version = xstrdup (delta->version); + + /* Obtain the change text for the new delta. If DELTA is to be the + new head of the tree, then its change text should be the contents + of the working file, and LEAFNODE's change text should be a diff. + Else, DELTA's change text should be a diff between LEAFNODE and + the working file. */ + + tmpfile = cvs_temp_name(); + status = RCS_checkout (rcs, NULL, commitpt->version, NULL, + ((rcs->expand != NULL + && strcmp (rcs->expand, "b") == 0) + ? "-kb" + : "-ko"), + tmpfile, + (RCSCHECKOUTPROC)0, NULL); + if (status != 0) + error (1, status < 0 ? errno : 0, + "could not check out revision %s of `%s'", + commitpt->version, rcs->path); + + bufsize = buflen = 0; + chtext = NULL; + chtextlen = 0; + changefile = cvs_temp_name(); + + /* Diff options should include --binary if the RCS file has -kb set + in its `expand' field. */ + diffopts = (rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 + ? "-a -n --binary" + : "-a -n"); + + if (strcmp (commitpt->version, rcs->head) == 0 && + numdots (delta->version) == 1) + { + /* If this revision is being inserted on the trunk, the change text + for the new delta should be the contents of the working file ... */ + bufsize = 0; + get_file (workfile, workfile, + rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r", + &dtext->text, &bufsize, &dtext->len); + + /* ... and the change text for the old delta should be a diff. */ + commitpt->text = (Deltatext *) xmalloc (sizeof (Deltatext)); + memset (commitpt->text, 0, sizeof (Deltatext)); + + bufsize = 0; + switch (diff_exec (workfile, tmpfile, diffopts, changefile)) + { + case 0: + case 1: + break; + case -1: + /* FIXME-update-dir: message does not include update_dir. */ + error (1, errno, "error diffing %s", workfile); + break; + default: + /* FIXME-update-dir: message does not include update_dir. */ + error (1, 0, "error diffing %s", workfile); + break; + } + + /* OK, the text file case here is really dumb. Logically + speaking we want diff to read the files in text mode, + convert them to the canonical form found in RCS files + (which, we hope at least, is independent of OS--always + bare linefeeds), and then work with change texts in that + format. However, diff_exec both generates change + texts and produces output for user purposes (e.g. patch.c), + and there is no way to distinguish between the two cases. + So we actually implement the text file case by writing the + change text as a text file, then reading it as a text file. + This should cause no harm, but doesn't strike me as + immensely clean. */ + get_file (changefile, changefile, + rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r", + &commitpt->text->text, &bufsize, &commitpt->text->len); + + /* If COMMITPT->TEXT->TEXT is NULL, it means that CHANGEFILE + was empty and that there are no differences between revisions. + In that event, we want to force RCS_rewrite to write an empty + string for COMMITPT's change text. Leaving the change text + field set NULL won't work, since that means "preserve the original + change text for this delta." */ + if (commitpt->text->text == NULL) + { + commitpt->text->text = xstrdup (""); + commitpt->text->len = 0; + } + } + else + { + /* This file is not being inserted at the head, but on a side + branch somewhere. Make a diff from the previous revision + to the working file. */ + switch (diff_exec (tmpfile, workfile, diffopts, changefile)) + { + case 0: + case 1: + break; + case -1: + /* FIXME-update-dir: message does not include update_dir. */ + error (1, errno, "error diffing %s", workfile); + break; + default: + /* FIXME-update-dir: message does not include update_dir. */ + error (1, 0, "error diffing %s", workfile); + break; + } + /* See the comment above, at the other get_file invocation, + regarding binary vs. text. */ + get_file (changefile, changefile, + rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r", + &dtext->text, &bufsize, + &dtext->len); + if (dtext->text == NULL) + { + dtext->text = xstrdup (""); + dtext->len = 0; + } + } + + /* Update DELTA linkage. It is important not to do this before + the very end of RCS_checkin; if an error arises that forces + us to abort checking in, we must not have malformed deltas + partially linked into the tree. + + If DELTA and COMMITPT are on different branches, do nothing -- + DELTA is linked to the tree through COMMITPT->BRANCHES, and we + don't want to change `next' pointers. + + Otherwise, if the nodes are both on the trunk, link DELTA to + COMMITPT; otherwise, link COMMITPT to DELTA. */ + + if (numdots (commitpt->version) == numdots (delta->version)) + { + if (strcmp (commitpt->version, rcs->head) == 0) + { + delta->next = rcs->head; + rcs->head = xstrdup (delta->version); + } + else + commitpt->next = xstrdup (delta->version); + } + + /* Add DELTA to RCS->VERSIONS. */ + if (rcs->versions == NULL) + rcs->versions = getlist(); + nodep = getnode(); + nodep->type = RCSVERS; + nodep->key = xstrdup (delta->version); + nodep->data = (char *) delta; + (void) addnode (rcs->versions, nodep); + + /* Write the new RCS file, inserting the new delta at COMMITPT. */ + if (!checkin_quiet) + { + cvs_output ("new revision: ", 14); + cvs_output (delta->version, 0); + cvs_output ("; previous revision: ", 21); + cvs_output (commitpt->version, 0); + cvs_output ("\n", 1); + } + + RCS_rewrite (rcs, dtext, commitpt->version); + + /* Removing the file here is an RCS user-visible behavior which + we almost surely do not need in the CVS case. In fact, getting + rid of it should clean up link_file and friends in import.c. */ + if (unlink_file (workfile) < 0) + /* FIXME-update-dir: message does not include update_dir. */ + error (1, errno, "cannot remove %s", workfile); + if (unlink_file (tmpfile) < 0) + error (0, errno, "cannot remove %s", tmpfile); + if (unlink_file (changefile) < 0) + error (0, errno, "cannot remove %s", changefile); + + if (!checkin_quiet) + cvs_output ("done\n", 5); + + checkin_done: + if (allocated_workfile) + free (workfile); + + if (commitpt != NULL && commitpt->text != NULL) + { + freedeltatext (commitpt->text); + commitpt->text = NULL; + } + + freedeltatext (dtext); + if (status != 0) + free_rcsvers_contents (delta); + + return status; +} + /* This structure is passed between RCS_cmp_file and cmp_file_buffer. */ struct cmp_file_data @@ -3175,7 +4102,11 @@ RCS_settag (rcs, tag, rev) const char *tag; const char *rev; { - int ret; + List *symbols; + Node *node; + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); /* FIXME: This check should be moved to RCS_check_tag. There is no reason for it to be here. */ @@ -3190,89 +4121,61 @@ RCS_settag (rcs, tag, rev) 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) + /* A revision number of NULL means use the head or default branch. + If rev is not NULL, it may be a symbolic tag or branch number; + expand it to the correct numeric revision or branch head. */ + if (rev == NULL) + rev = rcs->branch ? rcs->branch : rcs->head; + + /* 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) { - 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); - } + 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_at_front (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. */ +/* Delete the symbolic tag TAG from the RCS file RCS. Return 0 if + the tag was found (and removed), or 1 if it was not present. (In + either case, the tag will no longer be in RCS->SYMBOLS.) */ int -RCS_deltag (rcs, tag, noerr) +RCS_deltag (rcs, tag) 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; + List *symbols; + Node *node; + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); - /* 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; + symbols = RCS_symbols (rcs); + if (symbols == NULL) + return 1; - node = findnode (symbols, tag); - if (node != NULL) - delnode (node); - } - } + node = findnode (symbols, tag); + if (node == NULL) + return 1; - /* Deleting the tag will most likely have invalidated delta_pos. */ - rcs->flags |= NODELTA; + delnode (node); return 0; } @@ -3284,124 +4187,836 @@ RCS_setbranch (rcs, rev) RCSNode *rcs; const char *rev; { - int ret; + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); 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: +/* Lock revision REV. LOCK_QUIET is 1 to suppress output. 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. */ +/* FIXME-twp: if a lock owned by someone else is broken, should this + send mail to the lock owner? Prompt user? It seems like such an + obscure situation for CVS as almost not worth worrying much + about. */ int -RCS_lock (rcs, rev, noerr) +RCS_lock (rcs, rev, lock_quiet) RCSNode *rcs; const char *rev; - int noerr; + int lock_quiet; { - int ret; + List *locks; + Node *p; + char *user; + char *xrev = NULL; + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + locks = RCS_getlocks (rcs); + if (locks == NULL) + locks = rcs->locks = getlist(); + user = getcaller(); + + /* A revision number of NULL means lock the head or default branch. */ + if (rev == NULL) + xrev = RCS_head (rcs); + + /* If rev is a branch number, lock the latest revision on that + branch. I think that if the branch doesn't exist, it's + okay to return 0 -- that just means that the branch is new, + so we don't need to lock it anyway. -twp */ + else if (RCS_nodeisbranch (rcs, rev)) + { + xrev = RCS_getbranch (rcs, (char *) rev, 1); + if (xrev == NULL) + { + if (!lock_quiet) + error (0, 0, "%s: branch %s absent", rcs->path, rev); + return 1; + } + } + + if (xrev == NULL) + xrev = xstrdup (rev); + + /* Make sure that the desired revision exists. Technically, + we can update the locks list without even checking this, + but RCS 5.7 did this. And it can't hurt. */ + if (findnode (rcs->versions, xrev) == NULL) + { + if (!lock_quiet) + error (0, 0, "%s: revision %s absent", rcs->path, xrev); + free (xrev); + return 1; + } - ret = RCS_exec_lock (rcs->path, rev, noerr); - if (ret != 0) - return ret; + /* Is this rev already locked? */ + p = findnode (locks, xrev); + if (p != NULL) + { + if (strcmp (p->data, user) == 0) + { + /* We already own the lock on this revision, so do nothing. */ + free (xrev); + return 0; + } - /* Setting a lock will have changed the data in the file, so - delta_pos will no longer be correct. */ - rcs->flags |= NODELTA; + /* Break the lock. */ + if (!lock_quiet) + { + cvs_output (rev, 0); + cvs_output (" unlocked\n", 0); + } + delnode (p); + } + + /* Create a new lock. */ + p = getnode(); + p->key = xrev; /* already xstrdupped */ + p->data = xstrdup (getcaller()); + (void) addnode_at_front (locks, p); + + if (!lock_quiet) + { + cvs_output (xrev, 0); + cvs_output (" locked\n", 0); + } 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: +/* Unlock revision REV. UNLOCK_QUIET is 1 to suppress output. FIXME: Like RCS_lock, this can become a no-op if we do the checkin - ourselves. */ + ourselves. + + If REV is not null and is locked by someone else, break their + lock and notify them. It is an open issue whether RCS_unlock + queries the user about whether or not to break the lock. */ int -RCS_unlock (rcs, rev, noerr) +RCS_unlock (rcs, rev, unlock_quiet) RCSNode *rcs; const char *rev; - int noerr; + int unlock_quiet; { - int ret; + Node *lock; + List *locks; + char *user; + char *xrev = NULL; + + user = getcaller(); + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* If rev is NULL, unlock the latest revision (first in + rcs->locks) held by the caller. */ + if (rev == NULL) + { + Node *p; + + /* No-ops: attempts to unlock an empty tree or an unlocked file. */ + if (rcs->head == NULL) + { + if (!unlock_quiet) + cvs_outerr ("can't unlock an empty tree\n", 0); + return 0; + } + + locks = RCS_getlocks (rcs); + if (locks == NULL) + { + if (!unlock_quiet) + cvs_outerr ("No locks are set.\n", 0); + return 0; + } + + lock = NULL; + for (p = locks->list->next; p != locks->list; p = p->next) + { + if (strcmp (p->data, user) == 0) + { + if (lock != NULL) + { + if (!unlock_quiet) + error (0, 0, "\ +%s: multiple revisions locked by %s; please specify one", rcs->path, user); + return 1; + } + lock = p; + } + } + if (lock == NULL) + return 0; /* no lock found, ergo nothing to do */ + xrev = xstrdup (lock->key); + } + else if (RCS_nodeisbranch (rcs, rev)) + { + /* If rev is a branch number, unlock the latest revision on that + branch. */ + xrev = RCS_getbranch (rcs, (char *) rev, 1); + if (xrev == NULL) + { + error (0, 0, "%s: branch %s absent", rcs->path, rev); + return 1; + } + } + else + /* REV is an exact revision number. */ + xrev = xstrdup (rev); - ret = RCS_exec_unlock (rcs->path, rev, noerr); - if (ret != 0) - return ret; + lock = findnode (RCS_getlocks (rcs), xrev); + if (lock == NULL) + { + /* This revision isn't locked. */ + free (xrev); + return 0; + } - /* Setting a lock will have changed the data in the file, so - delta_pos will no longer be correct. */ - rcs->flags |= NODELTA; + if (strcmp (lock->data, user) != 0) + { + /* If the revision is locked by someone else, notify + them. Note that this shouldn't ever happen if RCS_unlock + is called with a NULL revision, since that means "whatever + revision is currently locked by the caller." */ + char *repos, *workfile; + repos = xstrdup (rcs->path); + workfile = strrchr (repos, '/'); + *workfile++ = '\0'; + notify_do ('C', workfile, user, NULL, NULL, repos); + free (repos); + } + + delnode (lock); + if (!unlock_quiet) + { + cvs_output (xrev, 0); + cvs_output (" unlocked\n", 0); + } + free (xrev); 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 +/* Add USER to the access list of RCS. Do nothing if already present. + FIXME-twp: check syntax of USER to make sure it's a valid id. */ + +void +RCS_addaccess (rcs, user) + RCSNode *rcs; + char *user; { - char *text; - struct allocblock *next; -}; -struct allocblock *blocks; + char *access, *a; -static void *block_alloc PROTO ((size_t)); + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); -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; + if (rcs->access == NULL) + rcs->access = xstrdup (user); + else + { + access = xstrdup (rcs->access); + for (a = strtok (access, " "); a != NULL; a = strtok (NULL, " ")) + { + if (strcmp (a, user) == 0) + { + free (access); + return; + } + } + rcs->access = (char *) xrealloc + (rcs->access, strlen (rcs->access) + strlen (user) + 2); + strcat (rcs->access, " "); + strcat (rcs->access, user); + } } -static void block_free PROTO ((void)); +/* Remove USER from the access list of RCS. */ -static void -block_free () +void +RCS_delaccess (rcs, user) + RCSNode *rcs; + char *user; { - struct allocblock *p; - struct allocblock *q; + char *p, *s; + int ulen; + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + if (rcs->access == NULL) + return; - p = blocks; + p = rcs->access; + ulen = strlen (user); while (p != NULL) { - free (p->text); - q = p->next; - free (p); - p = q; + if (p[ulen] == '\0' || p[ulen] == ' ') + if (strncmp (p, user, ulen) == 0) + break; + p = strchr (p, ' '); + if (p != NULL) + ++p; } - blocks = NULL; + + if (p == NULL) + return; + + s = p + ulen; + while (*s != '\0') + *p++ = *s++; + *p = '\0'; +} + +char * +RCS_getaccess (rcs) + RCSNode *rcs; +{ + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + return rcs->access; } +static int findtag PROTO ((Node *, void *)); + +/* Return a nonzero value if the revision specified by ARG is found. */ + +static int +findtag (node, arg) + Node *node; + void *arg; +{ + char *rev = (char *)arg; + + if (strcmp (node->data, rev) == 0) + return 1; + else + return 0; +} + +/* Delete revisions between REV1 and REV2. The changes between the two + revisions must be collapsed, and the result stored in the revision + immediately preceding the lower one. Return 0 for successful completion, + 1 otherwise. + + Solution: check out the revision preceding REV1 and the revision + following REV2. Use call_diff to find aggregate diffs between + these two revisions, and replace the delta text for the latter one + with the new aggregate diff. Alternatively, we could write a + function that takes two change texts and combines them to produce a + new change text, without checking out any revs or calling diff. It + would be hairy, but so, so cool. + + If INCLUSIVE is set, then TAG1 and TAG2, if non-NULL, tell us to + delete that revision as well (cvs admin -o tag1:tag2). If clear, + delete up to but not including that revision (cvs admin -o tag1::tag2). + This does not affect TAG1 or TAG2 being NULL; the meaning of the start + point in ::tag2 and :tag2 is the same and likewise for end points. */ + +int +RCS_delete_revs (rcs, tag1, tag2, inclusive) + RCSNode *rcs; + char *tag1; + char *tag2; + int inclusive; +{ + char *next; + Node *nodep; + RCSVers *revp = NULL; + RCSVers *beforep; + int status, found; + int save_noexec; + + char *branchpoint = NULL; + char *rev1 = NULL; + char *rev2 = NULL; + int rev1_inclusive = inclusive; + int rev2_inclusive = inclusive; + char *before = NULL; + char *after = NULL; + char *beforefile = NULL; + char *afterfile = NULL; + char *outfile = NULL; + + if (tag1 == NULL && tag2 == NULL) + return 0; + + /* Assume error status until everything is finished. */ + status = 1; + + /* Make sure both revisions exist. */ + if (tag1 != NULL) + { + rev1 = RCS_gettag (rcs, tag1, 1, NULL); + if (rev1 == NULL || (nodep = findnode (rcs->versions, rev1)) == NULL) + { + error (0, 0, "%s: Revision %s doesn't exist.", rcs->path, tag1); + goto delrev_done; + } + } + if (tag2 != NULL) + { + rev2 = RCS_gettag (rcs, tag2, 1, NULL); + if (rev2 == NULL || (nodep = findnode (rcs->versions, rev2)) == NULL) + { + error (0, 0, "%s: Revision %s doesn't exist.", rcs->path, tag2); + goto delrev_done; + } + } + + /* If rev1 is on the trunk and rev2 is NULL, rev2 should be + RCS->HEAD. (*Not* RCS_head(rcs), which may return rcs->branch + instead.) We need to check this special case early, in order + to make sure that rev1 and rev2 get ordered correctly. */ + if (rev2 == NULL && numdots (rev1) == 1) + { + rev2 = xstrdup (rcs->head); + rev2_inclusive = 1; + } + + if (rev2 == NULL) + rev2_inclusive = 1; + + if (rev1 != NULL && rev2 != NULL) + { + /* A range consisting of a branch number means the latest revision + on that branch. */ + if (RCS_isbranch (rcs, rev1) && strcmp (rev1, rev2) == 0) + rev1 = rev2 = RCS_getbranch (rcs, rev1, 0); + else + { + /* Make sure REV1 and REV2 are ordered correctly (in the + same order as the next field). For revisions on the + trunk, REV1 should be higher than REV2; for branches, + REV1 should be lower. */ + /* Shouldn't we just be giving an error in the case where + the user specifies the revisions in the wrong order + (that is, always swap on the trunk, never swap on a + branch, in the non-error cases)? It is not at all + clear to me that users who specify -o 1.4:1.2 really + meant to type -o 1.2:1.4, and the out of order usage + has never been documented, either by cvs.texinfo or + rcs(1). */ + char *temp; + int temp_inclusive; + if (numdots (rev1) == 1) + { + if (compare_revnums (rev1, rev2) <= 0) + { + temp = rev2; + rev2 = rev1; + rev1 = temp; + + temp_inclusive = rev2_inclusive; + rev2_inclusive = rev1_inclusive; + rev1_inclusive = temp_inclusive; + } + } + else if (compare_revnums (rev1, rev2) > 0) + { + temp = rev2; + rev2 = rev1; + rev1 = temp; + + temp_inclusive = rev2_inclusive; + rev2_inclusive = rev1_inclusive; + rev1_inclusive = temp_inclusive; + } + } + } + + /* Basically the same thing; make sure that the ordering is what we + need. */ + if (rev1 == NULL) + { + assert (rev2 != NULL); + if (numdots (rev2) == 1) + { + /* Swap rev1 and rev2. */ + int temp_inclusive; + + rev1 = rev2; + rev2 = NULL; + + temp_inclusive = rev2_inclusive; + rev2_inclusive = rev1_inclusive; + rev1_inclusive = temp_inclusive; + } + } + + /* Put the revision number preceding the first one to delete into + BEFORE (where "preceding" means according to the next field). + If the first revision to delete is the first revision on its + branch (e.g. 1.3.2.1), BEFORE should be the node on the trunk + at which the branch is rooted. If the first revision to delete + is the head revision of the trunk, set BEFORE to NULL. + + Note that because BEFORE may not be on the same branch as REV1, + it is not very handy for navigating the revision tree. It's + most useful just for checking out the revision preceding REV1. */ + before = NULL; + branchpoint = RCS_getbranchpoint (rcs, rev1 != NULL ? rev1 : rev2); + if (rev1 == NULL) + { + rev1 = xstrdup (branchpoint); + if (numdots (branchpoint) > 1) + { + char *bp; + bp = strrchr (branchpoint, '.'); + while (*--bp != '.') + ; + *bp = '\0'; + /* Note that this is exclusive, always, because the inclusive + flag doesn't affect the meaning when rev1 == NULL. */ + before = xstrdup (branchpoint); + *bp = '.'; + } + } + else if (strcmp (rev1, branchpoint) != 0) + { + /* Walk deltas from BRANCHPOINT on, looking for REV1. */ + nodep = findnode (rcs->versions, branchpoint); + revp = (RCSVers *) nodep->data; + while (revp->next != NULL && strcmp (revp->next, rev1) != 0) + { + revp = (RCSVers *) nodep->data; + nodep = findnode (rcs->versions, revp->next); + } + if (revp->next == NULL) + { + error (0, 0, "%s: Revision %s doesn't exist.", rcs->path, rev1); + goto delrev_done; + } + if (rev1_inclusive) + before = xstrdup (revp->version); + else + { + before = rev1; + nodep = findnode (rcs->versions, before); + rev1 = xstrdup (((RCSVers *)nodep->data)->next); + } + } + else if (!rev1_inclusive) + { + before = rev1; + nodep = findnode (rcs->versions, before); + rev1 = xstrdup (((RCSVers *)nodep->data)->next); + } + else if (numdots (branchpoint) > 1) + { + /* Example: rev1 is "1.3.2.1", branchpoint is "1.3.2.1". + Set before to "1.3". */ + char *bp; + bp = strrchr (branchpoint, '.'); + while (*--bp != '.') + ; + *bp = '\0'; + before = xstrdup (branchpoint); + *bp = '.'; + } + + /* If any revision between REV1 and REV2 is locked or is a branch point, + we can't delete that revision and must abort. */ + after = NULL; + next = rev1; + found = 0; + while (!found && next != NULL) + { + nodep = findnode (rcs->versions, next); + revp = (RCSVers *) nodep->data; + + if (rev2 != NULL) + found = (strcmp (revp->version, rev2) == 0); + next = revp->next; + + if ((!found && next != NULL) || rev2_inclusive || rev2 == NULL) + { + if (findnode (RCS_getlocks (rcs), revp->version)) + { + error (0, 0, "%s: can't remove locked revision %s", + rcs->path, + revp->version); + goto delrev_done; + } + if (revp->branches != NULL) + { + error (0, 0, "%s: can't remove branch point %s", + rcs->path, + revp->version); + goto delrev_done; + } + + /* Doing this only for the :: syntax is for compatibility. + See cvs.texinfo for somewhat more discussion. */ + if (!inclusive + && walklist (RCS_symbols (rcs), findtag, revp->version)) + { + /* We don't print which file this happens to on the theory + that the caller will print the name of the file in a + more useful fashion (fullname not rcs->path). */ + error (0, 0, "cannot remove revision %s because it has tags", + revp->version); + goto delrev_done; + } + + /* It's misleading to print the `deleting revision' output + here, since we may not actually delete these revisions. + But that's how RCS does it. Bleah. Someday this should be + moved to the point where the revs are actually marked for + deletion. -twp */ + cvs_output ("deleting revision ", 0); + cvs_output (revp->version, 0); + cvs_output ("\n", 1); + } + } + + if (rev2 == NULL) + ; + else if (found) + { + if (rev2_inclusive) + after = xstrdup (next); + else + after = xstrdup (revp->version); + } + else if (!inclusive) + { + /* In the case of an empty range, for example 1.2::1.2 or + 1.2::1.3, we want to just do nothing. */ + status = 0; + goto delrev_done; + } + else + { + /* This looks fishy in the cases where tag1 == NULL or tag2 == NULL. + Are those cases really impossible? */ + assert (tag1 != NULL); + assert (tag2 != NULL); + + error (0, 0, "%s: invalid revision range %s:%s", rcs->path, + tag1, tag2); + goto delrev_done; + } + + /* The conditionals at this point get really hairy. Here is the + general idea: + + IF before != NULL and after == NULL + THEN don't check out any revisions, just delete them + IF before == NULL and after != NULL + THEN only check out after's revision, and use it for the new deltatext + ELSE + check out both revisions and diff -n them. This could use + RCS_exec_rcsdiff with some changes, like being able + to suppress diagnostic messages and to direct output. */ + + assert (before != NULL || after != NULL); + + if (after != NULL) + { + char *diffbuf; + size_t bufsize, len; + + afterfile = cvs_temp_name(); + status = RCS_checkout (rcs, NULL, after, NULL, NULL, afterfile, + (RCSCHECKOUTPROC)0, NULL); + if (status > 0) + goto delrev_done; + + else if (status < 0) + { + error (0, errno, + "cannot check out revision %s of %s", after, rcs->path); + goto delrev_done; + } + + if (before == NULL) + { + /* We are deleting revisions from the head of the tree, + so must create a new head. */ + diffbuf = NULL; + bufsize = 0; + get_file (afterfile, afterfile, "r", &diffbuf, &bufsize, &len); + + save_noexec = noexec; + noexec = 0; + if (unlink_file (afterfile) < 0) + error (0, errno, "cannot remove %s", afterfile); + noexec = save_noexec; + + free (afterfile); + afterfile = NULL; + + free (rcs->head); + rcs->head = xstrdup (after); + } + else + { + beforefile = cvs_temp_name(); + status = RCS_checkout (rcs, NULL, before, NULL, NULL, beforefile, + (RCSCHECKOUTPROC)0, NULL); + if (status > 0) + goto delrev_done; + else if (status < 0) + { + error (0, errno, "cannot check out revision %s of %s", + before, rcs->path); + goto delrev_done; + } + + outfile = cvs_temp_name(); + status = diff_exec (beforefile, afterfile, "-n", outfile); + + if (status == 2) + { + /* Not sure we need this message; will diff_exec already + have printed an error? */ + error (0, 0, "%s: could not diff", rcs->path); + status = 1; + goto delrev_done; + } + + diffbuf = NULL; + bufsize = 0; + get_file (outfile, outfile, "r", &diffbuf, &bufsize, &len); + } + + /* Save the new change text in after's delta node. */ + nodep = findnode (rcs->versions, after); + revp = (RCSVers *) nodep->data; + + assert (revp->text == NULL); + + revp->text = (Deltatext *) xmalloc (sizeof (Deltatext)); + memset ((Deltatext *) revp->text, 0, sizeof (Deltatext)); + revp->text->version = xstrdup (revp->version); + revp->text->text = diffbuf; + revp->text->len = len; + + /* If DIFFBUF is NULL, it means that OUTFILE is empty and that + there are no differences between the two revisions. In that + case, we want to force RCS_copydeltas to write an empty string + for the new change text (leaving the text field set NULL + means "preserve the original change text for this delta," so + we don't want that). */ + if (revp->text->text == NULL) + revp->text->text = xstrdup (""); + } + + /* Walk through the revisions (again) to mark each one as + outdated. (FIXME: would it be safe to use the `dead' field for + this? Doubtful.) */ + for (next = rev1; + next != NULL && (after == NULL || strcmp (next, after) != 0); + next = revp->next) + { + nodep = findnode (rcs->versions, next); + revp = (RCSVers *) nodep->data; + revp->outdated = 1; + } + + /* Update delta links. If BEFORE == NULL, we're changing the + head of the tree and don't need to update any `next' links. */ + if (before != NULL) + { + /* If REV1 is the first node on its branch, then BEFORE is its + root node (on the trunk) and we have to update its branches + list. Otherwise, BEFORE is on the same branch as AFTER, and + we can just change BEFORE's `next' field to point to AFTER. + (This should be safe: since findnode manages its lists via + the `hashnext' and `hashprev' fields, rather than `next' and + `prev', mucking with `next' and `prev' should not corrupt the + delta tree's internal structure. Much. -twp) */ + + if (rev1 == NULL) + /* beforep's ->next field already should be equal to after, + which I think is always NULL in this case. */ + ; + else if (strcmp (rev1, branchpoint) == 0) + { + nodep = findnode (rcs->versions, before); + revp = (RCSVers *) nodep->data; + nodep = revp->branches->list->next; + while (nodep != revp->branches->list && + strcmp (nodep->key, rev1) != 0) + nodep = nodep->next; + assert (nodep != revp->branches->list); + if (after == NULL) + delnode (nodep); + else + { + free (nodep->key); + nodep->key = xstrdup (after); + } + } + else + { + nodep = findnode (rcs->versions, before); + beforep = (RCSVers *) nodep->data; + free (beforep->next); + beforep->next = xstrdup (after); + } + } + + status = 0; + + delrev_done: + if (rev1 != NULL) + free (rev1); + if (rev2 != NULL) + free (rev2); + if (branchpoint != NULL) + free (branchpoint); + if (before != NULL) + free (before); + if (after != NULL) + free (after); + + save_noexec = noexec; + noexec = 0; + if (beforefile != NULL) + { + if (unlink_file (beforefile) < 0) + error (0, errno, "cannot remove %s", beforefile); + free (beforefile); + } + if (afterfile != NULL) + { + if (unlink_file (afterfile) < 0) + error (0, errno, "cannot remove %s", afterfile); + free (afterfile); + } + if (outfile != NULL) + { + if (unlink_file (outfile) < 0) + error (0, errno, "cannot remove %s", outfile); + free (outfile); + } + noexec = save_noexec; + + return status; +} + +/* RCS_deltas and friends. Processing of the deltas in RCS files. */ + struct line { - /* Text of this line. */ + /* Text of this line. Part of the same malloc'd block as the struct + line itself (we probably should use the "struct hack" (char text[1]) + and save ourselves sizeof (char *) bytes). Does not include \n; + instead has_newline indicates the presence or absence of \n. */ char *text; /* Length of this line, not counting \n if has_newline is true. */ size_t len; @@ -3410,6 +5025,8 @@ struct line /* Nonzero if this line ends with \n. This will always be true except possibly for the last line. */ int has_newline; + /* Number of pointers to this struct line. */ + int refcount; }; struct linevector @@ -3434,30 +5051,36 @@ linevector_init (vec) vec->vector = NULL; } -static int linevector_add PROTO ((struct linevector *vec, char *text, +static int linevector_add PROTO ((struct linevector *vec, const 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. This + not be \n terminated. + 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. */ + number is out of range. + + Each of the lines in TEXT are copied to space which is managed with + the linevector (and freed by linevector_free). So the caller doesn't + need to keep TEXT around after the call to this function. */ static int linevector_add (vec, text, len, vers, pos) struct linevector *vec; - char *text; + const char *text; size_t len; RCSVers *vers; unsigned int pos; { - char *textend; + const char *textend; unsigned int i; unsigned int nnew; - char *p; - struct line *lines; + const char *p; + const char *nextline_text; + size_t nextline_len; + int nextline_newline; + struct line *q; if (len == 0) return 1; @@ -3469,8 +5092,6 @@ linevector_add (vec, text, len, vers, pos) 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) @@ -3490,29 +5111,41 @@ linevector_add (vec, text, len, vers, pos) if (pos > vec->nlines) return 0; - /* Actually add the lines, to LINES and VEC->VECTOR. */ + /* Actually add the lines, to VEC->VECTOR. */ i = pos; - lines[0].text = text; - lines[0].vers = vers; - lines[0].has_newline = 0; - vec->vector[i++] = &lines[0]; + nextline_text = text; + nextline_newline = 0; for (p = text; p < textend; ++p) if (*p == '\n') { - *p = '\0'; - lines[i - pos - 1].has_newline = 1; + nextline_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; + nextline_len = p - nextline_text; + q = (struct line *) xmalloc (sizeof (struct line) + nextline_len); + q->vers = vers; + q->text = (char *)q + sizeof (struct line); + q->len = nextline_len; + q->has_newline = nextline_newline; + q->refcount = 1; + memcpy (q->text, nextline_text, nextline_len); + vec->vector[i++] = q; + + nextline_text = (char *)p + 1; + nextline_newline = 0; + } + nextline_len = p - nextline_text; + q = (struct line *) xmalloc (sizeof (struct line) + nextline_len); + q->vers = vers; + q->text = (char *)q + sizeof (struct line); + q->len = nextline_len; + q->has_newline = nextline_newline; + q->refcount = 1; + memcpy (q->text, nextline_text, nextline_len); + vec->vector[i] = q; + vec->nlines += nnew; return 1; @@ -3533,6 +5166,11 @@ linevector_delete (vec, pos, nlines) unsigned int last; last = vec->nlines - nlines; + for (i = pos; i < pos + nlines; ++i) + { + if (--vec->vector[i]->refcount == 0) + free (vec->vector[i]); + } for (i = pos; i < last; ++i) vec->vector[i] = vec->vector[i + nlines]; vec->nlines -= nlines; @@ -3546,6 +5184,13 @@ linevector_copy (to, from) struct linevector *to; struct linevector *from; { + unsigned int ln; + + for (ln = 0; ln < to->nlines; ++ln) + { + if (--to->vector[ln]->refcount == 0) + free (to->vector[ln]); + } if (from->nlines > to->lines_alloced) { if (to->lines_alloced == 0) @@ -3558,18 +5203,27 @@ linevector_copy (to, from) memcpy (to->vector, from->vector, from->nlines * sizeof (*to->vector)); to->nlines = from->nlines; + for (ln = 0; ln < to->nlines; ++ln) + ++to->vector[ln]->refcount; } static void linevector_free PROTO ((struct linevector *)); -/* Free storage associated with linevector (that is, the vector but - not the lines pointed to). */ +/* Free storage associated with linevector. */ static void linevector_free (vec) struct linevector *vec; { + unsigned int ln; + if (vec->vector != NULL) + { + for (ln = 0; ln < vec->nlines; ++ln) + if (--vec->vector[ln]->refcount == 0) + free (vec->vector[ln]); + free (vec->vector); + } } static char *month_printname PROTO ((char *)); @@ -3630,7 +5284,7 @@ apply_rcs_changes (lines, diffbuf, difflen, name, addvers, delvers) enum {ADD, DELETE} type; unsigned long pos; unsigned long nlines; - char *new_lines; + const char *new_lines; size_t len; struct deltafrag *next; }; @@ -3683,9 +5337,8 @@ apply_rcs_changes (lines, diffbuf, difflen, name, addvers, delvers) break; } - /* Copy the text we are adding into allocated space. */ - df->new_lines = block_alloc (q - p); - memcpy (df->new_lines, p, q - p); + /* Stash away a pointer to the text we are adding. */ + df->new_lines = p; df->len = q - p; p = q; @@ -3735,9 +5388,9 @@ apply_rcs_changes (lines, diffbuf, difflen, name, addvers, delvers) 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. */ + and *RETLEN. The new buffer is allocated by xmalloc. + + Return 1 for success. On failure, call error and return 0. */ int rcs_change_text (name, textbuf, textlen, diffbuf, difflen, retbuf, retlen) @@ -3795,11 +5448,6 @@ rcs_change_text (name, textbuf, textlen, diffbuf, difflen, retbuf, retlen) 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; } @@ -3851,19 +5499,11 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) 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"); - } + 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; @@ -3940,12 +5580,7 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) { if (ishead) { - char *p; - - p = block_alloc (vallen); - memcpy (p, value, vallen); - - if (! linevector_add (&curlines, p, vallen, NULL, 0)) + if (! linevector_add (&curlines, value, vallen, NULL, 0)) error (1, 0, "invalid rcs file %s", rcs->path); ishead = 0; @@ -4084,7 +5719,18 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) /* Now output the date. */ ym = strchr (prvers->date, '.'); if (ym == NULL) - cvs_output ("?\?-??\?-??", 0); + { + /* ??- is an ANSI trigraph. The ANSI way to + avoid it is \? but some pre ANSI compilers + complain about the unrecognized escape + sequence. Of course string concatenation + ("??" "-???") is also an ANSI-ism. Testing + __STDC__ seems to be a can of worms, since + compilers do all kinds of things with it. */ + cvs_output ("??", 0); + cvs_output ("-???", 0); + cvs_output ("-??", 0); + } else { md = strchr (ym + 1, '.'); @@ -4102,8 +5748,9 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) cvs_output (ym - 2, 2); } cvs_output ("): ", 0); - cvs_output (headlines.vector[ln]->text, - headlines.vector[ln]->len); + if (headlines.vector[ln]->len != 0) + cvs_output (headlines.vector[ln]->text, + headlines.vector[ln]->len); cvs_output ("\n", 1); } } @@ -4141,7 +5788,6 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) linevector_free (&headlines); linevector_free (&trunklines); - block_free (); return; l_error: @@ -4151,6 +5797,720 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) error (1, 0, "%s does not appear to be a valid rcs file", rcs->path); } + +static RCSVers * +getdelta (fp, rcsfile) + FILE *fp; + char *rcsfile; +{ + RCSVers *vnode; + char *key, *value, *cp; + long fpos; + Node *kv; + + vnode = (RCSVers *) xmalloc (sizeof (RCSVers)); + memset (vnode, 0, sizeof (RCSVers)); + + /* Get revision number. This uses getrcskey because it doesn't + croak when encountering unexpected input. As a result, we have + to play unholy games with `key' and `value'. */ + fpos = ftell (fp); + getrcskey (fp, &key, &value, NULL); + + /* Make sure that it is a revision number and not a cabbage + or something. */ + for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) + /* do nothing */ ; + if (*cp != '\0' || strncmp (RCSDATE, value, strlen (RCSDATE)) != 0) + { + (void) fseek (fp, fpos, SEEK_SET); + free (vnode); + return NULL; + } + vnode->version = xstrdup (key); + + /* grab the value of the date from value */ + cp = value + strlen (RCSDATE);/* skip the "date" keyword */ + while (whitespace (*cp)) /* take space off front of value */ + cp++; + + vnode->date = xstrdup (cp); + + /* 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); + + /* 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; + } + + /* Note that "branches" and "next" are in fact mandatory, according + to doc/RCSFILES. We perhaps should be giving an error if they + are not there. */ + + /* fill in the branch list (if any branches exist) */ + fpos = ftell (fp); + (void) getrcskey (fp, &key, &value, NULL); + /* FIXME: should be handling various error conditions better. */ + if (key != NULL && strcmp (key, RCSDESC) == 0) + { + (void) fseek (fp, fpos, SEEK_SET); + return vnode; + } + if (value != (char *) NULL) + { + vnode->branches = getlist (); + do_branches (vnode->branches, value); + } + + /* fill in the next field if there is a next revision */ + fpos = ftell (fp); + (void) getrcskey (fp, &key, &value, NULL); + /* FIXME: should be handling various error conditions better. */ + if (key != NULL && strcmp (key, RCSDESC) == 0) + { + (void) fseek (fp, fpos, SEEK_SET); + return vnode; + } + if (value != (char *) NULL) + vnode->next = xstrdup (value); + + /* + * XXX - this is where we put the symbolic link stuff??? + * (into newphrases in the deltas). + */ + /* FIXME: Does not correctly handle errors, e.g. from stdio. */ + while (1) + { + fpos = ftell (fp); + if (getrcskey (fp, &key, &value, NULL) < 0) + break; + + assert (key != NULL); + + if (strcmp (key, RCSDESC) == 0) + 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; + } + /* if we have a new revision number, we're done with this delta */ + for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) + /* do nothing */ ; + if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) + break; + + /* At this point, key and value represent a user-defined field + in the delta node. */ + if (vnode->other_delta == NULL) + vnode->other_delta = getlist (); + kv = getnode (); + kv->type = RCSFIELD; + kv->key = xstrdup (key); + kv->data = xstrdup (value); + if (addnode (vnode->other_delta, kv) != 0) + { + /* Complaining about duplicate keys in newphrases seems + questionable, in that we don't know what they mean and + doc/RCSFILES has no prohibition on several newphrases + with the same key. But we can't store more than one as + long as we store them in a List *. */ + error (0, 0, "warning: duplicate key `%s' in RCS file `%s'", + key, rcsfile); + freenode (kv); + } + } + + /* We got here because we read beyond the end of a delta. Seek back + to the beginning of the erroneous read. */ + (void) fseek (fp, fpos, SEEK_SET); + + return vnode; +} + +static void +freedeltatext (d) + Deltatext *d; +{ + if (d->version != NULL) + free (d->version); + if (d->log != NULL) + free (d->log); + if (d->text != NULL) + free (d->text); + if (d->other != (List *) NULL) + dellist (&d->other); + free (d); +} + +static Deltatext * +RCS_getdeltatext (rcs, fp) + RCSNode *rcs; + FILE *fp; +{ + char *num; + char *key, *value; + int n; + Node *p; + Deltatext *d; + size_t textlen; + + /* Get the revision number. */ + n = getrevnum (fp, &num); + if (ferror (fp)) + error (1, errno, "%s: cannot read", rcs->path); + if (n == EOF) + { + /* If n == EOF and num == NULL, it means we reached EOF + naturally. That's fine. */ + if (num == NULL) + return NULL; + else + error (1, 0, "%s: unexpected EOF", rcs->path); + } + + p = findnode (rcs->versions, num); + if (p == NULL) + error (1, 0, "mismatch in rcs file %s between deltas and deltatexts", + rcs->path); + + d = (Deltatext *) xmalloc (sizeof (Deltatext)); + d->version = xstrdup (num); + + /* Get the log message. */ + if (getrcskey (fp, &key, &value, NULL) < 0) + error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num); + if (strcmp (key, "log") != 0) + error (1, 0, "%s, delta %s: expected `log', got `%s'", + rcs->path, num, key); + d->log = xstrdup (value); + + /* Get random newphrases. */ + d->other = getlist(); + for (n = getrcskey (fp, &key, &value, &textlen); + n >= 0 && strcmp (key, "text") != 0; + n = getrcskey (fp, &key, &value, &textlen)) + { + p = getnode(); + p->type = RCSFIELD; + p->key = xstrdup (key); + p->data = xstrdup (value); + if (addnode (d->other, p) < 0) + { + error (0, 0, "warning: %s, delta %s: duplicate field `%s'", + rcs->path, num, key); + } + } + if (n < 0) + error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num); + + /* Get the change text. We already know that this key is `text'. */ + d->text = (char *) malloc (textlen + 1); + d->len = textlen; + memcpy (d->text, value, textlen); + + return d; +} + +/* RCS output functions, for writing RCS format files from RCSNode + structures. + + For most of this work, RCS 5.7 uses an `aprintf' function which aborts + program upon error. Instead, these functions check the output status + of the stream right before closing it, and aborts if an error condition + is found. The RCS solution is probably the better one: it produces + more overhead, but will produce a clearer diagnostic in the case of + catastrophic error. In either case, however, the repository will probably + not get corrupted. */ + +static int +putsymbol_proc (symnode, fp) + Node *symnode; + void *fp; +{ + return fprintf ((FILE *) fp, "\n\t%s:%s", symnode->key, symnode->data); +} + +static int putlock_proc PROTO ((Node *, void *)); + +/* putlock_proc is like putsymbol_proc, but key and data are reversed. */ + +static int +putlock_proc (symnode, fp) + Node *symnode; + void *fp; +{ + return fprintf ((FILE *) fp, "\n\t%s:%s", symnode->data, symnode->key); +} + +static int +putrcsfield_proc (node, vfp) + Node *node; + void *vfp; +{ + FILE *fp = (FILE *) vfp; + + /* Some magic keys used internally by CVS start with `;'. Skip them. */ + if (node->key[0] == ';') + return 0; + + fprintf (fp, "\n%s\t", node->key); + if (node->data != NULL) + { + /* If the field's value contains evil characters, + it must be stringified. */ + /* FIXME: This does not quite get it right. "7jk8f" is not a legal + value for a value in a newpharse, according to doc/RCSFILES, + because digits are not valid in an "id". We might do OK by + always writing strings (enclosed in @@). Would be nice to + explicitly mention this one way or another in doc/RCSFILES. + A case where we are wrong in a much more clear-cut way is that + we let through non-graphic characters such as whitespace and + control characters. */ + int n = strcspn (node->data, "$,.:;@"); + if (node->data[n] == 0) + fputs (node->data, fp); + else + { + putc ('@', fp); + expand_at_signs (node->data, (off_t) strlen (node->data), fp); + putc ('@', fp); + } + } + + /* desc, log and text fields should not be terminated with semicolon; + all other fields should be. */ + if (strcmp (node->key, "desc") != 0 && + strcmp (node->key, "log") != 0 && + strcmp (node->key, "text") != 0) + { + putc (';', fp); + } + return 0; +} + +/* Output the admin node for RCS into stream FP. */ + +static void +RCS_putadmin (rcs, fp) + RCSNode *rcs; + FILE *fp; +{ + fprintf (fp, "%s\t%s;\n", RCSHEAD, rcs->head ? rcs->head : ""); + if (rcs->branch) + fprintf (fp, "%s\t%s;\n", RCSBRANCH, rcs->branch); + + fputs ("access", fp); + if (rcs->access) + { + char *p, *s; + s = xstrdup (rcs->access); + for (p = strtok (s, " \n\t"); p != NULL; p = strtok (NULL, " \n\t")) + fprintf (fp, "\n\t%s", p); + free (s); + } + fputs (";\n", fp); + + fputs (RCSSYMBOLS, fp); + walklist (RCS_symbols(rcs), putsymbol_proc, (void *) fp); + fputs (";\n", fp); + + fputs ("locks", fp); + if (rcs->locks_data) + fprintf (fp, "\t%s", rcs->locks_data); + else if (rcs->locks) + walklist (rcs->locks, putlock_proc, (void *) fp); + if (rcs->strict_locks) + fprintf (fp, "; strict"); + fputs (";\n", fp); + + if (rcs->comment) + { + fprintf (fp, "comment\t@"); + expand_at_signs (rcs->comment, (off_t) strlen (rcs->comment), fp); + fputs ("@;\n", fp); + } + if (rcs->expand && strcmp (rcs->expand, "kv") != 0) + fprintf (fp, "%s\t@%s@;\n", RCSEXPAND, rcs->expand); + + walklist (rcs->other, putrcsfield_proc, (void *) fp); + + putc ('\n', fp); +} + +static void +putdelta (vers, fp) + RCSVers *vers; + FILE *fp; +{ + Node *bp, *start; + + /* Skip if no revision was supplied, or if it is outdated (cvs admin -o) */ + if (vers == NULL || vers->outdated) + return; + + fprintf (fp, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches", + vers->version, + RCSDATE, vers->date, + "author", vers->author, + "state", vers->state ? vers->state : ""); + + if (vers->branches != NULL) + { + start = vers->branches->list; + for (bp = start->next; bp != start; bp = bp->next) + fprintf (fp, "\n\t%s", bp->key); + } + + fprintf (fp, ";\nnext\t%s;", vers->next ? vers->next : ""); + + walklist (vers->other_delta, putrcsfield_proc, fp); + + putc ('\n', fp); +} + +static void +RCS_putdtree (rcs, rev, fp) + RCSNode *rcs; + char *rev; + FILE *fp; +{ + RCSVers *versp; + Node *p, *branch; + + if (rev == NULL) + return; + + /* Find the delta node for this revision. */ + p = findnode (rcs->versions, rev); + assert (p != NULL); + versp = (RCSVers *) p->data; + + /* Print the delta node and recurse on its `next' node. This prints + the trunk. If there are any branches printed on this revision, + print those trunks as well. */ + putdelta (versp, fp); + RCS_putdtree (rcs, versp->next, fp); + if (versp->branches != NULL) + { + branch = versp->branches->list; + for (p = branch->next; p != branch; p = p->next) + RCS_putdtree (rcs, p->key, fp); + } +} + +static void +RCS_putdesc (rcs, fp) + RCSNode *rcs; + FILE *fp; +{ + fprintf (fp, "\n\n%s\n@", RCSDESC); + if (rcs->desc != NULL) + { + off_t len = (off_t) strlen (rcs->desc); + if (len > 0) + { + expand_at_signs (rcs->desc, len, fp); + if (rcs->desc[len-1] != '\n') + putc ('\n', fp); + } + } + fputs ("@\n", fp); +} + +static void +putdeltatext (fp, d) + FILE *fp; + Deltatext *d; +{ + fprintf (fp, "\n\n%s\nlog\n@", d->version); + if (d->log != NULL) + { + int loglen = strlen (d->log); + expand_at_signs (d->log, (off_t) loglen, fp); + if (d->log[loglen-1] != '\n') + putc ('\n', fp); + } + putc ('@', fp); + + walklist (d->other, putrcsfield_proc, fp); + + fputs ("\ntext\n@", fp); + if (d->text != NULL) + expand_at_signs (d->text, (off_t) d->len, fp); + fputs ("@\n", fp); +} + +/* TODO: the whole mechanism for updating deltas is kludgey... more + sensible would be to supply all the necessary info in a `newdeltatext' + field for RCSVers nodes. -twp */ + +/* Copy delta text nodes from FIN to FOUT. If NEWDTEXT is non-NULL, it + is a new delta text node, and should be added to the tree at the + node whose revision number is INSERTPT. (Note that trunk nodes are + written in decreasing order, and branch nodes are written in + increasing order.) */ + +static void +RCS_copydeltas (rcs, fin, fout, newdtext, insertpt) + RCSNode *rcs; + FILE *fin; + FILE *fout; + Deltatext *newdtext; + char *insertpt; +{ + Deltatext *dtext; + RCSVers *dadmin; + Node *np; + int insertbefore, found; + + /* Make a note of whether NEWDTEXT should be inserted + before or after its INSERTPT. */ + insertbefore = (newdtext != NULL && numdots (newdtext->version) == 1); + + found = 0; + while ((dtext = RCS_getdeltatext (rcs, fin)) != NULL) + { + found = (insertpt != NULL && strcmp (dtext->version, insertpt) == 0); + if (found && insertbefore) + putdeltatext (fout, newdtext); + + np = findnode (rcs->versions, dtext->version); + dadmin = (RCSVers *) np->data; + + /* If this revision has been outdated, just skip it. */ + if (dadmin->outdated) + continue; + + /* Update the change text for this delta. New change text + data may come from cvs admin -m, cvs admin -o, or cvs ci. */ + if (dadmin->text != NULL) + { + if (dadmin->text->log != NULL) + { + free (dtext->log); + dtext->log = dadmin->text->log; + dadmin->text->log = NULL; + } + if (dadmin->text->text != NULL) + { + free (dtext->text); + dtext->text = dadmin->text->text; + dtext->len = dadmin->text->len; + dadmin->text->text = NULL; + } + } + putdeltatext (fout, dtext); + freedeltatext (dtext); + + if (found && !insertbefore) + putdeltatext (fout, newdtext); + } + putc ('\n', fout); +} + +/* RCS_internal_lockfile and RCS_internal_unlockfile perform RCS-style + locking on the specified RCSFILE: for a file called `foo,v', open + for writing a file called `,foo,'. + + Note that we what do here is quite different from what RCS does. + RCS creates the ,foo, file before it reads the RCS file (if it + knows that it will be writing later), so that it actually serves as + a lock. We don't; instead we rely on CVS writelocks. This means + that if someone is running RCS on the file at the same time they + are running CVS on it, they might lose (we read the file, + then RCS writes it, then we write it, clobbering the + changes made by RCS). I believe the current sentiment about this + is "well, don't do that". + + A concern has been expressed about whether adopting the RCS + strategy would slow us down. I don't think so, since we need to + write the ,foo, file anyway (unless perhaps if O_EXCL is slower or + something). + + These do not perform quite the same function as the RCS -l option + for locking files: they are intended to prevent competing RCS + processes from stomping all over each other's laundry. Hence, + they are `internal' locking functions. + + Note that we don't clean up the ,foo, file on ^C. We probably should. + I'm not completely sure whether RCS does or not (I looked at the code + a little, and didn't find it). + + If there is an error, give a fatal error; if we return we always + return a non-NULL value. */ + +static FILE * +rcs_internal_lockfile (rcsfile) + char *rcsfile; +{ + char *lockfile; + int fd; + struct stat rstat; + FILE *fp; + + /* Get the lock file name: `,file,' for RCS file `file,v'. */ + lockfile = rcs_lockfilename (rcsfile); + + /* Use the existing RCS file mode, or read-only if this is a new + file. (Really, this is a lie -- if this is a new file, + RCS_checkin uses the permissions from the working copy. For + actually creating the file, we use 0444 as a safe default mode.) */ + if (stat (rcsfile, &rstat) < 0) + { + if (existence_error (errno)) + rstat.st_mode = S_IRUSR | S_IRGRP | S_IROTH; + else + error (1, errno, "cannot stat %s", rcsfile); + } + + /* Try to open exclusively. POSIX.1 guarantees that O_EXCL|O_CREAT + guarantees an exclusive open. According to the RCS source, with + NFS v2 we must also throw in O_TRUNC and use an open mask that makes + the file unwriteable. For extensive justification, see the comments for + rcswriteopen() in rcsedit.c, in RCS 5.7. This is kind of pointless + in the CVS case; see comment at the start of this file concerning + general ,foo, file strategy. + + There is some sentiment that with NFSv3 and such, that one can + rely on O_EXCL these days. This might be true for unix (I + don't really know), but I am still pretty skeptical in the case + of the non-unix systems. */ + fd = open (lockfile, OPEN_BINARY | O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, + S_IRUSR | S_IRGRP | S_IROTH); + + if (fd < 0) + { + error (1, errno, "could not open lock file `%s'", lockfile); + } + + free (lockfile); + + /* Force the file permissions, and return a stream object. */ + /* Because we change the modes later, we don't worry about + this in the non-HAVE_FCHMOD case. */ +#ifdef HAVE_FCHMOD + if (fchmod (fd, rstat.st_mode) < 0) + error (1, errno, "cannot change mode for %s", lockfile); +#endif + fp = fdopen (fd, FOPEN_BINARY_WRITE); + if (fp == NULL) + error (1, errno, "cannot fdopen %s", lockfile); + return fp; +} + +static void +rcs_internal_unlockfile (fp, rcsfile) + FILE *fp; + char *rcsfile; +{ + char *lockfile; + + /* Get the lock file name: `,file,' for RCS file `file,v'. */ + lockfile = rcs_lockfilename (rcsfile); + + /* Abort if we could not write everything successfully to LOCKFILE. + This is not a great error-handling mechanism, but should prevent + corrupting the repository. */ + + if (ferror (fp)) + error (1, 0, "error writing to lock file %s", lockfile); + if (fclose (fp) == EOF) + error (1, errno, "error closing lock file %s", lockfile); + + rename_file (lockfile, rcsfile); + free (lockfile); +} + +static char * +rcs_lockfilename (rcsfile) + char *rcsfile; +{ + char *lockfile, *lockp; + char *rcsbase, *rcsp, *rcsend; + int rcslen; + + /* Create the lockfile name. */ + rcslen = strlen (rcsfile); + lockfile = (char *) xmalloc (rcslen + 10); + rcsbase = last_component (rcsfile); + rcsend = rcsfile + rcslen - sizeof(RCSEXT); + for (lockp = lockfile, rcsp = rcsfile; rcsp < rcsbase; ++rcsp) + *lockp++ = *rcsp; + *lockp++ = ','; + while (rcsp <= rcsend) + *lockp++ = *rcsp++; + *lockp++ = ','; + *lockp = '\0'; + + return lockfile; +} + +/* Rewrite an RCS file. The basic idea here is that the caller should + first call RCS_reparsercsfile, then munge the data structures as + desired (via RCS_delete_revs, RCS_settag, &c), then call RCS_rewrite. */ + +void +RCS_rewrite (rcs, newdtext, insertpt) + RCSNode *rcs; + Deltatext *newdtext; + char *insertpt; +{ + FILE *fin, *fout; + + if (noexec) + return; + + fout = rcs_internal_lockfile (rcs->path); + + RCS_putadmin (rcs, fout); + RCS_putdtree (rcs, rcs->head, fout); + RCS_putdesc (rcs, fout); + + /* Open the original RCS file and seek to the first delta text. */ + if ((fin = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ)) == NULL) + error (1, errno, "cannot open RCS file `%s' for reading", rcs->path); + if (fseek (fin, rcs->delta_pos, SEEK_SET) < 0) + error (1, errno, "cannot fseek in RCS file %s", rcs->path); + + /* Update delta_pos to the current position in the output file. + Do NOT move these statements: they must be done after fin has + been positioned at the old delta_pos, but before any delta + texts have been written to fout. */ + rcs->delta_pos = ftell (fout); + if (rcs->delta_pos == -1) + error (1, errno, "cannot ftell in RCS file %s", rcs->path); + + RCS_copydeltas (rcs, fin, fout, newdtext, insertpt); + + if (ferror (fin)) + error (0, errno, "warning: when closing RCS file `%s'", rcs->path); + if (fclose (fin) < 0) + error (0, errno, "warning: closing RCS file `%s'", rcs->path); + + rcs_internal_unlockfile (fout, rcs->path); +} /* Annotate command. In rcs.c for historical reasons (from back when @@ -4176,7 +6536,7 @@ annotate_fileproc (callerdat, finfo) return (1); if (finfo->rcs->flags & PARTIAL) - RCS_reparsercsfile (finfo->rcs, 0, &fp); + RCS_reparsercsfile (finfo->rcs, &fp); version = RCS_getversion (finfo->rcs, tag, date, force_tag_match, (int *) NULL); @@ -4203,6 +6563,7 @@ static const char *const annotate_usage[] = "\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", + "(Specify the --help global option for a list of other help options)\n", NULL }; @@ -4274,3 +6635,57 @@ annotate (argc, argv) argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, 1); } + +/* + * For a given file with full pathname PATH and revision number REV, + * produce a file label suitable for passing to diff. The default + * file label as used by RCS 5.7 looks like this: + * + * FILENAME <tab> YYYY/MM/DD <sp> HH:MM:SS <tab> REVNUM + * + * The date and time used are the revision's last checkin date and time. + * If REV is NULL, use the working copy's mtime instead. + */ +char * +make_file_label (path, rev, rcs) + char *path; + char *rev; + RCSNode *rcs; +{ + char datebuf[MAXDATELEN]; + char *label; + char *file; + + file = last_component (path); + label = (char *) xmalloc (strlen (file) + + (rev == NULL ? 0 : strlen (rev)) + + 50); + + if (rev) + { + char *date; + RCS_getrevtime (rcs, rev, datebuf, 0); + date = printable_date (datebuf); + (void) sprintf (label, "-L%s\t%s\t%s", file, date, rev); + free (date); + } + else + { + struct stat sb; + struct tm *wm; + + if (CVS_STAT (file, &sb) < 0) + error (0, 1, "could not get info for `%s'", path); + else + { + wm = gmtime (&sb.st_mtime); + (void) sprintf (datebuf, "%04d/%02d/%02d %02d:%02d:%02d", + wm->tm_year + 1900, wm->tm_mon + 1, + wm->tm_mday, wm->tm_hour, + wm->tm_min, wm->tm_sec); + (void) sprintf (label, "-L%s\t%s", file, datebuf); + } + } + return label; +} + |