NetBSD/usr.bin/rcs/src/rcs.c

1563 lines
51 KiB
C
Raw Normal View History

1993-03-21 12:45:37 +03:00
/*
* RCS create/change operation
*/
#ifndef lint
static char rcsid[]=
"$Header: /cvsroot/src/usr.bin/rcs/src/Attic/rcs.c,v 1.1.1.1 1993/03/21 09:45:37 cgd Exp $ Purdue CS";
#endif
/* Copyright (C) 1982, 1988, 1989 Walter Tichy
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by Walter Tichy.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* Report all problems and direct all questions to:
* rcs-bugs@cs.purdue.edu
*
*/
/* $Log: rcs.c,v $
/* Revision 1.1.1.1 1993/03/21 09:45:37 cgd
/* initial import of 386bsd-0.1 sources
/*
* Revision 4.11 89/05/01 15:12:06 narten
* changed copyright header to reflect current distribution rules
*
* Revision 4.10 88/11/08 16:01:54 narten
* didn't install previous patch correctly
*
* Revision 4.9 88/11/08 13:56:01 narten
* removed include <sysexits.h> (not needed)
* minor fix for -A option
*
* Revision 4.8 88/11/08 12:01:58 narten
* changes from eggert@sm.unisys.com (Paul Eggert)
*
* Revision 4.8 88/08/09 19:12:27 eggert
* Don't access freed storage.
* Use execv(), not system(); yield proper exit status; remove lint.
*
* Revision 4.7 87/12/18 11:37:17 narten
* lint cleanups (Guy Harris)
*
* Revision 4.6 87/10/18 10:28:48 narten
* Updating verison numbers. Changes relative to 1.1 are actually
* relative to 4.3
*
* Revision 1.4 87/09/24 13:58:52 narten
* Sources now pass through lint (if you ignore printf/sprintf/fprintf
* warnings)
*
* Revision 1.3 87/03/27 14:21:55 jenkins
* Port to suns
*
* Revision 1.2 85/12/17 13:59:09 albitz
* Changed setstate to rcs_setstate because of conflict with random.o.
*
* Revision 1.1 84/01/23 14:50:09 kcs
* Initial revision
*
* Revision 4.3 83/12/15 12:27:33 wft
* rcs -u now breaks most recent lock if it can't find a lock by the caller.
*
* Revision 4.2 83/12/05 10:18:20 wft
* Added conditional compilation for sending mail.
* Alternatives: V4_2BSD, V6, USG, and other.
*
* Revision 4.1 83/05/10 16:43:02 wft
* Simplified breaklock(); added calls to findlock() and getcaller().
* Added option -b (default branch). Updated -s and -w for -b.
* Removed calls to stat(); now done by pairfilenames().
* Replaced most catchints() calls with restoreints().
* Removed check for exit status of delivermail().
* Directed all interactive output to stderr.
*
* Revision 3.9.1.1 83/12/02 22:08:51 wft
* Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
*
* Revision 3.9 83/02/15 15:38:39 wft
* Added call to fastcopy() to copy remainder of RCS file.
*
* Revision 3.8 83/01/18 17:37:51 wft
* Changed sendmail(): now uses delivermail, and asks whether to break the lock.
*
* Revision 3.7 83/01/15 18:04:25 wft
* Removed putree(); replaced with puttree() in rcssyn.c.
* Combined putdellog() and scanlogtext(); deleted putdellog().
* Cleaned up diagnostics and error messages. Fixed problem with
* mutilated files in case of deletions in 2 files in a single command.
* Changed marking of selector from 'D' to DELETE.
*
* Revision 3.6 83/01/14 15:37:31 wft
* Added ignoring of interrupts while new RCS file is renamed;
* Avoids deletion of RCS files by interrupts.
*
* Revision 3.5 82/12/10 21:11:39 wft
* Removed unused variables, fixed checking of return code from diff,
* introduced variant COMPAT2 for skipping Suffix on -A files.
*
* Revision 3.4 82/12/04 13:18:20 wft
* Replaced getdelta() with gettree(), changed breaklock to update
* field lockedby, added some diagnostics.
*
* Revision 3.3 82/12/03 17:08:04 wft
* Replaced getlogin() with getpwuid(), flcose() with ffclose(),
* /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
* fixed -u for missing revno. Disambiguated structure members.
*
* Revision 3.2 82/10/18 21:05:07 wft
* rcs -i now generates a file mode given by the umask minus write permission;
* otherwise, rcs keeps the mode, but removes write permission.
* I added a check for write error, fixed call to getlogin(), replaced
* curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
* conflicting, long identifiers.
*
* Revision 3.1 82/10/13 16:11:07 wft
* fixed type of variables receiving from getc() (char -> int).
*/
#include <sys/types.h>
#include <sys/stat.h>
#include "rcsbase.h"
#include <paths.h>
#ifndef lint
static char rcsbaseid[] = RCSBASE;
#endif
extern FILE * fopen();
extern char * bindex();
extern int expandsym(); /* get numeric revision name */
extern struct hshentry * getnum();
extern struct lock * addlock(); /* add a lock */
extern char * getid();
extern char Klog[], Khead[], Kaccess[], Kbranch[], Ktext[];
#ifdef COMPAT2
extern char Ksuffix[];
#endif
extern char * getcaller(); /* get login of caller */
extern struct hshentry * findlock(); /* find and remove lock */
extern struct hshentry * genrevs();
extern char * checkid(); /* check an identifier */
extern char * getfullRCSname(); /* get full path name of RCS file */
extern char * mktempfile(); /* temporary file name generator */
extern free();
extern void catchints();
extern void ignoreints();
extern int nerror; /* counter for errors */
extern int quietflag; /* diagnoses suppressed if true */
extern char curlogmsg[]; /* current log message */
extern char * resultfile; /* filename for fcopy */
extern FILE *fcopy; /* result file during editing */
extern FILE * finptr; /* RCS input file */
extern FILE * frewrite; /* new RCS file */
extern int rewriteflag; /* indicates whether input should be*/
/* echoed to frewrite */
char * newRCSfilename, * diffilename, * cutfilename;
char * RCSfilename, * workfilename;
extern struct stat RCSstat, workstat; /* file status of RCS and work file */
extern int haveRCSstat, haveworkstat;/* status indicators */
char accessorlst[strtsize];
FILE * fcut; /* temporary file to rebuild delta tree */
int oldumask; /* save umask */
int initflag, strictlock, strict_selected, textflag;
char * textfile, * accessfile;
char * caller, numrev[30]; /* caller's login; */
struct access * newaccessor, * rmvaccessor, * rplaccessor;
struct access *curaccess, *rmaccess;
struct hshentry * gendeltas[hshsize];
struct Lockrev {
char * revno;
struct Lockrev * nextrev;
};
struct Symrev {
char * revno;
char * ssymbol;
int override;
struct Symrev * nextsym;
};
struct Status {
char * revno;
char * status;
struct Status * nextstatus;
};
struct delrevpair {
char * strt;
char * end;
int code;
};
struct Lockrev * newlocklst, * rmvlocklst;
struct Symrev * assoclst, * lastassoc;
struct Status * statelst, * laststate;
struct delrevpair * delrev;
struct hshentry * cuthead, *cuttail, * delstrt;
char branchnum[revlength], * branchsym;
struct hshentry branchdummy;
char * commsyml;
char * headstate;
int lockhead,unlockcaller,chgheadstate,branchflag,commentflag;
int delaccessflag;
enum stringwork {copy, edit, empty}; /* expand and edit_expand not needed */
main (argc, argv)
int argc;
char * argv[];
{
char *comdusge;
int result;
struct access *removeaccess(), * getaccessor();
struct Lockrev *rmnewlocklst();
struct Lockrev *curlock, * rmvlock, *lockpt;
struct Status * curstate;
struct access *temp, *temptr;
int status;
status = 0;
nerror = 0;
catchints();
cmdid = "rcs";
quietflag = false;
comdusge ="command format:\nrcs -i -alogins -Alogins -e[logins] -b[rev] -c[commentleader] -l[rev] -u[rev] -L -U -nname[:rev] -Nname[:rev] -orange -sstate[:rev] -t[textfile] file....";
rplaccessor = nil; delstrt = nil;
accessfile = textfile = caller = nil;
branchflag = commentflag = chgheadstate = false;
lockhead = false; unlockcaller=false;
initflag= textflag = false;
strict_selected = 0;
caller=getcaller();
laststate = statelst = nil;
lastassoc = assoclst = nil;
curlock = rmvlock = newlocklst = rmvlocklst = nil;
curaccess = rmaccess = rmvaccessor = newaccessor = nil;
delaccessflag = false;
/* preprocessing command options */
while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
switch ((*argv)[1]) {
case 'i': /* initail version */
initflag = true;
break;
case 'b': /* change default branch */
if (branchflag)warn("Redfinition of option -b");
branchflag= true;
branchsym = (*argv)+2;
break;
case 'c': /* change comment symbol */
if (commentflag)warn("Redefinition of option -c");
commentflag = true;
commsyml = (*argv)+2;
break;
case 'a': /* add new accessor */
if ( (*argv)[2] == '\0') {
error("Login name missing after -a");
}
if ( (temp = getaccessor((*argv)+1)) ) {
if ( newaccessor )
curaccess->nextaccess = temp->nextaccess;
else
newaccessor = temp->nextaccess;
temp->nextaccess = nil;
curaccess = temp;
}
break;
case 'A': /* append access list according to accessfile */
if ( (*argv)[2] == '\0') {
error("Missing file name after -A");
break;
}
if ( accessfile) warn("Redefinition of option -A");
*argv = *argv+2;
if( pairfilenames(1, argv, true, false) > 0) {
releaselst(newaccessor);
newaccessor = curaccess = nil;
releaselst(rmvaccessor);
rmvaccessor = rmaccess = nil;
accessfile = RCSfilename;
}
else
accessfile = nil;
break;
case 'e': /* remove accessors */
if ( (*argv)[2] == '\0' ) {
delaccessflag = true;
break;
}
if ( (temp = getaccessor((*argv)+1)) ) {
if ( rmvaccessor )
rmaccess->nextaccess = temp->nextaccess;
else
rmvaccessor = temp->nextaccess;
temptr = temp->nextaccess;
temp->nextaccess = nil;
rmaccess = temp;
while( temptr ) {
newaccessor = removeaccess(temptr,newaccessor,false);
temptr = temptr->nextaccess;
}
curaccess = temp = newaccessor;
while( temp){
curaccess = temp;
temp = temp->nextaccess;
}
}
break;
case 'l': /* lock a revision if it is unlocked */
if ( (*argv)[2] == '\0'){ /* lock head or def. branch */
lockhead = true;
break;
}
lockpt = (struct Lockrev *)talloc(sizeof(struct Lockrev));
lockpt->revno = (*argv)+2;
lockpt->nextrev = nil;
if ( curlock )
curlock->nextrev = lockpt;
else
newlocklst = lockpt;
curlock = lockpt;
break;
case 'u': /* release lock of a locked revision */
if ( (*argv)[2] == '\0'){ /* unlock head */
unlockcaller=true;
break;
}
lockpt = (struct Lockrev *)talloc(sizeof(struct Lockrev));
lockpt->revno = (*argv)+2;
lockpt->nextrev = nil;
if (rmvlock)
rmvlock->nextrev = lockpt;
else
rmvlocklst = lockpt;
rmvlock = lockpt;
curlock = rmnewlocklst(lockpt);
break;
case 'L': /* set strict locking */
if (strict_selected++) { /* Already selected L or U? */
if (!strictlock) /* Already selected -U? */
warn("Option -L overrides -U");
}
strictlock = true;
break;
case 'U': /* release strict locking */
if (strict_selected++) { /* Already selected L or U? */
if (strictlock) /* Already selected -L? */
warn("Option -L overrides -U");
}
else
strictlock = false;
break;
case 'n': /* add new association: error, if name exists */
if ( (*argv)[2] == '\0') {
error("Missing symbolic name after -n");
break;
}
getassoclst(false, (*argv)+1);
break;
case 'N': /* add or change association */
if ( (*argv)[2] == '\0') {
error("Missing symbolic name after -N");
break;
}
getassoclst(true, (*argv)+1);
break;
case 'o': /* delete revisins */
if (delrev) warn("Redefinition of option -o");
if ( (*argv)[2] == '\0' ) {
error("Missing revision range after -o");
break;
}
getdelrev( (*argv)+1 );
break;
case 's': /* change state attribute of a revision */
if ( (*argv)[2] == '\0') {
error("State missing after -s");
break;
}
getstates( (*argv)+1);
break;
case 't': /* change descriptive text */
textflag=true;
if ((*argv)[2]!='\0'){
if (textfile!=nil)warn("Redefinition of -t option");
textfile = (*argv)+2;
}
break;
case 'q':
quietflag = true;
break;
default:
faterror("Unknown option: %s\n%s", *argv, comdusge);
};
} /* end processing of options */
if (argc<1) faterror("No input file\n%s", comdusge);
if (nerror) { /* exit, if any error in command options */
diagnose("%s aborted",cmdid);
exit(1);
}
if (accessfile) /* get replacement for access list */
getrplaccess();
if (nerror) {
diagnose("%s aborted",cmdid);
exit(1);
}
/* now handle all filenames */
do {
rewriteflag = false;
finptr=frewrite=NULL;
if ( initflag ) {
switch( pairfilenames(argc, argv, false, false) ) {
case -1: break; /* not exist; ok */
case 0: continue; /* error */
case 1: error("file %s exists already", RCSfilename);
VOID fclose(finptr);
continue;
}
}
else {
switch( pairfilenames(argc, argv, true, false) ) {
case -1: continue; /* not exist */
case 0: continue; /* errors */
case 1: break; /* file exists; ok*/
}
}
/* now RCSfilename contains the name of the RCS file, and
* workfilename contains the name of the working file.
* if !initflag, finptr contains the file descriptor for the
* RCS file. The admin node is initialized.
*/
diagnose("RCS file: %s", RCSfilename);
if (!trydiraccess(RCSfilename)) continue; /* give up */
if (!initflag && !checkaccesslist(caller)) continue; /* give up */
if (!trysema(RCSfilename,true)) continue; /* give up */
gettree(); /* read in delta tree */
/* update admin. node */
if (strict_selected) StrictLocks = strictlock;
if (commentflag) Comment = commsyml;
/* update default branch */
if (branchflag && expandsym(branchsym, branchnum)) {
if (countnumflds(branchnum)>0) {
branchdummy.num=branchnum;
Dbranch = &branchdummy;
} else
Dbranch = nil;
}
/* update access list */
if ( delaccessflag ) AccessList = nil;
if ( accessfile ) {
temp = rplaccessor;
while( temp ) {
temptr = temp->nextaccess;
if ( addnewaccess(temp) )
temp->nextaccess = nil;
temp = temptr;
}
}
temp = rmvaccessor;
while(temp) { /* remove accessors from accesslist */
AccessList = removeaccess(temp, AccessList,true);
temp = temp->nextaccess;
}
temp = newaccessor;
while( temp) { /* add new accessors */
temptr = temp->nextaccess;
if ( addnewaccess( temp ) )
temp->nextaccess = nil;
temp = temptr;
}
updateassoc(); /* update association list */
updatelocks(); /* update locks */
/* update state attribution */
if (chgheadstate) {
/* change state of default branch or head */
if (Dbranch==nil) {
if (Head==nil)
warn("Can't change states in an empty tree");
else Head->state = headstate;
} else {
rcs_setstate(Dbranch->num,headstate); /* Can't set directly */
}
}
curstate = statelst;
while( curstate ) {
rcs_setstate(curstate->revno,curstate->status);
curstate = curstate->nextstatus;
}
cuthead = cuttail = nil;
if ( delrev && removerevs()) {
/* rebuild delta tree if some deltas are deleted */
if ( cuttail )
VOID genrevs(cuttail->num, (char *)nil,(char *)nil,
(char *)nil, gendeltas);
buildtree();
}
/* prepare for rewriting the RCS file */
newRCSfilename=mktempfile(RCSfilename,NEWRCSFILE);
oldumask = umask(0222); /* turn off write bits */
if ((frewrite=fopen(newRCSfilename, "w"))==NULL) {
VOID fclose(finptr);
error("Can't open file %s",newRCSfilename);
continue;
}
VOID umask(oldumask);
putadmin(frewrite);
if ( Head )
puttree(Head, frewrite);
putdesc(initflag,textflag,textfile,quietflag);
rewriteflag = false;
if ( Head) {
if (!delrev) {
/* no revision deleted */
fastcopy(finptr,frewrite);
} else {
if ( cuttail )
buildeltatext(gendeltas);
else
scanlogtext((struct hshentry *)nil,empty);
/* copy rest of delta text nodes that are not deleted */
}
}
ffclose(frewrite); frewrite = NULL;
if ( ! nerror ) { /* move temporary file to RCS file if no error */
ignoreints(); /* ignore interrupts */
if(rename(newRCSfilename,RCSfilename)<0) {
error("Can't create RCS file %s; saved in %s",
RCSfilename, newRCSfilename);
newRCSfilename[0] = '\0'; /* avoid deletion by cleanup */
restoreints();
VOID cleanup();
break;
}
newRCSfilename[0]='\0'; /* avoid re-unlinking by cleanup()*/
/* update mode */
result=0;
if (!initflag) /* preserve mode bits */
result=chmod(RCSfilename,RCSstat.st_mode & ~0222);
elsif (haveworkstat==0) /* initialization, and work file exists */
result=chmod(RCSfilename,workstat.st_mode & ~0222);
if (result<0) warn("Can't set mode of %s",RCSfilename);
restoreints(); /* catch them all again */
diagnose("done");
} else {
diagnose("%s aborted; %s unchanged.",cmdid,RCSfilename);
status = 1;
nerror = 0;
}
} while (cleanup(),
++argv, --argc >=1);
exit(status);
} /* end of main (rcs) */
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 * temp, *temp2;
int c;
while( (c=(*++sp)) == ' ' || c == '\t' || c =='\n') ;
temp = sp;
temp2=checkid(sp, ':'); /* check for invalid symbolic name */
sp = temp2; 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;
}
pt = (struct Symrev *)talloc(sizeof(struct Symrev));
pt->ssymbol = temp;
pt->override = flag;
if (c == '\0') /* delete symbol */
pt->revno = nil;
else {
while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
if ( c == '\0' )
pt->revno = nil;
else
pt->revno = sp;
}
pt->nextsym = nil;
if (lastassoc)
lastassoc->nextsym = pt;
else
assoclst = pt;
lastassoc = pt;
return;
}
struct access * getaccessor( sp)
char *sp;
/* Function: get the accessor list of options -e and -a, */
/* and store in curpt */
{
struct access * curpt, * pt, *pre;
char *temp;
register c;
while( ( c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') ;
if ( c == '\0') {
error("Missing login name after option -a or -e");
return nil;
}
curpt = pt = nil;
while( c != '\0') {
temp=checkid(sp,',');
pt = (struct access *)talloc(sizeof(struct access));
pt->login = sp;
if ( curpt )
pre->nextaccess = pt;
else
curpt = pt;
pt->nextaccess = curpt;
pre = pt;
sp = temp; c = *sp; *sp = '\0';
while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
}
return pt;
}
getstates(sp)
char *sp;
/* Function: get one state attribute and the corresponding */
/* revision and store in statelst */
{
char *temp, *temp2;
struct Status *pt;
register c;
while( (c=(*++sp)) ==' ' || c == '\t' || c == '\n') ;
temp = sp;
temp2=checkid(sp,':'); /* check for invalid state attribute */
sp = temp2; c = *sp; *sp = '\0';
while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp;
if ( c == '\0' ) { /* change state of def. branch or Head */
chgheadstate = true;
headstate = temp;
return;
}
else if ( c != ':' ) {
error("Missing ':' after state in option -s");
return;
}
while( (c = *++sp) == ' ' || c == '\t' || c == '\n') ;
pt = (struct Status *)talloc(sizeof(struct Status));
pt->status = temp;
pt->revno = sp;
pt->nextstatus = nil;
if (laststate)
laststate->nextstatus = pt;
else
statelst = pt;
laststate = pt;
}
getrplaccess()
/* Function : get the accesslist of the 'accessfile' */
/* and place in rplaccessor */
{
register char *id, *nextp;
struct access *newaccess, *oldaccess;
if ( (finptr=fopen(accessfile, "r")) == NULL) {
faterror("Can't open file %s", accessfile);
}
Lexinit();
nextp = &accessorlst[0];
if ( ! getkey(Khead)) faterror("Missing head in %s", accessfile);
VOID getnum();
if ( ! getlex(SEMI) ) serror("Missing ';' after head in %s",accessfile);
if (getkey(Kbranch)) { /* optional */
Dbranch=getnum();
if (!getlex(SEMI)) serror("Missing ';' after branch list");
}
#ifdef COMPAT2
/* read suffix. Only in release 2 format */
if (getkey(Ksuffix)) {
if (nexttok==STRING) {
readstring(); nextlex(); /*through away the suffix*/
} elsif(nexttok==ID) {
nextlex();
}
if ( ! getlex(SEMI) ) serror("Missing ';' after suffix in %s",accessfile);
}
#endif
if (! getkey(Kaccess))fatserror("Missing access list in %s",accessfile);
oldaccess = nil;
while( id =getid() ) {
newaccess = (struct access *)talloc(sizeof(struct access));
newaccess->login = nextp;
newaccess->nextaccess = nil;
while( ( *nextp++ = *id++) != '\0') ;
if ( oldaccess )
oldaccess->nextaccess = newaccess;
else
rplaccessor = newaccess;
oldaccess = newaccess;
}
if ( ! getlex(SEMI))serror("Missing ';' after access list in %s",accessfile);
return;
}
getdelrev(sp)
char *sp;
/* Function: get revision range or branch to be deleted, */
/* and place in delrev */
{
int c;
struct delrevpair *pt;
if (delrev) free((char *)delrev);
pt = (struct delrevpair *)talloc(sizeof(struct delrevpair));
while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
if ( c == '<' || c == '-' ) { /* -o -rev or <rev */
while( (c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
pt->strt = sp; pt->code = 1;
while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
*sp = '\0';
pt->end = nil; delrev = pt;
return;
}
else {
pt->strt = sp;
while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
&& c != '-' && c != '<' ) c = *++sp;
*sp = '\0';
while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp;
if ( c == '\0' ) { /* -o rev or branch */
pt->end = nil; pt->code = 0;
delrev = pt;
return;
}
if ( c != '-' && c != '<') {
faterror("Invalid range %s %s after -o", pt->strt, sp);
free((char *)pt);
return;
}
while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
if ( c == '\0') { /* -o rev- or rev< */
pt->end = nil; pt->code = 2;
delrev = pt;
return;
}
}
/* -o rev1-rev2 or rev1<rev2 */
pt->end = sp; pt->code = 3; delrev = pt;
while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
*sp = '\0';
}
scanlogtext(delta,func)
struct hshentry * delta; enum stringwork func;
/* Function: Scans delta text nodes up to and including the one given
* by delta, or up to last one present, if delta==nil.
* For the one given by delta (if delta!=nil), the log message is saved into
* curlogmsg and the text is processed according to parameter func.
* Assumes the initial lexeme must be read in first.
* Does not advance nexttok after it is finished, except if delta==nil.
*/
{ struct hshentry * nextdelta;
do {
rewriteflag = false;
nextlex();
if (!(nextdelta=getnum())) {
if(delta)
faterror("Can't find delta for revision %s", delta->num);
else return; /* no more delta text nodes */
}
if ( nextdelta->selector != DELETE) {
rewriteflag = true;
VOID fprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
}
if (!getkey(Klog) || nexttok!=STRING)
serror("Missing log entry");
elsif (delta==nextdelta) {
VOID savestring(curlogmsg,logsize);
delta->log=curlogmsg;
} else {readstring();
if (delta!=nil) delta->log="";
}
nextlex();
if (!getkey(Ktext) || nexttok!=STRING)
fatserror("Missing delta text");
if(delta==nextdelta)
/* got the one we're looking for */
switch (func) {
case copy: copystring();
break;
case edit: editstring((struct hshentry *)nil);
break;
default: faterror("Wrong scanlogtext");
}
else readstring(); /* skip over it */
} while (delta!=nextdelta);
}
releaselst(sourcelst)
struct access * sourcelst;
/* Function: release the storages whose address are in sourcelst */
{
struct access * pt;
pt = sourcelst;
while(pt) {
struct access *pn = pt->nextaccess;
free((char *)pt);
pt = pn;
}
}
struct Lockrev * rmnewlocklst(which)
struct Lockrev * which;
/* Function: remove lock to revision which->revno from newlocklst */
{
struct Lockrev * pt, *pre;
while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){
struct Lockrev *pn = newlocklst->nextrev;
free((char *)newlocklst);
newlocklst = pn;
}
pt = pre = newlocklst;
while( pt ) {
if ( ! strcmp(pt->revno, which->revno) ) {
pre->nextrev = pt->nextrev;
free((char *)pt);
pt = pre->nextrev;
}
else {
pre = pt;
pt = pt->nextrev;
}
}
return pre;
}
struct access * removeaccess( who, sourcelst,flag)
struct access * who, * sourcelst;
int flag;
/* Function: remove the accessor-- who from sourcelst */
{
struct access *pt, *pre;
pt = sourcelst;
while( pt && (! strcmp(who->login, pt->login) )) {
pre = pt->nextaccess;
free((char *)pt);
pt = pre;
flag = false;
}
pre = sourcelst = pt;
while( pt ) {
if ( ! strcmp(who->login, pt->login) ) {
pre->nextaccess = pt->nextaccess;
free((char *)pt);
pt = pre->nextaccess;
flag = false;
}
else {
pre = pt;
pt = pt->nextaccess;
}
}
if ( flag ) warn("Can't remove a nonexisting accessor %s",who->login);
return sourcelst;
}
int addnewaccess( who )
struct access * who;
/* Function: add new accessor-- who into AccessList */
{
struct access *pt, *pre;
pre = pt = AccessList;
while( pt ) {
if ( strcmp( who->login, pt->login) ) {
pre = pt;
pt = pt->nextaccess;
}
else
return 0;
}
if ( pre == pt )
AccessList = who;
else
pre->nextaccess = who;
return 1;
}
sendmail(Delta, who)
char * Delta, *who;
/* Function: mail to who, informing him that his lock on delta was
* broken by caller. Ask first whether to go ahead. Return false on
* error or if user decides not to break the lock.
*/
{
char * messagefile;
int old1, old2, c, response;
FILE * mailmess;
VOID fprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
VOID fprintf(stderr, "Do you want to break the lock? [ny](n): ");
response=c=getchar();
while (!(c==EOF || c=='\n')) c=getchar();/*skip to end of line*/
if (response=='\n'||response=='n'||response=='N') return false;
/* go ahead with breaking */
messagefile=mktempfile("/tmp/", "RCSmailXXXXXX");
if ( (mailmess = fopen(messagefile, "w")) == NULL) {
faterror("Can't open file %s", messagefile);
}
VOID fprintf(mailmess, "Subject: Broken lock on %s\n\n",bindex(RCSfilename,'/'));
VOID fprintf(mailmess, "Your lock on revision %s of file %s\n",Delta, getfullRCSname());
VOID fprintf(mailmess,"has been broken by %s for the following reason:\n",caller);
VOID fputs("State the reason for breaking the lock:\n", stderr);
VOID fputs("(terminate with ^D or single '.')\n>> ", stderr);
old1 = '\n'; old2 = ' ';
for (; ;) {
c = getchar();
if ( c == EOF ) {
VOID putc('\n',stderr);
VOID fprintf(mailmess, "%c\n", old1);
break;
}
else if ( c == '\n' && old1 == '.' && old2 == '\n')
break;
else {
VOID fputc( old1, mailmess);
old2 = old1; old1 = c;
if (c== '\n') VOID fputs(">> ", stderr);
}
}
ffclose(mailmess);
/* ignore the exit status, even if delivermail unsuccessful */
VOID run(messagefile,(char*)nil,
_PATH_SENDMAIL,
who,(char*)nil);
VOID unlink(messagefile);
return(true);
}
static breaklock(who,delta)
char * who; struct hshentry * delta;
/* function: Finds the lock held by who on delta,
* and removes it.
* Sends mail if a lock different from the caller's is broken.
* Prints an error message if there is no such lock or error.
*/
{
register struct lock * next, * trail;
char * num;
struct lock dummy;
int whor, numr;
num=delta->num;
dummy.nextlock=next=Locks;
trail = &dummy;
while (next!=nil) {
if (num != nil)
numr = strcmp(num, next->delta->num);
whor=strcmp(who,next->login);
if (whor==0 && numr==0) break; /* exact match */
if (numr==0 && whor !=0) {
if (!sendmail( num, next->login)){
diagnose("%s still locked by %s",num,next->login);
return;
} else break; /* continue after loop */
}
trail=next;
next=next->nextlock;
}
if (next!=nil) {
/*found one */
diagnose("%s unlocked",next->delta->num);
trail->nextlock=next->nextlock;
next->delta->lockedby=nil;
Locks=dummy.nextlock;
} else {
error("no lock set on revision %s", num);
}
}
struct hshentry *searchcutpt(object, length, store)
char * object;
int length;
struct hshentry * * store;
/* Function: Search store and return entry with number being object. */
/* cuttail = nil, if the entry is Head; otherwise, cuttail */
/* is the entry point to the one with number being object */
{
while( compartial( (*store++)->num, object, length) ) ;
store--;
if ( *store == Head)
cuthead = nil;
else
cuthead = *(store -1);
return *store;
}
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 DELETE on selector */
{
struct hshentry *pt;
struct lock *lockpt;
int flag;
pt = strt;
flag = false;
while( pt != tail) {
if ( pt->branches ){ /* a branch point */
flag = true;
error("Can't remove branch point %s", pt->num);
}
lockpt = Locks;
while(lockpt && lockpt->delta != pt)
lockpt = lockpt->nextlock;
if ( lockpt ) {
flag = true;
error("Can't remove locked revision %s",pt->num);
}
pt = pt->next;
}
if ( ! flag ) {
pt = strt;
while( pt != tail ) {
pt->selector = DELETE;
diagnose("deleting revision %s ",pt->num);
pt = pt->next;
}
}
return flag;
}
removerevs()
/* Function: get the revision range to be removed, and place the */
/* first revision removed in delstrt, the revision before */
/* delstrt in cuthead( nil, if delstrt is head), and the */
/* revision after the last removed revision in cuttail(nil */
/* if the last is a leaf */
{
struct hshentry *target, *target2, * temp, *searchcutpt();
int length, flag;
flag = false;
if ( ! expandsym(delrev->strt, &numrev[0]) ) return 0;
target = genrevs(&numrev[0], (char *)nil, (char *)nil, (char *)nil, gendeltas);
if ( ! target ) return 0;
if ( cmpnum(target->num, &numrev[0]) ) flag = true;
length = countnumflds( &numrev[0] );
if ( delrev->code == 0 ) { /* -o rev or -o branch */
if ( length % 2)
temp=searchcutpt(target->num,length+1,gendeltas);
else if (flag) {
error("Revision %s does not exist", &numrev[0]);
return 0;
}
else
temp = searchcutpt(&numrev[0],length,gendeltas);
cuttail = target->next;
if ( branchpoint(temp, cuttail) ) {
cuttail = nil;
return 0;
}
delstrt = temp; /* first revision to be removed */
return 1;
}
if ( length % 2 ) { /* invalid branch after -o */
error("Invalid branch range %s after -o", &numrev[0]);
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 = nil;
return 0;
}
delstrt = temp;
return 1;
}
if ( delrev->code == 2 ) { /* -o rev- */
if ( length == 2 ) {
temp = searchcutpt(target->num, 1,gendeltas);
if ( flag)
cuttail = target;
else
cuttail = target->next;
}
else {
if ( flag){
cuthead = target;
if ( !(temp = target->next) ) return 0;
}
else
temp = searchcutpt(target->num, length, gendeltas);
getbranchno(temp->num, &numrev[0]); /* get branch number */
target = genrevs(&numrev[0], (char *)nil, (char *)nil, (char *)nil, gendeltas);
}
if ( branchpoint( temp, cuttail ) ) {
cuttail = nil;
return 0;
}
delstrt = temp;
return 1;
}
/* -o rev1-rev2 */
if ( ! expandsym(delrev->end, &numrev[0]) ) return 0;
if ( length != countnumflds( &numrev[0] ) ) {
error("Invalid revision range %s-%s", target->num, &numrev[0]);
return 0;
}
if ( length > 2 && compartial( &numrev[0], target->num, length-1) ) {
error("Invalid revision range %s-%s", target->num, &numrev[0]);
return 0;
}
target2 = genrevs( &numrev[0], (char *)nil, (char *)nil, (char *)nil,gendeltas);
if ( ! target2 ) return 0;
if ( length > 2) { /* delete revisions on branches */
if ( cmpnum(target->num, target2->num) > 0) {
if ( cmpnum(target2->num, &numrev[0]) )
flag = true;
else
flag = false;
temp = target;
target = target2;
target2 = temp;
}
if ( flag ) {
if ( ! cmpnum(target->num, target2->num) ) {
error("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
if ( cmpnum(target2->num, &numrev[0]) )
flag = true;
else
flag = false;
if ( flag ) {
if ( ! cmpnum(target->num, target2->num) ) {
error("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 = nil;
return 0;
}
delstrt = temp;
return 1;
}
updateassoc()
/* Function: add or delete(if revno is nil) association */
/* which is stored in assoclst */
{
struct Symrev * curassoc;
struct assoc * pre, * pt;
struct hshentry * target;
/* add new associations */
curassoc = assoclst;
while( curassoc ) {
if ( curassoc->revno == nil ) { /* delete symbol */
pre = pt = Symbols;
while( pt && strcmp(pt->symbol,curassoc->ssymbol) ) {
pre = pt;
pt = pt->nextassoc;
}
if ( pt )
if ( pre == pt )
Symbols = pt->nextassoc;
else
pre->nextassoc = pt->nextassoc;
else
warn("Can't delete nonexisting symbol %s",curassoc->ssymbol);
}
else if ( expandsym( curassoc->revno, &numrev[0] ) ) {
/* add symbol */
target = (struct hshentry *) talloc(sizeof(struct hshentry));
target->num = &numrev[0];
VOID addsymbol(target, curassoc->ssymbol, curassoc->override);
}
curassoc = curassoc->nextsym;
}
}
updatelocks()
/* Function: remove lock for caller or first lock if unlockcaller==true;
* remove locks which are stored in rmvlocklst,
* add new locks which are stored in newlocklst,
* add lock for Dbranch or Head if lockhead==true.
*/
{
struct hshentry *target;
struct Lockrev *lockpt;
if(unlockcaller == true) { /* find lock for caller */
if ( Head ) {
if (Locks) {
target=findlock(caller,true);
if (target==nil) {
breaklock(caller, Locks->delta); /* remove most recent lock */
} else {
diagnose("%s unlocked",target->num);
}
} else {
warn("There are no locks set.");
}
} else {
warn("Can't unlock an empty tree");
}
}
/* remove locks which are stored in rmvlocklst */
lockpt = rmvlocklst;
while( lockpt ) {
if (expandsym(lockpt->revno, numrev)) {
target = genrevs(numrev, (char *)nil, (char *)nil, (char *)nil, gendeltas);
if ( target )
if ( !(countnumflds(numrev)%2) && cmpnum(target->num,numrev))
error("Can't unlock nonexisting revision %s",lockpt->revno);
else
breaklock(caller, target);
/* breaklock does its own diagnose */
}
lockpt = lockpt->nextrev;
}
/* add new locks which stored in newlocklst */
lockpt = newlocklst;
while( lockpt ) {
setlock(lockpt->revno,caller);
lockpt = lockpt->nextrev;
}
if ( lockhead == true) { /* lock default branch or head */
if (Dbranch) {
setlock(Dbranch->num,caller);
} elsif ( Head) {
if (addlock(Head, caller))
diagnose("%s locked",Head->num);
} else {
warn("Can't lock an empty tree");
}
}
}
setlock(rev,who)
char * rev, * who;
/* Function: Given a revision or branch number, finds the correponding
* delta and locks it for who.
*/
{
struct lock *lpt;
struct hshentry *target;
if (expandsym(rev, &numrev[0]) ){
target = genrevs(&numrev[0],(char *) nil,(char *) nil,
(char *)nil, gendeltas);
if ( target )
if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num,&numrev[0]))
error("Can't lock nonexisting revision %s",numrev);
else
if(lpt=addlock(target, who))
diagnose("%s locked",lpt->delta->num);
}
}
rcs_setstate(rev,status)
char * 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[0]) ) {
target = genrevs(&numrev[0],(char *) nil, (char *)nil,
(char *) nil, gendeltas);
if ( target )
if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num, &numrev[0]) )
error("Can't set state of nonexisting revision %s to %s",
numrev,status);
else
target->state = status;
}
}
buildeltatext(deltas)
struct hshentry ** deltas;
/* Function: put the delta text on frewrite and make necessary */
/* change to delta text */
{
int i, c, exit_stats;
cuttail->selector = DELETE;
initeditfiles("/tmp/");
scanlogtext(deltas[0], copy);
i = 1;
if ( cuthead ) {
cutfilename=mktempfile("/tmp/", "RCScutXXXXXX");
if ( (fcut = fopen(cutfilename, "w")) == NULL) {
faterror("Can't open temporary file %s", cutfilename);
}
while( deltas[i-1] != cuthead ) {
scanlogtext(deltas[i++], edit);
}
finishedit((struct hshentry *)nil); rewind(fcopy);
while( (c = getc(fcopy)) != EOF) VOID putc(c, fcut);
swapeditfiles(false);
ffclose(fcut);
}
while( deltas[i-1] != cuttail)
scanlogtext(deltas[i++], edit);
finishedit((struct hshentry *)nil); ffclose(fcopy);
if ( cuthead ) {
diffilename=mktempfile("/tmp/", "RCSdifXXXXXX");
exit_stats = run((char*)nil,diffilename,
DIFF,"-n",cutfilename,resultfile,(char*)nil);
if (exit_stats != 0 && exit_stats != (1 << BYTESIZ))
faterror ("diff failed");
if(!putdtext(cuttail->num,curlogmsg,diffilename,frewrite)) return;
}
else
if (!putdtext(cuttail->num,curlogmsg,resultfile,frewrite)) return;
scanlogtext((struct hshentry *)nil,empty); /* read the rest of the deltas */
}
buildtree()
/* Function: actually removes revisions whose selector field */
/* is DELETE, and rebuilds the linkage of deltas. */
/* asks for reconfirmation if deleting last revision*/
{
int c, response;
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 {
if ( cuttail == nil && !quietflag) {
VOID fprintf(stderr,"Do you really want to delete all revisions ?[ny](n): ");
c = response = getchar();
while( c != EOF && c != '\n') c = getchar();
if ( response != 'y' && response != 'Y') {
diagnose("No revision deleted");
Delta = delstrt;
while( Delta) {
Delta->selector = 'S';
Delta = Delta->next;
}
return;
}
}
Head = cuttail;
}
return;
}