1741 lines
44 KiB
C
1741 lines
44 KiB
C
/* Change RCS file attributes. */
|
|
|
|
/* Copyright 1982, 1988, 1989 Walter Tichy
|
|
Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
|
|
Distributed under license by the Free Software Foundation, Inc.
|
|
|
|
This file is part of RCS.
|
|
|
|
RCS is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
RCS is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with RCS; see the file COPYING.
|
|
If not, write to the Free Software Foundation,
|
|
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
Report problems and direct all questions to:
|
|
|
|
rcs-bugs@cs.purdue.edu
|
|
|
|
*/
|
|
|
|
/*
|
|
* $Log: rcschgh.c,v $
|
|
* Revision 1.1 1996/06/28 23:05:34 msf
|
|
* Initial revision
|
|
*
|
|
*/
|
|
|
|
|
|
#include "rcsbase.h"
|
|
#include "rcspriv.h"
|
|
|
|
struct Lockrev {
|
|
char const *revno;
|
|
struct Lockrev * nextrev;
|
|
};
|
|
|
|
struct Symrev {
|
|
char const *revno;
|
|
char const *ssymbol;
|
|
int override;
|
|
struct Symrev * nextsym;
|
|
};
|
|
|
|
struct Message {
|
|
char const *revno;
|
|
struct cbuf message;
|
|
struct Message *nextmessage;
|
|
};
|
|
|
|
struct Status {
|
|
char const *revno;
|
|
char const *status;
|
|
struct Status * nextstatus;
|
|
};
|
|
|
|
enum changeaccess {append, erase};
|
|
struct chaccess {
|
|
char const *login;
|
|
enum changeaccess command;
|
|
struct chaccess *nextchaccess;
|
|
};
|
|
|
|
struct delrevpair {
|
|
char const *strt;
|
|
char const *end;
|
|
int code;
|
|
};
|
|
|
|
static int branchpoint P((struct hshentry*,struct hshentry*));
|
|
static int buildeltatext P((struct hshentries const*));
|
|
static int doaccess P((void));
|
|
static int doassoc P((void));
|
|
static int dolocks P((void));
|
|
static int domessages P((void));
|
|
static int rcs_setstate P((char const*,char const*));
|
|
static int removerevs P((void));
|
|
static int setlock P((char const*));
|
|
static struct Lockrev **rmnewlocklst P((char const*));
|
|
static struct hshentry *searchcutpt P((char const*,int,struct hshentries*));
|
|
static void buildtree P((void));
|
|
static void cleanup P((void));
|
|
static int getaccessor P((char*,enum changeaccess));
|
|
static int getassoclst P((int,char*));
|
|
static void getchaccess P((char const*,enum changeaccess));
|
|
static void getdelrev P((char*));
|
|
static int getmessage P((const char *, char *));
|
|
static int getstates P((const char*, char *));
|
|
static void scanlogtext P((struct hshentry*,int));
|
|
|
|
static struct buf numrev;
|
|
static char const *headstate;
|
|
static int chgheadstate, exitstatus, lockhead, unlockcaller;
|
|
static struct Lockrev *newlocklst, *rmvlocklst;
|
|
static struct Message *messagelst, **nextmessage;
|
|
static struct Status *statelst, **nextstate;
|
|
static struct Symrev *assoclst, **nextassoc;
|
|
static struct chaccess *chaccess, **nextchaccess;
|
|
static struct delrevpair delrev;
|
|
static struct hshentry *cuthead, *cuttail, *delstrt;
|
|
static struct hshentries *gendeltas;
|
|
|
|
/* Memory heaps managed locally: one for -A files; one for persistent opts */
|
|
static struct rcsHeap accessHeap = {NULL, NULL, NULL};
|
|
static struct rcsHeap optsHeap = {NULL, NULL, NULL};
|
|
|
|
rcsChangeHdr(conn, rcspath, nameval, namevalNum, opts)
|
|
RCSCONN conn;
|
|
const char *rcspath;
|
|
struct rcsNameVal nameval[];
|
|
int namevalNum;
|
|
struct rcsChangeHdrOpts *opts;
|
|
{
|
|
/* Routine to read or write header information */
|
|
|
|
int i;
|
|
char *ptr;
|
|
const char *cptr;
|
|
char *textfile;
|
|
char const *branchsym, *commsyml;
|
|
int branchflag, changed, expmode;
|
|
static int initflag;
|
|
int strictlock, strict_selected, textflag;
|
|
int keepRCStime;
|
|
size_t commsymlen;
|
|
static struct buf branchnum;
|
|
struct Lockrev *lockpt;
|
|
struct Lockrev **curlock, **rmvlock;
|
|
struct Status * curstate;
|
|
static char *obsRevisions; /* rcs -o revisions */
|
|
static bool has_been_called = false; /* for first time init */
|
|
struct rcsNameVal *nvptr; /* arg currently being parsed */
|
|
int lasterr; /* last name/val arg error */
|
|
int rc;
|
|
|
|
nosetid();
|
|
|
|
/* In case of internal core code error, return fatal error
|
|
* to caller, rather than exiting.
|
|
*/
|
|
if ((rc = sigsetjmp(rcsApiEnv, 1)) != 0) return (rc);
|
|
|
|
/* Initialize variables for this invocation only */
|
|
nextassoc = &assoclst;
|
|
nextchaccess = &chaccess;
|
|
nextmessage = &messagelst;
|
|
nextstate = &statelst;
|
|
branchsym = commsyml = textfile = 0;
|
|
branchflag = strictlock = false;
|
|
commsymlen = 0;
|
|
curlock = &newlocklst;
|
|
rmvlock = &rmvlocklst;
|
|
expmode = -1;
|
|
initflag= textflag = false;
|
|
strict_selected = 0;
|
|
lasterr = 0;
|
|
changed = false;
|
|
|
|
|
|
/* Initialize variables for duration of connection (unless overridden)*/
|
|
if (!has_been_called) {
|
|
initflag= false;
|
|
rcsMailtxt = obsRevisions = NULL;
|
|
suppress_mail = false;
|
|
has_been_called = true;
|
|
bufautobegin(&branchnum);
|
|
}
|
|
|
|
/* Process per-connection options */
|
|
if (opts) { /* handle options; else keep prevous settings/defaults */
|
|
|
|
/* Free old heap (which holds the old per-connection options */
|
|
rcsHeapFree(&optsHeap);
|
|
|
|
initflag = opts->initflag;
|
|
suppress_mail = opts->nomail;
|
|
|
|
if (rcsMailtxt) {
|
|
rcsMailtxt = NULL;
|
|
}
|
|
if (opts->mailtxt) {
|
|
i = strlen(opts->mailtxt);
|
|
if (!(rcsMailtxt = rcsHeapAlloc(&optsHeap, i+2)))
|
|
faterror("Out of Memory");
|
|
memcpy(rcsMailtxt, opts->mailtxt, i+1);
|
|
|
|
/* Ensure that mail message ends with a newline */
|
|
if (!i || rcsMailtxt[i-1] != '\n') {
|
|
rcsMailtxt[i++] = '\n';
|
|
rcsMailtxt[i] = '\0';
|
|
}
|
|
}
|
|
|
|
if (obsRevisions) {
|
|
obsRevisions = NULL;
|
|
}
|
|
if (opts->obsRevisions && *opts->obsRevisions) {
|
|
char *ptr;
|
|
size_t len;
|
|
len = strlen(opts->obsRevisions);
|
|
if (!(ptr = rcsHeapAlloc(&optsHeap, ++len)))
|
|
faterror("Out of Memory");
|
|
memcpy(ptr, opts->obsRevisions, len);
|
|
getdelrev(ptr);
|
|
}
|
|
}
|
|
|
|
/* Ugly kludge for rcs -Afile:
|
|
* Since the RCS code is structured to allow only one file
|
|
* open at a time, and memory mgmt is set up to free per-file
|
|
* memory once a file is closed, we need to deal with all other
|
|
* files before working with the real RCS file. So we look to
|
|
* see whether we have an rcs -A operation (which requires parsing
|
|
* a different RCS file), and deal with that first.
|
|
*/
|
|
for (i = namevalNum, nvptr = nameval; i>0 ; i--, nvptr++) {
|
|
if ((nvptr->flags & rcsNameValFILE) &&
|
|
(!nvptr->namespace || !nvptr->namespace[0]) &&
|
|
( nvptr->name[0] == 'a' && !strcmp(nvptr->name, Kaccess)))
|
|
{
|
|
const char *val;
|
|
if (!(val = nvptr->val)) val = "";
|
|
|
|
if (rcsParamName[0])
|
|
rcsCloseArch(); /* close old file */
|
|
|
|
if (rcsOpenArch(false,true, val) < 0) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* walk through other file's access list;
|
|
* add to list of logins to add to our RCS file
|
|
*/
|
|
while (AccessList) {
|
|
/* Save in second heap; cannot use fstr_save,
|
|
* since "other" RCS file now open.
|
|
*/
|
|
size_t len = strlen(AccessList->login);
|
|
char *login = rcsHeapAlloc(&accessHeap, ++len);
|
|
if (!login) faterror("Out of Memory");
|
|
memcpy(login, AccessList->login, len);
|
|
getchaccess(login, append);
|
|
AccessList = AccessList->nextaccess;
|
|
}
|
|
/* Close other RCS file; clear rcsParamName (since it
|
|
* is used to know what RCS file is cur. open)
|
|
*/
|
|
Izclose(&finptr);
|
|
rcsParamName[0] = '\0';
|
|
}
|
|
}
|
|
|
|
/* Open or reopen for update the archive file, depending upon
|
|
* whether it is already open (for read)
|
|
*
|
|
* Do this here, because some per-file argument processing
|
|
* uses per-file memory (ftalloc), which would otherwise
|
|
* be wiped out when the new file were opened.
|
|
*/
|
|
if (!strcmp(rcsParamName, rcspath)) {
|
|
if ((rc = rcsreopen(initflag ? false : true )) < 0) {
|
|
cleanup();
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
else {
|
|
if (rcsParamName[0]) rcsCloseArch(); /* close different archive */
|
|
if((rc = rcsOpenArch(true, initflag ? false : true, rcspath)) <0) {
|
|
cleanup();
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* Process per-file arguments */
|
|
for (i = namevalNum, nvptr = nameval; i>0 ; i--, nvptr++) {
|
|
if (!nvptr->namespace || !nvptr->namespace[0]) {
|
|
/* RCS-defined field */
|
|
const char *val;
|
|
if (!(val = nvptr->val)) val = "";
|
|
|
|
switch(nvptr->name[0]) {
|
|
case 'a': /* "access", "author" */
|
|
|
|
if (nvptr->revision) { /* delta header */
|
|
if (strcmp(nvptr->name, Kauthor)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
/* Attempt to change "author" field of delta */
|
|
lasterr = nvptr->errcode = ERR_READONLY;
|
|
continue;
|
|
}
|
|
|
|
/* admin header, with field beginning with 'a' */
|
|
if (strcmp(nvptr->name, Kaccess)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* rcs -a, rcs -A, rcs -e ("access" field) */
|
|
if (nvptr->flags & rcsNameValFILE) { /* rcs -A<val> */
|
|
break;
|
|
}
|
|
|
|
if (nvptr->flags & rcsNameValDELETE) { /* rcs -e */
|
|
if (rc = getaccessor(fstr_save(val), erase)) {
|
|
lasterr = rc;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* rcs -a */
|
|
if (rc = getaccessor(fstr_save(val), append)) {
|
|
lasterr = rc;
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'b': /* "branch", "branches" */
|
|
|
|
if (nvptr->revision) { /* delta header */
|
|
if (strcmp(nvptr->name, K_branches)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
/* Attempt to change "branches" field of delta */
|
|
lasterr = nvptr->errcode = ERR_READONLY;
|
|
continue;
|
|
}
|
|
|
|
/* admin header, with field beginning with 'b' */
|
|
if (strcmp(nvptr->name, Kbranch)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* rcs -b ("branch") field */
|
|
branchflag = true;
|
|
if (nvptr->flags & rcsNameValDELETE)
|
|
branchsym = "";
|
|
else
|
|
branchsym = val;
|
|
break;
|
|
|
|
case 'c': /* "comment" */
|
|
/* if delta header, or admin field not "comment", err */
|
|
if (nvptr->revision || strcmp(nvptr->name, Kcomment)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* rcs -c ("comment") field */
|
|
if (nvptr->flags & rcsNameValDELETE) {
|
|
commsyml = "";
|
|
commsymlen = 0;
|
|
}
|
|
else {
|
|
commsyml = val;
|
|
commsymlen = strlen(commsyml);
|
|
}
|
|
break;
|
|
|
|
case 'd': /* "date", "desc" */
|
|
{
|
|
size_t len;
|
|
|
|
/* admin header */
|
|
if (!nvptr->revision) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* delta header */
|
|
if (!strcmp(nvptr->name, Kdate)) { /* "date" */
|
|
lasterr = nvptr->errcode = ERR_READONLY;
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(nvptr->name, Kdesc)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* rcs -t- ("desc") field */
|
|
textflag = true;
|
|
textfile = ftnalloc(char, (len = strlen(val))+2);
|
|
memcpy(textfile+1, val, len+1);
|
|
*textfile = '-'; /* -<string>; add rcsnameValFILE?? */
|
|
break;
|
|
}
|
|
|
|
case 'e': /* "expand" */
|
|
/* if delta header, or admin field not "expand", err */
|
|
if (nvptr->revision || strcmp(nvptr->name, Kexpand)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* rcs -k ("expand") field */
|
|
if (nvptr->flags & rcsNameValDELETE) {
|
|
expmode = str2expmode("kv");
|
|
}
|
|
else if ((rc = str2expmode(val)) < 0) {
|
|
lasterr = nvptr->errcode = ERR_BAD_VALUE;
|
|
continue;
|
|
}
|
|
else expmode = rc;
|
|
break;
|
|
|
|
case 'h': /* "head" */
|
|
/* if delta header, or admin field not "head", err */
|
|
if (nvptr->revision || strcmp(nvptr->name, Khead)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* Attempt to change admin "head" field */
|
|
lasterr = nvptr->errcode = ERR_READONLY;
|
|
continue;
|
|
|
|
case 'l': /* "locks", "log" */
|
|
if (nvptr->revision) { /* delta header */
|
|
if (strcmp(nvptr->name, Klog)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
/* rcs -m ("log") field */
|
|
if ((rc = getmessage(nvptr->revision,
|
|
fstr_save(val))) < 0)
|
|
{
|
|
lasterr = nvptr->errcode = rc;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* admin header */
|
|
if (strcmp(nvptr->name, Klocks)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
/* rcs -l, rcs -u ("locks") field */
|
|
|
|
/* <revno> or <login>:<revno> value, or NULL */
|
|
/* set ptr to point to <revno>, or NULL */
|
|
|
|
if (!*val) cptr = NULL;
|
|
else if (cptr = strchr(val, ':')) {
|
|
++cptr; /* skip "<login>:" */
|
|
}
|
|
else cptr = val;
|
|
|
|
if (nvptr->flags & rcsNameValDELETE) { /* rcs -u */
|
|
if (!cptr || !*cptr) {
|
|
unlockcaller = true;
|
|
break;
|
|
}
|
|
|
|
/* Add lock to remove list */
|
|
if (!cptr) cptr = val;
|
|
*rmvlock = lockpt = ftalloc(struct Lockrev);
|
|
lockpt->revno = cptr;
|
|
lockpt->nextrev = 0;
|
|
rmvlock = &lockpt->nextrev;
|
|
curlock = rmnewlocklst(lockpt->revno);
|
|
break;
|
|
}
|
|
|
|
/* rcs -l */
|
|
if (cptr && cptr != val) {/* <login>:<revno> */
|
|
int j = strlen(author);
|
|
if (strncmp(author, val, j) || val[j] != ':') {
|
|
/* attempt to add lock owned by someone else */
|
|
lasterr = nvptr->errcode = ERR_BAD_VALUE;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!cptr || !*cptr) {
|
|
/* Lock head or default branch */
|
|
lockhead = true;
|
|
break;
|
|
}
|
|
*curlock = lockpt = ftalloc(struct Lockrev);
|
|
lockpt->revno = cptr;
|
|
lockpt->nextrev = 0;
|
|
curlock = &lockpt->nextrev;
|
|
break;
|
|
|
|
case 'n': /* "next" */
|
|
/* if admin header, or delta field not "next", err */
|
|
if (!nvptr->revision || strcmp(nvptr->name, Knext)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* Attempt to change delta "next" field */
|
|
lasterr = nvptr->errcode = ERR_READONLY;
|
|
continue;
|
|
|
|
case 's': /* "symbols", "strict", "state" */
|
|
if (nvptr->revision) { /* delta header */
|
|
if (strcmp(nvptr->name, Kstate)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
/* rcs -s ("state") field */
|
|
if ((rc = getstates(nvptr->revision,
|
|
fstr_save(val))) < 0)
|
|
{
|
|
lasterr = nvptr->errcode = rc;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* admin header */
|
|
if (!strcmp(nvptr->name, Kstrict)) {
|
|
strict_selected = true;
|
|
if (nvptr->flags & rcsNameValDELETE); /* rcs -U */
|
|
else { /* rcs -L */
|
|
/* -L takes precidence */
|
|
strictlock = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (strcmp(nvptr->name, Ksymbols)) {
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
/* rcs -n, rcs -N ("symbols") field */
|
|
ptr = fstr_save(val);
|
|
if (nvptr->flags & rcsNameValDELETE) {
|
|
char *ptr2;
|
|
/* make <name>:<rev> look like <name> for delete */
|
|
if (ptr2 = strchr(ptr, ':')) *ptr2 = '\0';
|
|
}
|
|
if ((rc =
|
|
getassoclst(nvptr->flags & rcsNameValUNIQUE, ptr))
|
|
< 0) {
|
|
lasterr = nvptr->errcode = rc;
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
lasterr = nvptr->errcode = ERR_BAD_NAME;
|
|
continue;
|
|
}
|
|
|
|
nvptr->errcode = 0;
|
|
continue;
|
|
} /* end of RCS-defined fields */
|
|
|
|
/* Set application block field */
|
|
if ((rc = addnameval(nvptr->revision, nvptr->namespace, nvptr->name,
|
|
nvptr->val,
|
|
((nvptr->flags & rcsNameValUNIQUE) ? false : true)
|
|
)
|
|
) < 0) {
|
|
lasterr = nvptr->errcode = rc;
|
|
continue;
|
|
}
|
|
changed = true;
|
|
|
|
} /* end of loop on values to set */
|
|
|
|
/* Flags set per-session (not here):
|
|
* -I (interactive)
|
|
* -q (quiet)
|
|
* -x (suffixes)
|
|
*/
|
|
|
|
#if 0 /* FIX: Do we allow telling the server the version of the files?
|
|
* If so, this is on a per-session basis.
|
|
* -V: setRCSversion(val);
|
|
*/
|
|
#endif
|
|
#if 0
|
|
/* FIX: Add timezone (-z) to per-session data.
|
|
* zone_set(val)
|
|
*/
|
|
#endif
|
|
/*
|
|
* RCSname contains the name of the RCS file.
|
|
* if !initflag, finptr contains the RILE *pointer for the
|
|
* RCS file. The admin node is initialized.
|
|
*/
|
|
|
|
diagnose("RCS file: %s\n", RCSname);
|
|
|
|
changed |= initflag | textflag;
|
|
keepRCStime = Ttimeflag;
|
|
if (!initflag) {
|
|
if (!(checkaccesslist())) {
|
|
cleanup();
|
|
return ERR_NO_PERM;
|
|
}
|
|
gettree(); /* Read the delta tree. */
|
|
}
|
|
|
|
/* update admin. node */
|
|
if (strict_selected) {
|
|
changed |= StrictLocks ^ strictlock;
|
|
StrictLocks = strictlock;
|
|
}
|
|
if (
|
|
commsyml &&
|
|
(
|
|
commsymlen != Comment.size ||
|
|
memcmp(commsyml, Comment.string, commsymlen) != 0
|
|
)
|
|
) {
|
|
Comment.string = commsyml;
|
|
Comment.size = strlen(commsyml);
|
|
changed = true;
|
|
}
|
|
if (0 <= expmode && Expand != expmode) {
|
|
Expand = expmode;
|
|
changed = true;
|
|
}
|
|
|
|
/* update default branch */
|
|
if (branchflag && expandsym(branchsym, &branchnum)) {
|
|
if (countnumflds(branchnum.string)) {
|
|
if (cmpnum(Dbranch, branchnum.string) != 0) {
|
|
Dbranch = branchnum.string;
|
|
changed = true;
|
|
}
|
|
} else
|
|
if (Dbranch) {
|
|
Dbranch = 0;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
changed |= doaccess(); /* Update access list. */
|
|
|
|
changed |= doassoc(); /* Update association list. */
|
|
|
|
changed |= dolocks(); /* Update locks. */
|
|
|
|
changed |= domessages(); /* Update log messages. */
|
|
|
|
/* update state attribution */
|
|
if (chgheadstate) {
|
|
/* change state of default branch or head */
|
|
if (!Dbranch) {
|
|
if (!Head)
|
|
rcswarn("can't change states in an empty tree");
|
|
else if (strcmp(Head->state, headstate) != 0) {
|
|
Head->state = headstate;
|
|
changed = true;
|
|
}
|
|
} else
|
|
changed |= rcs_setstate(Dbranch,headstate);
|
|
}
|
|
for (curstate = statelst; curstate; curstate = curstate->nextstatus)
|
|
changed |= rcs_setstate(curstate->revno,curstate->status);
|
|
|
|
cuthead = cuttail = 0;
|
|
if (delrev.strt && removerevs()) { /* implies -o; no delta() allowed */
|
|
/* rebuild delta tree if some deltas are deleted */
|
|
if ( cuttail )
|
|
VOID genrevs(
|
|
cuttail->num, (char *)0, (char *)0, (char *)0,
|
|
&gendeltas
|
|
);
|
|
buildtree();
|
|
changed = true;
|
|
keepRCStime = false;
|
|
}
|
|
|
|
if (nerror) {
|
|
cleanup();
|
|
return -1;
|
|
}
|
|
|
|
/* Start rewrite of RCS file here */
|
|
putadmin();
|
|
if ( Head )
|
|
puttree(Head, frewrite);
|
|
|
|
rcsNoStdin = true;
|
|
putdesc(textflag, textfile);
|
|
rcsNoStdin = false;
|
|
|
|
if ( Head) {
|
|
if (delrev.strt || messagelst) {
|
|
if (!cuttail || buildeltatext(gendeltas)) {
|
|
advise_access(finptr, MADV_SEQUENTIAL);
|
|
scanlogtext((struct hshentry *)0, false);
|
|
/* copy rest of delta text nodes that are not deleted */
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (initflag) {
|
|
/* Adjust things for donerewrite's sake. */
|
|
if (stat(workname, &RCSstat) != 0) {
|
|
# if bad_creat0
|
|
mode_t m = umask(0);
|
|
(void) umask(m);
|
|
RCSstat.st_mode = (S_IRUSR|S_IRGRP|S_IROTH) & ~m;
|
|
# else
|
|
changed = -1;
|
|
# endif
|
|
}
|
|
RCSstat.st_nlink = 0;
|
|
keepRCStime = false;
|
|
}
|
|
|
|
if (donerewrite(changed,
|
|
keepRCStime ? RCSstat.st_mtime : (time_t)-1
|
|
) != 0) {
|
|
cleanup();
|
|
return ERR_INTERNAL;
|
|
}
|
|
|
|
cleanup();
|
|
return lasterr ? (namevalNum == 1 ? lasterr : ERR_PARTIAL) : 0;
|
|
} /* end of header (rcs) */
|
|
|
|
|
|
|
|
static void
|
|
cleanup()
|
|
{
|
|
if (nerror) exitstatus = EXIT_FAILURE;
|
|
/* Izclose(&finptr); */
|
|
/* Ozclose(&fcopy); */
|
|
/* ORCSclose(); */
|
|
/* dirtempunlink(); */
|
|
rcsCloseArch(); /* common cleanup (closing Arch) */
|
|
rcsHeapFree(&accessHeap); /* cleanup -A heap used for this file */
|
|
}
|
|
|
|
|
|
static int
|
|
getassoclst(flag, sp)
|
|
int flag;
|
|
char * sp;
|
|
/* Function: associate a symbolic name to a revision or branch, */
|
|
/* and store in assoclst */
|
|
|
|
{
|
|
struct Symrev * pt;
|
|
char const *temp;
|
|
int c;
|
|
|
|
while ((c = *++sp) == ' ' || c == '\t' || c =='\n')
|
|
continue;
|
|
temp = sp;
|
|
if (!(sp = checksym(sp, ':'))) /* check for invalid symbolic name */
|
|
return ERR_BAD_VALUE;
|
|
|
|
c = *sp; *sp = '\0';
|
|
while( c == ' ' || c == '\t' || c == '\n') c = *++sp;
|
|
|
|
if ( c != ':' && c != '\0') {
|
|
error("invalid string %s after option -n or -N",sp);
|
|
return ERR_BAD_VALUE;
|
|
}
|
|
|
|
pt = talloc(struct Symrev);
|
|
pt->ssymbol = temp;
|
|
pt->override = flag;
|
|
if (c == '\0') /* delete symbol */
|
|
pt->revno = 0;
|
|
else {
|
|
while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
|
|
continue;
|
|
pt->revno = sp;
|
|
}
|
|
pt->nextsym = 0;
|
|
*nextassoc = pt;
|
|
nextassoc = &pt->nextsym;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
getchaccess(login, command)
|
|
char const *login;
|
|
enum changeaccess command;
|
|
{
|
|
register struct chaccess *pt;
|
|
|
|
if (!(pt = rcsHeapAlloc(&accessHeap, sizeof(struct chaccess))))
|
|
faterror("Out of Memory");
|
|
pt->login = login;
|
|
pt->command = command;
|
|
pt->nextchaccess = 0;
|
|
*nextchaccess = pt;
|
|
nextchaccess = &pt->nextchaccess;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
getaccessor(opt, command)
|
|
char *opt;
|
|
enum changeaccess command;
|
|
/* Function: get the accessor list of options -e and -a, */
|
|
/* and store in chaccess */
|
|
|
|
|
|
{
|
|
register c;
|
|
register char *sp;
|
|
|
|
sp = opt;
|
|
while ((c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',')
|
|
continue;
|
|
if ( c == '\0') {
|
|
if (command == erase && sp-opt == 1) {
|
|
getchaccess((char*)0, command);
|
|
return 0;
|
|
}
|
|
error("missing login name after option -a or -e");
|
|
return ERR_APPL_ARGS;
|
|
}
|
|
|
|
while( c != '\0') {
|
|
getchaccess(sp, command);
|
|
if (!(sp = checkid(sp,','))) return ERR_APPL_ARGS;
|
|
c = *sp; *sp = '\0';
|
|
while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
getmessage(revno, option)
|
|
const char *revno;
|
|
char *option;
|
|
{
|
|
struct Message *pt;
|
|
struct cbuf cb;
|
|
cb = cleanlogmsg(option, strlen(option));
|
|
if (!cb.size) return ERR_BAD_VALUE;
|
|
pt = ftalloc(struct Message);
|
|
pt->revno = revno;
|
|
pt->message = cb;
|
|
*nextmessage = pt;
|
|
nextmessage = &pt->nextmessage;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
getstates(revno, sp)
|
|
const char *revno;
|
|
char *sp;
|
|
{
|
|
char const *temp;
|
|
struct Status *pt;
|
|
register c;
|
|
|
|
/* Skip lead whitespace */
|
|
for (c = *sp; c == ' ' || c == '\t' || c == '\n'; c = *++sp);
|
|
temp = sp;
|
|
|
|
if (!(sp = checkid(sp,':')) || *sp == ':')
|
|
/* check for invalid state attribute */
|
|
return ERR_BAD_VALUE;
|
|
|
|
/* Trim trailing whitespace */
|
|
c = *sp; *sp = '\0';
|
|
while (c == ' ' || c == '\t' || c == '\n' ) c == *++sp;
|
|
if (c) return ERR_BAD_VALUE;
|
|
|
|
/* temp points to a trimmed state value (no whitespace) */
|
|
|
|
if (!revno || !*revno) {
|
|
chgheadstate = true;
|
|
headstate = temp;
|
|
return 0;
|
|
}
|
|
|
|
/* Skip lead whitespace (note that trailing whitespace is left
|
|
* in revno; this reproduces original logic
|
|
*/
|
|
for (c = *revno; c == ' ' || c == '\t' || c == '\n'; c = *++revno);
|
|
pt = ftalloc(struct Status);
|
|
pt->status = temp;
|
|
pt->revno = revno;
|
|
pt->nextstatus = 0;
|
|
*nextstate = pt;
|
|
nextstate = &pt->nextstatus;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
getdelrev(sp)
|
|
char *sp;
|
|
/* Function: get revision range or branch to be deleted, */
|
|
/* and place in delrev */
|
|
{
|
|
int c;
|
|
struct delrevpair *pt;
|
|
int separator;
|
|
|
|
pt = &delrev;
|
|
|
|
while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
|
|
continue;
|
|
|
|
/* Support old ambiguous '-' syntax; this will go away. */
|
|
if (strchr(sp,':'))
|
|
separator = ':';
|
|
else {
|
|
if (strchr(sp,'-') && VERSION(5) <= RCSversion)
|
|
warn("`-' is obsolete in `-o%s'; use `:' instead", sp);
|
|
separator = '-';
|
|
}
|
|
|
|
if (c == separator) { /* -o:rev */
|
|
while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
|
|
continue;
|
|
pt->strt = sp; pt->code = 1;
|
|
while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
|
|
*sp = '\0';
|
|
pt->end = 0;
|
|
return;
|
|
}
|
|
else {
|
|
pt->strt = sp;
|
|
while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
|
|
&& c != separator ) c = *++sp;
|
|
*sp = '\0';
|
|
while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp;
|
|
if ( c == '\0' ) { /* -o rev or branch */
|
|
pt->code = 0;
|
|
pt->end = 0;
|
|
return;
|
|
}
|
|
if (c != separator) {
|
|
error("invalid range %s %s after -o", pt->strt, sp);
|
|
}
|
|
while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
|
|
continue;
|
|
if (!c) { /* -orev: */
|
|
pt->code = 2;
|
|
pt->end = 0;
|
|
return;
|
|
}
|
|
}
|
|
/* -orev1:rev2 */
|
|
pt->end = sp; pt->code = 3;
|
|
while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
|
|
*sp = '\0';
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
scanlogtext(delta,edit)
|
|
struct hshentry *delta;
|
|
int edit;
|
|
/* Function: Scans delta text nodes up to and including the one given
|
|
* by delta, or up to last one present, if !delta.
|
|
* For the one given by delta (if delta), the log message is saved into
|
|
* delta->log if delta==cuttail; the text is edited if EDIT is set, else copied.
|
|
* Assumes the initial lexeme must be read in first.
|
|
* Does not advance nexttok after it is finished, except if !delta.
|
|
*/
|
|
{
|
|
struct hshentry const *nextdelta;
|
|
struct cbuf cb;
|
|
|
|
for (;;) {
|
|
foutptr = 0;
|
|
if (eoflex()) {
|
|
if(delta)
|
|
rcsfaterror("can't find delta for revision %s",
|
|
delta->num
|
|
);
|
|
return; /* no more delta text nodes */
|
|
}
|
|
nextlex();
|
|
if (!(nextdelta=getnum()))
|
|
fatserror("delta number corrupted");
|
|
if (nextdelta->selector) {
|
|
foutptr = frewrite;
|
|
aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
|
|
}
|
|
getkeystring(Klog);
|
|
if (nextdelta == cuttail) {
|
|
cb = savestring(&curlogbuf);
|
|
if (!delta->log.string)
|
|
delta->log = cleanlogmsg(curlogbuf.string, cb.size);
|
|
nextlex();
|
|
delta->igtext = getphrases(Ktext);
|
|
} else {
|
|
if (nextdelta->log.string && nextdelta->selector) {
|
|
foutptr = 0;
|
|
readstring();
|
|
foutptr = frewrite;
|
|
putstring(foutptr, false, nextdelta->log, true);
|
|
afputc(nextc, foutptr);
|
|
} else
|
|
readstring();
|
|
ignorephrases(Ktext);
|
|
}
|
|
getkeystring(Ktext);
|
|
|
|
if (delta==nextdelta)
|
|
break;
|
|
readstring(); /* skip over it */
|
|
|
|
}
|
|
/* got the one we're looking for */
|
|
if (edit)
|
|
editstring((struct hshentry*)0);
|
|
else
|
|
enterstring();
|
|
}
|
|
|
|
|
|
static struct Lockrev **
|
|
rmnewlocklst(which)
|
|
char const *which;
|
|
/* Remove lock to revision WHICH from newlocklst. */
|
|
{
|
|
struct Lockrev *pt, **pre;
|
|
|
|
pre = &newlocklst;
|
|
while ((pt = *pre))
|
|
if (strcmp(pt->revno, which) != 0)
|
|
pre = &pt->nextrev;
|
|
else {
|
|
*pre = pt->nextrev;
|
|
/* tfree(pt); */
|
|
}
|
|
return pre;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
doaccess()
|
|
{
|
|
register struct chaccess *ch;
|
|
register struct access **p, *t;
|
|
register int changed = false;
|
|
|
|
for (ch = chaccess; ch; ch = ch->nextchaccess) {
|
|
switch (ch->command) {
|
|
case erase:
|
|
if (!ch->login) {
|
|
if (AccessList) {
|
|
AccessList = 0;
|
|
changed = true;
|
|
}
|
|
} else
|
|
for (p = &AccessList; (t = *p); p = &t->nextaccess)
|
|
if (strcmp(ch->login, t->login) == 0) {
|
|
*p = t->nextaccess;
|
|
changed = true;
|
|
break;
|
|
}
|
|
break;
|
|
case append:
|
|
for (p = &AccessList; ; p = &t->nextaccess)
|
|
if (!(t = *p)) {
|
|
*p = t = ftalloc(struct access);
|
|
t->login = ch->login;
|
|
t->nextaccess = 0;
|
|
changed = true;
|
|
break;
|
|
} else if (strcmp(ch->login, t->login) == 0)
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
|
|
static struct hshentry *
|
|
searchcutpt(object, length, store)
|
|
char const *object;
|
|
int length;
|
|
struct hshentries *store;
|
|
/* Function: Search store and return entry with number being object. */
|
|
/* cuttail = 0, if the entry is Head; otherwise, cuttail */
|
|
/* is the entry point to the one with number being object */
|
|
|
|
{
|
|
cuthead = 0;
|
|
while (compartial(store->first->num, object, length)) {
|
|
cuthead = store->first;
|
|
store = store->rest;
|
|
}
|
|
return store->first;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
branchpoint(strt, tail)
|
|
struct hshentry *strt, *tail;
|
|
/* Function: check whether the deltas between strt and tail */
|
|
/* are locked or branch point, return 1 if any is */
|
|
/* locked or branch point; otherwise, return 0 and */
|
|
/* mark deleted */
|
|
|
|
{
|
|
struct hshentry *pt;
|
|
struct rcslock const *lockpt;
|
|
|
|
for (pt = strt; pt != tail; pt = pt->next) {
|
|
if ( pt->branches ){ /* a branch point */
|
|
rcserror("can't remove branch point %s", pt->num);
|
|
return true;
|
|
}
|
|
for (lockpt = Locks; lockpt; lockpt = lockpt->nextlock)
|
|
if (lockpt->delta == pt) {
|
|
rcserror("can't remove locked revision %s", pt->num);
|
|
return true;
|
|
}
|
|
pt->selector = false;
|
|
diagnose("deleting revision %s\n",pt->num);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
removerevs()
|
|
/* Function: get the revision range to be removed, and place the */
|
|
/* first revision removed in delstrt, the revision before */
|
|
/* delstrt in cuthead (0, if delstrt is head), and the */
|
|
/* revision after the last removed revision in cuttail (0 */
|
|
/* if the last is a leaf */
|
|
|
|
{
|
|
struct hshentry *target, *target2, *temp;
|
|
int length;
|
|
int cmp;
|
|
|
|
if (!expandsym(delrev.strt, &numrev)) return 0;
|
|
target = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
|
|
if ( ! target ) return 0;
|
|
cmp = cmpnum(target->num, numrev.string);
|
|
length = countnumflds(numrev.string);
|
|
|
|
if (delrev.code == 0) { /* -o rev or -o branch */
|
|
if (length & 1)
|
|
temp=searchcutpt(target->num,length+1,gendeltas);
|
|
else if (cmp) {
|
|
rcserror("Revision %s doesn't exist.", numrev.string);
|
|
return 0;
|
|
}
|
|
else
|
|
temp = searchcutpt(numrev.string, length, gendeltas);
|
|
cuttail = target->next;
|
|
if ( branchpoint(temp, cuttail) ) {
|
|
cuttail = 0;
|
|
return 0;
|
|
}
|
|
delstrt = temp; /* first revision to be removed */
|
|
return 1;
|
|
}
|
|
|
|
if (length & 1) { /* invalid branch after -o */
|
|
rcserror("invalid branch range %s after -o", numrev.string);
|
|
return 0;
|
|
}
|
|
|
|
if (delrev.code == 1) { /* -o -rev */
|
|
if ( length > 2 ) {
|
|
temp = searchcutpt( target->num, length-1, gendeltas);
|
|
cuttail = target->next;
|
|
}
|
|
else {
|
|
temp = searchcutpt(target->num, length, gendeltas);
|
|
cuttail = target;
|
|
while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
|
|
cuttail = cuttail->next;
|
|
}
|
|
if ( branchpoint(temp, cuttail) ){
|
|
cuttail = 0;
|
|
return 0;
|
|
}
|
|
delstrt = temp;
|
|
return 1;
|
|
}
|
|
|
|
if (delrev.code == 2) { /* -o rev- */
|
|
if ( length == 2 ) {
|
|
temp = searchcutpt(target->num, 1,gendeltas);
|
|
if (cmp)
|
|
cuttail = target;
|
|
else
|
|
cuttail = target->next;
|
|
}
|
|
else {
|
|
if (cmp) {
|
|
cuthead = target;
|
|
if ( !(temp = target->next) ) return 0;
|
|
}
|
|
else
|
|
temp = searchcutpt(target->num, length, gendeltas);
|
|
getbranchno(temp->num, &numrev); /* get branch number */
|
|
VOID genrevs(numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas);
|
|
}
|
|
if ( branchpoint( temp, cuttail ) ) {
|
|
cuttail = 0;
|
|
return 0;
|
|
}
|
|
delstrt = temp;
|
|
return 1;
|
|
}
|
|
|
|
/* -o rev1-rev2 */
|
|
if (!expandsym(delrev.end, &numrev)) return 0;
|
|
if (
|
|
length != countnumflds(numrev.string)
|
|
|| (length>2 && compartial(numrev.string, target->num, length-1))
|
|
) {
|
|
rcserror("invalid revision range %s-%s",
|
|
target->num, numrev.string
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
target2 = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
|
|
if ( ! target2 ) return 0;
|
|
|
|
if ( length > 2) { /* delete revisions on branches */
|
|
if ( cmpnum(target->num, target2->num) > 0) {
|
|
cmp = cmpnum(target2->num, numrev.string);
|
|
temp = target;
|
|
target = target2;
|
|
target2 = temp;
|
|
}
|
|
if (cmp) {
|
|
if ( ! cmpnum(target->num, target2->num) ) {
|
|
rcserror("Revisions %s-%s don't exist.",
|
|
delrev.strt, delrev.end
|
|
);
|
|
return 0;
|
|
}
|
|
cuthead = target;
|
|
temp = target->next;
|
|
}
|
|
else
|
|
temp = searchcutpt(target->num, length, gendeltas);
|
|
cuttail = target2->next;
|
|
}
|
|
else { /* delete revisions on trunk */
|
|
if ( cmpnum( target->num, target2->num) < 0 ) {
|
|
temp = target;
|
|
target = target2;
|
|
target2 = temp;
|
|
}
|
|
else
|
|
cmp = cmpnum(target2->num, numrev.string);
|
|
if (cmp) {
|
|
if ( ! cmpnum(target->num, target2->num) ) {
|
|
rcserror("Revisions %s-%s don't exist.",
|
|
delrev.strt, delrev.end
|
|
);
|
|
return 0;
|
|
}
|
|
cuttail = target2;
|
|
}
|
|
else
|
|
cuttail = target2->next;
|
|
temp = searchcutpt(target->num, length, gendeltas);
|
|
}
|
|
if ( branchpoint(temp, cuttail) ) {
|
|
cuttail = 0;
|
|
return 0;
|
|
}
|
|
delstrt = temp;
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
doassoc()
|
|
/* Add or delete (if !revno) association that is stored in assoclst. */
|
|
{
|
|
char const *p;
|
|
int changed = false;
|
|
struct Symrev const *curassoc;
|
|
struct assoc **pre, *pt;
|
|
|
|
/* add new associations */
|
|
for (curassoc = assoclst; curassoc; curassoc = curassoc->nextsym) {
|
|
char const *ssymbol = curassoc->ssymbol;
|
|
|
|
if (!curassoc->revno) { /* delete symbol */
|
|
for (pre = &Symbols; ; pre = &pt->nextassoc)
|
|
if (!(pt = *pre)) {
|
|
rcswarn("can't delete nonexisting symbol %s", ssymbol);
|
|
break;
|
|
} else if (strcmp(pt->symbol, ssymbol) == 0) {
|
|
*pre = pt->nextassoc;
|
|
changed = true;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if (curassoc->revno[0]) {
|
|
p = 0;
|
|
if (expandsym(curassoc->revno, &numrev))
|
|
p = fstr_save(numrev.string);
|
|
} else if (!(p = tiprev()))
|
|
rcserror("no latest revision to associate with symbol %s",
|
|
ssymbol
|
|
);
|
|
if (p)
|
|
changed |= addsymbol(p, ssymbol, curassoc->override);
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
dolocks()
|
|
/* Function: remove lock for caller or first lock if unlockcaller is set;
|
|
* remove locks which are stored in rmvlocklst,
|
|
* add new locks which are stored in newlocklst,
|
|
* add lock for Dbranch or Head if lockhead is set.
|
|
*/
|
|
{
|
|
struct Lockrev const *lockpt;
|
|
struct hshentry *target;
|
|
int changed = false;
|
|
|
|
if (unlockcaller) { /* find lock for caller */
|
|
if ( Head ) {
|
|
if (Locks) {
|
|
switch (findlock(true, &target)) {
|
|
case 0:
|
|
/* remove most recent lock (it isn't owned by caller)*/
|
|
changed |=
|
|
(checklock(Locks->delta->num,true,false,0) > 0);
|
|
break;
|
|
case 1:
|
|
diagnose("%s unlocked\n",target->num);
|
|
changed = true;
|
|
break;
|
|
}
|
|
} else {
|
|
rcswarn("No locks are set.");
|
|
}
|
|
} else {
|
|
rcswarn("can't unlock an empty tree");
|
|
}
|
|
}
|
|
|
|
/* remove locks which are stored in rmvlocklst */
|
|
for (lockpt = rmvlocklst; lockpt; lockpt = lockpt->nextrev)
|
|
if (expandsym(lockpt->revno, &numrev)) {
|
|
target = genrevs(numrev.string, (char *)0, (char *)0, (char *)0, &gendeltas);
|
|
if ( target )
|
|
if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
|
|
rcserror("can't unlock nonexisting revision %s",
|
|
lockpt->revno
|
|
);
|
|
else
|
|
changed |= (checklock(target->num,true,false,0) > 0);
|
|
/* checklock does its own diagnose */
|
|
}
|
|
|
|
/* add new locks which stored in newlocklst */
|
|
for (lockpt = newlocklst; lockpt; lockpt = lockpt->nextrev)
|
|
changed |= setlock(lockpt->revno);
|
|
|
|
if (lockhead) /* lock default branch or head */
|
|
if (Dbranch)
|
|
changed |= setlock(Dbranch);
|
|
else if (Head)
|
|
changed |= setlock(Head->num);
|
|
else
|
|
rcswarn("can't lock an empty tree");
|
|
return changed;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
setlock(rev)
|
|
char const *rev;
|
|
/* Function: Given a revision or branch number, finds the corresponding
|
|
* delta and locks it for caller.
|
|
*/
|
|
{
|
|
struct hshentry *target;
|
|
int r;
|
|
|
|
if (expandsym(rev, &numrev)) {
|
|
target = genrevs(numrev.string, (char*)0, (char*)0,
|
|
(char*)0, &gendeltas);
|
|
if ( target )
|
|
if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
|
|
rcserror("can't lock nonexisting revision %s",
|
|
numrev.string
|
|
);
|
|
else {
|
|
if ((r = addlock(target, false)) < 0 &&
|
|
checklock(target->num,true,false,0) > 0)
|
|
r = addlock(target, true);
|
|
if (0 <= r) {
|
|
if (r)
|
|
diagnose("%s locked\n", target->num);
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
domessages()
|
|
{
|
|
struct hshentry *target;
|
|
struct Message *p;
|
|
int changed = false;
|
|
|
|
for (p = messagelst; p; p = p->nextmessage)
|
|
if (
|
|
expandsym(p->revno, &numrev) &&
|
|
(target = genrevs(
|
|
numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas
|
|
))
|
|
) {
|
|
/*
|
|
* We can't check the old log -- it's much later in the file.
|
|
* We pessimistically assume that it changed.
|
|
*/
|
|
target->log = p->message;
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
|
|
static int
|
|
rcs_setstate(rev,status)
|
|
char const *rev, *status;
|
|
/* Function: Given a revision or branch number, finds the corresponding delta
|
|
* and sets its state to status.
|
|
*/
|
|
{
|
|
struct hshentry *target;
|
|
|
|
if (expandsym(rev, &numrev)) {
|
|
target = genrevs(numrev.string, (char*)0, (char*)0,
|
|
(char*)0, &gendeltas);
|
|
if ( target )
|
|
if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
|
|
rcserror("can't set state of nonexisting revision %s",
|
|
numrev.string
|
|
);
|
|
else if (strcmp(target->state, status) != 0) {
|
|
target->state = status;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
buildeltatext(deltas)
|
|
struct hshentries const *deltas;
|
|
/* Function: put the delta text on frewrite and make necessary */
|
|
/* change to delta text */
|
|
{
|
|
register FILE *fcut; /* temporary file to rebuild delta tree */
|
|
char const *cutname;
|
|
|
|
fcut = 0;
|
|
cuttail->selector = false;
|
|
scanlogtext(deltas->first, false);
|
|
if ( cuthead ) {
|
|
cutname = maketemp(3);
|
|
if (!(fcut = fopenSafer(cutname, FOPEN_WPLUS_WORK))) {
|
|
efaterror(cutname);
|
|
}
|
|
|
|
while (deltas->first != cuthead) {
|
|
deltas = deltas->rest;
|
|
scanlogtext(deltas->first, true);
|
|
}
|
|
|
|
snapshotedit(fcut);
|
|
Orewind(fcut);
|
|
aflush(fcut);
|
|
}
|
|
|
|
while (deltas->first != cuttail)
|
|
scanlogtext((deltas = deltas->rest)->first, true);
|
|
finishedit((struct hshentry*)0, (FILE*)0, true);
|
|
/* Ozclose(&fcopy); - closed in CloseRcsDataFile */
|
|
|
|
if (fcut) {
|
|
char const *diffname = maketemp(0);
|
|
char const *diffv[6 + !!OPEN_O_BINARY];
|
|
char const **diffp = diffv;
|
|
*++diffp = DIFF;
|
|
*++diffp = DIFFFLAGS;
|
|
# if OPEN_O_BINARY
|
|
if (Expand == BINARY_EXPAND)
|
|
*++diffp == "--binary";
|
|
# endif
|
|
*++diffp = "-";
|
|
*++diffp = resultname;
|
|
*++diffp = 0;
|
|
switch (runv(fileno(fcut), diffname, diffv)) {
|
|
case DIFF_FAILURE: case DIFF_SUCCESS: break;
|
|
default: rcsfaterror("diff failed");
|
|
}
|
|
Ofclose(fcut);
|
|
return putdtext(cuttail,diffname,frewrite,true);
|
|
} else
|
|
return putdtext(cuttail,resultname,frewrite,false);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
buildtree()
|
|
/* Function: actually removes revisions whose selector field */
|
|
/* is false, and rebuilds the linkage of deltas. */
|
|
/* asks for reconfirmation if deleting last revision*/
|
|
{
|
|
struct hshentry * Delta;
|
|
struct branchhead *pt, *pre;
|
|
|
|
if ( cuthead )
|
|
if ( cuthead->next == delstrt )
|
|
cuthead->next = cuttail;
|
|
else {
|
|
pre = pt = cuthead->branches;
|
|
while( pt && pt->hsh != delstrt ) {
|
|
pre = pt;
|
|
pt = pt->nextbranch;
|
|
}
|
|
if ( cuttail )
|
|
pt->hsh = cuttail;
|
|
else if ( pt == pre )
|
|
cuthead->branches = pt->nextbranch;
|
|
else
|
|
pre->nextbranch = pt->nextbranch;
|
|
}
|
|
else {
|
|
#ifdef REMOVE
|
|
if (!cuttail && !quietflag) {
|
|
if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) {
|
|
rcserror("No revision deleted");
|
|
Delta = delstrt;
|
|
while( Delta) {
|
|
Delta->selector = true;
|
|
Delta = Delta->next;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
#endif /* REMOVE */
|
|
Head = cuttail;
|
|
}
|
|
return;
|
|
}
|
|
|
|
#if RCS_lint
|
|
/* This lets us lint everything all at once. */
|
|
|
|
char const *cmdid = "";
|
|
|
|
#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();}
|
|
|
|
int
|
|
main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
go(ciId, ciExit);
|
|
go(coId, coExit);
|
|
go(identId, identExit);
|
|
go(mergeId, mergeExit);
|
|
go(rcsId, exiterr);
|
|
go(rcscleanId, rcscleanExit);
|
|
go(rcsdiffId, rdiffExit);
|
|
go(rcsmergeId, rmergeExit);
|
|
go(rlogId, rlogExit);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MAIN
|
|
|
|
void interrupt(context)
|
|
void *context;
|
|
{
|
|
printf("got an interrupt\n");
|
|
}
|
|
|
|
/* test program for pairnames() and getfullRCSname() */
|
|
|
|
main(argc, argv)
|
|
int argc; char *argv[];
|
|
{
|
|
int result;
|
|
struct RcsDataFile *rcsfileptr;
|
|
struct RcsSessionState *state;
|
|
struct RcsSession *sess;
|
|
struct RcsHeaderOpts *hdropts;
|
|
|
|
/* Create objects to hold arguments */
|
|
state = CreateRcsSessionState();
|
|
state->cmdid = "rcs"; /* name of command (for
|
|
* use in error messages)
|
|
*/
|
|
hdropts = CreateRcsHeaderOpts();
|
|
|
|
while(--argc, ++argv, argc>=1 && ((*argv)[0] == '-')) {
|
|
switch ((*argv)[1]) {
|
|
|
|
case 'q': state->quietflag=true;
|
|
break;
|
|
case 'I': state->interactive=true;
|
|
break;
|
|
case 'w': if (!argv[0][2]) error("-w missing author");
|
|
else state->auth = argv[0]+2;
|
|
break;
|
|
case 'x': state->suffixes = argv[0]+2;
|
|
break;
|
|
case 'i': hdropts->initflag=true;
|
|
break;
|
|
case 'L': hdropts->strictflag=true;
|
|
break;
|
|
case 'U': hdropts->nostrictflag=true;
|
|
break;
|
|
case 'M': hdropts->mailflag=true;
|
|
break;
|
|
case 'T': hdropts->Timeflag=true;
|
|
break;
|
|
case 'b': hdropts->branchflag = argv[0]+2;
|
|
break;
|
|
case 'c': hdropts->commentflag = argv[0]+2;
|
|
break;
|
|
case 'a': hdropts->add_accessflag = argv[0]+2;
|
|
break;
|
|
case 'A': hdropts->append_accessflag = argv[0]+2;
|
|
break;
|
|
case 'e': hdropts->erase_accessflag = argv[0]+2;
|
|
break;
|
|
case 'l': hdropts->lockflag = argv[0]+2;
|
|
break;
|
|
case 'u': hdropts->unlockflag = argv[0]+2;
|
|
break;
|
|
case 'n': hdropts->nameflag = argv[0]+2;
|
|
break;
|
|
case 'N': hdropts->Nameflag = argv[0]+2;
|
|
break;
|
|
case 'm': hdropts->messageflag = argv[0]+2;
|
|
break;
|
|
case 'o': hdropts->delrevflag = argv[0]+2;
|
|
break;
|
|
case 's': hdropts->stateflag = argv[0]+2;
|
|
break;
|
|
case 'k': hdropts->keyexpandflag = argv[0]+2;
|
|
break;
|
|
default: error("unknown option: %s", *argv);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!(sess = CreateRcsSession(state, "bonnie", "rcssrv", NULL,
|
|
NULL, NULL, interrupt, NULL))) {
|
|
printf("Cannot create session\n");
|
|
exit(1);
|
|
}
|
|
|
|
if ((sess->openSession)(sess)) {
|
|
printf("Cannot open session\n");
|
|
exit(1);
|
|
}
|
|
|
|
for(;--argc>=0;++argv) {
|
|
char *base;
|
|
char *path;
|
|
if ((base = strrchr(argv[0], '/'))) {
|
|
*base++ = '\0';
|
|
path = argv[0];
|
|
}
|
|
else {
|
|
base = argv[0];
|
|
path = 0;
|
|
}
|
|
|
|
/* Zero means error; -1 is returned if file doesn't exist,
|
|
* but we are initializing it (which is okay)
|
|
*/
|
|
if ((result = OpenRcsDataFile(sess,&rcsfileptr,base,path,true,
|
|
hdropts->initflag ? false : true)) == 0) {
|
|
printf("Unable to locate/open RCS file\n");
|
|
exit(2);
|
|
}
|
|
if (result > 0 && hdropts->initflag) {
|
|
printf("RCS file for %s already exists\n", base);
|
|
}
|
|
else header(sess, rcsfileptr, hdropts);
|
|
|
|
CloseRcsDataFile(sess, rcsfileptr);
|
|
|
|
}
|
|
/* Terminate Session */
|
|
(sess->destroy)(sess);
|
|
}
|
|
#endif /* MAIN */
|