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