diff options
Diffstat (limited to 'contrib/cvs/src/rcs.c')
-rw-r--r-- | contrib/cvs/src/rcs.c | 9074 |
1 files changed, 0 insertions, 9074 deletions
diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c deleted file mode 100644 index 3358e91..0000000 --- a/contrib/cvs/src/rcs.c +++ /dev/null @@ -1,9074 +0,0 @@ -/* - * Copyright (C) 1986-2005 The Free Software Foundation, Inc. - * - * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, - * and others. - * - * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk - * Portions Copyright (C) 1989-1992, Brian Berliner - * - * You may distribute under the terms of the GNU General Public License as - * 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 - * - * $FreeBSD$ - */ - -#include <assert.h> -#include "cvs.h" -#include "edit.h" -#include "hardlink.h" - -/* These need to be source after cvs.h or HAVE_MMAP won't be set... */ -#ifdef HAVE_MMAP -# include <sys/mman.h> -# ifndef HAVE_GETPAGESIZE -# include "getpagesize.h" -# endif -# ifndef MAP_FAILED -# define MAP_FAILED NULL -# endif -#endif - -#ifdef MMAP_FALLBACK_TEST -void *my_mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) -{ - if (rand() & 1) return mmap(addr, len, prot, flags, fd, offset); - return NULL; -} -#define mmap my_mmap -#endif - -int datesep = '/'; -int preserve_perms = 0; - -/* The RCS -k options, and a set of enums that must match the array. - These come first so that we can use enum kflag in function - prototypes. */ -static const char *const kflags[] = - {"kv", "kvl", "k", "v", "o", "b", (char *) NULL}; -enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B }; - -/* A structure we use to buffer the contents of an RCS file. The - various fields are only referenced directly by the rcsbuf_* - functions. We declare the struct here so that we can allocate it - on the stack, rather than in memory. */ - -struct rcsbuffer -{ - /* Points to the current position in the buffer. */ - char *ptr; - /* Points just after the last valid character in the buffer. */ - char *ptrend; - /* The file. */ - FILE *fp; - /* The name of the file, used for error messages. */ - const char *filename; - /* The starting file position of the data in the buffer. */ - unsigned long pos; - /* The length of the value. */ - size_t vlen; - /* Whether the value contains an '@' string. If so, we can not - compress whitespace characters. */ - int at_string; - /* The number of embedded '@' characters in an '@' string. If - this is non-zero, we must search the string for pairs of '@' - and convert them to a single '@'. */ - int embedded_at; - /* Whether the buffer has been mmap'ed or not. */ - int mmapped; -}; - -static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile)); -static char *RCS_getdatebranch PROTO((RCSNode * rcs, const char *date, - const char *branch)); -static void rcsbuf_open PROTO ((struct rcsbuffer *, FILE *fp, - const char *filename, unsigned long pos)); -static void rcsbuf_close PROTO ((struct rcsbuffer *)); -static int rcsbuf_getkey PROTO ((struct rcsbuffer *, char **keyp, - char **valp)); -static int rcsbuf_getrevnum PROTO ((struct rcsbuffer *, char **revp)); -static char *rcsbuf_fill PROTO ((struct rcsbuffer *, char *ptr, char **keyp, - char **valp)); -static int rcsbuf_valcmp PROTO ((struct rcsbuffer *)); -static char *rcsbuf_valcopy PROTO ((struct rcsbuffer *, char *val, int polish, - size_t *lenp)); -static void rcsbuf_valpolish PROTO ((struct rcsbuffer *, char *val, int polish, - size_t *lenp)); -static void rcsbuf_valpolish_internal PROTO ((struct rcsbuffer *, char *to, - const char *from, size_t *lenp)); -static unsigned long rcsbuf_ftell PROTO ((struct rcsbuffer *)); -static void rcsbuf_get_buffered PROTO ((struct rcsbuffer *, char **datap, - size_t *lenp)); -static void rcsbuf_cache PROTO ((RCSNode *, struct rcsbuffer *)); -static void rcsbuf_cache_close PROTO ((void)); -static void rcsbuf_cache_open PROTO ((RCSNode *, long, FILE **, - struct rcsbuffer *)); -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 *RCS_addbranch PROTO ((RCSNode *, const char *)); -static char *truncate_revnum_in_place PROTO ((char *)); -static char *truncate_revnum PROTO ((const char *)); -static char *printable_date PROTO((const char *)); -static char *escape_keyword_value PROTO ((const char *, int *)); -static void expand_keywords PROTO((RCSNode *, RCSVers *, const char *, - const char *, size_t, enum kflag, char *, - size_t, char **, size_t *)); -static void cmp_file_buffer PROTO((void *, const char *, size_t)); - -/* Routines for reading, parsing and writing RCS files. */ -static RCSVers *getdelta PROTO ((struct rcsbuffer *, char *, char **, - char **)); -static Deltatext *RCS_getdeltatext PROTO ((RCSNode *, FILE *, - struct rcsbuffer *)); -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 *, struct rcsbuffer *, - FILE *, Deltatext *, char *)); -static int count_delta_actions PROTO ((Node *, void *)); -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 ((const char *)); - -/* The RCS file reading functions are called a lot, and they do some - string comparisons. This macro speeds things up a bit by skipping - the function call when the first characters are different. It - evaluates its arguments multiple times. */ -#define STREQ(a, b) (*(char *)(a) == *(char *)(b) && strcmp ((a), (b)) == 0) - -static char * getfullCVSname PROTO ((char *, char **)); - -/* - * We don't want to use isspace() from the C library because: - * - * 1. The definition of "whitespace" in RCS files includes ASCII - * backspace, but the C locale doesn't. - * 2. isspace is an very expensive function call in some implementations - * due to the addition of wide character support. - */ -static const char spacetab[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, /* 0x00 - 0x0f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x8f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 0xf0 - 0xff */ -}; - -#define whitespace(c) (spacetab[(unsigned char)c] != 0) - -static char *rcs_lockfile; -static int rcs_lockfd = -1; - - - -/* - * char * - * locate_rcs ( const char* file, const char *repository , int *inattic ) - * - * Find an RCS file in the repository, case insensitively when the cased name - * doesn't exist, we are running as the server, and a client has asked us to - * ignore case. - * - * Most parts of CVS will want to rely instead on RCS_parse which calls this - * function and is called by recurse.c which then puts the result in useful - * places like the rcs field of struct file_info. - * - * INPUTS - * - * repository the repository (including the directory) - * file the filename within that directory (without RCSEXT). - * inattic NULL or a pointer to the output boolean - * - * OUTPUTS - * - * inattic If this input was non-null, the destination will be - * set to true if the file was found in the attic or - * false if not. If no RCS file is found, this value - * is undefined. - * - * RETURNS - * - * a newly-malloc'd array containing the absolute pathname of the RCS - * file that was found or NULL when none was found. - * - * ERRORS - * - * errno can be set by the return value of the final call to - * locate_file_in_dir(). This should resolve to the system's existence error - * value (sometime ENOENT) if the Attic directory did not exist and ENOENT if - * the Attic was found but no matching files were found in the Attic or its - * parent. - */ -static char * -locate_rcs (repository, file, inattic) - const char *repository; - const char *file; - int *inattic; -{ - char *retval; - - /* First, try to find the file as cased. */ - retval = xmalloc (strlen (repository) - + sizeof (CVSATTIC) - + strlen (file) - + sizeof (RCSEXT) - + 3); - sprintf (retval, "%s/%s%s", repository, file, RCSEXT); - if (isreadable (retval)) - { - if (inattic) - *inattic = 0; - return retval; - } - sprintf (retval, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT); - if (isreadable (retval)) - { - if (inattic) - *inattic = 1; - return retval; - } - free (retval); - - return NULL; -} - - - -/* A few generic thoughts on error handling, in particular the - printing of unexpected characters that we find in the RCS file - (that is, why we use '\x%x' rather than %c or some such). - - * Avoiding %c means we don't have to worry about what is printable - and other such stuff. In error handling, often better to keep it - simple. - - * Hex rather than decimal or octal because character set standards - tend to use hex. - - * Saying "character 0x%x" might make it sound like we are printing - a file offset. So we use '\x%x'. - - * Would be nice to print the offset within the file, but I can - imagine various portability hassles (in particular, whether - unsigned long is always big enough to hold file offsets). */ - -/* Parse an rcsfile given a user file name and a repository. If there is - an error, we print an error message and return NULL. If the file - does not exist, we return NULL without printing anything (I'm not - sure this allows the caller to do anything reasonable, but it is - the current behavior). */ -RCSNode * -RCS_parse (file, repos) - const char *file; - const char *repos; -{ - RCSNode *rcs; - FILE *fp; - RCSNode *retval = NULL; - char *rcsfile; - int inattic; - - /* We're creating a new RCSNode, so there is no hope of finding it - in the cache. */ - rcsbuf_cache_close (); - - if ((rcsfile = locate_rcs (repos, file, &inattic)) == NULL) - { - /* Handle the error cases */ - } - else if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL) - { - rcs = RCS_parsercsfile_i(fp, rcsfile); - if (rcs != NULL) - { - rcs->flags |= VALID; - if ( inattic ) - rcs->flags |= INATTIC; - } - - free ( rcsfile ); - retval = rcs; - } - else if (! existence_error (errno)) - { - error (0, errno, "cannot open %s", rcsfile); - free (rcsfile); - } - - return retval; -} - -/* - * Parse a specific rcsfile. - */ -RCSNode * -RCS_parsercsfile (rcsfile) - const char *rcsfile; -{ - FILE *fp; - RCSNode *rcs; - - /* We're creating a new RCSNode, so there is no hope of finding it - in the cache. */ - rcsbuf_cache_close (); - - /* open the rcsfile */ - if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL) - { - error (0, errno, "Couldn't open rcs file `%s'", rcsfile); - return (NULL); - } - - rcs = RCS_parsercsfile_i (fp, rcsfile); - - return (rcs); -} - - - -/* - */ -static RCSNode * -RCS_parsercsfile_i (fp, rcsfile) - FILE *fp; - const char *rcsfile; -{ - RCSNode *rdata; - struct rcsbuffer rcsbuf; - char *key, *value; - - /* make a node */ - rdata = (RCSNode *) xmalloc (sizeof (RCSNode)); - memset ((char *)rdata, 0, sizeof (RCSNode)); - rdata->refcount = 1; - rdata->path = xstrdup (rcsfile); - - /* Process HEAD, BRANCH, and EXPAND keywords from the RCS header. - - 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. */ - - rcsbuf_open (&rcsbuf, fp, rcsfile, 0); - - if (! rcsbuf_getkey (&rcsbuf, &key, &value)) - goto l_error; - if (STREQ (key, RCSDESC)) - goto l_error; - - if (STREQ (RCSHEAD, key) && value != NULL) - rdata->head = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *)NULL); - - if (! rcsbuf_getkey (&rcsbuf, &key, &value)) - goto l_error; - if (STREQ (key, RCSDESC)) - goto l_error; - - if (STREQ (RCSBRANCH, key) && value != NULL) - { - char *cp; - - rdata->branch = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *)NULL); - if ((numdots (rdata->branch) & 1) != 0) - { - /* turn it into a branch if it's a revision */ - cp = strrchr (rdata->branch, '.'); - *cp = '\0'; - } - } - - /* Look ahead for expand, stopping when we see desc or a revision - number. */ - while (1) - { - char *cp; - - if (STREQ (RCSEXPAND, key)) - { - rdata->expand = rcsbuf_valcopy (&rcsbuf, value, 0, - (size_t *)NULL); - break; - } - - for (cp = key; - (isdigit ((unsigned char)*cp) || *cp == '.') && *cp != '\0'; - cp++) - /* do nothing */ ; - if (*cp == '\0') - break; - - if (STREQ (RCSDESC, key)) - break; - - if (! rcsbuf_getkey (&rcsbuf, &key, &value)) - break; - } - - rdata->flags |= PARTIAL; - - rcsbuf_cache (rdata, &rcsbuf); - - return rdata; - -l_error: - error (0, 0, "`%s' does not appear to be a valid rcs file", - rcsfile); - rcsbuf_close (&rcsbuf); - freercsnode (&rdata); - fclose (fp); - return NULL; -} - - - -/* Do the real work of parsing an RCS file. - - On error, die with a fatal error; if it returns at all it was successful. - - If PFP is NULL, close the file when done. Otherwise, leave it open - and store the FILE * in *PFP. */ -void -RCS_reparsercsfile (rdata, pfp, rcsbufp) - RCSNode *rdata; - FILE **pfp; - struct rcsbuffer *rcsbufp; -{ - FILE *fp; - char *rcsfile; - struct rcsbuffer rcsbuf; - Node *q, *kv; - RCSVers *vnode; - int gotkey; - char *cp; - char *key, *value; - - assert (rdata != NULL); - rcsfile = rdata->path; - - rcsbuf_cache_open (rdata, 0, &fp, &rcsbuf); - - /* 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 (); - - /* - * process all the special header information, break out when we get to - * the first revision delta - */ - gotkey = 0; - for (;;) - { - /* get the next key/value pair */ - if (!gotkey) - { - if (! rcsbuf_getkey (&rcsbuf, &key, &value)) - { - error (1, 0, "`%s' does not appear to be a valid rcs file", - rcsfile); - } - } - - gotkey = 0; - - /* Skip head, branch and expand tags; we already have them. */ - if (STREQ (key, RCSHEAD) - || STREQ (key, RCSBRANCH) - || STREQ (key, RCSEXPAND)) - { - continue; - } - - if (STREQ (key, "access")) - { - if (value != NULL) - { - /* We pass the POLISH parameter as 1 because - RCS_addaccess expects nothing but spaces. FIXME: - It would be easy and more efficient to change - RCS_addaccess. */ - if (rdata->access) - { - error (0, 0, - "Duplicate `access' keyword found in RCS file."); - free (rdata->access); - } - rdata->access = rcsbuf_valcopy (&rcsbuf, value, 1, NULL); - } - continue; - } - - /* We always save lock information, so that we can handle - -kkvl correctly when checking out a file. */ - if (STREQ (key, "locks")) - { - if (value != NULL) - { - if (rdata->locks_data) - { - error (0, 0, - "Duplicate `locks' keyword found in RCS file."); - free (rdata->locks_data); - } - rdata->locks_data = rcsbuf_valcopy (&rcsbuf, value, 0, NULL); - } - if (! rcsbuf_getkey (&rcsbuf, &key, &value)) - { - error (1, 0, "premature end of file reading %s", rcsfile); - } - if (STREQ (key, "strict") && value == NULL) - { - rdata->strict_locks = 1; - } - else - gotkey = 1; - continue; - } - - if (STREQ (RCSSYMBOLS, key)) - { - if (value != NULL) - { - if (rdata->symbols_data) - { - error (0, 0, - "Duplicate `%s' keyword found in RCS file.", - RCSSYMBOLS); - free (rdata->symbols_data); - } - rdata->symbols_data = rcsbuf_valcopy (&rcsbuf, value, 0, NULL); - } - continue; - } - - /* - * check key for '.''s and digits (probably a rev) if it is a - * 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 ((unsigned char) *cp) || *cp == '.') && *cp != '\0'; - cp++) - /* do nothing */ ; - /* Note that when comparing with RCSDATE, we are not massaging - VALUE from the string found in the RCS file. This is OK - since we know exactly what to expect. */ - if (*cp == '\0' && strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) == 0) - break; - - if (STREQ (key, RCSDESC)) - break; - - if (STREQ (key, "comment")) - { - if (rdata->comment) - { - error (0, 0, - "warning: duplicate key `%s' in RCS file `%s'", - key, rcsfile); - free (rdata->comment); - } - rdata->comment = rcsbuf_valcopy (&rcsbuf, value, 0, NULL); - continue; - } - if (rdata->other == NULL) - rdata->other = getlist (); - kv = getnode (); - kv->type = rcsbuf_valcmp (&rcsbuf) ? RCSCMPFLD : RCSFIELD; - kv->key = xstrdup (key); - kv->data = rcsbuf_valcopy (&rcsbuf, value, kv->type == RCSFIELD, - (size_t *) NULL); - 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 */ - } - - /* We got out of the loop, so we have the first part of the first - revision delta in KEY (the revision) and VALUE (the date key - and its value). This is what getdelta expects to receive. */ - - while ((vnode = getdelta (&rcsbuf, rcsfile, &key, &value)) != NULL) - { - /* get the node */ - q = getnode (); - q->type = RCSVERS; - q->delproc = rcsvers_delproc; - q->data = vnode; - q->key = vnode->version; - - /* add the nodes to the list */ - if (addnode (rdata->versions, q)) - error (1, 0, "Multiple %s revision deltas found in `%s'", - q->key, rcsfile); - } - - /* Here KEY and VALUE are whatever caused getdelta to return NULL. */ - - if (STREQ (key, RCSDESC)) - { - if (rdata->desc != NULL) - { - error (0, 0, - "warning: duplicate key `%s' in RCS file `%s'", - key, rcsfile); - free (rdata->desc); - } - rdata->desc = rcsbuf_valcopy (&rcsbuf, value, 1, (size_t *) NULL); - } - - rdata->delta_pos = rcsbuf_ftell (&rcsbuf); - - if (pfp == NULL) - rcsbuf_cache (rdata, &rcsbuf); - else - { - *pfp = fp; - *rcsbufp = rcsbuf; - } - rdata->flags &= ~PARTIAL; -} - -/* Move RCS into or out of the Attic, depending on TOATTIC. If the - file is already in the desired place, return without doing - anything. At some point may want to think about how this relates - to RCS_rewrite but that is a bit hairy (if one wants renames to be - atomic, or that kind of thing). If there is an error, print a message - and return 1. On success, return 0. */ -int -RCS_setattic (rcs, toattic) - RCSNode *rcs; - int toattic; -{ - char *newpath; - const char *p; - char *q; - - /* Some systems aren't going to let us rename an open file. */ - rcsbuf_cache_close (); - - /* Could make the pathname computations in this file, and probably - in other parts of rcs.c too, easier if the REPOS and FILE - arguments to RCS_parse got stashed in the RCSNode. */ - - if (toattic) - { - mode_t omask; - - if (rcs->flags & INATTIC) - return 0; - - /* Example: rcs->path is "/foo/bar/baz,v". */ - newpath = xmalloc (strlen (rcs->path) + sizeof CVSATTIC + 5); - p = last_component (rcs->path); - strncpy (newpath, rcs->path, p - rcs->path); - strcpy (newpath + (p - rcs->path), CVSATTIC); - - /* Create the Attic directory if it doesn't exist. */ - omask = umask (cvsumask); - if (CVS_MKDIR (newpath, 0777) < 0 && errno != EEXIST) - error (0, errno, "cannot make directory %s", newpath); - (void) umask (omask); - - strcat (newpath, "/"); - strcat (newpath, p); - - if (CVS_RENAME (rcs->path, newpath) < 0) - { - int save_errno = errno; - - /* The checks for isreadable look awfully fishy, but - I'm going to leave them here for now until I - can think harder about whether they take care of - some cases which should be handled somehow. */ - - if (isreadable (rcs->path) || !isreadable (newpath)) - { - error (0, save_errno, "cannot rename %s to %s", - rcs->path, newpath); - free (newpath); - return 1; - } - } - } - else - { - if (!(rcs->flags & INATTIC)) - return 0; - - newpath = xmalloc (strlen (rcs->path)); - - /* Example: rcs->path is "/foo/bar/Attic/baz,v". */ - p = last_component (rcs->path); - strncpy (newpath, rcs->path, p - rcs->path - 1); - newpath[p - rcs->path - 1] = '\0'; - q = newpath + (p - rcs->path - 1) - (sizeof CVSATTIC - 1); - assert (strncmp (q, CVSATTIC, sizeof CVSATTIC - 1) == 0); - strcpy (q, p); - - if (CVS_RENAME (rcs->path, newpath) < 0) - { - error (0, errno, "failed to move `%s' out of the attic", - rcs->path); - free (newpath); - return 1; - } - } - - free (rcs->path); - rcs->path = newpath; - - return 0; -} - -/* - * Fully parse the RCS file. Store all keyword/value pairs, fetch the - * log messages for each revision, and fetch add and delete counts for - * each revision (we could fetch the entire text for each revision, - * but the only caller, log_fileproc, doesn't need that information, - * so we don't waste the memory required to store it). The add and - * delete counts are stored on the OTHER field of the RCSVERSNODE - * structure, under the names ";add" and ";delete", so that we don't - * waste the memory space of extra fields in RCSVERSNODE for code - * which doesn't need this information. - */ - -void -RCS_fully_parse (rcs) - RCSNode *rcs; -{ - FILE *fp; - struct rcsbuffer rcsbuf; - - RCS_reparsercsfile (rcs, &fp, &rcsbuf); - - while (1) - { - char *key, *value; - Node *vers; - RCSVers *vnode; - - /* Rather than try to keep track of how much information we - have read, just read to the end of the file. */ - if (!rcsbuf_getrevnum (&rcsbuf, &key)) - break; - - vers = findnode (rcs->versions, key); - if (!vers) - error (1, 0, - "Delta text %s without revision information in `%s'.", - key, rcs->path); - - vnode = vers->data; - - while (rcsbuf_getkey (&rcsbuf, &key, &value)) - { - if (!STREQ (key, "text")) - { - Node *kv; - - if (vnode->other == NULL) - vnode->other = getlist (); - kv = getnode (); - kv->type = rcsbuf_valcmp (&rcsbuf) ? RCSCMPFLD : RCSFIELD; - kv->key = xstrdup (key); - kv->data = rcsbuf_valcopy (&rcsbuf, value, kv->type == RCSFIELD, - (size_t *)NULL); - if (addnode (vnode->other, kv) != 0) - { - error (0, 0, - "\ -warning: duplicate key `%s' in version `%s' of RCS file `%s'", - key, vnode->version, rcs->path); - freenode (kv); - } - - continue; - } - - if (!STREQ (vnode->version, rcs->head)) - { - unsigned long add, del; - char buf[50]; - Node *kv; - - /* This is a change text. Store the add and delete - counts. */ - add = 0; - del = 0; - if (value != NULL) - { - size_t vallen; - const char *cp; - - rcsbuf_valpolish (&rcsbuf, value, 0, &vallen); - cp = value; - while (cp < value + vallen) - { - char op; - unsigned long count; - - op = *cp++; - if (op != 'a' && op != 'd') - error (1, 0, "\ -unrecognized operation '\\x%x' in %s revision %s", - op, rcs->path, vnode->version); - (void) strtoul (cp, (char **) &cp, 10); - if (*cp++ != ' ') - error (1, 0, "space expected in %s revision %s", - rcs->path, vnode->version); - count = strtoul (cp, (char **) &cp, 10); - if (*cp++ != '\012') - error (1, 0, "linefeed expected in %s revision %s", - rcs->path, vnode->version); - - if (op == 'd') - del += count; - else - { - add += count; - while (count != 0) - { - if (*cp == '\012') - --count; - else if (cp == value + vallen) - { - if (count != 1) - error (1, 0, "\ -premature end of value in %s revision %s", - rcs->path, vnode->version); - else - break; - } - ++cp; - } - } - } - } - - sprintf (buf, "%lu", add); - kv = getnode (); - kv->type = RCSFIELD; - kv->key = xstrdup (";add"); - kv->data = xstrdup (buf); - if (addnode (vnode->other, kv) != 0) - { - error (0, 0, - "\ -warning: duplicate key `%s' in version `%s' of RCS file `%s'", - key, vnode->version, rcs->path); - freenode (kv); - } - - sprintf (buf, "%lu", del); - kv = getnode (); - kv->type = RCSFIELD; - kv->key = xstrdup (";delete"); - kv->data = xstrdup (buf); - if (addnode (vnode->other, kv) != 0) - { - error (0, 0, - "\ -warning: duplicate key `%s' in version `%s' of RCS file `%s'", - key, vnode->version, rcs->path); - freenode (kv); - } - } - - /* We have found the "text" key which ends the data for - this revision. Break out of the loop and go on to the - next revision. */ - break; - } - } - - rcsbuf_cache (rcs, &rcsbuf); -} - - - -/* - * freercsnode - free up the info for an RCSNode - */ -void -freercsnode (rnodep) - RCSNode **rnodep; -{ - if (rnodep == NULL || *rnodep == NULL) - return; - - ((*rnodep)->refcount)--; - if ((*rnodep)->refcount != 0) - { - *rnodep = (RCSNode *) NULL; - return; - } - free ((*rnodep)->path); - if ((*rnodep)->head != (char *) NULL) - free ((*rnodep)->head); - if ((*rnodep)->branch != (char *) NULL) - free ((*rnodep)->branch); - free_rcsnode_contents (*rnodep); - free ((char *) *rnodep); - *rnodep = (RCSNode *) NULL; -} - -/* - * free_rcsnode_contents - free up the contents of an RCSNode without - * freeing the node itself, or the file name, or the head, or the - * path. This returns the RCSNode to the state it is in immediately - * after a call to RCS_parse. - */ -static void -free_rcsnode_contents (rnode) - RCSNode *rnode; -{ - dellist (&rnode->versions); - if (rnode->symbols != (List *) NULL) - dellist (&rnode->symbols); - if (rnode->symbols_data != (char *) NULL) - free (rnode->symbols_data); - if (rnode->expand != NULL) - free (rnode->expand); - if (rnode->other != (List *) NULL) - dellist (&rnode->other); - 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); -} - -/* free_rcsvers_contents -- free up the contents of an RCSVers node, - but also free the pointer to the node itself. */ -/* Note: The `hardlinks' list is *not* freed, since it is merely a - pointer into the `hardlist' structure (defined in hardlink.c), and - that structure is freed elsewhere in the program. */ - -static void -free_rcsvers_contents (rnode) - RCSVers *rnode; -{ - if (rnode->branches != (List *) NULL) - dellist (&rnode->branches); - if (rnode->date != (char *) NULL) - free (rnode->date); - if (rnode->next != (char *) NULL) - free (rnode->next); - if (rnode->author != (char *) NULL) - free (rnode->author); - if (rnode->state != (char *) NULL) - 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 (p->data); -} - -/* These functions retrieve keys and values from an RCS file using a - buffer. We use this somewhat complex approach because it turns out - that for many common operations, CVS spends most of its time - reading keys, so it's worth doing some fairly hairy optimization. */ - -/* The number of bytes we try to read each time we need more data. */ - -#define RCSBUF_BUFSIZE (8192) - -/* The buffer we use to store data. This grows as needed. */ - -static char *rcsbuf_buffer = NULL; -static size_t rcsbuf_buffer_size = 0; - -/* Whether rcsbuf_buffer is in use. This is used as a sanity check. */ - -static int rcsbuf_inuse; - -/* Set up to start gathering keys and values from an RCS file. This - initializes RCSBUF. */ - -static void -rcsbuf_open (rcsbuf, fp, filename, pos) - struct rcsbuffer *rcsbuf; - FILE *fp; - const char *filename; - unsigned long pos; -{ -#ifdef HAVE_MMAP - void *p; - struct stat fs; - size_t mmap_off = 0; -#endif - - if (rcsbuf_inuse) - error (1, 0, "rcsbuf_open: internal error"); - rcsbuf_inuse = 1; - -#ifdef HAVE_MMAP - /* When we have mmap, it is much more efficient to let the system do the - * buffering and caching for us - */ - - if ( fstat (fileno(fp), &fs) < 0 ) - error ( 1, errno, "Could not stat RCS archive %s for mapping", filename ); - - if (pos) - { - size_t ps = getpagesize (); - mmap_off = ( pos / ps ) * ps; - } - - /* Map private here since this particular buffer is read only */ - p = mmap ( NULL, fs.st_size - mmap_off, PROT_READ | PROT_WRITE, - MAP_PRIVATE, fileno(fp), mmap_off ); - if (p != NULL && p != MAP_FAILED) - { - if (rcsbuf_buffer) free (rcsbuf_buffer); - rcsbuf_buffer = p; - rcsbuf_buffer_size = fs.st_size - mmap_off; - rcsbuf->mmapped = 1; - rcsbuf->ptr = rcsbuf_buffer + pos - mmap_off; - rcsbuf->ptrend = rcsbuf_buffer + fs.st_size - mmap_off; - rcsbuf->pos = mmap_off; - } - else - { -#ifndef MMAP_FALLBACK_TEST - error (0, errno, "Could not map memory to RCS archive %s", filename); -#endif -#endif /* HAVE_MMAP */ - if (rcsbuf_buffer_size < RCSBUF_BUFSIZE) - expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, RCSBUF_BUFSIZE); - - rcsbuf->mmapped = 0; - rcsbuf->ptr = rcsbuf_buffer; - rcsbuf->ptrend = rcsbuf_buffer; - rcsbuf->pos = pos; -#ifdef HAVE_MMAP - } -#endif /* HAVE_MMAP */ - rcsbuf->fp = fp; - rcsbuf->filename = filename; - rcsbuf->vlen = 0; - rcsbuf->at_string = 0; - rcsbuf->embedded_at = 0; -} - -/* Stop gathering keys from an RCS file. */ - -static void -rcsbuf_close (rcsbuf) - struct rcsbuffer *rcsbuf; -{ - if (! rcsbuf_inuse) - error (1, 0, "rcsbuf_close: internal error"); -#ifdef HAVE_MMAP - if (rcsbuf->mmapped) - { - munmap ( rcsbuf_buffer, rcsbuf_buffer_size ); - rcsbuf_buffer = NULL; - rcsbuf_buffer_size = 0; - } -#endif - rcsbuf_inuse = 0; -} - -/* Read a key/value pair from an RCS file. This sets *KEYP to point - to the key, and *VALUEP to point to the value. A missing or empty - value is indicated by setting *VALUEP to NULL. - - This function returns 1 on success, or 0 on EOF. If there is an - error reading the file, or an EOF in an unexpected location, it - gives a fatal error. - - This sets *KEYP and *VALUEP to point to storage managed by - rcsbuf_getkey. Moreover, *VALUEP has not been massaged from the - RCS format: it may contain embedded whitespace and embedded '@' - characters. Call rcsbuf_valcopy or rcsbuf_valpolish to do - appropriate massaging. */ - -/* Note that the extreme hair in rcsbuf_getkey is because profiling - statistics show that it was worth it. */ - -static int -rcsbuf_getkey (rcsbuf, keyp, valp) - struct rcsbuffer *rcsbuf; - char **keyp; - char **valp; -{ - register const char * const my_spacetab = spacetab; - register char *ptr, *ptrend; - char c; - -#define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0) - - rcsbuf->vlen = 0; - rcsbuf->at_string = 0; - rcsbuf->embedded_at = 0; - - ptr = rcsbuf->ptr; - ptrend = rcsbuf->ptrend; - - /* Sanity check. */ - assert (ptr >= rcsbuf_buffer && ptr <= rcsbuf_buffer + rcsbuf_buffer_size); - assert (ptrend >= rcsbuf_buffer && ptrend <= rcsbuf_buffer + rcsbuf_buffer_size); - - /* If the pointer is more than RCSBUF_BUFSIZE bytes into the - buffer, move back to the start of the buffer. This keeps the - buffer from growing indefinitely. */ - if (!rcsbuf->mmapped && ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE) - { - int len; - - len = ptrend - ptr; - - /* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes - at a time, so we can't have more bytes than that past PTR. */ - assert (len <= RCSBUF_BUFSIZE); - - /* Update the POS field, which holds the file offset of the - first byte in the RCSBUF_BUFFER buffer. */ - rcsbuf->pos += ptr - rcsbuf_buffer; - - memcpy (rcsbuf_buffer, ptr, len); - ptr = rcsbuf_buffer; - ptrend = ptr + len; - rcsbuf->ptrend = ptrend; - } - - /* Skip leading whitespace. */ - - while (1) - { - if (ptr >= ptrend) - { - ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL); - if (ptr == NULL) - return 0; - ptrend = rcsbuf->ptrend; - } - - c = *ptr; - if (! my_whitespace (c)) - break; - - ++ptr; - } - - /* We've found the start of the key. */ - - *keyp = ptr; - - if (c != ';') - { - while (1) - { - ++ptr; - if (ptr >= ptrend) - { - ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL); - if (ptr == NULL) - error (1, 0, "EOF in key in RCS file %s", - rcsbuf->filename); - ptrend = rcsbuf->ptrend; - } - c = *ptr; - if (c == ';' || my_whitespace (c)) - break; - } - } - - /* Here *KEYP points to the key in the buffer, C is the character - we found at the of the key, and PTR points to the location in - the buffer where we found C. We must set *PTR to \0 in order - to terminate the key. If the key ended with ';', then there is - no value. */ - - *ptr = '\0'; - ++ptr; - - if (c == ';') - { - *valp = NULL; - rcsbuf->ptr = ptr; - return 1; - } - - /* C must be whitespace. Skip whitespace between the key and the - value. If we find ';' now, there is no value. */ - - while (1) - { - if (ptr >= ptrend) - { - ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL); - if (ptr == NULL) - error (1, 0, "EOF while looking for value in RCS file %s", - rcsbuf->filename); - ptrend = rcsbuf->ptrend; - } - c = *ptr; - if (c == ';') - { - *valp = NULL; - rcsbuf->ptr = ptr + 1; - return 1; - } - if (! my_whitespace (c)) - break; - ++ptr; - } - - /* Now PTR points to the start of the value, and C is the first - character of the value. */ - - if (c != '@') - *valp = ptr; - else - { - char *pat; - size_t vlen; - - /* Optimize the common case of a value composed of a single - '@' string. */ - - rcsbuf->at_string = 1; - - ++ptr; - - *valp = ptr; - - while (1) - { - while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL) - { - /* Note that we pass PTREND as the PTR value to - rcsbuf_fill, so that we will wind up setting PTR to - the location corresponding to the old PTREND, so - that we don't search the same bytes again. */ - ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp); - if (ptr == NULL) - error (1, 0, - "EOF while looking for end of string in RCS file %s", - rcsbuf->filename); - ptrend = rcsbuf->ptrend; - } - - /* Handle the special case of an '@' right at the end of - the known bytes. */ - if (pat + 1 >= ptrend) - { - /* Note that we pass PAT, not PTR, here. */ - pat = rcsbuf_fill (rcsbuf, pat, keyp, valp); - if (pat == NULL) - { - /* EOF here is OK; it just means that the last - character of the file was an '@' terminating a - value for a key type which does not require a - trailing ';'. */ - pat = rcsbuf->ptrend - 1; - - } - ptrend = rcsbuf->ptrend; - - /* Note that the value of PTR is bogus here. This is - OK, because we don't use it. */ - } - - if (pat + 1 >= ptrend || pat[1] != '@') - break; - - /* We found an '@' pair in the string. Keep looking. */ - ++rcsbuf->embedded_at; - ptr = pat + 2; - } - - /* Here PAT points to the final '@' in the string. */ - - *pat = '\0'; - - vlen = pat - *valp; - if (vlen == 0) - *valp = NULL; - rcsbuf->vlen = vlen; - - ptr = pat + 1; - } - - /* Certain keywords only have a '@' string. If there is no '@' - string, then the old getrcskey function assumed that they had - no value, and we do the same. */ - - { - char *k; - - k = *keyp; - if (STREQ (k, RCSDESC) - || STREQ (k, "text") - || STREQ (k, "log")) - { - if (c != '@') - *valp = NULL; - rcsbuf->ptr = ptr; - return 1; - } - } - - /* If we've already gathered a '@' string, try to skip whitespace - and find a ';'. */ - if (c == '@') - { - while (1) - { - char n; - - if (ptr >= ptrend) - { - ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp); - if (ptr == NULL) - error (1, 0, "EOF in value in RCS file %s", - rcsbuf->filename); - ptrend = rcsbuf->ptrend; - } - n = *ptr; - if (n == ';') - { - /* We're done. We already set everything up for this - case above. */ - rcsbuf->ptr = ptr + 1; - return 1; - } - if (! my_whitespace (n)) - break; - ++ptr; - } - - /* The value extends past the '@' string. We need to undo the - '@' stripping done in the default case above. This - case never happens in a plain RCS file, but it can happen - if user defined phrases are used. */ - ((*valp)--)[rcsbuf->vlen++] = '@'; - } - - /* Here we have a value which is not a simple '@' string. We need - to gather up everything until the next ';', including any '@' - strings. *VALP points to the start of the value. If - RCSBUF->VLEN is not zero, then we have already read an '@' - string, and PTR points to the data following the '@' string. - Otherwise, PTR points to the start of the value. */ - - while (1) - { - char *start, *psemi, *pat; - - /* Find the ';' which must end the value. */ - start = ptr; - while ((psemi = memchr (ptr, ';', ptrend - ptr)) == NULL) - { - int slen; - - /* Note that we pass PTREND as the PTR value to - rcsbuf_fill, so that we will wind up setting PTR to the - location corresponding to the old PTREND, so that we - don't search the same bytes again. */ - slen = start - *valp; - ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp); - if (ptr == NULL) - error (1, 0, "EOF in value in RCS file %s", rcsbuf->filename); - start = *valp + slen; - ptrend = rcsbuf->ptrend; - } - - /* See if there are any '@' strings in the value. */ - pat = memchr (start, '@', psemi - start); - - if (pat == NULL) - { - size_t vlen; - - /* We're done with the value. Trim any trailing - whitespace. */ - - rcsbuf->ptr = psemi + 1; - - start = *valp; - while (psemi > start && my_whitespace (psemi[-1])) - --psemi; - *psemi = '\0'; - - vlen = psemi - start; - if (vlen == 0) - *valp = NULL; - rcsbuf->vlen = vlen; - - return 1; - } - - /* We found an '@' string in the value. We set RCSBUF->AT_STRING - and RCSBUF->EMBEDDED_AT to indicate that we won't be able to - compress whitespace correctly for this type of value. - Since this type of value never arises in a normal RCS file, - this should not be a big deal. It means that if anybody - adds a phrase which can have both an '@' string and regular - text, they will have to handle whitespace compression - themselves. */ - - rcsbuf->at_string = 1; - rcsbuf->embedded_at = -1; - - ptr = pat + 1; - - while (1) - { - while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL) - { - /* Note that we pass PTREND as the PTR value to - rcsbuff_fill, so that we will wind up setting PTR - to the location corresponding to the old PTREND, so - that we don't search the same bytes again. */ - ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp); - if (ptr == NULL) - error (1, 0, - "EOF while looking for end of string in RCS file %s", - rcsbuf->filename); - ptrend = rcsbuf->ptrend; - } - - /* Handle the special case of an '@' right at the end of - the known bytes. */ - if (pat + 1 >= ptrend) - { - ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp); - if (ptr == NULL) - error (1, 0, "EOF in value in RCS file %s", - rcsbuf->filename); - ptrend = rcsbuf->ptrend; - } - - if (pat[1] != '@') - break; - - /* We found an '@' pair in the string. Keep looking. */ - ptr = pat + 2; - } - - /* Here PAT points to the final '@' in the string. */ - ptr = pat + 1; - } - -#undef my_whitespace -} - -/* Read an RCS revision number from an RCS file. This sets *REVP to - point to the revision number; it will point to space that is - managed by the rcsbuf functions, and is only good until the next - call to rcsbuf_getkey or rcsbuf_getrevnum. - - This function returns 1 on success, or 0 on EOF. If there is an - error reading the file, or an EOF in an unexpected location, it - gives a fatal error. */ - -static int -rcsbuf_getrevnum (rcsbuf, revp) - struct rcsbuffer *rcsbuf; - char **revp; -{ - char *ptr, *ptrend; - char c; - - ptr = rcsbuf->ptr; - ptrend = rcsbuf->ptrend; - - *revp = NULL; - - /* Skip leading whitespace. */ - - while (1) - { - if (ptr >= ptrend) - { - ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL); - if (ptr == NULL) - return 0; - ptrend = rcsbuf->ptrend; - } - - c = *ptr; - if (! whitespace (c)) - break; - - ++ptr; - } - - if (! isdigit ((unsigned char) c) && c != '.') - error (1, 0, - "\ -unexpected '\\x%x' reading revision number in RCS file %s", - c, rcsbuf->filename); - - *revp = ptr; - - do - { - ++ptr; - if (ptr >= ptrend) - { - ptr = rcsbuf_fill (rcsbuf, ptr, revp, (char **) NULL); - if (ptr == NULL) - error (1, 0, - "unexpected EOF reading revision number in RCS file %s", - rcsbuf->filename); - ptrend = rcsbuf->ptrend; - } - - c = *ptr; - } - while (isdigit ((unsigned char) c) || c == '.'); - - if (! whitespace (c)) - error (1, 0, "\ -unexpected '\\x%x' reading revision number in RCS file %s", - c, rcsbuf->filename); - - *ptr = '\0'; - - rcsbuf->ptr = ptr + 1; - - return 1; -} - -/* Fill RCSBUF_BUFFER with bytes from the file associated with RCSBUF, - updating PTR and the PTREND field. If KEYP and *KEYP are not NULL, - then *KEYP points into the buffer, and must be adjusted if the - buffer is changed. Likewise for VALP. Returns the new value of - PTR, or NULL on error. */ - -static char * -rcsbuf_fill (rcsbuf, ptr, keyp, valp) - struct rcsbuffer *rcsbuf; - char *ptr; - char **keyp; - char **valp; -{ - int got; - - if (rcsbuf->mmapped) - return NULL; - - if (rcsbuf->ptrend - rcsbuf_buffer + RCSBUF_BUFSIZE > rcsbuf_buffer_size) - { - int poff, peoff, koff, voff; - - poff = ptr - rcsbuf_buffer; - peoff = rcsbuf->ptrend - rcsbuf_buffer; - koff = keyp == NULL ? 0 : *keyp - rcsbuf_buffer; - voff = valp == NULL ? 0 : *valp - rcsbuf_buffer; - - expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, - rcsbuf_buffer_size + RCSBUF_BUFSIZE); - - ptr = rcsbuf_buffer + poff; - rcsbuf->ptrend = rcsbuf_buffer + peoff; - if (keyp != NULL) - *keyp = rcsbuf_buffer + koff; - if (valp != NULL) - *valp = rcsbuf_buffer + voff; - } - - got = fread (rcsbuf->ptrend, 1, RCSBUF_BUFSIZE, rcsbuf->fp); - if (got == 0) - { - if (ferror (rcsbuf->fp)) - error (1, errno, "cannot read %s", rcsbuf->filename); - return NULL; - } - - rcsbuf->ptrend += got; - - return ptr; -} - -/* Test whether the last value returned by rcsbuf_getkey is a composite - value or not. */ - -static int -rcsbuf_valcmp (rcsbuf) - struct rcsbuffer *rcsbuf; -{ - return rcsbuf->at_string && rcsbuf->embedded_at < 0; -} - -/* Copy the value VAL returned by rcsbuf_getkey into a memory buffer, - returning the memory buffer. Polish the value like - rcsbuf_valpolish, q.v. */ - -static char * -rcsbuf_valcopy (rcsbuf, val, polish, lenp) - struct rcsbuffer *rcsbuf; - char *val; - int polish; - size_t *lenp; -{ - size_t vlen; - int embedded_at; - char *ret; - - if (val == NULL) - { - if (lenp != NULL) - *lenp = 0; - return NULL; - } - - vlen = rcsbuf->vlen; - embedded_at = rcsbuf->embedded_at < 0 ? 0 : rcsbuf->embedded_at; - - ret = xmalloc (vlen - embedded_at + 1); - - if (rcsbuf->at_string ? embedded_at == 0 : ! polish) - { - /* No special action to take. */ - memcpy (ret, val, vlen + 1); - if (lenp != NULL) - *lenp = vlen; - return ret; - } - - rcsbuf_valpolish_internal (rcsbuf, ret, val, lenp); - return ret; -} - -/* Polish the value VAL returned by rcsbuf_getkey. The POLISH - parameter is non-zero if multiple embedded whitespace characters - should be compressed into a single whitespace character. Note that - leading and trailing whitespace was already removed by - rcsbuf_getkey. Within an '@' string, pairs of '@' characters are - compressed into a single '@' character regardless of the value of - POLISH. If LENP is not NULL, set *LENP to the length of the value. */ - -static void -rcsbuf_valpolish (rcsbuf, val, polish, lenp) - struct rcsbuffer *rcsbuf; - char *val; - int polish; - size_t *lenp; -{ - if (val == NULL) - { - if (lenp != NULL) - *lenp= 0; - return; - } - - if (rcsbuf->at_string ? rcsbuf->embedded_at == 0 : ! polish) - { - /* No special action to take. */ - if (lenp != NULL) - *lenp = rcsbuf->vlen; - return; - } - - rcsbuf_valpolish_internal (rcsbuf, val, val, lenp); -} - -/* Internal polishing routine, called from rcsbuf_valcopy and - rcsbuf_valpolish. */ - -static void -rcsbuf_valpolish_internal (rcsbuf, to, from, lenp) - struct rcsbuffer *rcsbuf; - char *to; - const char *from; - size_t *lenp; -{ - size_t len; - - len = rcsbuf->vlen; - - if (! rcsbuf->at_string) - { - char *orig_to; - size_t clen; - - orig_to = to; - - for (clen = len; clen > 0; ++from, --clen) - { - char c; - - c = *from; - if (whitespace (c)) - { - /* Note that we know that clen can not drop to zero - while we have whitespace, because we know there is - no trailing whitespace. */ - while (whitespace (from[1])) - { - ++from; - --clen; - } - c = ' '; - } - *to++ = c; - } - - *to = '\0'; - - if (lenp != NULL) - *lenp = to - orig_to; - } - else - { - const char *orig_from; - char *orig_to; - int embedded_at; - size_t clen; - - orig_from = from; - orig_to = to; - - embedded_at = rcsbuf->embedded_at; - assert (embedded_at > 0); - - if (lenp != NULL) - *lenp = len - embedded_at; - - for (clen = len; clen > 0; ++from, --clen) - { - char c; - - c = *from; - *to++ = c; - if (c == '@') - { - ++from; - - /* Sanity check. - * - * FIXME: I restored this to an abort from an assert based on - * advice from Larry Jones that asserts should not be used to - * confirm the validity of an RCS file... This leaves two - * issues here: 1) I am uncertain that the fact that we will - * only find double '@'s hasn't already been confirmed; and: - * 2) If this is the proper place to spot the error in the RCS - * file, then we should print a much clearer error here for the - * user!!!!!!! - * - * - DRP - */ - if (*from != '@' || clen == 0) - abort (); - - --clen; - - --embedded_at; - if (embedded_at == 0) - { - /* We've found all the embedded '@' characters. - We can just memcpy the rest of the buffer after - this '@' character. */ - if (orig_to != orig_from) - memcpy (to, from + 1, clen - 1); - else - memmove (to, from + 1, clen - 1); - from += clen; - to += clen - 1; - break; - } - } - } - - /* Sanity check. */ - assert (from == orig_from + len - && to == orig_to + (len - rcsbuf->embedded_at)); - - *to = '\0'; - } -} - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - -/* Copy the next word from the value VALP returned by rcsbuf_getkey into a - memory buffer, updating VALP and returning the memory buffer. Return - NULL when there are no more words. */ - -static char * -rcsbuf_valword (rcsbuf, valp) - struct rcsbuffer *rcsbuf; - char **valp; -{ - register const char * const my_spacetab = spacetab; - register char *ptr, *pat; - char c; - -# define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0) - - if (*valp == NULL) - return NULL; - - for (ptr = *valp; my_whitespace (*ptr); ++ptr) ; - if (*ptr == '\0') - { - assert (ptr - *valp == rcsbuf->vlen); - *valp = NULL; - rcsbuf->vlen = 0; - return NULL; - } - - /* PTR now points to the start of a value. Find out whether it is - a num, an id, a string or a colon. */ - c = *ptr; - if (c == ':') - { - rcsbuf->vlen -= ++ptr - *valp; - *valp = ptr; - return xstrdup (":"); - } - - if (c == '@') - { - int embedded_at = 0; - size_t vlen; - - pat = ++ptr; - while ((pat = strchr (pat, '@')) != NULL) - { - if (pat[1] != '@') - break; - ++embedded_at; - pat += 2; - } - - /* Here PAT points to the final '@' in the string. */ - *pat++ = '\0'; - assert (rcsbuf->at_string); - vlen = rcsbuf->vlen - (pat - *valp); - rcsbuf->vlen = pat - ptr - 1; - rcsbuf->embedded_at = embedded_at; - ptr = rcsbuf_valcopy (rcsbuf, ptr, 0, (size_t *) NULL); - *valp = pat; - rcsbuf->vlen = vlen; - if (strchr (pat, '@') == NULL) - rcsbuf->at_string = 0; - else - rcsbuf->embedded_at = -1; - return ptr; - } - - /* *PTR is neither `:', `;' nor `@', so it should be the start of a num - or an id. Make sure it is not another special character. */ - if (c == '$' || c == '.' || c == ',') - { - error (1, 0, "invalid special character in RCS field in %s", - rcsbuf->filename); - } - - pat = ptr; - while (1) - { - /* Legitimate ID characters are digits, dots and any `graphic - printing character that is not a special.' This test ought - to do the trick. */ - c = *++pat; - if (!isprint ((unsigned char) c) || - c == ';' || c == '$' || c == ',' || c == '@' || c == ':') - break; - } - - /* PAT points to the last non-id character in this word, and C is - the character in its memory cell. Check to make sure that it - is a legitimate word delimiter -- whitespace or end. */ - if (c != '\0' && !my_whitespace (c)) - error (1, 0, "invalid special character in RCS field in %s", - rcsbuf->filename); - - *pat = '\0'; - rcsbuf->vlen -= pat - *valp; - *valp = pat; - return xstrdup (ptr); - -# undef my_whitespace -} - -#endif - -/* Return the current position of an rcsbuf. */ - -static unsigned long -rcsbuf_ftell (rcsbuf) - struct rcsbuffer *rcsbuf; -{ - return rcsbuf->pos + (rcsbuf->ptr - rcsbuf_buffer); -} - -/* Return a pointer to any data buffered for RCSBUF, along with the - length. */ - -static void -rcsbuf_get_buffered (rcsbuf, datap, lenp) - struct rcsbuffer *rcsbuf; - char **datap; - size_t *lenp; -{ - *datap = rcsbuf->ptr; - *lenp = rcsbuf->ptrend - rcsbuf->ptr; -} - -/* CVS optimizes by quickly reading some header information from a - file. If it decides it needs to do more with the file, it reopens - it. We speed that up here by maintaining a cache of a single open - file, to save the time it takes to reopen the file in the common - case. */ - -static RCSNode *cached_rcs; -static struct rcsbuffer cached_rcsbuf; - -/* Cache RCS and RCSBUF. This takes responsibility for closing - RCSBUF->FP. */ - -static void -rcsbuf_cache (rcs, rcsbuf) - RCSNode *rcs; - struct rcsbuffer *rcsbuf; -{ - if (cached_rcs != NULL) - rcsbuf_cache_close (); - cached_rcs = rcs; - ++rcs->refcount; - cached_rcsbuf = *rcsbuf; -} - -/* If there is anything in the cache, close it. */ - -static void -rcsbuf_cache_close () -{ - if (cached_rcs != NULL) - { - rcsbuf_close (&cached_rcsbuf); - if (fclose (cached_rcsbuf.fp) != 0) - error (0, errno, "cannot close %s", cached_rcsbuf.filename); - freercsnode (&cached_rcs); - cached_rcs = NULL; - } -} - -/* Open an rcsbuffer for RCS, getting it from the cache if possible. - Set *FPP to the file, and *RCSBUFP to the rcsbuf. The file should - be put at position POS. */ - -static void -rcsbuf_cache_open (rcs, pos, pfp, prcsbuf) - RCSNode *rcs; - long pos; - FILE **pfp; - struct rcsbuffer *prcsbuf; -{ - if (cached_rcs == rcs && !cached_rcsbuf.mmapped) - { - if (rcsbuf_ftell (&cached_rcsbuf) != pos) - { - if (fseek (cached_rcsbuf.fp, pos, SEEK_SET) != 0) - error (1, 0, "cannot fseek RCS file %s", - cached_rcsbuf.filename); - cached_rcsbuf.ptr = rcsbuf_buffer; - cached_rcsbuf.ptrend = rcsbuf_buffer; - cached_rcsbuf.pos = pos; - } - *pfp = cached_rcsbuf.fp; - - /* When RCS_parse opens a file using fopen_case, it frees the - filename which we cached in CACHED_RCSBUF and stores a new - file name in RCS->PATH. We avoid problems here by always - copying the filename over. FIXME: This is hackish. */ - cached_rcsbuf.filename = rcs->path; - - *prcsbuf = cached_rcsbuf; - - cached_rcs = NULL; - - /* Removing RCS from the cache removes a reference to it. */ - --rcs->refcount; - if (rcs->refcount <= 0) - error (1, 0, "rcsbuf_cache_open: internal error"); - } - else - { - /* FIXME: If these routines can be rewritten to not write to the - * rcs file buffer, there would be a considerably larger memory savings - * from using mmap since the shared file would never need be copied to - * process memory. - * - * If this happens, cached mmapped buffers would be usable, but don't - * forget to make sure rcs->pos < pos here... - */ - if (cached_rcs != NULL) - rcsbuf_cache_close (); - - *pfp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ); - if (*pfp == NULL) - error (1, 0, "unable to reopen `%s'", rcs->path); - if (pos != 0) - { - if (fseek (*pfp, pos, SEEK_SET) != 0) - error (1, 0, "cannot fseek RCS file %s", rcs->path); - } - rcsbuf_open (prcsbuf, *pfp, rcs->path, pos); - } -} - - -/* - * process the symbols list of the rcs file - */ -static void -do_symbols (list, val) - List *list; - char *val; -{ - Node *p; - char *cp = val; - char *tag, *rev; - - assert (cp); - - for (;;) - { - /* skip leading whitespace */ - while (whitespace (*cp)) - cp++; - - /* if we got to the end, we are done */ - if (*cp == '\0') - break; - - /* split it up into tag and rev */ - tag = cp; - cp = strchr (cp, ':'); - *cp++ = '\0'; - rev = cp; - while (!whitespace (*cp) && *cp != '\0') - cp++; - if (*cp != '\0') - *cp++ = '\0'; - - /* make a new node and add it to the list */ - p = getnode (); - p->key = xstrdup (tag); - p->data = xstrdup (rev); - (void) addnode (list, p); - } -} - -/* - * process the 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; - - assert (cp); - - 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 -do_branches (list, val) - List *list; - char *val; -{ - Node *p; - char *cp = val; - char *branch; - - for (;;) - { - /* skip leading whitespace */ - while (whitespace (*cp)) - cp++; - - /* if we got to the end, we are done */ - if (*cp == '\0') - break; - - /* find the end of this branch */ - branch = cp; - while (!whitespace (*cp) && *cp != '\0') - cp++; - if (*cp != '\0') - *cp++ = '\0'; - - /* make a new node and add it to the list */ - p = getnode (); - p->key = xstrdup (branch); - (void) addnode (list, p); - } -} - -/* - * Version Number - * - * Returns the requested version number of the RCS file, satisfying tags and/or - * dates, and walking branches, if necessary. - * - * The result is returned; null-string if error. - */ -char * -RCS_getversion (rcs, tag, date, force_tag_match, simple_tag) - RCSNode *rcs; - const char *tag; - const char *date; - int force_tag_match; - int *simple_tag; -{ - if (simple_tag != NULL) - *simple_tag = 0; - - /* make sure we have something to look at... */ - assert (rcs != NULL); - - if (tag && date) - { - char *branch, *rev; - - if (! RCS_nodeisbranch (rcs, tag)) - { - /* We can't get a particular date if the tag is not a - branch. */ - return NULL; - } - - /* Work out the branch. */ - if (! isdigit ((unsigned char) tag[0])) - branch = RCS_whatbranch (rcs, tag); - else - branch = xstrdup (tag); - - /* Fetch the revision of branch as of date. */ - rev = RCS_getdatebranch (rcs, date, branch); - free (branch); - return (rev); - } - else if (tag) - return RCS_gettag (rcs, tag, force_tag_match, simple_tag); - else if (date) - return RCS_getdate (rcs, date, force_tag_match); - else - return RCS_head (rcs); - -} - - - -/* - * Get existing revision number corresponding to tag or revision. - * Similar to RCS_gettag but less interpretation imposed. - * For example: - * -- If tag designates a magic branch, RCS_tag2rev - * returns the magic branch number. - * -- If tag is a branch tag, returns the branch number, not - * the revision of the head of the branch. - * If tag or revision is not valid or does not exist in file, - * return NULL. - */ -char * -RCS_tag2rev (rcs, tag) - RCSNode *rcs; - char *tag; -{ - char *rev, *pa, *pb; - int i; - - assert (rcs != NULL); - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - /* If a valid revision, try to look it up */ - if ( RCS_valid_rev (tag) ) - { - /* Make a copy so we can scribble on it */ - rev = xstrdup (tag); - - /* If revision exists, return the copy */ - if (RCS_exist_rev (rcs, tag)) - return rev; - - /* Nope, none such. If tag is not a branch we're done. */ - i = numdots (rev); - if ((i & 1) == 1 ) - { - pa = strrchr (rev, '.'); - if (i == 1 || *(pa-1) != RCS_MAGIC_BRANCH || *(pa-2) != '.') - { - free (rev); - error (1, 0, "revision `%s' does not exist", tag); - } - } - - /* Try for a real (that is, exists in the RCS deltas) branch - (RCS_exist_rev just checks for real revisions and revisions - which have tags pointing to them). */ - pa = RCS_getbranch (rcs, rev, 1); - if (pa != NULL) - { - free (pa); - return rev; - } - - /* Tag is branch, but does not exist, try corresponding - * magic branch tag. - * - * FIXME: assumes all magic branches are of - * form "n.n.n ... .0.n". I'll fix if somebody can - * send me a method to get a magic branch tag with - * the 0 in some other position -- <dan@gasboy.com> - */ - pa = strrchr (rev, '.'); - if (!pa) - /* This might happen, for instance, if an RCS file only contained - * revisions 2.x and higher, and REV == "1". - */ - error (1, 0, "revision `%s' does not exist", tag); - - pb = xmalloc (strlen (rev) + 3); - *pa++ = 0; - (void) sprintf (pb, "%s.%d.%s", rev, RCS_MAGIC_BRANCH, pa); - free (rev); - rev = pb; - if (RCS_exist_rev (rcs, rev)) - return rev; - error (1, 0, "revision `%s' does not exist", tag); - } - - - RCS_check_tag (tag); /* exit if not a valid tag */ - - /* If tag is "HEAD", special case to get head RCS revision */ - if (tag && STREQ (tag, TAG_HEAD)) - return (RCS_head (rcs)); - - /* If valid tag let translate_symtag say yea or nay. */ - rev = translate_symtag (rcs, tag); - - if (rev) - return rev; - - /* Trust the caller to print warnings. */ - return NULL; -} - -/* - * Find the revision for a specific tag. - * If force_tag_match is set, return NULL if an exact match is not - * possible otherwise return RCS_head (). We are careful to look for - * and handle "magic" revisions specially. - * - * If the matched tag is a branch tag, find the head of the branch. - * - * Returns pointer to newly malloc'd string, or NULL. - */ -char * -RCS_gettag (rcs, symtag, force_tag_match, simple_tag) - RCSNode *rcs; - const char *symtag; - int force_tag_match; - int *simple_tag; -{ - char *tag; - - if (simple_tag != NULL) - *simple_tag = 0; - - /* make sure we have something to look at... */ - assert (rcs != NULL); - - /* XXX this is probably not necessary, --jtc */ - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - /* If symtag is "HEAD", special case to get head RCS revision */ - if (symtag && STREQ (symtag, TAG_HEAD)) -#if 0 /* This #if 0 is only in the Cygnus code. Why? Death support? */ - if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC)) - return ((char *) NULL); /* head request for removed file */ - else -#endif - return RCS_head (rcs); - - if (!isdigit ((unsigned char) symtag[0])) - { - char *version; - - /* If we got a symbolic tag, resolve it to a numeric */ - version = translate_symtag (rcs, symtag); - if (version != NULL) - { - int dots; - char *magic, *branch, *cp; - - tag = version; - - /* - * If this is a magic revision, we turn it into either its - * physical branch equivalent (if one exists) or into - * its base revision, which we assume exists. - */ - dots = numdots (tag); - if (dots > 2 && (dots & 1) != 0) - { - branch = strrchr (tag, '.'); - cp = branch++ - 1; - while (*cp != '.') - cp--; - - /* see if we have .magic-branch. (".0.") */ - magic = xmalloc (strlen (tag) + 1); - (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); - if (strncmp (magic, cp, strlen (magic)) == 0) - { - /* it's magic. See if the branch exists */ - *cp = '\0'; /* turn it into a revision */ - (void) sprintf (magic, "%s.%s", tag, branch); - branch = RCS_getbranch (rcs, magic, 1); - free (magic); - if (branch != NULL) - { - free (tag); - return branch; - } - return tag; - } - free (magic); - } - } - else - { - /* The tag wasn't there, so return the head or NULL */ - if (force_tag_match) - return NULL; - else - return RCS_head (rcs); - } - } - else - tag = xstrdup (symtag); - - /* tag is always allocated and numeric now. */ - - /* - * numeric tag processing: - * 1) revision number - just return it - * 2) branch number - find head of branch - */ - - /* strip trailing dots */ - while (tag[strlen (tag) - 1] == '.') - tag[strlen (tag) - 1] = '\0'; - - if ((numdots (tag) & 1) == 0) - { - char *branch; - - /* we have a branch tag, so we need to walk the branch */ - branch = RCS_getbranch (rcs, tag, force_tag_match); - free (tag); - return branch; - } - else - { - Node *p; - - /* we have a revision tag, so make sure it exists */ - p = findnode (rcs->versions, tag); - if (p != NULL) - { - /* We have found a numeric revision for the revision tag. - To support expanding the RCS keyword Name, if - SIMPLE_TAG is not NULL, tell the the caller that this - is a simple tag which co will recognize. FIXME: Are - there other cases in which we should set this? In - particular, what if we expand RCS keywords internally - without calling co? */ - if (simple_tag != NULL) - *simple_tag = 1; - return tag; - } - else - { - /* The revision wasn't there, so return the head or NULL */ - free (tag); - if (force_tag_match) - return NULL; - else - return RCS_head (rcs); - } - } -} - -/* - * Return a "magic" revision as a virtual branch off of REV for the RCS file. - * A "magic" revision is one which is unique in the RCS file. By unique, I - * mean we return a revision which: - * - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH) - * - has a revision component which is not an existing branch off REV - * - has a revision component which is not an existing magic revision - * - is an even-numbered revision, to avoid conflicts with vendor branches - * The first point is what makes it "magic". - * - * As an example, if we pass in 1.37 as REV, we will look for an existing - * branch called 1.37.2. If it did not exist, we would look for an - * existing symbolic tag with a numeric part equal to 1.37.0.2. If that - * didn't exist, then we know that the 1.37.2 branch can be reserved by - * creating a symbolic tag with 1.37.0.2 as the numeric part. - * - * This allows us to fork development with very little overhead -- just a - * symbolic tag is used in the RCS file. When a commit is done, a physical - * branch is dynamically created to hold the new revision. - * - * Note: We assume that REV is an RCS revision and not a branch number. - */ -static char *check_rev; -char * -RCS_magicrev (rcs, rev) - RCSNode *rcs; - char *rev; -{ - int rev_num; - char *xrev, *test_branch, *local_branch_num; - - xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */ - check_rev = xrev; - - local_branch_num = getenv("CVS_LOCAL_BRANCH_NUM"); - if (local_branch_num) - { - rev_num = atoi(local_branch_num); - if (rev_num < 2) - rev_num = 2; - else - rev_num &= ~1; - } - else - rev_num = 2; - - /* only look at even numbered branches */ - for ( ; ; rev_num += 2) - { - /* see if the physical branch exists */ - (void) sprintf (xrev, "%s.%d", rev, rev_num); - test_branch = RCS_getbranch (rcs, xrev, 1); - if (test_branch != NULL) /* it did, so keep looking */ - { - free (test_branch); - continue; - } - - /* now, create a "magic" revision */ - (void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num); - - /* walk the symbols list to see if a magic one already exists */ - if (walklist (RCS_symbols(rcs), checkmagic_proc, NULL) != 0) - continue; - - /* we found a free magic branch. Claim it as ours */ - return (xrev); - } -} - -/* - * walklist proc to look for a match in the symbols list. - * Returns 0 if the symbol does not match, 1 if it does. - */ -static int -checkmagic_proc (p, closure) - Node *p; - void *closure; -{ - if (STREQ (check_rev, p->data)) - return (1); - else - return (0); -} - -/* - * Given an RCSNode, returns non-zero if the specified revision number - * or symbolic tag resolves to a "branch" within the rcs file. - * - * FIXME: this is the same as RCS_nodeisbranch except for the special - * case for handling a null rcsnode. - */ -int -RCS_isbranch (rcs, rev) - RCSNode *rcs; - const char *rev; -{ - /* numeric revisions are easy -- even number of dots is a branch */ - if (isdigit ((unsigned char) *rev)) - return ((numdots (rev) & 1) == 0); - - /* assume a revision if you can't find the RCS info */ - if (rcs == NULL) - return (0); - - /* now, look for a match in the symbols list */ - return (RCS_nodeisbranch (rcs, rev)); -} - -/* - * Given an RCSNode, returns non-zero if the specified revision number - * or symbolic tag resolves to a "branch" within the rcs file. We do - * take into account any magic branches as well. - */ -int -RCS_nodeisbranch (rcs, rev) - RCSNode *rcs; - const char *rev; -{ - int dots; - char *version; - - assert (rcs != NULL); - - /* numeric revisions are easy -- even number of dots is a branch */ - if (isdigit ((unsigned char) *rev)) - return ((numdots (rev) & 1) == 0); - - version = translate_symtag (rcs, rev); - if (version == NULL) - return (0); - dots = numdots (version); - if ((dots & 1) == 0) - { - free (version); - return (1); - } - - /* got a symbolic tag match, but it's not a branch; see if it's magic */ - if (dots > 2) - { - char *magic; - char *branch = strrchr (version, '.'); - char *cp = branch - 1; - while (*cp != '.') - cp--; - - /* see if we have .magic-branch. (".0.") */ - magic = xmalloc (strlen (version) + 1); - (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); - if (strncmp (magic, cp, strlen (magic)) == 0) - { - free (magic); - free (version); - return (1); - } - free (magic); - } - free (version); - return (0); -} - -/* - * Returns a pointer to malloc'ed memory which contains the branch - * for the specified *symbolic* tag. Magic branches are handled correctly. - */ -char * -RCS_whatbranch (rcs, rev) - RCSNode *rcs; - const char *rev; -{ - char *version; - int dots; - - /* assume no branch if you can't find the RCS info */ - if (rcs == NULL) - return ((char *) NULL); - - /* now, look for a match in the symbols list */ - version = translate_symtag (rcs, rev); - if (version == NULL) - return ((char *) NULL); - dots = numdots (version); - if ((dots & 1) == 0) - return (version); - - /* got a symbolic tag match, but it's not a branch; see if it's magic */ - if (dots > 2) - { - char *magic; - char *branch = strrchr (version, '.'); - char *cp = branch++ - 1; - while (*cp != '.') - cp--; - - /* see if we have .magic-branch. (".0.") */ - magic = xmalloc (strlen (version) + 1); - (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); - if (strncmp (magic, cp, strlen (magic)) == 0) - { - /* yep. it's magic. now, construct the real branch */ - *cp = '\0'; /* turn it into a revision */ - (void) sprintf (magic, "%s.%s", version, branch); - free (version); - return (magic); - } - free (magic); - } - free (version); - return ((char *) NULL); -} - -/* - * Get the head of the specified branch. If the branch does not exist, - * return NULL or RCS_head depending on force_tag_match. - * Returns NULL or a newly malloc'd string. - */ -char * -RCS_getbranch (rcs, tag, force_tag_match) - RCSNode *rcs; - const char *tag; - int force_tag_match; -{ - Node *p, *head; - RCSVers *vn; - char *xtag; - char *nextvers; - char *cp; - - /* make sure we have something to look at... */ - assert (rcs != NULL); - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - /* find out if the tag contains a dot, or is on the trunk */ - cp = strrchr (tag, '.'); - - /* trunk processing is the special case */ - if (cp == NULL) - { - xtag = xmalloc (strlen (tag) + 1 + 1); /* +1 for an extra . */ - (void) strcpy (xtag, tag); - (void) strcat (xtag, "."); - for (cp = rcs->head; cp != NULL;) - { - if (strncmp (xtag, cp, strlen (xtag)) == 0) - break; - p = findnode (rcs->versions, cp); - if (p == NULL) - { - free (xtag); - if (force_tag_match) - return (NULL); - else - return (RCS_head (rcs)); - } - vn = p->data; - cp = vn->next; - } - free (xtag); - if (cp == NULL) - { - if (force_tag_match) - return (NULL); - else - return (RCS_head (rcs)); - } - return (xstrdup (cp)); - } - - /* if it had a `.', terminate the string so we have the base revision */ - *cp = '\0'; - - /* look up the revision this branch is based on */ - p = findnode (rcs->versions, tag); - - /* put the . back so we have the branch again */ - *cp = '.'; - - if (p == NULL) - { - /* if the base revision didn't exist, return head or NULL */ - if (force_tag_match) - return (NULL); - else - return (RCS_head (rcs)); - } - - /* find the first element of the branch we are looking for */ - vn = p->data; - if (vn->branches == NULL) - return (NULL); - xtag = xmalloc (strlen (tag) + 1 + 1); /* 1 for the extra '.' */ - (void) strcpy (xtag, tag); - (void) strcat (xtag, "."); - head = vn->branches->list; - for (p = head->next; p != head; p = p->next) - if (strncmp (p->key, xtag, strlen (xtag)) == 0) - break; - free (xtag); - - if (p == head) - { - /* we didn't find a match so return head or NULL */ - if (force_tag_match) - return (NULL); - else - return (RCS_head (rcs)); - } - - /* now walk the next pointers of the branch */ - nextvers = p->key; - do - { - p = findnode (rcs->versions, nextvers); - if (p == NULL) - { - /* a link in the chain is missing - return head or NULL */ - if (force_tag_match) - return (NULL); - else - return (RCS_head (rcs)); - } - vn = p->data; - nextvers = vn->next; - } while (nextvers != NULL); - - /* we have the version in our hand, so go for it */ - return (xstrdup (vn->version)); -} - -/* Returns the head of the branch which REV is on. REV can be a - branch tag or non-branch tag; symbolic or numeric. - - Returns a newly malloc'd string. Returns NULL if a symbolic name - isn't found. */ - -char * -RCS_branch_head (rcs, rev) - RCSNode *rcs; - char *rev; -{ - char *num; - char *br; - char *retval; - - assert (rcs != NULL); - - if (RCS_nodeisbranch (rcs, rev)) - return RCS_getbranch (rcs, rev, 1); - - if (isdigit ((unsigned char) *rev)) - num = xstrdup (rev); - else - { - num = translate_symtag (rcs, rev); - if (num == NULL) - return NULL; - } - br = truncate_revnum (num); - retval = RCS_getbranch (rcs, br, 1); - free (br); - free (num); - return retval; -} - -/* 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); - free (branch); - return NULL; - } - rev = 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. - * Returns NULL or a newly malloc'd string. - */ -char * -RCS_head (rcs) - RCSNode *rcs; -{ - /* make sure we have something to look at... */ - assert (rcs != NULL); - - /* - * NOTE: we call getbranch with force_tag_match set to avoid any - * possibility of recursion - */ - if (rcs->branch) - return (RCS_getbranch (rcs, rcs->branch, 1)); - else - return (xstrdup (rcs->head)); -} - -/* - * Get the most recent revision, based on the supplied date, but use some - * funky stuff and follow the vendor branch maybe - */ -char * -RCS_getdate (rcs, date, force_tag_match) - RCSNode *rcs; - const char *date; - int force_tag_match; -{ - char *cur_rev = NULL; - char *retval = NULL; - Node *p; - RCSVers *vers = NULL; - - /* make sure we have something to look at... */ - assert (rcs != NULL); - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - /* if the head is on a branch, try the branch first */ - if (rcs->branch != NULL) - { - retval = RCS_getdatebranch (rcs, date, rcs->branch); - if (retval != NULL) - return (retval); - } - - /* otherwise if we have a trunk, try it */ - if (rcs->head) - { - p = findnode (rcs->versions, rcs->head); - if (p == NULL) - { - error (0, 0, "%s: head revision %s doesn't exist", rcs->path, - rcs->head); - } - while (p != NULL) - { - /* if the date of this one is before date, take it */ - vers = p->data; - if (RCS_datecmp (vers->date, date) <= 0) - { - cur_rev = vers->version; - break; - } - - /* if there is a next version, find the node */ - if (vers->next != NULL) - p = findnode (rcs->versions, vers->next); - else - p = (Node *) NULL; - } - } - else - error (0, 0, "%s: no head revision", rcs->path); - - /* - * at this point, either we have the revision we want, or we have the - * first revision on the trunk (1.1?) in our hands, or we've come up - * completely empty - */ - - /* if we found what we're looking for, and it's not 1.1 return it */ - if (cur_rev != NULL) - { - if (! STREQ (cur_rev, "1.1")) - return (xstrdup (cur_rev)); - - /* This is 1.1; if the date of 1.1 is not the same as that for the - 1.1.1.1 version, then return 1.1. This happens when the first - version of a file is created by a regular cvs add and commit, - and there is a subsequent cvs import of the same file. */ - p = findnode (rcs->versions, "1.1.1.1"); - if (p) - { - char *date_1_1 = vers->date; - - assert (p->data != NULL); - - vers = p->data; - if (RCS_datecmp (vers->date, date_1_1) != 0) - return xstrdup ("1.1"); - } - } - - /* look on the vendor branch */ - retval = RCS_getdatebranch (rcs, date, CVSBRANCH); - - /* - * if we found a match, return it; otherwise, we return the first - * revision on the trunk or NULL depending on force_tag_match and the - * date of the first rev - */ - if (retval != NULL) - return (retval); - - if (vers && (!force_tag_match || RCS_datecmp (vers->date, date) <= 0)) - return xstrdup (vers->version); - else - return NULL; -} - - - -/* - * Look up the last element on a branch that was put in before the specified - * date (return the rev or NULL) - */ -static char * -RCS_getdatebranch (rcs, date, branch) - RCSNode *rcs; - const char *date; - const char *branch; -{ - char *cur_rev = NULL; - char *cp; - char *xbranch, *xrev; - Node *p; - RCSVers *vers; - - /* look up the first revision on the branch */ - xrev = xstrdup (branch); - cp = strrchr (xrev, '.'); - if (cp == NULL) - { - free (xrev); - return (NULL); - } - *cp = '\0'; /* turn it into a revision */ - - assert (rcs != NULL); - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - p = findnode (rcs->versions, xrev); - free (xrev); - if (p == NULL) - return (NULL); - vers = p->data; - - /* Tentatively use this revision, if it is early enough. */ - if (RCS_datecmp (vers->date, date) <= 0) - cur_rev = vers->version; - - /* If no branches list, return now. This is what happens if the branch - is a (magic) branch with no revisions yet. */ - if (vers->branches == NULL) - return xstrdup (cur_rev); - - /* walk the branches list looking for the branch number */ - xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */ - (void) strcpy (xbranch, branch); - (void) strcat (xbranch, "."); - for (p = vers->branches->list->next; p != vers->branches->list; p = p->next) - if (strncmp (p->key, xbranch, strlen (xbranch)) == 0) - break; - free (xbranch); - if (p == vers->branches->list) - { - /* 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); - - /* walk the next pointers until you find the end, or the date is too late */ - while (p != NULL) - { - vers = p->data; - if (RCS_datecmp (vers->date, date) <= 0) - cur_rev = vers->version; - else - break; - - /* if there is a next version, find the node */ - if (vers->next != NULL) - p = findnode (rcs->versions, vers->next); - else - p = (Node *) NULL; - } - - /* Return whatever we found, which may be NULL. */ - return xstrdup (cur_rev); -} - - - -/* - * Compare two dates in RCS format. Beware the change in format on January 1, - * 2000, when years go from 2-digit to full format. - */ -int -RCS_datecmp (date1, date2) - const char *date1, *date2; -{ - int length_diff = strlen (date1) - strlen (date2); - - return length_diff ? length_diff : strcmp (date1, date2); -} - - - -/* Look up revision REV in RCS and return the date specified for the - revision minus FUDGE seconds (FUDGE will generally be one, so that the - logically previous revision will be found later, or zero, if we want - the exact date). - - The return value is the date being returned as a time_t, or (time_t)-1 - on error (previously was documented as zero on error; I haven't checked - the callers to make sure that they really check for (time_t)-1, but - the latter is what this function really returns). If DATE is non-NULL, - then it must point to MAXDATELEN characters, and we store the same - return value there in DATEFORM format. */ -time_t -RCS_getrevtime (rcs, rev, date, fudge) - RCSNode *rcs; - const char *rev; - char *date; - int fudge; -{ - char tdate[MAXDATELEN]; - struct tm xtm, *ftm; - time_t revdate = 0; - Node *p; - RCSVers *vers; - - /* make sure we have something to look at... */ - assert (rcs != NULL); - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - /* look up the revision */ - p = findnode (rcs->versions, rev); - if (p == NULL) - return (-1); - vers = p->data; - - /* split up the date */ - if (sscanf (vers->date, SDATEFORM, &xtm.tm_year, &xtm.tm_mon, - &xtm.tm_mday, &xtm.tm_hour, &xtm.tm_min, &xtm.tm_sec) != 6) - error (1, 0, "%s: invalid date for revision %s (%s)", rcs->path, - rev, vers->date); - - /* If the year is from 1900 to 1999, RCS files contain only two - digits, and sscanf gives us a year from 0-99. If the year is - 2000+, RCS files contain all four digits and we subtract 1900, - because the tm_year field should contain years since 1900. */ - - if (xtm.tm_year >= 100 && xtm.tm_year < 2000) - error (0, 0, "%s: non-standard date format for revision %s (%s)", - rcs->path, rev, vers->date); - if (xtm.tm_year >= 1900) - xtm.tm_year -= 1900; - - /* put the date in a form getdate can grok */ - (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", xtm.tm_mon, - xtm.tm_mday, xtm.tm_year + 1900, xtm.tm_hour, - xtm.tm_min, xtm.tm_sec); - - /* turn it into seconds since the epoch */ - revdate = get_date (tdate, (struct timeb *) NULL); - if (revdate != (time_t) -1) - { - revdate -= fudge; /* remove "fudge" seconds */ - if (date) - { - /* put an appropriate string into ``date'' if we were given one */ - ftm = gmtime (&revdate); - (void) sprintf (date, DATEFORM, - ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), - ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, - ftm->tm_min, ftm->tm_sec); - } - } - return revdate; -} - -List * -RCS_getlocks (rcs) - RCSNode *rcs; -{ - assert(rcs != NULL); - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) 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, (FILE **) NULL, (struct rcsbuffer *) NULL); - - if (rcs->symbols_data) { - rcs->symbols = getlist (); - do_symbols (rcs->symbols, rcs->symbols_data); - free(rcs->symbols_data); - rcs->symbols_data = NULL; - } - - return rcs->symbols; -} - -/* - * Return the version associated with a particular symbolic tag. - * Returns NULL or a newly malloc'd string. - */ -static char * -translate_symtag (rcs, tag) - RCSNode *rcs; - const char *tag; -{ - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - if (rcs->symbols != NULL) - { - Node *p; - - /* The symbols have already been converted into a list. */ - p = findnode (rcs->symbols, tag); - if (p == NULL) - return NULL; - - return xstrdup (p->data); - } - - if (rcs->symbols_data != NULL) - { - size_t len; - char *cp, *last; - - /* Look through the RCS symbols information. This is like - do_symbols, but we don't add the information to a list. In - most cases, we will only be called once for this file, so - generating the list is unnecessary overhead. */ - - len = strlen (tag); - cp = rcs->symbols_data; - /* Keeping track of LAST below isn't strictly necessary, now that tags - * should be parsed for validity before they are accepted, but tags - * with spaces used to cause the code below to loop indefintely, so - * I have corrected for that. Now, in the event that I missed - * something, the server cannot be hung. -DRP - */ - last = NULL; - while ((cp = strchr (cp, tag[0])) != NULL) - { - if (cp == last) break; - if ((cp == rcs->symbols_data || whitespace (cp[-1])) - && strncmp (cp, tag, len) == 0 - && cp[len] == ':') - { - char *v, *r; - - /* We found the tag. Return the version number. */ - - cp += len + 1; - v = cp; - while (! whitespace (*cp) && *cp != '\0') - ++cp; - r = xmalloc (cp - v + 1); - strncpy (r, v, cp - v); - r[cp - v] = '\0'; - return r; - } - - while (! whitespace (*cp) && *cp != '\0') - ++cp; - if (*cp == '\0') - break; - last = cp; - } - } - - return NULL; -} - -/* - * The argument ARG is the getopt remainder of the -k option specified on the - * command line. This function returns malloc'ed space that can be used - * directly in calls to RCS V5, with the -k flag munged correctly. - */ -char * -RCS_check_kflag (arg) - const char *arg; -{ - static const char *const keyword_usage[] = - { - "%s %s: invalid RCS keyword expansion mode\n", - "Valid expansion modes include:\n", - " -kkv\tGenerate keywords using the default form.\n", - " -kkvl\tLike -kkv, except locker's name inserted.\n", - " -kk\tGenerate only keyword names in keyword strings.\n", - " -kv\tGenerate only keyword values in keyword strings.\n", - " -ko\tGenerate the old keyword string (no changes from checked in file).\n", - " -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n", - "(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; - - if (arg) - { - for (cpp = kflags; *cpp != NULL; cpp++) - { - if (STREQ (arg, *cpp)) - break; - } - } - - if (arg == NULL || *cpp == NULL) - { - usage (keyword_usage); - } - - (void) sprintf (karg, "-k%s", *cpp); - return (xstrdup (karg)); -} - -/* - * Do some consistency checks on the symbolic tag... These should equate - * pretty close to what RCS checks, though I don't know for certain. - */ -void -RCS_check_tag (tag) - const char *tag; -{ - char *invalid = "$,.:;@"; /* invalid RCS tag characters */ - const char *cp; - - /* - * The first character must be an alphabetic letter. The remaining - * characters cannot be non-visible graphic characters, and must not be - * in the set of "invalid" RCS identifier characters. - */ - if (isalpha ((unsigned char) *tag)) - { - for (cp = tag; *cp; cp++) - { - if (!isgraph ((unsigned char) *cp)) - error (1, 0, "tag `%s' has non-visible graphic characters", - tag); - if (strchr (invalid, *cp)) - error (1, 0, "tag `%s' must not contain the characters `%s'", - tag, invalid); - } - } - else - error (1, 0, "tag `%s' must start with a letter", tag); -} - -/* - * TRUE if argument has valid syntax for an RCS revision or - * branch number. All characters must be digits or dots, first - * and last characters must be digits, and no two consecutive - * characters may be dots. - * - * Intended for classifying things, so this function doesn't - * call error. - */ -int -RCS_valid_rev (rev) - char *rev; -{ - char last, c; - last = *rev++; - if (!isdigit ((unsigned char) last)) - return 0; - while ((c = *rev++)) /* Extra parens placate -Wall gcc option */ - { - if (c == '.') - { - if (last == '.') - return 0; - continue; - } - last = c; - if (!isdigit ((unsigned char) c)) - return 0; - } - if (!isdigit ((unsigned char) last)) - return 0; - return 1; -} - -/* - * Return true if RCS revision with TAG is a dead revision. - */ -int -RCS_isdead (rcs, tag) - RCSNode *rcs; - const char *tag; -{ - Node *p; - RCSVers *version; - - assert (rcs != NULL); - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - p = findnode (rcs->versions, tag); - if (p == NULL) - return (0); - - version = p->data; - return (version->dead); -} - -/* Return the RCS keyword expansion mode. For example "b" for binary. - Returns a pointer into storage which is allocated and freed along with - the rest of the RCS information; the caller should not modify this - storage. Returns NULL if the RCS file does not specify a keyword - expansion mode; for all other errors, die with a fatal error. */ -char * -RCS_getexpand (rcs) - RCSNode *rcs; -{ - /* Since RCS_parsercsfile_i now reads expand, don't need to worry - about RCS_reparsercsfile. */ - assert (rcs != NULL); - return rcs->expand; -} - -/* Set keyword expansion mode to EXPAND. For example "b" for binary. */ -void -RCS_setexpand (rcs, expand) - RCSNode *rcs; - const char *expand; -{ - /* Since RCS_parsercsfile_i now reads expand, don't need to worry - about RCS_reparsercsfile. */ - assert (rcs != NULL); - if (rcs->expand != NULL) - free (rcs->expand); - rcs->expand = xstrdup (expand); -} - -/* RCS keywords, and a matching enum. */ -struct rcs_keyword -{ - const char *string; - size_t len; - int expandit; -}; -#define KEYWORD_INIT(s) (s), sizeof (s) - 1 -static struct rcs_keyword keywords[] = -{ - { KEYWORD_INIT ("Author"), 1 }, - { KEYWORD_INIT ("Date"), 1 }, - { KEYWORD_INIT ("CVSHeader"), 1 }, - { KEYWORD_INIT ("Header"), 1 }, - { KEYWORD_INIT ("Id"), 1 }, - { KEYWORD_INIT ("Locker"), 1 }, - { KEYWORD_INIT ("Log"), 1 }, - { KEYWORD_INIT ("Name"), 1 }, - { KEYWORD_INIT ("RCSfile"), 1 }, - { KEYWORD_INIT ("Revision"), 1 }, - { KEYWORD_INIT ("Source"), 1 }, - { KEYWORD_INIT ("State"), 1 }, - { NULL, 0, 0 }, - { NULL, 0, 0 } -}; -enum keyword -{ - KEYWORD_AUTHOR = 0, - KEYWORD_DATE, - KEYWORD_CVSHEADER, - KEYWORD_HEADER, - KEYWORD_ID, - KEYWORD_LOCKER, - KEYWORD_LOG, - KEYWORD_NAME, - KEYWORD_RCSFILE, - KEYWORD_REVISION, - KEYWORD_SOURCE, - KEYWORD_STATE, - KEYWORD_LOCALID -}; -enum keyword keyword_local = KEYWORD_ID; - -/* Convert an RCS date string into a readable string. This is like - the RCS date2str function. */ - -static char * -printable_date (rcs_date) - const char *rcs_date; -{ - int year, mon, mday, hour, min, sec; - char buf[100]; - - (void) sscanf (rcs_date, SDATEFORM, &year, &mon, &mday, &hour, &min, - &sec); - if (year < 1900) - year += 1900; - sprintf (buf, "%04d%c%02d%c%02d %02d:%02d:%02d", - year, datesep, mon, datesep, mday, hour, min, sec); - return xstrdup (buf); -} - -/* Escape the characters in a string so that it can be included in an - RCS value. */ - -static char * -escape_keyword_value (value, free_value) - const char *value; - int *free_value; -{ - char *ret, *t; - const char *s; - - for (s = value; *s != '\0'; s++) - { - char c; - - c = *s; - if (c == '\t' - || c == '\n' - || c == '\\' - || c == ' ' - || c == '$') - { - break; - } - } - - if (*s == '\0') - { - *free_value = 0; - return (char *) value; - } - - ret = xmalloc (strlen (value) * 4 + 1); - *free_value = 1; - - for (s = value, t = ret; *s != '\0'; s++, t++) - { - switch (*s) - { - default: - *t = *s; - break; - case '\t': - *t++ = '\\'; - *t = 't'; - break; - case '\n': - *t++ = '\\'; - *t = 'n'; - break; - case '\\': - *t++ = '\\'; - *t = '\\'; - break; - case ' ': - *t++ = '\\'; - *t++ = '0'; - *t++ = '4'; - *t = '0'; - break; - case '$': - *t++ = '\\'; - *t++ = '0'; - *t++ = '4'; - *t = '4'; - break; - } - } - - *t = '\0'; - - return ret; -} - -/* Expand RCS keywords in the memory buffer BUF of length LEN. This - applies to file RCS and version VERS. If NAME is not NULL, and is - not a numeric revision, then it is the symbolic tag used for the - checkout. EXPAND indicates how to expand the keywords. This - function sets *RETBUF and *RETLEN to the new buffer and length. - This function may modify the buffer BUF. If BUF != *RETBUF, then - RETBUF is a newly allocated buffer. */ - -static void -expand_keywords (rcs, ver, name, log, loglen, expand, buf, len, retbuf, retlen) - RCSNode *rcs; - RCSVers *ver; - const char *name; - const char *log; - size_t loglen; - enum kflag expand; - char *buf; - size_t len; - char **retbuf; - size_t *retlen; -{ - struct expand_buffer - { - struct expand_buffer *next; - char *data; - size_t len; - int free_data; - } *ebufs = NULL; - struct expand_buffer *ebuf_last = NULL; - size_t ebuf_len = 0; - char *locker; - char *srch, *srch_next; - size_t srch_len; - - if (expand == KFLAG_O || expand == KFLAG_B) - { - *retbuf = buf; - *retlen = len; - return; - } - - /* If we are using -kkvl, dig out the locker information if any. */ - locker = NULL; - if (expand == KFLAG_KVL) - { - Node *lock; - lock = findnode (RCS_getlocks(rcs), ver->version); - if (lock != NULL) - locker = xstrdup (lock->data); - } - - /* RCS keywords look like $STRING$ or $STRING: VALUE$. */ - srch = buf; - srch_len = len; - while ((srch_next = memchr (srch, '$', srch_len)) != NULL) - { - char *s, *send; - size_t slen; - const struct rcs_keyword *keyword; - enum keyword kw; - char *value; - int free_value; - char *sub; - size_t sublen; - - srch_len -= (srch_next + 1) - srch; - srch = srch_next + 1; - - /* Look for the first non alphabetic character after the '$'. */ - send = srch + srch_len; - for (s = srch; s < send; s++) - if (! isalpha ((unsigned char) *s)) - break; - - /* If the first non alphabetic character is not '$' or ':', - then this is not an RCS keyword. */ - if (s == send || (*s != '$' && *s != ':')) - continue; - - /* See if this is one of the keywords. */ - slen = s - srch; - for (keyword = keywords; keyword->string != NULL; keyword++) - { - if (keyword->expandit - && keyword->len == slen - && strncmp (keyword->string, srch, slen) == 0) - { - break; - } - } - if (keyword->string == NULL) - continue; - - kw = (enum keyword) (keyword - keywords); - - /* If the keyword ends with a ':', then the old value consists - of the characters up to the next '$'. If there is no '$' - before the end of the line, though, then this wasn't an RCS - keyword after all. */ - if (*s == ':') - { - for (; s < send; s++) - if (*s == '$' || *s == '\n') - break; - if (s == send || *s != '$') - continue; - } - - /* At this point we must replace the string from SRCH to S - with the expansion of the keyword KW. */ - - /* Get the value to use. */ - free_value = 0; - if (expand == KFLAG_K) - value = NULL; - else - { - switch (kw) - { - default: - abort (); - - case KEYWORD_AUTHOR: - value = ver->author; - break; - - case KEYWORD_DATE: - value = printable_date (ver->date); - free_value = 1; - break; - - case KEYWORD_CVSHEADER: - case KEYWORD_HEADER: - case KEYWORD_ID: - case KEYWORD_LOCALID: - { - const char *path; - int free_path; - char *date; - char *old_path; - - old_path = NULL; - if (kw == KEYWORD_HEADER || - (kw == KEYWORD_LOCALID && - keyword_local == KEYWORD_HEADER)) - path = rcs->path; - else if (kw == KEYWORD_CVSHEADER || - (kw == KEYWORD_LOCALID && - keyword_local == KEYWORD_CVSHEADER)) - path = getfullCVSname(rcs->path, &old_path); - else - path = last_component (rcs->path); - path = escape_keyword_value (path, &free_path); - date = printable_date (ver->date); - value = xmalloc (strlen (path) - + strlen (ver->version) - + strlen (date) - + strlen (ver->author) - + strlen (ver->state) - + (locker == NULL ? 0 : strlen (locker)) - + 20); - - sprintf (value, "%s %s %s %s %s%s%s", - path, ver->version, date, ver->author, - ver->state, - locker != NULL ? " " : "", - locker != NULL ? locker : ""); - if (free_path) - /* If free_path is set then we know we allocated path - * and we can discard the const. - */ - free ((char *)path); - if (old_path) - free (old_path); - free (date); - free_value = 1; - } - break; - - case KEYWORD_LOCKER: - value = locker; - break; - - case KEYWORD_LOG: - case KEYWORD_RCSFILE: - value = escape_keyword_value (last_component (rcs->path), - &free_value); - break; - - case KEYWORD_NAME: - if (name != NULL && ! isdigit ((unsigned char) *name)) - value = (char *) name; - else - value = NULL; - break; - - case KEYWORD_REVISION: - value = ver->version; - break; - - case KEYWORD_SOURCE: - value = escape_keyword_value (rcs->path, &free_value); - break; - - case KEYWORD_STATE: - value = ver->state; - break; - } - } - - sub = xmalloc (keyword->len - + (value == NULL ? 0 : strlen (value)) - + 10); - if (expand == KFLAG_V) - { - /* Decrement SRCH and increment S to remove the $ - characters. */ - --srch; - ++srch_len; - ++s; - sublen = 0; - } - else - { - strcpy (sub, keyword->string); - sublen = strlen (keyword->string); - if (expand != KFLAG_K) - { - sub[sublen] = ':'; - sub[sublen + 1] = ' '; - sublen += 2; - } - } - if (value != NULL) - { - strcpy (sub + sublen, value); - sublen += strlen (value); - } - if (expand != KFLAG_V && expand != KFLAG_K) - { - sub[sublen] = ' '; - ++sublen; - sub[sublen] = '\0'; - } - - if (free_value) - free (value); - - /* The Log keyword requires special handling. This behaviour - is taken from RCS 5.7. The special log message is what RCS - uses for ci -k. */ - if (kw == KEYWORD_LOG - && (sizeof "checked in with -k by " <= loglen - || log == NULL - || strncmp (log, "checked in with -k by ", - sizeof "checked in with -k by " - 1) != 0)) - { - char *start; - char *leader; - size_t leader_len, leader_sp_len; - const char *logend; - const char *snl; - int cnl; - char *date; - const char *sl; - - /* We are going to insert the trailing $ ourselves, before - the log message, so we must remove it from S, if we - haven't done so already. */ - if (expand != KFLAG_V) - ++s; - - /* CVS never has empty log messages, but old RCS files might. */ - if (log == NULL) - log = ""; - - /* Find the start of the line. */ - start = srch; - while (start > buf && start[-1] != '\n') - --start; - - /* Copy the start of the line to use as a comment leader. */ - leader_len = srch - start; - if (expand != KFLAG_V) - --leader_len; - leader = xmalloc (leader_len); - memcpy (leader, start, leader_len); - leader_sp_len = leader_len; - while (leader_sp_len > 0 && leader[leader_sp_len - 1] == ' ') - --leader_sp_len; - - /* RCS does some checking for an old style of Log here, - but we don't bother. RCS issues a warning if it - changes anything. */ - - /* Count the number of newlines in the log message so that - we know how many copies of the leader we will need. */ - cnl = 0; - logend = log + loglen; - for (snl = log; snl < logend; snl++) - if (*snl == '\n') - ++cnl; - - /* If the log message did not end in a newline, increment - * the newline count so we have space for the extra leader. - * Failure to do so results in a buffer overrun. - */ - if (loglen && snl[-1] != '\n') - ++cnl; - - date = printable_date (ver->date); - sub = xrealloc (sub, - (sublen - + sizeof "Revision" - + strlen (ver->version) - + strlen (date) - + strlen (ver->author) - + loglen - /* Use CNL + 2 below: One leader for each log - * line, plus the Revision/Author/Date line, - * plus a trailing blank line. - */ - + (cnl + 2) * leader_len - + 20)); - if (expand != KFLAG_V) - { - sub[sublen] = '$'; - ++sublen; - } - sub[sublen] = '\n'; - ++sublen; - memcpy (sub + sublen, leader, leader_len); - sublen += leader_len; - sprintf (sub + sublen, "Revision %s %s %s\n", - ver->version, date, ver->author); - sublen += strlen (sub + sublen); - free (date); - - sl = log; - while (sl < logend) - { - if (*sl == '\n') - { - memcpy (sub + sublen, leader, leader_sp_len); - sublen += leader_sp_len; - sub[sublen] = '\n'; - ++sublen; - ++sl; - } - else - { - const char *slnl; - - memcpy (sub + sublen, leader, leader_len); - sublen += leader_len; - for (slnl = sl; slnl < logend && *slnl != '\n'; ++slnl) - ; - if (slnl < logend) - ++slnl; - memcpy (sub + sublen, sl, slnl - sl); - sublen += slnl - sl; - if (slnl == logend && slnl[-1] != '\n') - { - /* There was no EOL at the end of the log message. Add - * one. - */ - sub[sublen] = '\n'; - ++sublen; - } - sl = slnl; - } - } - - memcpy (sub + sublen, leader, leader_sp_len); - sublen += leader_sp_len; - - free (leader); - } - - /* Now SUB contains a string which is to replace the string - from SRCH to S. SUBLEN is the length of SUB. */ - - if (srch + sublen == s) - { - memcpy (srch, sub, sublen); - free (sub); - } - else - { - struct expand_buffer *ebuf; - - /* We need to change the size of the buffer. We build a - list of expand_buffer structures. Each expand_buffer - structure represents a portion of the final output. We - concatenate them back into a single buffer when we are - done. This minimizes the number of potentially large - buffer copies we must do. */ - - if (ebufs == NULL) - { - ebufs = (struct expand_buffer *) xmalloc (sizeof *ebuf); - ebufs->next = NULL; - ebufs->data = buf; - ebufs->free_data = 0; - ebuf_len = srch - buf; - ebufs->len = ebuf_len; - ebuf_last = ebufs; - } - else - { - assert (srch >= ebuf_last->data); - assert (srch <= ebuf_last->data + ebuf_last->len); - ebuf_len -= ebuf_last->len - (srch - ebuf_last->data); - ebuf_last->len = srch - ebuf_last->data; - } - - ebuf = (struct expand_buffer *) xmalloc (sizeof *ebuf); - ebuf->data = sub; - ebuf->len = sublen; - ebuf->free_data = 1; - ebuf->next = NULL; - ebuf_last->next = ebuf; - ebuf_last = ebuf; - ebuf_len += sublen; - - ebuf = (struct expand_buffer *) xmalloc (sizeof *ebuf); - ebuf->data = s; - ebuf->len = srch_len - (s - srch); - ebuf->free_data = 0; - ebuf->next = NULL; - ebuf_last->next = ebuf; - ebuf_last = ebuf; - ebuf_len += srch_len - (s - srch); - } - - srch_len -= (s - srch); - srch = s; - } - - if (locker != NULL) - free (locker); - - if (ebufs == NULL) - { - *retbuf = buf; - *retlen = len; - } - else - { - char *ret; - - ret = xmalloc (ebuf_len); - *retbuf = ret; - *retlen = ebuf_len; - while (ebufs != NULL) - { - struct expand_buffer *next; - - memcpy (ret, ebufs->data, ebufs->len); - ret += ebufs->len; - if (ebufs->free_data) - free (ebufs->data); - next = ebufs->next; - free (ebufs); - ebufs = next; - } - } -} - - - -/* Check out a revision from an RCS file. - - If PFN is not NULL, then ignore WORKFILE and SOUT. Call PFN zero - or more times with the contents of the file. CALLERDAT is passed, - uninterpreted, to PFN. (The current code will always call PFN - exactly once for a non empty file; however, the current code - assumes that it can hold the entire file contents in memory, which - is not a good assumption, and might change in the future). - - Otherwise, if WORKFILE is not NULL, check out the revision to - WORKFILE. However, if WORKFILE is not NULL, and noexec is set, - then don't do anything. - - Otherwise, if WORKFILE is NULL, check out the revision to SOUT. If - SOUT is RUN_TTY, then write the contents of the revision to - standard output. When using SOUT, the output is generally a - temporary file; don't bother to get the file modes correct. When - NOEXEC is set, WORKFILEs are not written but SOUTs are. - - REV is the numeric revision to check out. It may be NULL, which - means to check out the head of the default branch. - - If NAMETAG is not NULL, and is not a numeric revision, then it is - the tag that should be used when expanding the RCS Name keyword. - - OPTIONS is a string such as "-kb" or "-kv" for keyword expansion - options. It may be NULL to use the default expansion mode of the - file, typically "-kkv". - - On an error which prevented checking out the file, either print a - nonfatal error and return 1, or give a fatal error. On success, - return 0. */ - -/* This function mimics the behavior of `rcs co' almost exactly. The - chief difference is in its support for preserving file ownership, - permissions, and special files across checkin and checkout -- see - comments in RCS_checkin for some issues about this. -twp */ - -int -RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) - RCSNode *rcs; - const char *workfile; - const char *rev; - const char *nametag; - const char *options; - const char *sout; - RCSCHECKOUTPROC pfn; - void *callerdat; -{ - int free_rev = 0; - enum kflag expand; - FILE *fp; - FILE *ofp = NULL; - struct stat sb; - struct rcsbuffer rcsbuf; - char *key; - char *value; - size_t len; - int free_value = 0; - char *log = NULL; - size_t loglen = 0; - Node *vp = NULL; -#ifdef PRESERVE_PERMISSIONS_SUPPORT - uid_t rcs_owner = (uid_t) -1; - gid_t rcs_group = (gid_t) -1; - mode_t rcs_mode; - int change_rcs_owner_or_group = 0; - int change_rcs_mode = 0; - int special_file = 0; - unsigned long devnum_long; - dev_t devnum = 0; -#endif - - if (trace) - { - (void) fprintf (stderr, "%s-> RCS_checkout (%s, %s, %s, %s, %s)\n", -#ifdef SERVER_SUPPORT - server_active ? "S" : " ", -#else - "", -#endif - rcs->path, - rev != NULL ? rev : "", - nametag != NULL ? nametag : "", - options != NULL ? options : "", - (pfn != NULL ? "(function)" - : (workfile != NULL - ? workfile - : (sout != RUN_TTY ? sout : "(stdout)")))); - } - - assert (rev == NULL || isdigit ((unsigned char) *rev)); - - if (noexec && !server_active && workfile != NULL) - return 0; - - assert (sout == RUN_TTY || workfile == NULL); - assert (pfn == NULL || (sout == RUN_TTY && workfile == NULL)); - - /* Some callers, such as Checkin or remove_file, will pass us a - branch. */ - if (rev != NULL && (numdots (rev) & 1) == 0) - { - rev = RCS_getbranch (rcs, rev, 1); - if (rev == NULL) - error (1, 0, "internal error: bad branch tag in checkout"); - free_rev = 1; - } - - if (rev == NULL || STREQ (rev, rcs->head)) - { - int gothead; - - /* We want the head revision. Try to read it directly. */ - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, &fp, &rcsbuf); - else - rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf); - - gothead = 0; - if (! rcsbuf_getrevnum (&rcsbuf, &key)) - error (1, 0, "unexpected EOF reading %s", rcs->path); - - if (!STREQ (rcs->head, key)) - error (1, 0, "Expected head revision %s, found %s.", - rcs->head, key); - - while (rcsbuf_getkey (&rcsbuf, &key, &value)) - { - if (STREQ (key, "log")) - { - if (log) - { - error (0, 0, -"Duplicate log keyword found for head revision in RCS file."); - free (log); - } - log = rcsbuf_valcopy (&rcsbuf, value, 0, &loglen); - } - else if (STREQ (key, "text")) - { - gothead = 1; - break; - } - } - - if (! gothead) - { - error (0, 0, "internal error: cannot find head text"); - if (free_rev) - /* It's okay to discard the const when free_rev is set, because - * we know we allocated it in this function. - */ - free ((char *)rev); - return 1; - } - - rcsbuf_valpolish (&rcsbuf, value, 0, &len); - - if (fstat (fileno (fp), &sb) < 0) - error (1, errno, "cannot fstat %s", rcs->path); - - rcsbuf_cache (rcs, &rcsbuf); - } - else - { - struct rcsbuffer *rcsbufp; - - /* It isn't the head revision of the trunk. We'll need to - walk through the deltas. */ - - fp = NULL; - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, &fp, &rcsbuf); - - if (fp == NULL) - { - /* If RCS_deltas didn't close the file, we could use fstat - here too. Probably should change it thusly.... */ - if (stat (rcs->path, &sb) < 0) - error (1, errno, "cannot stat %s", rcs->path); - rcsbufp = NULL; - } - else - { - if (fstat (fileno (fp), &sb) < 0) - error (1, errno, "cannot fstat %s", rcs->path); - rcsbufp = &rcsbuf; - } - - RCS_deltas (rcs, fp, rcsbufp, rev, RCS_FETCH, &value, &len, - &log, &loglen); - free_value = 1; - } - - /* If OPTIONS is NULL or the empty string, then the old code would - invoke the RCS co program with no -k option, which means that - co would use the string we have stored in rcs->expand. */ - if ((options == NULL || options[0] == '\0') && rcs->expand == NULL) - expand = KFLAG_KV; - else - { - const char *ouroptions; - const char * const *cpp; - - if (options != NULL && options[0] != '\0') - { - assert (options[0] == '-' && options[1] == 'k'); - ouroptions = options + 2; - } - else - ouroptions = rcs->expand; - - for (cpp = kflags; *cpp != NULL; cpp++) - if (STREQ (*cpp, ouroptions)) - break; - - if (*cpp != NULL) - expand = (enum kflag) (cpp - kflags); - else - { - error (0, 0, - "internal error: unsupported substitution string -k%s", - ouroptions); - expand = KFLAG_KV; - } - } - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - /* Handle special files and permissions, if that is desired. */ - if (preserve_perms) - { - RCSVers *vers; - Node *info; - - vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev); - if (vp == NULL) - error (1, 0, "internal error: no revision information for %s", - rev == NULL ? rcs->head : rev); - vers = vp->data; - - /* First we look for symlinks, which are simplest to handle. */ - info = findnode (vers->other_delta, "symlink"); - if (info != NULL) - { - char *dest; - - if (pfn != NULL || (workfile == NULL && sout == RUN_TTY)) - error (1, 0, "symbolic link %s:%s cannot be piped", - rcs->path, vers->version); - if (workfile == NULL) - dest = sout; - else - dest = workfile; - - /* Remove `dest', just in case. It's okay to get ENOENT here, - since we just want the file not to be there. (TODO: decide - whether it should be considered an error for `dest' to exist - at this point. If so, the unlink call should be removed and - `symlink' should signal the error. -twp) */ - if (CVS_UNLINK (dest) < 0 && !existence_error (errno)) - error (1, errno, "cannot remove %s", dest); - if (symlink (info->data, dest) < 0) - error (1, errno, "cannot create symbolic link from %s to %s", - dest, (char *)info->data); - if (free_value) - free (value); - if (free_rev) - /* It's okay to discard the const when free_rev is set, because - * we know we allocated it in this function. - */ - free ((char *)rev); - return 0; - } - - /* Next, we look at this file's hardlinks field, and see whether - it is linked to any other file that has been checked out. - If so, we don't do anything else -- just link it to that file. - - If we are checking out a file to a pipe or temporary storage, - none of this should matter. Hence the `workfile != NULL' - wrapper around the whole thing. -twp */ - - if (workfile != NULL) - { - List *links = vers->hardlinks; - if (links != NULL) - { - Node *uptodate_link; - - /* For each file in the hardlinks field, check to see - if it exists, and if so, if it has been checked out - this iteration. When walklist returns, uptodate_link - should point to a hardlist node representing a file - in `links' which has recently been checked out, or - NULL if no file in `links' has yet been checked out. */ - - uptodate_link = NULL; - (void) walklist (links, find_checkedout_proc, &uptodate_link); - dellist (&links); - - /* If we've found a file that `workfile' is supposed to be - linked to, and it has been checked out since CVS was - invoked, then simply link workfile to that file and return. - - If one of these conditions is not met, then - workfile is the first one in its hardlink group to - be checked out, and we must continue with a full - checkout. */ - - if (uptodate_link != NULL) - { - struct hardlink_info *hlinfo = uptodate_link->data; - - if (link (uptodate_link->key, workfile) < 0) - error (1, errno, "cannot link %s to %s", - workfile, uptodate_link->key); - hlinfo->checked_out = 1; /* probably unnecessary */ - if (free_value) - free (value); - if (free_rev) - /* It's okay to discard the const when free_rev is set, - * because we know we allocated it in this function. - */ - free ((char *)rev); - return 0; - } - } - } - - info = findnode (vers->other_delta, "owner"); - if (info != NULL) - { - change_rcs_owner_or_group = 1; - rcs_owner = (uid_t) strtoul (info->data, NULL, 10); - } - info = findnode (vers->other_delta, "group"); - if (info != NULL) - { - change_rcs_owner_or_group = 1; - rcs_group = (gid_t) strtoul (info->data, NULL, 10); - } - info = findnode (vers->other_delta, "permissions"); - if (info != NULL) - { - change_rcs_mode = 1; - rcs_mode = (mode_t) strtoul (info->data, NULL, 8); - } - info = findnode (vers->other_delta, "special"); - if (info != NULL) - { - /* If the size of `devtype' changes, fix the sscanf call also */ - char devtype[16]; - - if (sscanf (info->data, "%15s %lu", - devtype, &devnum_long) < 2) - error (1, 0, "%s:%s has bad `special' newphrase %s", - workfile, vers->version, (char *)info->data); - devnum = devnum_long; - if (STREQ (devtype, "character")) - special_file = S_IFCHR; - else if (STREQ (devtype, "block")) - special_file = S_IFBLK; - else - error (0, 0, "%s is a special file of unsupported type `%s'", - workfile, (char *)info->data); - } - } -#endif /* PRESERVE_PERMISSIONS_SUPPORT */ - - if (expand != KFLAG_O && expand != KFLAG_B) - { - char *newvalue; - - /* Don't fetch the delta node again if we already have it. */ - if (vp == NULL) - { - vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev); - if (vp == NULL) - error (1, 0, "internal error: no revision information for %s", - rev == NULL ? rcs->head : rev); - } - - expand_keywords (rcs, vp->data, nametag, log, loglen, - expand, value, len, &newvalue, &len); - - if (newvalue != value) - { - if (free_value) - free (value); - value = newvalue; - free_value = 1; - } - } - - if (free_rev) - /* It's okay to discard the const when free_rev is set, because - * we know we allocated it in this function. - */ - free ((char *)rev); - - if (log != NULL) - { - free (log); - log = NULL; - } - - if (pfn != NULL) - { -#ifdef PRESERVE_PERMISSIONS_SUPPORT - if (special_file) - error (1, 0, "special file %s cannot be piped to anything", - rcs->path); -#endif - /* The PFN interface is very simple to implement right now, as - we always have the entire file in memory. */ - if (len != 0) - pfn (callerdat, value, len); - } -#ifdef PRESERVE_PERMISSIONS_SUPPORT - else if (special_file) - { -# ifdef HAVE_MKNOD - char *dest; - - /* Can send either to WORKFILE or to SOUT, as long as SOUT is - not RUN_TTY. */ - dest = workfile; - if (dest == NULL) - { - if (sout == RUN_TTY) - error (1, 0, "special file %s cannot be written to stdout", - rcs->path); - dest = sout; - } - - /* Unlink `dest', just in case. It's okay if this provokes a - ENOENT error. */ - if (CVS_UNLINK (dest) < 0 && existence_error (errno)) - error (1, errno, "cannot remove %s", dest); - if (mknod (dest, special_file, devnum) < 0) - error (1, errno, "could not create special file %s", - dest); -# else - error (1, 0, -"cannot create %s: unable to create special files on this system", -workfile); -# endif - } -#endif - else - { - /* Not a special file: write to WORKFILE or SOUT. */ - if (workfile == NULL) - { - if (sout == RUN_TTY) - ofp = stdout; - else - { - /* Symbolic links should be removed before replacement, so that - `fopen' doesn't follow the link and open the wrong file. */ - if (islink (sout)) - if (unlink_file (sout) < 0) - error (1, errno, "cannot remove %s", sout); - ofp = CVS_FOPEN (sout, expand == KFLAG_B ? "wb" : "w"); - if (ofp == NULL) - error (1, errno, "cannot open %s", sout); - } - } - else - { - /* Output is supposed to go to WORKFILE, so we should open that - file. Symbolic links should be removed first (see above). */ - if (islink (workfile)) - if (unlink_file (workfile) < 0) - error (1, errno, "cannot remove %s", workfile); - - ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w"); - - /* If the open failed because the existing workfile was not - writable, try to chmod the file and retry the open. */ - if (ofp == NULL && errno == EACCES - && isfile (workfile) && !iswritable (workfile)) - { - xchmod (workfile, 1); - ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w"); - } - - if (ofp == NULL) - { - error (0, errno, "cannot open %s", workfile); - if (free_value) - free (value); - return 1; - } - } - - if (workfile == NULL && sout == RUN_TTY) - { - 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 - { - /* 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 (0, errno, "cannot write %s", - (workfile != NULL - ? workfile - : (sout != RUN_TTY ? sout : "stdout"))); - if (free_value) - free (value); - return 1; - } - p += nstep; - nleft -= nstep; - if (nleft < nstep) - nstep = nleft; - } - } - } - - if (free_value) - free (value); - - if (workfile != NULL) - { - int ret; - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - if (!special_file && fclose (ofp) < 0) - { - error (0, errno, "cannot close %s", workfile); - return 1; - } - - if (change_rcs_owner_or_group) - { - if (chown (workfile, rcs_owner, rcs_group) < 0) - error (0, errno, "could not change owner or group of %s", - workfile); - } - - ret = chmod (workfile, - change_rcs_mode - ? rcs_mode - : sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)); -#else - if (fclose (ofp) < 0) - { - error (0, errno, "cannot close %s", workfile); - return 1; - } - - ret = chmod (workfile, - sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)); -#endif - if (ret < 0) - { - error (0, errno, "cannot change mode of file %s", - workfile); - } - } - else if (sout != RUN_TTY) - { - if ( -#ifdef PRESERVE_PERMISSIONS_SUPPORT - !special_file && -#endif - fclose (ofp) < 0) - { - error (0, errno, "cannot close %s", sout); - return 1; - } - } - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - /* If we are in the business of preserving hardlinks, then - mark this file as having been checked out. */ - if (preserve_perms && workfile != NULL) - update_hardlink_info (workfile); -#endif - - 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; - char *defaultrev = NULL; - - /* 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 (STREQ (p->data, user)) - { - 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 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). */ - - defaultrev = RCS_getbranch (rcs, rcs->branch, 0); - p = findnode (rcs->versions, defaultrev); - if (defaultrev != NULL) - free (defaultrev); - if (!p) - { - error (0, 0, "RCS file `%s' does not contain its default revision.", - rcs->path); - return NULL; - } - - return 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 PROTO ((const RCSVers *)); - -static char * -max_rev (branchnode) - 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; - - assert (branch); - - /* 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); - free (branchpoint); - return NULL; - } - free (branchpoint); - branchnode = 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. - If FLAGS & RCS_FLAGS_KEEPFILE, don't unlink the working file; - unlinking the working file is standard RCS behavior, but is rarely - appropriate for CVS. - - This function should almost exactly mimic the behavior of `rcs ci'. The - principal point of difference is the support here for preserving file - ownership and permissions in the delta nodes. This is not a clean - solution -- precisely because it diverges from RCS's behavior -- but - it doesn't seem feasible to do this anywhere else in the code. [-twp] - - Return value is -1 for error (and errno is set to indicate the - error), positive for error (and an error message has been printed), - or zero for success. */ - -int -RCS_checkin (rcs, workfile_in, message, rev, citime, flags) - RCSNode *rcs; - const char *workfile_in; - const char *message; - const char *rev; - time_t citime; - int flags; -{ - RCSVers *delta, *commitpt; - Deltatext *dtext; - Node *nodep; - char *tmpfile, *changefile; - int dargc = 0; - size_t darg_allocated = 0; - char **dargv = NULL; - size_t bufsize; - int status, checkin_quiet; - struct tm *ftm; - time_t modtime; - int adding_branch = 0; - char *workfile = xstrdup (workfile_in); -#ifdef PRESERVE_PERMISSIONS_SUPPORT - struct stat sb; -#endif - - commitpt = NULL; - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - /* Get basename of working file. Is there a library function to - do this? I couldn't find one. -twp */ - if (workfile == NULL) - { - char *p; - int extlen = strlen (RCSEXT); - assert (rcs->path); - workfile = xstrdup (last_component (rcs->path)); - p = workfile + (strlen (workfile) - extlen); - assert (strncmp (p, RCSEXT, extlen) == 0); - *p = '\0'; - } - - /* If the filename is a symbolic link, follow it and replace it - with the destination of the link. We need to do this before - calling rcs_internal_lockfile, or else we won't put the lock in - the right place. */ - resolve_symlink (&(rcs->path)); - - 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 if (flags & RCS_FLAGS_USETIME) - modtime = citime; - 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"); - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - /* If permissions should be preserved on this project, then - save the permission info. */ - if (preserve_perms) - { - Node *np; - char buf[64]; /* static buffer should be safe: see usage. -twp */ - - delta->other_delta = getlist(); - - if (CVS_LSTAT (workfile, &sb) < 0) - error (1, errno, "cannot lstat %s", workfile); - - if (S_ISLNK (sb.st_mode)) - { - np = getnode(); - np->type = RCSFIELD; - np->key = xstrdup ("symlink"); - np->data = xreadlink (workfile); - addnode (delta->other_delta, np); - } - else - { - (void) sprintf (buf, "%u", sb.st_uid); - np = getnode(); - np->type = RCSFIELD; - np->key = xstrdup ("owner"); - np->data = xstrdup (buf); - addnode (delta->other_delta, np); - - (void) sprintf (buf, "%u", sb.st_gid); - np = getnode(); - np->type = RCSFIELD; - np->key = xstrdup ("group"); - np->data = xstrdup (buf); - addnode (delta->other_delta, np); - - (void) sprintf (buf, "%o", sb.st_mode & 07777); - np = getnode(); - np->type = RCSFIELD; - np->key = xstrdup ("permissions"); - np->data = xstrdup (buf); - addnode (delta->other_delta, np); - - /* Save device number. */ - switch (sb.st_mode & S_IFMT) - { - case S_IFREG: break; - case S_IFCHR: - case S_IFBLK: -# ifdef HAVE_STRUCT_STAT_ST_RDEV - np = getnode(); - np->type = RCSFIELD; - np->key = xstrdup ("special"); - sprintf (buf, "%s %lu", - ((sb.st_mode & S_IFMT) == S_IFCHR - ? "character" : "block"), - (unsigned long) sb.st_rdev); - np->data = xstrdup (buf); - addnode (delta->other_delta, np); -# else - error (0, 0, -"can't preserve %s: unable to save device files on this system", -workfile); -# endif - break; - - default: - error (0, 0, "special file %s has unknown type", workfile); - } - - /* Save hardlinks. */ - delta->hardlinks = list_linked_files_on_disk (workfile); - } - } -#endif - - /* 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->delproc = rcsvers_delproc; - nodep->data = delta; - nodep->key = delta->version; - (void) addnode (rcs->versions, nodep); - - dtext->version = xstrdup (newrev); - bufsize = 0; -#ifdef PRESERVE_PERMISSIONS_SUPPORT - if (preserve_perms && !S_ISREG (sb.st_mode)) - /* Pretend file is empty. */ - bufsize = 0; - else -#endif - get_file (workfile, workfile, - rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r", - &dtext->text, &bufsize, &dtext->len); - - if (!checkin_quiet) - { - cvs_output ("initial revision: ", 0); - cvs_output (rcs->head, 0); - cvs_output ("\n", 1); - } - - /* We are probably about to invalidate any cached file. */ - rcsbuf_cache_close (); - - 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); - - if ((flags & RCS_FLAGS_KEEPFILE) == 0) - { - 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); - - status = 0; - goto checkin_done; - } - - /* 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 - || STREQ (commitpt->version, rcs->head)) - 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 ((unsigned char) *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); - assert (tip != NULL); - 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); - if (!delta->version) - { - free (branch); - free (newrev); - status = 1; - goto checkin_done; - } - adding_branch = 1; - 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 = 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 (! STREQ (nodep->data, delta->author)) - { - /* If we are adding a branch, then leave the old lock around. - That is sensible in the sense that when adding a branch, - we don't need to use the lock to tell us where to check - in. It is fishy in the sense that if it is our own lock, - we break it. However, this is the RCS 5.7 behavior (at - the end of addbranch in ci.c in RCS 5.7, it calls - removelock only if it is our own lock, not someone - else's). */ - - if (!adding_branch) - { - error (0, 0, "%s: revision %s locked by %s", - rcs->path, - nodep->key, (char *)nodep->data); - status = 1; - goto checkin_done; - } - } - else - 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 - && STREQ (rcs->expand, "b")) - ? "-kb" - : "-ko"), - tmpfile, - (RCSCHECKOUTPROC)0, NULL); - if (status != 0) - error (1, 0, - "could not check out revision %s of `%s'", - commitpt->version, rcs->path); - - bufsize = 0; - changefile = cvs_temp_name(); - - /* Diff options should include --binary if the RCS file has -kb set - in its `expand' field. */ - run_add_arg_p (&dargc, &darg_allocated, &dargv, "-a"); - run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n"); - if (rcs->expand && STREQ (rcs->expand, "b")) - run_add_arg_p (&dargc, &darg_allocated, &dargv, "--binary"); - - if (STREQ (commitpt->version, rcs->head) && - 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; -#ifdef PRESERVE_PERMISSIONS_SUPPORT - if (preserve_perms && !S_ISREG (sb.st_mode)) - /* Pretend file is empty. */ - ; - else -#endif - get_file (workfile, workfile, - rcs->expand != NULL && STREQ (rcs->expand, "b") ? "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, NULL, NULL, - dargc, dargv, 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 && STREQ (rcs->expand, "b") ? "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, NULL, NULL, - dargc, dargv, 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 && STREQ (rcs->expand, "b") ? "rb" : "r", - &dtext->text, &bufsize, - &dtext->len); - if (dtext->text == NULL) - { - dtext->text = xstrdup (""); - dtext->len = 0; - } - } - - run_arg_free_p (dargc, dargv); - free (dargv); - - /* 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 (STREQ (commitpt->version, rcs->head)) - { - 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->delproc = rcsvers_delproc; - nodep->data = delta; - nodep->key = delta->version; - (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); - - if ((flags & RCS_FLAGS_KEEPFILE) == 0) - { - 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); - free (tmpfile); - if (unlink_file (changefile) < 0) - error (0, errno, "cannot remove %s", changefile); - free (changefile); - - if (!checkin_quiet) - cvs_output ("done\n", 5); - - checkin_done: - free (workfile); - - if (commitpt != NULL && commitpt->text != NULL) - { - freedeltatext (commitpt->text); - commitpt->text = NULL; - } - - freedeltatext (dtext); - if (status != 0) - { - /* If delta has not been added to a List, then freeing the Node key - * won't free delta->version. - */ - if (delta->version) free (delta->version); - free_rcsvers_contents (delta); - } - - return status; -} - - - -/* This structure is passed between RCS_cmp_file and cmp_file_buffer. */ -struct cmp_file_data -{ - const char *filename; - FILE *fp; - int different; -}; - -/* Compare the contents of revision REV1 of RCS file RCS with the - contents of REV2 if given, otherwise, compare with the contents of - the file FILENAME. OPTIONS is a string for the keyword - expansion options. Return 0 if the contents of the revision are - the same as the contents of the file, 1 if they are different. */ -int -RCS_cmp_file (rcs, rev1, rev1_cache, rev2, options, filename) - RCSNode *rcs; - const char *rev1; - char **rev1_cache; - const char *rev2; - const char *options; - const char *filename; -{ - int binary; - - if (options != NULL && options[0] != '\0') - binary = STREQ (options, "-kb"); - else - { - char *expand; - - expand = RCS_getexpand (rcs); - if (expand != NULL && STREQ (expand, "b")) - binary = 1; - else - binary = 0; - } - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - /* If CVS is to deal properly with special files (when - PreservePermissions is on), the best way is to check out the - revision to a temporary file and call `xcmp' on the two disk - files. xcmp needs to handle non-regular files properly anyway, - so calling it simplifies RCS_cmp_file. We *could* just yank - the delta node out of the version tree and look for device - numbers, but writing to disk and calling xcmp is a better - abstraction (therefore probably more robust). -twp */ - - if (preserve_perms) - { - char *tmp; - int retcode; - - tmp = cvs_temp_name(); - retcode = RCS_checkout(rcs, NULL, rev, NULL, options, tmp, NULL, NULL); - if (retcode != 0) - return 1; - - retcode = xcmp (tmp, filename); - if (CVS_UNLINK (tmp) < 0) - error (0, errno, "cannot remove %s", tmp); - free (tmp); - return retcode; - } - else -#endif - { - FILE *fp; - struct cmp_file_data data; - const char *use_file1; - char *tmpfile = NULL; - - if (rev2 != NULL) - { - /* Open & cache rev1 */ - tmpfile = cvs_temp_name(); - if (RCS_checkout (rcs, NULL, rev1, NULL, options, tmpfile, - (RCSCHECKOUTPROC)0, NULL)) - error (1, errno, - "cannot check out revision %s of %s", - rev1, rcs->path); - use_file1 = tmpfile; - if (rev1_cache != NULL) - *rev1_cache = tmpfile; - } - else - use_file1 = filename; - - fp = CVS_FOPEN (use_file1, binary ? FOPEN_BINARY_READ : "r"); - if (fp == NULL) - /* FIXME-update-dir: should include update_dir in message. */ - error (1, errno, "cannot open file %s for comparing", use_file1); - - data.filename = use_file1; - data.fp = fp; - data.different = 0; - - if (RCS_checkout (rcs, (char *)NULL, rev2 ? rev2 : rev1, - (char *)NULL, options, RUN_TTY, cmp_file_buffer, - (void *)&data )) - error (1, errno, - "cannot check out revision %s of %s", - rev2 ? rev2 : rev1, rcs->path); - - /* If we have not yet found a difference, make sure that we are at - the end of the file. */ - if (!data.different) - { - if (getc (fp) != EOF) - data.different = 1; - } - - fclose (fp); - if (rev1_cache == NULL && tmpfile) - { - if (CVS_UNLINK (tmpfile ) < 0) - error (0, errno, "cannot remove %s", tmpfile); - free (tmpfile); - } - - return data.different; - } -} - - - -/* This is a subroutine of RCS_cmp_file. It is passed to - RCS_checkout. */ -#define CMP_BUF_SIZE (8 * 1024) - -static void -cmp_file_buffer (callerdat, buffer, len) - void *callerdat; - const char *buffer; - size_t len; -{ - struct cmp_file_data *data = (struct cmp_file_data *)callerdat; - char *filebuf; - - /* If we've already found a difference, we don't need to check - further. */ - if (data->different) - return; - - filebuf = xmalloc (len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len); - - while (len > 0) - { - size_t checklen; - - checklen = len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len; - if (fread (filebuf, 1, checklen, data->fp) != checklen) - { - if (ferror (data->fp)) - error (1, errno, "cannot read file %s for comparing", - data->filename); - data->different = 1; - free (filebuf); - return; - } - - if (memcmp (filebuf, buffer, checklen) != 0) - { - data->different = 1; - free (filebuf); - return; - } - - buffer += checklen; - len -= checklen; - } - - free (filebuf); -} - - - -/* For RCS file RCS, make symbolic tag TAG point to revision REV. - This validates that TAG is OK for a user to use. Return value is - -1 for error (and errno is set to indicate the error), positive for - error (and an error message has been printed), or zero for success. */ - -int -RCS_settag (rcs, tag, rev) - RCSNode *rcs; - const char *tag; - const char *rev; -{ - List *symbols; - Node *node; - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - /* FIXME: This check should be moved to RCS_check_tag. There is no - reason for it to be here. */ - if (STREQ (tag, TAG_BASE) - || STREQ (tag, TAG_HEAD)) - { - /* Print the name of the tag might be considered redundant - with the caller, which also prints it. Perhaps this helps - clarify why the tag name is considered reserved, I don't - know. */ - error (0, 0, "Attempt to add reserved tag name %s", tag); - return 1; - } - - /* 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) - { - 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); - } - - return 0; -} - -/* 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) - RCSNode *rcs; - const char *tag; -{ - List *symbols; - Node *node; - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - symbols = RCS_symbols (rcs); - if (symbols == NULL) - return 1; - - node = findnode (symbols, tag); - if (node == NULL) - return 1; - - delnode (node); - - return 0; -} - -/* Set the default branch of RCS to REV. */ - -int -RCS_setbranch (rcs, rev) - RCSNode *rcs; - const char *rev; -{ - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - if (rev && ! *rev) - rev = NULL; - - if (rev == NULL && rcs->branch == NULL) - return 0; - if (rev != NULL && rcs->branch != NULL && STREQ (rev, rcs->branch)) - return 0; - - if (rcs->branch != NULL) - free (rcs->branch); - rcs->branch = xstrdup (rev); - - return 0; -} - -/* Lock revision REV. LOCK_QUIET is 1 to suppress output. FIXME: - Most of the callers only call us because RCS_checkin still tends to - like a lock (a relic of old behavior inherited from the RCS ci - program). If we clean this up, only "cvs admin -l" will still need - to call RCS_lock. */ - -/* 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, lock_quiet) - RCSNode *rcs; - const char *rev; - int lock_quiet; -{ - List *locks; - Node *p; - char *user; - char *xrev = NULL; - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) 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); - else - xrev = RCS_gettag (rcs, rev, 1, (int *) NULL); - - /* 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 (xrev == NULL || findnode (rcs->versions, xrev) == NULL) - { - if (!lock_quiet) - error (0, 0, "%s: revision %s absent", rcs->path, rev); - free (xrev); - return 1; - } - - /* Is this rev already locked? */ - p = findnode (locks, xrev); - if (p != NULL) - { - if (STREQ (p->data, user)) - { - /* We already own the lock on this revision, so do nothing. */ - free (xrev); - return 0; - } - -#if 0 - /* Well, first of all, "rev" below should be "xrev" to avoid - core dumps. But more importantly, should we really be - breaking the lock unconditionally? What CVS 1.9 does (via - RCS) is to prompt "Revision 1.1 is already locked by fred. - Do you want to break the lock? [ny](n): ". Well, we don't - want to interact with the user (certainly not at the - server/protocol level, and probably not in the command-line - client), but isn't it more sensible to give an error and - let the user run "cvs admin -u" if they want to break the - lock? */ - - /* Break the lock. */ - if (!lock_quiet) - { - cvs_output (rev, 0); - cvs_output (" unlocked\n", 0); - } - delnode (p); -#else - error (1, 0, "Revision %s is already locked by %s", xrev, (char *)p->data); -#endif - } - - /* 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. UNLOCK_QUIET is 1 to suppress output. FIXME: - Like RCS_lock, this can become a no-op if we do the checkin - 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, unlock_quiet) - RCSNode *rcs; - char *rev; - int unlock_quiet; -{ - Node *lock; - List *locks; - char *user; - char *xrev = NULL; - - user = getcaller(); - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - /* If rev is NULL, unlock the revision held by the caller; if more - than one, make the user specify the revision explicitly. This - differs from RCS which unlocks 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 (STREQ (p->data, user)) - { - 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) - { - if (!unlock_quiet) - error (0, 0, "No locks are set for %s.\n", user); - return 0; /* no lock found, ergo nothing to do */ - } - xrev = xstrdup (lock->key); - } - else - { - xrev = RCS_gettag (rcs, rev, 1, (int *) NULL); - if (xrev == NULL) - { - error (0, 0, "%s: revision %s absent", rcs->path, rev); - return 1; - } - } - - lock = findnode (RCS_getlocks (rcs), xrev); - if (lock == NULL) - { - /* This revision isn't locked. */ - free (xrev); - return 0; - } - - if (! STREQ (lock->data, user)) - { - /* 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; - if (!unlock_quiet) - error (0, 0, "\ -%s: revision %s locked by %s; breaking lock", rcs->path, xrev, (char *)lock->data); - 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; -} - -/* 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 *access, *a; - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - if (rcs->access == NULL) - rcs->access = xstrdup (user); - else - { - access = xstrdup (rcs->access); - for (a = strtok (access, " "); a != NULL; a = strtok (NULL, " ")) - { - if (STREQ (a, user)) - { - free (access); - return; - } - } - free (access); - rcs->access = (char *) xrealloc - (rcs->access, strlen (rcs->access) + strlen (user) + 2); - strcat (rcs->access, " "); - strcat (rcs->access, user); - } -} - -/* Remove USER from the access list of RCS. */ - -void -RCS_delaccess (rcs, user) - RCSNode *rcs; - char *user; -{ - char *p, *s; - int ulen; - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - if (rcs->access == NULL) - return; - - if (user == NULL) - { - free (rcs->access); - rcs->access = NULL; - return; - } - - p = rcs->access; - ulen = strlen (user); - while (p != NULL) - { - if (strncmp (p, user, ulen) == 0 && (p[ulen] == '\0' || p[ulen] == ' ')) - break; - p = strchr (p, ' '); - if (p != NULL) - ++p; - } - - 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, (FILE **) NULL, (struct rcsbuffer *) 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 (STREQ (node->data, rev)) - return 1; - else - return 0; -} - -static int findmagictag PROTO ((Node *, void *)); - -/* Return a nonzero value if a magic tag rooted at ARG is found. */ - -static int -findmagictag (node, arg) - Node *node; - void *arg; -{ - char *rev = (char *)arg; - size_t len = strlen (rev); - - if (strncmp (node->data, rev, len) == 0 && - strncmp ((char *)node->data + len, ".0.", 3) == 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) && STREQ (rev1, rev2)) - { - char *tmp = RCS_getbranch (rcs, rev1, 0); - free (rev1); - free (rev2); - rev1 = rev2 = tmp; - } - 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 (! STREQ (rev1, branchpoint)) - { - /* Walk deltas from BRANCHPOINT on, looking for REV1. */ - nodep = findnode (rcs->versions, branchpoint); - revp = nodep->data; - while (revp->next != NULL && ! STREQ (revp->next, rev1)) - { - revp = 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 = nodep->data; - - if (rev2 != NULL) - found = STREQ (revp->version, rev2); - 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) || - walklist (RCS_symbols (rcs), findmagictag, 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; - } - - if (after == NULL && before == NULL) - { - /* The user is trying to delete all revisions. While an - RCS file without revisions makes sense to RCS (e.g. the - state after "rcs -i"), CVS has never been able to cope with - it. So at least for now we just make this an error. - - We don't include rcs->path in the message since "cvs admin" - already printed "RCS file:" and the name. */ - error (1, 0, "attempt to delete all revisions"); - } - - /* 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. */ - - if (after != NULL) - { - char *diffbuf; - size_t bufsize, len; - -#if defined (WOE32) && !defined (__CYGWIN32__) - /* FIXME: This is an awful kludge, but at least until I have - time to work on it a little more and test it, I'd rather - give a fatal error than corrupt the file. I think that we - need to use "-kb" and "--binary" and "rb" to get_file - (probably can do it always, not just for binary files, if - we are consistent between the RCS_checkout and the diff). */ - { - char *expand = RCS_getexpand (rcs); - if (expand != NULL && STREQ (expand, "b")) - error (1, 0, - "admin -o not implemented yet for binary on this system"); - } -#endif /* WOE32 */ - - afterfile = cvs_temp_name(); - status = RCS_checkout (rcs, NULL, after, NULL, "-ko", afterfile, - (RCSCHECKOUTPROC)0, NULL); - if (status > 0) - 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 - { - int dargc = 0; - size_t darg_allocated = 0; - char **dargv = NULL; - - beforefile = cvs_temp_name(); - status = RCS_checkout (rcs, NULL, before, NULL, "-ko", beforefile, - (RCSCHECKOUTPROC)0, NULL); - if (status > 0) - goto delrev_done; - - outfile = cvs_temp_name(); - run_add_arg_p (&dargc, &darg_allocated, &dargv, "-a"); - run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n"); - status = diff_exec (beforefile, afterfile, NULL, NULL, - dargc, dargv, outfile); - run_arg_free_p (dargc, dargv); - free (dargv); - - 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 = 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 || ! STREQ (next, after)); - next = revp->next) - { - nodep = findnode (rcs->versions, next); - revp = 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 (STREQ (rev1, branchpoint)) - { - nodep = findnode (rcs->versions, before); - revp = nodep->data; - nodep = revp->branches->list->next; - while (nodep != revp->branches->list && - ! STREQ (nodep->key, rev1)) - 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 = nodep->data; - free (beforep->next); - beforep->next = xstrdup (after); - } - } - - status = 0; - - delrev_done: - if (rev1 != NULL) - free (rev1); - if (rev2 && rev2 != rev1) - 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; -} - -/* - * TRUE if there exists a symbolic tag "tag" in file. - */ -int -RCS_exist_tag (rcs, tag) - RCSNode *rcs; - char *tag; -{ - - assert (rcs != NULL); - - if (findnode (RCS_symbols (rcs), tag)) - return 1; - return 0; - -} - -/* - * TRUE if RCS revision number "rev" exists. - * This includes magic branch revisions, not found in rcs->versions, - * but only in rcs->symbols, requiring a list walk to find them. - * Take advantage of list walk callback function already used by - * RCS_delete_revs, above. - */ -int -RCS_exist_rev (rcs, rev) - RCSNode *rcs; - char *rev; -{ - - assert (rcs != NULL); - - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); - - if (findnode(rcs->versions, rev) != 0) - return 1; - - if (walklist (RCS_symbols(rcs), findtag, rev) != 0) - return 1; - - return 0; - -} - - -/* RCS_deltas and friends. Processing of the deltas in RCS files. */ - -struct 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; - /* Version in which it was introduced. */ - RCSVers *vers; - /* Nonzero if this line ends with \n. This will always be true - except possibly for the last line. */ - int has_newline; - /* Number of pointers to this struct line. */ - int refcount; -}; - -struct linevector -{ - /* How many lines in use for this linevector? */ - unsigned int nlines; - /* How many lines allocated for this linevector? */ - unsigned int lines_alloced; - /* Pointer to array containing a pointer to each line. */ - struct line **vector; -}; - -static void linevector_init PROTO ((struct linevector *)); - -/* Initialize *VEC to be a linevector with no lines. */ -static void -linevector_init (vec) - struct linevector *vec; -{ - vec->lines_alloced = 0; - vec->nlines = 0; - vec->vector = NULL; -} - -static 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. - 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. - - 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; - const char *text; - size_t len; - RCSVers *vers; - unsigned int pos; -{ - const char *textend; - unsigned int i; - unsigned int nnew; - const char *p; - const char *nextline_text; - size_t nextline_len; - int nextline_newline; - struct line *q; - - if (len == 0) - return 1; - - textend = text + len; - - /* Count the number of lines we will need to add. */ - nnew = 1; - for (p = text; p < textend; ++p) - if (*p == '\n' && p + 1 < textend) - ++nnew; - - /* Expand VEC->VECTOR if needed. */ - if (vec->nlines + nnew >= vec->lines_alloced) - { - if (vec->lines_alloced == 0) - vec->lines_alloced = 10; - while (vec->nlines + nnew >= vec->lines_alloced) - vec->lines_alloced *= 2; - vec->vector = xrealloc (vec->vector, - vec->lines_alloced * sizeof (*vec->vector)); - } - - /* Make room for the new lines in VEC->VECTOR. */ - for (i = vec->nlines + nnew - 1; i >= pos + nnew; --i) - vec->vector[i] = vec->vector[i - nnew]; - - if (pos > vec->nlines) - return 0; - - /* Actually add the lines, to VEC->VECTOR. */ - i = pos; - nextline_text = text; - nextline_newline = 0; - for (p = text; p < textend; ++p) - if (*p == '\n') - { - nextline_newline = 1; - if (p + 1 == textend) - /* If there are no characters beyond the last newline, we - don't consider it another line. */ - break; - 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; -} - -static void linevector_copy PROTO ((struct linevector *, struct linevector *)); - -/* Copy FROM to TO, copying the vectors but not the lines pointed to. */ -static void -linevector_copy (to, from) - struct linevector *to; - struct linevector *from; -{ - 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) - to->lines_alloced = 10; - while (from->nlines > to->lines_alloced) - to->lines_alloced *= 2; - to->vector = (struct line **) - xrealloc (to->vector, to->lines_alloced * sizeof (*to->vector)); - } - memcpy (to->vector, from->vector, - from->nlines * sizeof (*to->vector)); - to->nlines = from->nlines; - for (ln = 0; ln < to->nlines; ++ln) - ++to->vector[ln]->refcount; -} - -static void linevector_free PROTO ((struct linevector *)); - -/* 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] && --vec->vector[ln]->refcount == 0) - free (vec->vector[ln]); - - free (vec->vector); - } -} - -static char *month_printname PROTO ((char *)); - -/* Given a textual string giving the month (1-12), terminated with any - character not recognized by atoi, return the 3 character name to - print it with. I do not think it is a good idea to change these - strings based on the locale; they are standard abbreviations (for - example in rfc822 mail messages) which should be widely understood. - Returns a pointer into static readonly storage. */ -static char * -month_printname (month) - char *month; -{ - static const char *const months[] = - {"Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; - int mnum; - - mnum = atoi (month); - if (mnum < 1 || mnum > 12) - return "???"; - return (char *)months[mnum - 1]; -} - -static int -apply_rcs_changes PROTO ((struct linevector *, const char *, size_t, - const char *, RCSVers *, RCSVers *)); - -/* Apply changes to the line vector LINES. DIFFBUF is a buffer of - * length DIFFLEN holding the change text from an RCS file (the output - * of diff -n). NAME is used in error messages. The VERS field of - * any line added is set to ADDVERS. The VERS field of any line - * deleted is set to DELVERS, unless DELVERS is NULL, in which case - * the VERS field of deleted lines is unchanged. - * - * RETURNS - * Non-zero if the change text is applied successfully to ORIG_LINES. - * - * If the change text does not appear to apply to ORIG_LINES (e.g., a - * line number is invalid), this function will return zero and ORIG_LINES - * will remain unmolested. - * - * ERRORS - * If the change text is improperly formatted (e.g., it is not the output - * of diff -n), the function calls error with a status of 1, causing the - * program to exit. - */ -static int -apply_rcs_changes (orig_lines, diffbuf, difflen, name, addvers, delvers) - struct linevector *orig_lines; - const char *diffbuf; - size_t difflen; - const char *name; - RCSVers *addvers; - RCSVers *delvers; -{ - const char *p; - const char *q; - int op; - /* The RCS format throws us for a loop in that the deltafrags (if - we define a deltafrag as an add or a delete) need to be applied - in reverse order. So we stick them into a linked list. */ - struct deltafrag { - enum {FRAG_ADD, FRAG_DELETE} type; - unsigned long pos; - unsigned long nlines; - const char *new_lines; - size_t len; - struct deltafrag *next; - }; - struct deltafrag *dfhead; - struct deltafrag **dftail; - struct deltafrag *df; - unsigned long numlines, lastmodline, offset; - struct linevector lines; - int err; - - dfhead = NULL; - dftail = &dfhead; - numlines = orig_lines->nlines; /* start with init # of lines */ - for (p = diffbuf; p != NULL && p < diffbuf + difflen; ) - { - op = *p++; - if (op != 'a' && op != 'd') - /* Can't just skip over the deltafrag, because the value - of op determines the syntax. */ - error (1, 0, "unrecognized operation '\\x%x' in %s", - op, name); - *dftail = df = xmalloc (sizeof *df); - *(dftail = &df->next) = NULL; - - df->pos = strtoul (p, (char **) &q, 10); - - if (p == q) - error (1, 0, "number expected in %s", name); - p = q; - if (*p++ != ' ') - error (1, 0, "space expected in %s", name); - df->nlines = strtoul (p, (char **) &q, 10); - if (p == q) - error (1, 0, "number expected in %s", name); - p = q; - if (*p++ != '\012') - error (1, 0, "linefeed expected in %s", name); - - if (op == 'a') - { - unsigned int i; - - df->type = FRAG_ADD; - i = df->nlines; - /* The text we want is the number of lines specified, or - until the end of the value, whichever comes first (it - will be the former except in the case where we are - adding a line which does not end in newline). */ - for (q = p; i != 0; ++q) - if (*q == '\n') - --i; - else if (q == diffbuf + difflen) - { - if (i != 1) - error (1, 0, "premature end of change in %s", name); - else - break; - } - - /* Stash away a pointer to the text we are adding. */ - df->new_lines = p; - df->len = q - p; - - p = q; - numlines += df->nlines; - } - else - { - /* Correct for the fact that line numbers in RCS files - start with 1. */ - --df->pos; - - assert (op == 'd'); - df->type = FRAG_DELETE; - numlines -= df->nlines; - } - } - - /* New temp data structure to hold new org before - copy back into original structure. */ - lines.nlines = lines.lines_alloced = numlines; - lines.vector = xmalloc (numlines * sizeof *lines.vector); - - /* We changed the list order to first to last -- so the - list never gets larger than the size numlines. */ - lastmodline = 0; - - /* offset created when adding/removing lines - between new and original structure */ - offset = 0; - err = 0; - for (df = dfhead; df != NULL; ) - { - unsigned int ln; - unsigned long deltaend; - - if (df->pos > orig_lines->nlines) - err = 1; - - /* On error, just free the rest of the list. */ - if (!err) - { - /* Here we need to get to the line where the next insert will - begin, which is DF->pos in ORIG_LINES. We will fill up to - DF->pos - OFFSET in LINES with original items. */ - for (deltaend = df->pos - offset; - lastmodline < deltaend; - lastmodline++) - { - /* we need to copy from the orig structure into new one */ - lines.vector[lastmodline] = - orig_lines->vector[lastmodline + offset]; - lines.vector[lastmodline]->refcount++; - } - - switch (df->type) - { - case FRAG_ADD: - { - const char *textend, *p; - const char *nextline_text; - struct line *q; - int nextline_newline; - size_t nextline_len; - - textend = df->new_lines + df->len; - nextline_newline = 0; - nextline_text = df->new_lines; - for (p = df->new_lines; p < textend; ++p) - { - if (*p == '\n') - { - nextline_newline = 1; - if (p + 1 == textend) - { - /* If there are no characters beyond the - last newline, we don't consider it - another line. */ - break; - } - - nextline_len = p - nextline_text; - q = xmalloc (sizeof *q + nextline_len); - q->vers = addvers; - q->text = (char *)(q + 1); - q->len = nextline_len; - q->has_newline = nextline_newline; - q->refcount = 1; - memcpy (q->text, nextline_text, nextline_len); - lines.vector[lastmodline++] = q; - offset--; - - nextline_text = (char *)p + 1; - nextline_newline = 0; - } - } - nextline_len = p - nextline_text; - q = xmalloc (sizeof *q + nextline_len); - q->vers = addvers; - q->text = (char *)(q + 1); - q->len = nextline_len; - q->has_newline = nextline_newline; - q->refcount = 1; - memcpy (q->text, nextline_text, nextline_len); - lines.vector[lastmodline++] = q; - - /* For each line we add the offset between the #'s - decreases. */ - offset--; - break; - } - - case FRAG_DELETE: - /* we are removing this many lines from the source. */ - offset += df->nlines; - - if (df->pos + df->nlines > orig_lines->nlines) - err = 1; - else if (delvers) - for (ln = df->pos; ln < df->pos + df->nlines; ++ln) - if (orig_lines->vector[ln]->refcount > 1) - /* Annotate needs this but, since the original - * vector is disposed of before returning from - * this function, we only need keep track if - * there are multiple references. - */ - orig_lines->vector[ln]->vers = delvers; - break; - } - } - - df = df->next; - free (dfhead); - dfhead = df; - } - - if (err) - { - /* No reason to try and move a half-mutated and known invalid - * text into the output buffer. - */ - linevector_free (&lines); - } - else - { - /* add the rest of the remaining lines to the data vector */ - for (; lastmodline < numlines; lastmodline++) - { - /* we need to copy from the orig structure into new one */ - lines.vector[lastmodline] = orig_lines->vector[lastmodline - + offset]; - lines.vector[lastmodline]->refcount++; - } - - /* Move the lines vector to the original structure for output, - * first deleting the old. - */ - linevector_free (orig_lines); - orig_lines->vector = lines.vector; - orig_lines->lines_alloced = numlines; - orig_lines->nlines = lines.nlines; - } - - return !err; -} - -/* Apply an RCS change text to a buffer. The function name starts - with rcs rather than RCS because this does not take an RCSNode - argument. NAME is used in error messages. TEXTBUF is the text - buffer to change, and TEXTLEN is the size. DIFFBUF and DIFFLEN are - the change buffer and size. The new buffer is returned in *RETBUF - and *RETLEN. The new buffer is allocated by xmalloc. - - Return 1 for success. On failure, call error and return 0. */ - -int -rcs_change_text (name, textbuf, textlen, diffbuf, difflen, retbuf, retlen) - const char *name; - char *textbuf; - size_t textlen; - const char *diffbuf; - size_t difflen; - char **retbuf; - size_t *retlen; -{ - struct linevector lines; - int ret; - - *retbuf = NULL; - *retlen = 0; - - linevector_init (&lines); - - if (! linevector_add (&lines, textbuf, textlen, NULL, 0)) - error (1, 0, "cannot initialize line vector"); - - if (! apply_rcs_changes (&lines, diffbuf, difflen, name, NULL, NULL)) - { - error (0, 0, "invalid change text in %s", name); - ret = 0; - } - else - { - char *p; - size_t n; - unsigned int ln; - - n = 0; - for (ln = 0; ln < lines.nlines; ++ln) - /* 1 for \n */ - n += lines.vector[ln]->len + 1; - - p = xmalloc (n); - *retbuf = p; - - for (ln = 0; ln < lines.nlines; ++ln) - { - memcpy (p, lines.vector[ln]->text, lines.vector[ln]->len); - p += lines.vector[ln]->len; - if (lines.vector[ln]->has_newline) - *p++ = '\n'; - } - - *retlen = p - *retbuf; - assert (*retlen <= n); - - ret = 1; - } - - linevector_free (&lines); - - return ret; -} - -/* Walk the deltas in RCS to get to revision VERSION. - - If OP is RCS_ANNOTATE, then write annotations using cvs_output. - - If OP is RCS_FETCH, then put the contents of VERSION into a - newly-malloc'd array and put a pointer to it in *TEXT. Each line - is \n terminated; the caller is responsible for converting text - files if desired. The total length is put in *LEN. - - If FP is non-NULL, it should be a file descriptor open to the file - RCS with file position pointing to the deltas. We close the file - when we are done. - - If LOG is non-NULL, then *LOG is set to the log message of VERSION, - and *LOGLEN is set to the length of the log message. - - On error, give a fatal error. */ - -void -RCS_deltas (rcs, fp, rcsbuf, version, op, text, len, log, loglen) - RCSNode *rcs; - FILE *fp; - struct rcsbuffer *rcsbuf; - const char *version; - enum rcs_delta_op op; - char **text; - size_t *len; - char **log; - size_t *loglen; -{ - struct rcsbuffer rcsbuf_local; - char *branchversion; - char *cpversion; - char *key; - char *value; - size_t vallen; - RCSVers *vers; - RCSVers *prev_vers; - RCSVers *trunk_vers; - char *next; - int ishead, isnext, isversion, onbranch; - Node *node; - struct linevector headlines; - struct linevector curlines; - struct linevector trunklines; - int foundhead; - - assert (version); - - if (fp == NULL) - { - rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf_local); - rcsbuf = &rcsbuf_local; - } - - assert (rcsbuf); - - if (log) *log = NULL; - - ishead = 1; - vers = NULL; - prev_vers = NULL; - trunk_vers = NULL; - next = NULL; - onbranch = 0; - foundhead = 0; - - linevector_init (&curlines); - linevector_init (&headlines); - linevector_init (&trunklines); - - /* We set BRANCHVERSION to the version we are currently looking - for. Initially, this is the version on the trunk from which - VERSION branches off. If VERSION is not a branch, then - BRANCHVERSION is just VERSION. */ - branchversion = xstrdup (version); - cpversion = strchr (branchversion, '.'); - if (cpversion != NULL) - cpversion = strchr (cpversion + 1, '.'); - if (cpversion != NULL) - *cpversion = '\0'; - - do { - if (! rcsbuf_getrevnum (rcsbuf, &key)) - error (1, 0, "unexpected EOF reading RCS file %s", rcs->path); - - /* look up the revision */ - node = findnode (rcs->versions, key); - if (!node) - error (1, 0, - "Delta text %s without revision information in `%s'.", - key, rcs->path); - - if (next != NULL && ! STREQ (next, key)) - { - /* This is not the next version we need. It is a branch - version which we want to ignore. */ - isnext = 0; - isversion = 0; - } - else - { - isnext = 1; - - /* Stash the previous version. */ - prev_vers = vers; - - vers = node->data; - next = vers->next; - - /* Compare key and trunkversion now, because key points to - storage controlled by rcsbuf_getkey. */ - if (STREQ (branchversion, key)) - isversion = 1; - else - isversion = 0; - } - - while (1) - { - if (! rcsbuf_getkey (rcsbuf, &key, &value)) - error (1, 0, "%s does not appear to be a valid rcs file", - rcs->path); - - if (log != NULL - && isversion - && STREQ (key, "log") - && STREQ (branchversion, version)) - { - if (*log != NULL) - { - error (0, 0, "Duplicate `log' keyword in RCS file (`%s').", - rcs->path); - free (*log); - } - *log = rcsbuf_valcopy (rcsbuf, value, 0, loglen); - } - - if (STREQ (key, "text")) - { - rcsbuf_valpolish (rcsbuf, value, 0, &vallen); - if (ishead) - { - if (! linevector_add (&curlines, value, vallen, NULL, 0)) - error (1, 0, "invalid rcs file %s", rcs->path); - - ishead = 0; - } - else if (isnext) - { - if (! apply_rcs_changes (&curlines, value, vallen, - rcs->path, - onbranch ? vers : NULL, - onbranch ? NULL : prev_vers)) - error (1, 0, "invalid change text in %s", rcs->path); - } - break; - } - } - - if (isversion) - { - /* This is either the version we want, or it is the - branchpoint to the version we want. */ - if (STREQ (branchversion, version)) - { - /* This is the version we want. */ - linevector_copy (&headlines, &curlines); - foundhead = 1; - if (onbranch) - { - /* We have found this version by tracking up a - branch. Restore back to the lines we saved - when we left the trunk, and continue tracking - down the trunk. */ - onbranch = 0; - vers = trunk_vers; - next = vers->next; - linevector_copy (&curlines, &trunklines); - } - } - else - { - Node *p; - - /* We need to look up the branch. */ - onbranch = 1; - - if (numdots (branchversion) < 2) - { - unsigned int ln; - - /* We are leaving the trunk; save the current - lines so that we can restore them when we - continue tracking down the trunk. */ - trunk_vers = vers; - linevector_copy (&trunklines, &curlines); - - /* Reset the version information we have - accumulated so far. It only applies to the - changes from the head to this version. */ - for (ln = 0; ln < curlines.nlines; ++ln) - curlines.vector[ln]->vers = NULL; - } - - /* The next version we want is the entry on - VERS->branches which matches this branch. For - example, suppose VERSION is 1.21.4.3 and - BRANCHVERSION was 1.21. Then we look for an entry - starting with "1.21.4" and we'll put it (probably - 1.21.4.1) in NEXT. We'll advance BRANCHVERSION by - two dots (in this example, to 1.21.4.3). */ - - if (vers->branches == NULL) - error (1, 0, "missing expected branches in %s", - rcs->path); - if (!cpversion) - error (1, 0, "Invalid revision number in `%s'.", - rcs->path); - *cpversion = '.'; - ++cpversion; - cpversion = strchr (cpversion, '.'); - if (cpversion == NULL) - error (1, 0, "version number confusion in %s", - rcs->path); - for (p = vers->branches->list->next; - p != vers->branches->list; - p = p->next) - if (strncmp (p->key, branchversion, - cpversion - branchversion) == 0) - break; - if (p == vers->branches->list) - error (1, 0, "missing expected branch in %s", - rcs->path); - - next = p->key; - - cpversion = strchr (cpversion + 1, '.'); - if (cpversion != NULL) - *cpversion = '\0'; - } - } - if (op == RCS_FETCH && foundhead) - break; - } while (next != NULL); - - free (branchversion); - - rcsbuf_cache (rcs, rcsbuf); - - if (! foundhead) - error (1, 0, "could not find desired version %s in %s", - version, rcs->path); - - /* Now print out or return the data we have just computed. */ - switch (op) - { - case RCS_ANNOTATE: - { - unsigned int ln; - - for (ln = 0; ln < headlines.nlines; ++ln) - { - char *buf; - /* Period which separates year from month in date. */ - char *ym; - /* Period which separates month from day in date. */ - char *md; - RCSVers *prvers; - - prvers = headlines.vector[ln]->vers; - if (prvers == NULL) - prvers = vers; - - buf = xmalloc (strlen (prvers->version) + 24); - sprintf (buf, "%-12s (%-8.8s ", - prvers->version, - prvers->author); - cvs_output (buf, 0); - free (buf); - - /* Now output the date. */ - ym = strchr (prvers->date, '.'); - if (ym == NULL) - { - /* ??- 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, '.'); - if (md == NULL) - cvs_output ("??", 0); - else - cvs_output (md + 1, 2); - - cvs_output ("-", 1); - cvs_output (month_printname (ym + 1), 0); - cvs_output ("-", 1); - /* Only output the last two digits of the year. Our output - lines are long enough as it is without printing the - century. */ - cvs_output (ym - 2, 2); - } - cvs_output ("): ", 0); - if (headlines.vector[ln]->len != 0) - cvs_output (headlines.vector[ln]->text, - headlines.vector[ln]->len); - cvs_output ("\n", 1); - } - } - break; - case RCS_FETCH: - { - char *p; - size_t n; - unsigned int ln; - - assert (text != NULL); - assert (len != NULL); - - n = 0; - for (ln = 0; ln < headlines.nlines; ++ln) - /* 1 for \n */ - n += headlines.vector[ln]->len + 1; - p = xmalloc (n); - *text = p; - for (ln = 0; ln < headlines.nlines; ++ln) - { - memcpy (p, headlines.vector[ln]->text, - headlines.vector[ln]->len); - p += headlines.vector[ln]->len; - if (headlines.vector[ln]->has_newline) - *p++ = '\n'; - } - *len = p - *text; - assert (*len <= n); - } - break; - } - - linevector_free (&curlines); - linevector_free (&headlines); - linevector_free (&trunklines); - - return; -} - -/* Read the information for a single delta from the RCS buffer RCSBUF, - whose name is RCSFILE. *KEYP and *VALP are either NULL, or the - first key/value pair to read, as set by rcsbuf_getkey. Return NULL - if there are no more deltas. Store the key/value pair which - terminated the read in *KEYP and *VALP. */ - -static RCSVers * -getdelta (rcsbuf, rcsfile, keyp, valp) - struct rcsbuffer *rcsbuf; - char *rcsfile; - char **keyp; - char **valp; -{ - RCSVers *vnode; - char *key, *value, *cp; - Node *kv; - - /* Get revision number if it wasn't passed in. This uses - rcsbuf_getkey because it doesn't croak when encountering - unexpected input. As a result, we have to play unholy games - with `key' and `value'. */ - if (*keyp != NULL) - { - key = *keyp; - value = *valp; - } - else - { - if (! rcsbuf_getkey (rcsbuf, &key, &value)) - error (1, 0, "%s: unexpected EOF", rcsfile); - } - - /* Make sure that it is a revision number and not a cabbage - or something. */ - for (cp = key; - (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0'; - cp++) - /* do nothing */ ; - /* Note that when comparing with RCSDATE, we are not massaging - VALUE from the string found in the RCS file. This is OK since - we know exactly what to expect. */ - if (*cp != '\0' || strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) != 0) - { - *keyp = key; - *valp = value; - return NULL; - } - - vnode = (RCSVers *) xmalloc (sizeof (RCSVers)); - memset (vnode, 0, sizeof (RCSVers)); - - vnode->version = xstrdup (key); - - /* Grab the value of the date from value. Note that we are not - massaging VALUE from the string found in the RCS file. */ - cp = value + (sizeof RCSDATE) - 1; /* skip the "date" keyword */ - while (whitespace (*cp)) /* take space off front of value */ - cp++; - - vnode->date = xstrdup (cp); - - /* Get author field. */ - if (! rcsbuf_getkey (rcsbuf, &key, &value)) - { - error (1, 0, "unexpected end of file reading %s", rcsfile); - } - if (! STREQ (key, "author")) - error (1, 0, "\ -unable to parse %s; `author' not in the expected place", rcsfile); - vnode->author = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL); - - /* Get state field. */ - if (! rcsbuf_getkey (rcsbuf, &key, &value)) - { - error (1, 0, "unexpected end of file reading %s", rcsfile); - } - if (! STREQ (key, "state")) - error (1, 0, "\ -unable to parse %s; `state' not in the expected place", rcsfile); - vnode->state = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL); - /* The value is optional, according to rcsfile(5). */ - if (value != NULL && STREQ (value, RCSDEAD)) - { - vnode->dead = 1; - } - - /* Note that "branches" and "next" are in fact mandatory, according - to doc/RCSFILES. */ - - /* fill in the branch list (if any branches exist) */ - if (! rcsbuf_getkey (rcsbuf, &key, &value)) - { - error (1, 0, "unexpected end of file reading %s", rcsfile); - } - if (STREQ (key, RCSDESC)) - { - *keyp = key; - *valp = value; - /* Probably could/should be a fatal error. */ - error (0, 0, "warning: 'branches' keyword missing from %s", rcsfile); - return vnode; - } - if (value != (char *) NULL) - { - vnode->branches = getlist (); - /* Note that we are not massaging VALUE from the string found - in the RCS file. */ - do_branches (vnode->branches, value); - } - - /* fill in the next field if there is a next revision */ - if (! rcsbuf_getkey (rcsbuf, &key, &value)) - { - error (1, 0, "unexpected end of file reading %s", rcsfile); - } - if (STREQ (key, RCSDESC)) - { - *keyp = key; - *valp = value; - /* Probably could/should be a fatal error. */ - error (0, 0, "warning: 'next' keyword missing from %s", rcsfile); - return vnode; - } - if (value != (char *) NULL) - vnode->next = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL); - - /* - * XXX - this is where we put the symbolic link stuff??? - * (into newphrases in the deltas). - */ - while (1) - { - if (! rcsbuf_getkey (rcsbuf, &key, &value)) - error (1, 0, "unexpected end of file reading %s", rcsfile); - - /* The `desc' keyword is the end of the deltas. */ - if (strcmp (key, RCSDESC) == 0) - break; - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - - /* The `hardlinks' value is a group of words, which must - be parsed separately and added as a list to vnode->hardlinks. */ - if (strcmp (key, "hardlinks") == 0) - { - char *word; - - vnode->hardlinks = getlist(); - while ((word = rcsbuf_valword (rcsbuf, &value)) != NULL) - { - Node *n = getnode(); - n->key = word; - addnode (vnode->hardlinks, n); - } - continue; - } -#endif - - /* 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 (STREQ (key, RCSDEAD)) - { - vnode->dead = 1; - if (vnode->state != NULL) - free (vnode->state); - vnode->state = xstrdup (RCSDEAD); - continue; - } - /* if we have a new revision number, we're done with this delta */ - for (cp = key; - (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0'; - cp++) - /* do nothing */ ; - /* Note that when comparing with RCSDATE, we are not massaging - VALUE from the string found in the RCS file. This is OK - since we know exactly what to expect. */ - 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 = rcsbuf_valcmp (rcsbuf) ? RCSCMPFLD : RCSFIELD; - kv->key = xstrdup (key); - kv->data = rcsbuf_valcopy (rcsbuf, value, kv->type == RCSFIELD, - (size_t *) NULL); - 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); - } - } - - /* Return the key which caused us to fail back to the caller. */ - *keyp = key; - *valp = value; - - 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, rcsbuf) - RCSNode *rcs; - FILE *fp; - struct rcsbuffer *rcsbuf; -{ - char *num; - char *key, *value; - Node *p; - Deltatext *d; - - /* Get the revision number. */ - if (! rcsbuf_getrevnum (rcsbuf, &num)) - { - /* If 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) - error (1, 0, - "Delta text %s without revision information in `%s'.", - num, rcs->path); - - d = (Deltatext *) xmalloc (sizeof (Deltatext)); - d->version = xstrdup (num); - - /* Get the log message. */ - if (! rcsbuf_getkey (rcsbuf, &key, &value)) - error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num); - if (! STREQ (key, "log")) - error (1, 0, "%s, delta %s: expected `log', got `%s'", - rcs->path, num, key); - d->log = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL); - - /* Get random newphrases. */ - d->other = getlist(); - while (1) - { - if (! rcsbuf_getkey (rcsbuf, &key, &value)) - error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num); - - if (STREQ (key, "text")) - break; - - p = getnode(); - p->type = rcsbuf_valcmp (rcsbuf) ? RCSCMPFLD : RCSFIELD; - p->key = xstrdup (key); - p->data = rcsbuf_valcopy (rcsbuf, value, p->type == RCSFIELD, - (size_t *) NULL); - if (addnode (d->other, p) < 0) - { - error (0, 0, "warning: %s, delta %s: duplicate field `%s'", - rcs->path, num, key); - } - } - - /* Get the change text. We already know that this key is `text'. */ - d->text = rcsbuf_valcopy (rcsbuf, value, 0, &d->len); - - 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, fparg) - Node *symnode; - void *fparg; -{ - FILE *fp = (FILE *) fparg; - - /* A fiddly optimization: this code used to just call fprintf, but - in an old repository with hundreds of tags this can get called - hundreds of thousands of times when doing a cvs tag. Since - tagging is a relatively common operation, and using putc and - fputs is just as comprehensible, the change is worthwhile. */ - putc ('\n', fp); - putc ('\t', fp); - fputs (symnode->key, fp); - putc (':', fp); - fputs (symnode->data, fp); - return 0; -} - -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", (char *)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. */ - - if (node->type == RCSCMPFLD || strpbrk (node->data, "$,.:;@") == NULL) - 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 (! STREQ (node->key, "desc") && - ! STREQ (node->key, "log") && - ! STREQ (node->key, "text")) - { - putc (';', fp); - } - return 0; -} - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - -/* Save a filename in a `hardlinks' RCS field. NODE->KEY will contain - a full pathname, but currently only basenames are stored in the RCS - node. Assume that the filename includes nasty characters and - @-escape it. */ - -static int -puthardlink_proc (node, vfp) - Node *node; - void *vfp; -{ - FILE *fp = (FILE *) vfp; - char *basename = strrchr (node->key, '/'); - - if (basename == NULL) - basename = node->key; - else - ++basename; - - putc ('\t', fp); - putc ('@', fp); - (void) expand_at_signs (basename, strlen (basename), fp); - putc ('@', fp); - - return 0; -} - -#endif - -/* 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); - /* If we haven't had to convert the symbols to a list yet, don't - force a conversion now; just write out the string. */ - if (rcs->symbols == NULL && rcs->symbols_data != NULL) - { - fputs ("\n\t", fp); - fputs (rcs->symbols_data, fp); - } - else - 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 && ! STREQ (rcs->expand, "kv")) - 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); - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - if (vers->hardlinks) - { - fprintf (fp, "\nhardlinks"); - walklist (vers->hardlinks, puthardlink_proc, fp); - putc (';', fp); - } -#endif - putc ('\n', fp); -} - -static void -RCS_putdtree (rcs, rev, fp) - RCSNode *rcs; - char *rev; - FILE *fp; -{ - RCSVers *versp; - Node *p, *branch; - - /* Previously, this function used a recursive implementation, but - if the trunk has a huge number of revisions and the program - stack is not big, a stack overflow could occur, so this - nonrecursive version was developed to be more safe. */ - Node *branchlist, *onebranch; - List *branches; - List *onebranchlist; - - if (rev == NULL) - return; - - branches = getlist(); - - for (; rev != NULL;) - { - /* Find the delta node for this revision. */ - p = findnode (rcs->versions, rev); - if (p == NULL) - { - error (1, 0, - "error parsing repository file %s, file may be corrupt.", - rcs->path); - } - - versp = p->data; - - /* Print the delta node and go for its `next' node. This - prints the trunk. If there are any branches printed on this - revision, mark we have some. */ - putdelta (versp, fp); - /* Store branch information into branch list so to write its - trunk afterwards */ - if (versp->branches != NULL) - { - branch = getnode(); - branch->data = versp->branches; - - addnode(branches, branch); - } - - rev = versp->next; - } - - /* If there are any branches printed on this revision, - print those trunks as well. */ - branchlist = branches->list; - for (branch = branchlist->next; - branch != branchlist; - branch = branch->next) - { - onebranchlist = (List *)(branch->data); - onebranch = onebranchlist->list; - for (p = onebranch->next; p != onebranch; p = p->next) - RCS_putdtree (rcs, p->key, fp); - - branch->data = NULL; /* so to prevent its freeing on dellist */ - } - - dellist(&branches); -} - -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, rcsbufin, fout, newdtext, insertpt) - RCSNode *rcs; - FILE *fin; - struct rcsbuffer *rcsbufin; - FILE *fout; - Deltatext *newdtext; - char *insertpt; -{ - int actions; - RCSVers *dadmin; - Node *np; - int insertbefore, found; - char *bufrest; - int nls; - size_t buflen; - char buf[8192]; - int got; - - /* Count the number of versions for which we have to do some - special operation. */ - actions = walklist (rcs->versions, count_delta_actions, (void *) NULL); - - /* Make a note of whether NEWDTEXT should be inserted - before or after its INSERTPT. */ - insertbefore = (newdtext != NULL && numdots (newdtext->version) == 1); - - while (actions != 0 || newdtext != NULL) - { - Deltatext *dtext; - - dtext = RCS_getdeltatext (rcs, fin, rcsbufin); - - /* We shouldn't hit EOF here, because that would imply that - some action was not taken, or that we could not insert - NEWDTEXT. */ - if (dtext == NULL) - error (1, 0, "internal error: EOF too early in RCS_copydeltas"); - - found = (insertpt != NULL && STREQ (dtext->version, insertpt)); - if (found && insertbefore) - { - putdeltatext (fout, newdtext); - newdtext = NULL; - insertpt = NULL; - } - - np = findnode (rcs->versions, dtext->version); - if (!np) - error (1, 0, - "Delta text %s without revision information in `%s'.", - dtext->version, rcs->path); - - dadmin = np->data; - - /* If this revision has been outdated, just skip it. */ - if (dadmin->outdated) - { - freedeltatext (dtext); - --actions; - 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 || dadmin->text->text != NULL) - --actions; - 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); - newdtext = NULL; - insertpt = NULL; - } - } - - /* Copy the rest of the file directly, without bothering to - interpret it. The caller will handle error checking by calling - ferror. - - We just wrote a newline to the file, either in putdeltatext or - in the caller. However, we may not have read the corresponding - newline from the file, because rcsbuf_getkey returns as soon as - it finds the end of the '@' string for the desc or text key. - Therefore, we may read three newlines when we should really - only write two, and we check for that case here. This is not - an semantically important issue; we only do it to make our RCS - files look traditional. */ - - nls = 3; - - rcsbuf_get_buffered (rcsbufin, &bufrest, &buflen); - if (buflen > 0) - { - if (bufrest[0] != '\n' - || strncmp (bufrest, "\n\n\n", buflen < 3 ? buflen : 3) != 0) - { - nls = 0; - } - else - { - if (buflen < 3) - nls -= buflen; - else - { - ++bufrest; - --buflen; - nls = 0; - } - } - - fwrite (bufrest, 1, buflen, fout); - } - if (!rcsbufin->mmapped) - { - /* This bit isn't necessary when using mmap since the entire file - * will already be available via the RCS buffer. Besides, the - * mmap code doesn't always keep the file pointer up to date, so - * this adds some data twice. - */ - while ((got = fread (buf, 1, sizeof buf, fin)) != 0) - { - if (nls > 0 - && got >= nls - && buf[0] == '\n' - && strncmp (buf, "\n\n\n", nls) == 0) - { - fwrite (buf + 1, 1, got - 1, fout); - } - else - { - fwrite (buf, 1, got, fout); - } - - nls = 0; - } - } -} - -/* A helper procedure for RCS_copydeltas. This is called via walklist - to count the number of RCS revisions for which some special action - is required. */ - -static int -count_delta_actions (np, ignore) - Node *np; - void *ignore; -{ - RCSVers *dadmin = np->data; - - if (dadmin->outdated) - return 1; - - if (dadmin->text != NULL - && (dadmin->text->log != NULL || dadmin->text->text != NULL)) - { - return 1; - } - - return 0; -} - -/* - * Clean up temporary files - */ -RETSIGTYPE -rcs_cleanup () -{ - /* Note that the checks for existence_error are because we are - called from a signal handler, so we don't know whether the - files got created. */ - - /* FIXME: Do not perform buffered I/O from an interrupt handler like - this (via error). However, I'm leaving the error-calling code there - in the hope that on the rare occasion the error call is actually made - (e.g., a fluky I/O error or permissions problem prevents the deletion - of a just-created file) reentrancy won't be an issue. */ - if (rcs_lockfile != NULL) - { - char *tmp = rcs_lockfile; - rcs_lockfile = NULL; - if (rcs_lockfd >= 0) - { - if (close (rcs_lockfd) != 0) - error (0, errno, "error closing lock file %s", tmp); - rcs_lockfd = -1; - } - if (unlink_file (tmp) < 0 - && !existence_error (errno)) - error (0, errno, "cannot remove %s", tmp); - } -} - -/* 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. - - 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; -{ - struct stat rstat; - FILE *fp; - static int first_call = 1; - - if (first_call) - { - first_call = 0; - /* clean up if we get a signal */ -#ifdef SIGABRT - (void) SIG_register (SIGABRT, rcs_cleanup); -#endif -#ifdef SIGHUP - (void) SIG_register (SIGHUP, rcs_cleanup); -#endif -#ifdef SIGINT - (void) SIG_register (SIGINT, rcs_cleanup); -#endif -#ifdef SIGQUIT - (void) SIG_register (SIGQUIT, rcs_cleanup); -#endif -#ifdef SIGPIPE - (void) SIG_register (SIGPIPE, rcs_cleanup); -#endif -#ifdef SIGTERM - (void) SIG_register (SIGTERM, rcs_cleanup); -#endif - } - - /* Get the lock file name: `,file,' for RCS file `file,v'. */ - assert (rcs_lockfile == NULL); - assert (rcs_lockfd < 0); - rcs_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. */ - rcs_lockfd = open (rcs_lockfile, - OPEN_BINARY | O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, - S_IRUSR | S_IRGRP | S_IROTH); - - if (rcs_lockfd < 0) - { - error (1, errno, "could not open lock file `%s'", rcs_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 (rcs_lockfd, rstat.st_mode) < 0) - error (1, errno, "cannot change mode for %s", rcs_lockfile); -#endif - fp = fdopen (rcs_lockfd, FOPEN_BINARY_WRITE); - if (fp == NULL) - error (1, errno, "cannot fdopen %s", rcs_lockfile); - - return fp; -} - -static void -rcs_internal_unlockfile (fp, rcsfile) - FILE *fp; - char *rcsfile; -{ - assert (rcs_lockfile != NULL); - assert (rcs_lockfd >= 0); - - /* 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)) - /* Using errno here may well be misleanding since the most recent - call that set errno may not have anything whatsoever to do with - the error that set the flag, but it's better than nothing. The - real solution is to check each call to fprintf rather than waiting - until the end like this. */ - error (1, errno, "error writing to lock file %s", rcs_lockfile); - - /* Flush and sync the file, or the user may be told the commit completed, - * while a server crash/power failure could still cause the data to be - * lost. - * - * Invoking rename(",<file>," , "<file>,v") on Linux and almost all UNIXs - * only flushes the inode for the target file to disk, it does not - * guarantee flush of the kernel buffers allocated for the ,<file>,. - * Depending upon the load on the machine, the Linux kernel's flush daemon - * process may not flush for a while. In the meantime the CVS transaction - * could have been declared committed to the end CVS user (CVS process has - * returned the final "OK"). If the machine crashes prior to syncing the - * changes to disk, the committed transaction can be lost. - */ - if (fflush (fp) != 0) - error (1, errno, "error flushing file `%s' to kernel buffers", - rcs_lockfile); -#ifdef HAVE_FSYNC - if (fsync (rcs_lockfd) < 0) - error (1, errno, "error fsyncing file `%s'", rcs_lockfile); -#endif - - if (fclose (fp) == EOF) - error (1, errno, "error closing lock file %s", rcs_lockfile); - rcs_lockfd = -1; - - rename_file (rcs_lockfile, rcsfile); - - { - /* Use a temporary to make sure there's no interval - (after rcs_lockfile has been freed but before it's set to NULL) - during which the signal handler's use of rcs_lockfile would - reference freed memory. */ - char *tmp = rcs_lockfile; - rcs_lockfile = NULL; - free (tmp); - } -} - -static char * -rcs_lockfilename (rcsfile) - const char *rcsfile; -{ - char *lockfile, *lockp; - const 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; - struct rcsbuffer rcsbufin; - - assert (rcs); - - if (noexec) - return; - - /* Make sure we're operating on an actual file and not a symlink. */ - resolve_symlink (&(rcs->path)); - - 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. */ - rcsbuf_cache_open (rcs, rcs->delta_pos, &fin, &rcsbufin); - - /* 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, &rcsbufin, fout, newdtext, insertpt); - - /* We don't want to call rcsbuf_cache here, since we're about to - delete the file. */ - rcsbuf_close (&rcsbufin); - if (ferror (fin)) - /* The only case in which using errno here would be meaningful - is if we happen to have left errno unmolested since the call - which produced the error (e.g. fread). That is pretty - fragile even if it happens to sometimes be true. The real - solution is to make sure that all the code which reads - from fin checks for errors itself (some does, some doesn't). */ - error (0, 0, "warning: ferror set while rewriting 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); -} - -/* Abandon changes to an RCS file. */ - -void -RCS_abandon (rcs) - RCSNode *rcs; -{ - free_rcsnode_contents (rcs); - rcs->symbols_data = NULL; - rcs->expand = NULL; - rcs->access = NULL; - rcs->locks_data = NULL; - rcs->comment = NULL; - rcs->desc = NULL; - rcs->flags |= PARTIAL; -} - -/* - * 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. - * - * /dev/null is not statted but assumed to have been created on the Epoch. - * At least using the POSIX.2 definition of patch, this should cause creation - * of files on platforms such as Windoze where the null IO device isn't named - * /dev/null to be parsed by patch properly. - */ -char * -make_file_label (path, rev, rcs) - const char *path; - const char *rev; - RCSNode *rcs; -{ - char datebuf[MAXDATELEN + 1]; - char *label; - - label = (char *) xmalloc (strlen (path) - + (rev == NULL ? 0 : strlen (rev) + 1) - + MAXDATELEN - + 2); - - if (rev) - { - char date[MAXDATELEN + 1]; - /* revs cannot be attached to /dev/null ... duh. */ - assert (strcmp(DEVNULL, path)); - RCS_getrevtime (rcs, rev, datebuf, 0); - (void) date_to_internet (date, datebuf); - (void) sprintf (label, "-L%s\t%s\t%s", path, date, rev); - } - else - { - struct stat sb; - struct tm *wm; - - if (strcmp(DEVNULL, path)) - { - const char *file = last_component (path); - if (CVS_STAT (file, &sb) < 0) - /* Assume that if the stat fails,then the later read for the - * diff will too. - */ - error (1, errno, "could not get info for `%s'", path); - wm = gmtime (&sb.st_mtime); - } - else - { - time_t t = 0; - wm = gmtime(&t); - } - - (void) tm_to_internet (datebuf, wm); - (void) sprintf (label, "-L%s\t%s", path, datebuf); - } - return label; -} - -void -RCS_setlocalid (arg) - const char *arg; -{ - char *copy, *next, *key; - - copy = xstrdup(arg); - next = copy; - key = strtok(next, "="); - - keywords[KEYWORD_LOCALID].string = xstrdup(key); - keywords[KEYWORD_LOCALID].len = strlen(key); - keywords[KEYWORD_LOCALID].expandit = 1; - - /* options? */ - while (key = strtok(NULL, ",")) { - if (!strcmp(key, keywords[KEYWORD_ID].string)) - keyword_local = KEYWORD_ID; - else if (!strcmp(key, keywords[KEYWORD_HEADER].string)) - keyword_local = KEYWORD_HEADER; - else if (!strcmp(key, keywords[KEYWORD_CVSHEADER].string)) - keyword_local = KEYWORD_CVSHEADER; - else - error(1, 0, "Unknown LocalId mode: %s", key); - } - free(copy); -} - -void -RCS_setincexc (arg) - const char *arg; -{ - char *key; - char *copy, *next; - int include = 0; - struct rcs_keyword *keyword; - - copy = xstrdup(arg); - next = copy; - switch (*next++) { - case 'e': - include = 0; - break; - case 'i': - include = 1; - break; - default: - free(copy); - return; - } - - if (include) - for (keyword = keywords; keyword->string != NULL; keyword++) - { - keyword->expandit = 0; - } - - key = strtok(next, ","); - while (key) { - for (keyword = keywords; keyword->string != NULL; keyword++) { - if (strcmp (keyword->string, key) == 0) - keyword->expandit = include; - } - key = strtok(NULL, ","); - } - free(copy); - return; -} - -#define ATTIC "/" CVSATTIC -static char * -getfullCVSname(CVSname, pathstore) - char *CVSname, **pathstore; -{ - if (current_parsed_root->directory) { - int rootlen; - char *c = NULL; - int alen = sizeof(ATTIC) - 1; - - *pathstore = xstrdup(CVSname); - if ((c = strrchr(*pathstore, '/')) != NULL) { - if (c - *pathstore >= alen) { - if (!strncmp(c - alen, ATTIC, alen)) { - while (*c != '\0') { - *(c - alen) = *c; - c++; - } - *(c - alen) = '\0'; - } - } - } - - rootlen = strlen(current_parsed_root->directory); - if (!strncmp(*pathstore, current_parsed_root->directory, rootlen) && - (*pathstore)[rootlen] == '/') - CVSname = (*pathstore + rootlen + 1); - else - CVSname = (*pathstore); - } - return CVSname; -} |