/* * RCS create/change operation */ static char rcsid[]= "$Header: /usr/wft/RCS/SRC/RCS/rcs.c,v 3.9 83/02/15 15:38:39 wft Exp $ Purdue CS"; /*************************************************************************** * create RCS files or change RCS file attributes * Compatibility with release 2: define COMPAT2 *************************************************************************** * * Copyright (C) 1982 by Walter F. Tichy * Purdue University * Computer Science Department * West Lafayette, IN 47907 * * All rights reserved. No part of this software may be sold or distributed * in any form or by any means without the prior written permission of the * author. */ /* $Log: rcs.c,v $ * 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 #include #include #include #include "rcsbase.h" static char rcsbaseid[] = RCSBASE; extern FILE * fopen(); extern curdir(); 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 * getkeyval(); extern char * Klog, *Khead, *Kaccess, *Ksuffix, *Ktext; extern struct passwd *getpwuid(); extern char * malloc(); extern struct hshentry * genrevs(); extern struct hshentry * breaklock(); /* remove locks */ 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 int nextc; /* next input character */ extern int nerror; /* counter for errors */ extern int quietflag; /* diagnoses suppressed if true */ extern char curlogmsg[]; /* current log message */ extern char * resultfile, *editfile; /* filename for fcopy and fedit */ extern FILE *fcopy; /* result file during editing */ extern FILE *fedit; /* edit file */ extern FILE * finptr; /* RCS input file */ extern FILE * frewrite; /* new RCS file */ char * RCSfilename, * workfilename; char * newRCSfilename, * diffilename, * cutfilename; char accessorlst[strtsize]; FILE * fcut; /* temporary file to rebuild delta tree */ int rewriteflag; /* indicates whether input should be echoed to frewrite */ struct stat filestatus; /* used for preserving mode of an exisiting RCS file*/ int oldumask; /* saves 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 command[80], * commsyml; char * headstate; int headoverride, lockhead, unlockcaller, chgheadstate, commentflag; int delaccessflag; enum stringwork {copy, edit, empty}; /* expand and edit_expand not needed */ main (argc, argv) int argc; char * argv[]; { char *comdusge; struct access *removeaccess(), * getaccessor(); struct Lockrev *rmnewlocklst(); struct Lockrev *curlock, * rmvlock, *lockpt; struct Status * curstate; struct hshentry * target; struct access *temp, *temptr; nerror = 0; catchints(); cmdid = "rcs"; quietflag = false; comdusge ="command format:\nrcs -i -alogins -Alogins -e[logins] -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; commentflag = chgheadstate = false; lockhead = false; unlockcaller=false; initflag= textflag = false; strict_selected = 0; caller=getpwuid(getuid())->pw_name; 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 '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 */ lockhead = true; break; } lockpt = (struct Lockrev *)malloc(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 *)malloc(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; nerror=0; if ( initflag ) { switch( pairfilenames(argc, argv, false, false) ) { case -1: break; case 0: continue; /* can't open */ case 1: error("file %s exists already", RCSfilename); fclose(finptr); continue; } } else { switch( pairfilenames(argc, argv, true, false) ) { case -1: continue; /* not exist */ case 0: continue; /* can't open */ case 1: /* file exists */ fstat(fileno(finptr), &filestatus);/*grab mode*/ break; } } /* 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 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 */ if ( lockhead == true) { /* lock head */ if ( Head) { if (addlock(Head, caller)) diagnose("%s locked",Head->num); } else { warn("Can't lock an empty tree"); } } if(unlockcaller == true) { /* find lock for caller */ if ( Head ) { breaklock(caller, nil); /* breaklock does it's own diagnose */ } else { warn("Can't unlock an empty tree"); } } updatelock(); /* update state attribution */ if (chgheadstate && Head) Head->state = headstate; curstate = statelst; while( curstate ) { if ( expandsym(curstate->revno, &numrev[0]) ) { target = genrevs(&numrev[0], nil, nil, nil, gendeltas); if ( target ) if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num, &numrev[0]) ) error("Can't set state %s of a nonexistent revision %s", curstate->status, curstate->revno); else target->state = curstate->status; } curstate = curstate->nextstatus; } cuthead = cuttail = nil; if ( delrev && removerevs()) { /* rebuild delta tree if some deltas are deleted */ if ( cuttail ) genrevs(cuttail->num, nil,nil, 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) { fclose(finptr); error("Can't open file %s",newRCSfilename); continue; } 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(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 */ catchints(); cleanup(); break; } newRCSfilename[0]='\0'; /* avoid re-unlinking by cleanup()*/ if (!initflag) /* preserve mode bits */ if (chmod(RCSfilename,filestatus.st_mode & ~0222)<0) warn("Can't set mode of %s",RCSfilename); catchints(); /* catch them all again */ diagnose("done"); } else { diagnose("%s unchanged.",RCSfilename); } } while (cleanup(), ++argv, --argc >=1); exit(nerror!=0); } /* 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 *)malloc(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 *)malloc(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' ) { /* state attribute of 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 *)malloc(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, *curaccess; 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); getnum(); if ( ! getlex(SEMI) ) serror("Missing ';' after head in %s",accessfile); #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); curaccess = nil; while( id =getid() ) { newaccess = (struct access *)malloc(sizeof(struct access)); newaccess->login = nextp; newaccess->nextaccess = nil; while( ( *nextp++ = *id++) != '\0') ; if ( curaccess ) curaccess->nextaccess = newaccess; else rplaccessor = newaccess; curaccess = 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(delrev); pt = (struct delrevpair *)malloc(sizeof(struct delrevpair)); while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ; if ( c == '<' || c == '-' ) { /* -o -rev or 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(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 rev1end = 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; fprintf(frewrite,DELNUMFORM,nextdelta->num,Klog); } if (!getkey(Klog) || nexttok!=STRING) serror("Missing log entry"); elsif (delta==nextdelta) { 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(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) { free(pt); pt = pt->nextaccess; } } struct Lockrev * rmnewlocklst(which) struct Lockrev * which; /* Function: remove lock to revision which->revno form newlocklst */ { struct Lockrev * pt, *pre; while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){ free(newlocklst); newlocklst = newlocklst->nextrev; } pt = pre = newlocklst; while( pt ) { if ( ! strcmp(pt->revno, which->revno) ) { free(pt); pt = pt->nextrev; pre->nextrev = pt; } 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) )) { free(pt); flag = false; pt = pt->nextaccess; } pre = sourcelst = pt; while( pt ) { if ( ! strcmp(who->login, pt->login) ) { free(pt); flag = false; pt = pt->nextaccess; pre->nextaccess = pt; } 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, exitstatus; FILE * mailmess; fprintf(stdout, "Revision %s is already locked by %s.\n", Delta, who); fprintf(stdout, "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 (c == EOF) { clearerr(stdin); c = 'n'; } if (response=='\n'||response=='n'||response=='N') return false; /* go ahead with breaking */ messagefile=mktempfile("/tmp/", "RCSmailXXXXX"); if ( (mailmess = fopen(messagefile, "w")) == NULL) { faterror("Can't open file %s", messagefile); } fprintf(mailmess, "Subject: Broken lock on %s\n\n",RCSfilename); fprintf(mailmess, "Your lock on revision %s of file %s\n",Delta, getfullRCSname()); fprintf(mailmess,"has been broken by %s for the following reason:\n",caller); fputs("State the reason for breaking the lock:\n", stdout); fputs("(terminate with ^D or single '.')\n>> ", stdout); old1 = '\n'; old2 = ' '; for (; ;) { c = getchar(); if ( c == EOF ) { clearerr(stdin); putc('\n',stdout); fprintf(mailmess, "%c\n", old1); break; } else if ( c == '\n' && old1 == '.' && old2 == '\n') break; else { fputc( old1, mailmess); old2 = old1; old1 = c; if (c== '\n') fputs(">> ", stdout); } } ffclose(mailmess); #ifdef V4_2BSD sprintf(command, "/usr/lib/sendmail %s < %s",who,messagefile); #else sprintf(command, "/etc/delivermail -w %s < %s",who,messagefile); #endif exitstatus = system(command); unlink(messagefile); return(exitstatus==EX_OK); } struct hshentry * breaklock(who,delta) char * who; struct hshentry * delta; /* function: Finds the lock held by who on delta, * removes it, and returns a pointer to the delta. * delta may be nil; then the first lock held by who is chosen. * Prints an error message and returns nil if there is no such lock or error. */ { register struct lock * next, * trail; char * num; struct lock dummy; int whor, numr; num=(delta==nil)?nil:delta->num; dummy.nextlock=next=Locks; trail = &dummy; while (next!=nil) { numr = strcmp(num, next->delta->num); if ((whor=strcmp(who,next->login))==0 && (num==nil || numr==0)) break; /* found a lock */ if (num!=nil && numr==0 && whor !=0) { if (!sendmail( num, next->login)){ diagnose("%s still locked by %s",num,next->login); return nil; } 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; return next->delta; } else { if (delta) error("no lock set by %s for revision %s", who, num); else error("no lock set by %s",who); return nil; } } 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], nil, nil, 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], nil, nil, 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], nil, nil, 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 *) malloc(sizeof(struct hshentry)); target->num = &numrev[0]; addsymbol(target, curassoc->ssymbol, curassoc->override); } curassoc = curassoc->nextsym; } } updatelock() /* Function: remove locks which are stored in rmvlocklst, */ /* add new locks which are stored in newlocklst, */ { struct hshentry *target; struct Lockrev *lockpt; struct lock *lpt; /* remove locks which stored in rmvlocklst */ lockpt = rmvlocklst; while( lockpt ) { if (expandsym(lockpt->revno, &numrev[0]) ) { target = genrevs(&numrev[0], nil, nil, nil, gendeltas); if ( target ) if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num,&numrev[0]) ) error("Can't unlock a nonexisting revision %s",lockpt->revno); else breaklock(caller, target); /* breaklock does it's own diagnose */ } lockpt = lockpt->nextrev; } /* add new locks which stored in newlocklst */ lockpt = newlocklst; while( lockpt ) { if (expandsym(lockpt->revno, &numrev[0]) ){ target = genrevs(&numrev[0], nil, nil, nil, gendeltas); if ( target ) if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num,&numrev[0])) error("Can't lock a nonexisting revision %s",lockpt->revno); else if(lpt=addlock(target, caller)) diagnose("%s locked",lpt->delta->num); } lockpt = lockpt->nextrev; } } 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(nil); rewind(fcopy); while( (c = getc(fcopy)) != EOF) putc(c, fcut); swapeditfiles(false); ffclose(fcut); } while( deltas[i-1] != cuttail) scanlogtext(deltas[i++], edit); finishedit(nil); ffclose(fcopy); if ( cuthead ) { diffilename=mktempfile("/tmp/", "RCSdifXXXXXX"); sprintf(command, "%s -n %s %s > %s", DIFF,cutfilename, resultfile, diffilename); exit_stats = system (command); 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(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) { fprintf(stderr,"Do you really want to delete all revisions ?[ny](n): "); c = response = getchar(); while( c != EOF && c != '\n') c = getchar(); if (c == EOF) clearerr(stdin); if ( response != 'y' && response != 'Y') { diagnose("No revision deleted"); Delta = delstrt; while( Delta) { Delta->selector = 'S'; Delta = Delta->next; } return; } } Head = cuttail; } return; }