+/*
+ * RCS create/change operation
+ */
+#ifndef lint
+static char rcsid[]=
+"$Header: /usr/src/local/bin/rcs/src/RCS/rcs.c,v 4.11 89/05/01 15:12:06 narten 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 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;
+}
+