Sync usage with man page.
[netbsd-mini2440.git] / usr.bin / rcs / src / rcs.c
blob4688b23408f1079c6898f3b5018491fd92bd0a90
1 /*
2 * RCS create/change operation
3 */
4 #ifndef lint
5 static char rcsid[]=
6 "$Header: /pub/NetBSD/misc/repositories/cvsroot/src/usr.bin/rcs/src/Attic/rcs.c,v 1.1 1993/03/21 09:58:06 cgd Exp $ Purdue CS";
7 #endif
8 /* Copyright (C) 1982, 1988, 1989 Walter Tichy
9 * All rights reserved.
11 * Redistribution and use in source and binary forms are permitted
12 * provided that the above copyright notice and this paragraph are
13 * duplicated in all such forms and that any documentation,
14 * advertising materials, and other materials related to such
15 * distribution and use acknowledge that the software was developed
16 * by Walter Tichy.
17 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21 * Report all problems and direct all questions to:
22 * rcs-bugs@cs.purdue.edu
36 /* $Log: rcs.c,v $
37 * Revision 4.11 89/05/01 15:12:06 narten
38 * changed copyright header to reflect current distribution rules
40 * Revision 4.10 88/11/08 16:01:54 narten
41 * didn't install previous patch correctly
43 * Revision 4.9 88/11/08 13:56:01 narten
44 * removed include <sysexits.h> (not needed)
45 * minor fix for -A option
47 * Revision 4.8 88/11/08 12:01:58 narten
48 * changes from eggert@sm.unisys.com (Paul Eggert)
50 * Revision 4.8 88/08/09 19:12:27 eggert
51 * Don't access freed storage.
52 * Use execv(), not system(); yield proper exit status; remove lint.
54 * Revision 4.7 87/12/18 11:37:17 narten
55 * lint cleanups (Guy Harris)
57 * Revision 4.6 87/10/18 10:28:48 narten
58 * Updating verison numbers. Changes relative to 1.1 are actually
59 * relative to 4.3
61 * Revision 1.4 87/09/24 13:58:52 narten
62 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
63 * warnings)
65 * Revision 1.3 87/03/27 14:21:55 jenkins
66 * Port to suns
68 * Revision 1.2 85/12/17 13:59:09 albitz
69 * Changed setstate to rcs_setstate because of conflict with random.o.
71 * Revision 1.1 84/01/23 14:50:09 kcs
72 * Initial revision
74 * Revision 4.3 83/12/15 12:27:33 wft
75 * rcs -u now breaks most recent lock if it can't find a lock by the caller.
77 * Revision 4.2 83/12/05 10:18:20 wft
78 * Added conditional compilation for sending mail.
79 * Alternatives: V4_2BSD, V6, USG, and other.
81 * Revision 4.1 83/05/10 16:43:02 wft
82 * Simplified breaklock(); added calls to findlock() and getcaller().
83 * Added option -b (default branch). Updated -s and -w for -b.
84 * Removed calls to stat(); now done by pairfilenames().
85 * Replaced most catchints() calls with restoreints().
86 * Removed check for exit status of delivermail().
87 * Directed all interactive output to stderr.
89 * Revision 3.9.1.1 83/12/02 22:08:51 wft
90 * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
92 * Revision 3.9 83/02/15 15:38:39 wft
93 * Added call to fastcopy() to copy remainder of RCS file.
95 * Revision 3.8 83/01/18 17:37:51 wft
96 * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
98 * Revision 3.7 83/01/15 18:04:25 wft
99 * Removed putree(); replaced with puttree() in rcssyn.c.
100 * Combined putdellog() and scanlogtext(); deleted putdellog().
101 * Cleaned up diagnostics and error messages. Fixed problem with
102 * mutilated files in case of deletions in 2 files in a single command.
103 * Changed marking of selector from 'D' to DELETE.
105 * Revision 3.6 83/01/14 15:37:31 wft
106 * Added ignoring of interrupts while new RCS file is renamed;
107 * Avoids deletion of RCS files by interrupts.
109 * Revision 3.5 82/12/10 21:11:39 wft
110 * Removed unused variables, fixed checking of return code from diff,
111 * introduced variant COMPAT2 for skipping Suffix on -A files.
113 * Revision 3.4 82/12/04 13:18:20 wft
114 * Replaced getdelta() with gettree(), changed breaklock to update
115 * field lockedby, added some diagnostics.
117 * Revision 3.3 82/12/03 17:08:04 wft
118 * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
119 * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
120 * fixed -u for missing revno. Disambiguated structure members.
122 * Revision 3.2 82/10/18 21:05:07 wft
123 * rcs -i now generates a file mode given by the umask minus write permission;
124 * otherwise, rcs keeps the mode, but removes write permission.
125 * I added a check for write error, fixed call to getlogin(), replaced
126 * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
127 * conflicting, long identifiers.
129 * Revision 3.1 82/10/13 16:11:07 wft
130 * fixed type of variables receiving from getc() (char -> int).
134 #include <sys/types.h>
135 #include <sys/stat.h>
136 #include "rcsbase.h"
137 #include <paths.h>
138 #ifndef lint
139 static char rcsbaseid[] = RCSBASE;
140 #endif
142 extern FILE * fopen();
143 extern char * bindex();
144 extern int expandsym(); /* get numeric revision name */
145 extern struct hshentry * getnum();
146 extern struct lock * addlock(); /* add a lock */
147 extern char * getid();
148 extern char Klog[], Khead[], Kaccess[], Kbranch[], Ktext[];
149 #ifdef COMPAT2
150 extern char Ksuffix[];
151 #endif
152 extern char * getcaller(); /* get login of caller */
153 extern struct hshentry * findlock(); /* find and remove lock */
154 extern struct hshentry * genrevs();
155 extern char * checkid(); /* check an identifier */
156 extern char * getfullRCSname(); /* get full path name of RCS file */
157 extern char * mktempfile(); /* temporary file name generator */
158 extern free();
159 extern void catchints();
160 extern void ignoreints();
161 extern int nerror; /* counter for errors */
162 extern int quietflag; /* diagnoses suppressed if true */
163 extern char curlogmsg[]; /* current log message */
164 extern char * resultfile; /* filename for fcopy */
165 extern FILE *fcopy; /* result file during editing */
166 extern FILE * finptr; /* RCS input file */
167 extern FILE * frewrite; /* new RCS file */
168 extern int rewriteflag; /* indicates whether input should be*/
169 /* echoed to frewrite */
171 char * newRCSfilename, * diffilename, * cutfilename;
172 char * RCSfilename, * workfilename;
173 extern struct stat RCSstat, workstat; /* file status of RCS and work file */
174 extern int haveRCSstat, haveworkstat;/* status indicators */
176 char accessorlst[strtsize];
177 FILE * fcut; /* temporary file to rebuild delta tree */
178 int oldumask; /* save umask */
180 int initflag, strictlock, strict_selected, textflag;
181 char * textfile, * accessfile;
182 char * caller, numrev[30]; /* caller's login; */
183 struct access * newaccessor, * rmvaccessor, * rplaccessor;
184 struct access *curaccess, *rmaccess;
185 struct hshentry * gendeltas[hshsize];
187 struct Lockrev {
188 char * revno;
189 struct Lockrev * nextrev;
192 struct Symrev {
193 char * revno;
194 char * ssymbol;
195 int override;
196 struct Symrev * nextsym;
199 struct Status {
200 char * revno;
201 char * status;
202 struct Status * nextstatus;
205 struct delrevpair {
206 char * strt;
207 char * end;
208 int code;
211 struct Lockrev * newlocklst, * rmvlocklst;
212 struct Symrev * assoclst, * lastassoc;
213 struct Status * statelst, * laststate;
214 struct delrevpair * delrev;
215 struct hshentry * cuthead, *cuttail, * delstrt;
216 char branchnum[revlength], * branchsym;
217 struct hshentry branchdummy;
218 char * commsyml;
219 char * headstate;
220 int lockhead,unlockcaller,chgheadstate,branchflag,commentflag;
221 int delaccessflag;
222 enum stringwork {copy, edit, empty}; /* expand and edit_expand not needed */
225 main (argc, argv)
226 int argc;
227 char * argv[];
229 char *comdusge;
230 int result;
231 struct access *removeaccess(), * getaccessor();
232 struct Lockrev *rmnewlocklst();
233 struct Lockrev *curlock, * rmvlock, *lockpt;
234 struct Status * curstate;
235 struct access *temp, *temptr;
236 int status;
238 status = 0;
239 nerror = 0;
240 catchints();
241 cmdid = "rcs";
242 quietflag = false;
243 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....";
244 rplaccessor = nil; delstrt = nil;
245 accessfile = textfile = caller = nil;
246 branchflag = commentflag = chgheadstate = false;
247 lockhead = false; unlockcaller=false;
248 initflag= textflag = false;
249 strict_selected = 0;
251 caller=getcaller();
252 laststate = statelst = nil;
253 lastassoc = assoclst = nil;
254 curlock = rmvlock = newlocklst = rmvlocklst = nil;
255 curaccess = rmaccess = rmvaccessor = newaccessor = nil;
256 delaccessflag = false;
258 /* preprocessing command options */
259 while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
260 switch ((*argv)[1]) {
262 case 'i': /* initail version */
263 initflag = true;
264 break;
266 case 'b': /* change default branch */
267 if (branchflag)warn("Redfinition of option -b");
268 branchflag= true;
269 branchsym = (*argv)+2;
270 break;
272 case 'c': /* change comment symbol */
273 if (commentflag)warn("Redefinition of option -c");
274 commentflag = true;
275 commsyml = (*argv)+2;
276 break;
278 case 'a': /* add new accessor */
279 if ( (*argv)[2] == '\0') {
280 error("Login name missing after -a");
282 if ( (temp = getaccessor((*argv)+1)) ) {
283 if ( newaccessor )
284 curaccess->nextaccess = temp->nextaccess;
285 else
286 newaccessor = temp->nextaccess;
287 temp->nextaccess = nil;
288 curaccess = temp;
290 break;
292 case 'A': /* append access list according to accessfile */
293 if ( (*argv)[2] == '\0') {
294 error("Missing file name after -A");
295 break;
297 if ( accessfile) warn("Redefinition of option -A");
298 *argv = *argv+2;
299 if( pairfilenames(1, argv, true, false) > 0) {
300 releaselst(newaccessor);
301 newaccessor = curaccess = nil;
302 releaselst(rmvaccessor);
303 rmvaccessor = rmaccess = nil;
304 accessfile = RCSfilename;
306 else
307 accessfile = nil;
308 break;
310 case 'e': /* remove accessors */
311 if ( (*argv)[2] == '\0' ) {
312 delaccessflag = true;
313 break;
315 if ( (temp = getaccessor((*argv)+1)) ) {
316 if ( rmvaccessor )
317 rmaccess->nextaccess = temp->nextaccess;
318 else
319 rmvaccessor = temp->nextaccess;
320 temptr = temp->nextaccess;
321 temp->nextaccess = nil;
322 rmaccess = temp;
323 while( temptr ) {
324 newaccessor = removeaccess(temptr,newaccessor,false);
325 temptr = temptr->nextaccess;
327 curaccess = temp = newaccessor;
328 while( temp){
329 curaccess = temp;
330 temp = temp->nextaccess;
333 break;
335 case 'l': /* lock a revision if it is unlocked */
336 if ( (*argv)[2] == '\0'){ /* lock head or def. branch */
337 lockhead = true;
338 break;
340 lockpt = (struct Lockrev *)talloc(sizeof(struct Lockrev));
341 lockpt->revno = (*argv)+2;
342 lockpt->nextrev = nil;
343 if ( curlock )
344 curlock->nextrev = lockpt;
345 else
346 newlocklst = lockpt;
347 curlock = lockpt;
348 break;
350 case 'u': /* release lock of a locked revision */
351 if ( (*argv)[2] == '\0'){ /* unlock head */
352 unlockcaller=true;
353 break;
355 lockpt = (struct Lockrev *)talloc(sizeof(struct Lockrev));
356 lockpt->revno = (*argv)+2;
357 lockpt->nextrev = nil;
358 if (rmvlock)
359 rmvlock->nextrev = lockpt;
360 else
361 rmvlocklst = lockpt;
362 rmvlock = lockpt;
364 curlock = rmnewlocklst(lockpt);
365 break;
367 case 'L': /* set strict locking */
368 if (strict_selected++) { /* Already selected L or U? */
369 if (!strictlock) /* Already selected -U? */
370 warn("Option -L overrides -U");
372 strictlock = true;
373 break;
375 case 'U': /* release strict locking */
376 if (strict_selected++) { /* Already selected L or U? */
377 if (strictlock) /* Already selected -L? */
378 warn("Option -L overrides -U");
380 else
381 strictlock = false;
382 break;
384 case 'n': /* add new association: error, if name exists */
385 if ( (*argv)[2] == '\0') {
386 error("Missing symbolic name after -n");
387 break;
389 getassoclst(false, (*argv)+1);
390 break;
392 case 'N': /* add or change association */
393 if ( (*argv)[2] == '\0') {
394 error("Missing symbolic name after -N");
395 break;
397 getassoclst(true, (*argv)+1);
398 break;
400 case 'o': /* delete revisins */
401 if (delrev) warn("Redefinition of option -o");
402 if ( (*argv)[2] == '\0' ) {
403 error("Missing revision range after -o");
404 break;
406 getdelrev( (*argv)+1 );
407 break;
409 case 's': /* change state attribute of a revision */
410 if ( (*argv)[2] == '\0') {
411 error("State missing after -s");
412 break;
414 getstates( (*argv)+1);
415 break;
417 case 't': /* change descriptive text */
418 textflag=true;
419 if ((*argv)[2]!='\0'){
420 if (textfile!=nil)warn("Redefinition of -t option");
421 textfile = (*argv)+2;
423 break;
425 case 'q':
426 quietflag = true;
427 break;
428 default:
429 faterror("Unknown option: %s\n%s", *argv, comdusge);
431 } /* end processing of options */
433 if (argc<1) faterror("No input file\n%s", comdusge);
434 if (nerror) { /* exit, if any error in command options */
435 diagnose("%s aborted",cmdid);
436 exit(1);
438 if (accessfile) /* get replacement for access list */
439 getrplaccess();
440 if (nerror) {
441 diagnose("%s aborted",cmdid);
442 exit(1);
445 /* now handle all filenames */
446 do {
447 rewriteflag = false;
448 finptr=frewrite=NULL;
450 if ( initflag ) {
451 switch( pairfilenames(argc, argv, false, false) ) {
452 case -1: break; /* not exist; ok */
453 case 0: continue; /* error */
454 case 1: error("file %s exists already", RCSfilename);
455 VOID fclose(finptr);
456 continue;
459 else {
460 switch( pairfilenames(argc, argv, true, false) ) {
461 case -1: continue; /* not exist */
462 case 0: continue; /* errors */
463 case 1: break; /* file exists; ok*/
468 /* now RCSfilename contains the name of the RCS file, and
469 * workfilename contains the name of the working file.
470 * if !initflag, finptr contains the file descriptor for the
471 * RCS file. The admin node is initialized.
474 diagnose("RCS file: %s", RCSfilename);
476 if (!trydiraccess(RCSfilename)) continue; /* give up */
477 if (!initflag && !checkaccesslist(caller)) continue; /* give up */
478 if (!trysema(RCSfilename,true)) continue; /* give up */
480 gettree(); /* read in delta tree */
482 /* update admin. node */
483 if (strict_selected) StrictLocks = strictlock;
484 if (commentflag) Comment = commsyml;
486 /* update default branch */
487 if (branchflag && expandsym(branchsym, branchnum)) {
488 if (countnumflds(branchnum)>0) {
489 branchdummy.num=branchnum;
490 Dbranch = &branchdummy;
491 } else
492 Dbranch = nil;
495 /* update access list */
496 if ( delaccessflag ) AccessList = nil;
497 if ( accessfile ) {
498 temp = rplaccessor;
499 while( temp ) {
500 temptr = temp->nextaccess;
501 if ( addnewaccess(temp) )
502 temp->nextaccess = nil;
503 temp = temptr;
506 temp = rmvaccessor;
507 while(temp) { /* remove accessors from accesslist */
508 AccessList = removeaccess(temp, AccessList,true);
509 temp = temp->nextaccess;
511 temp = newaccessor;
512 while( temp) { /* add new accessors */
513 temptr = temp->nextaccess;
514 if ( addnewaccess( temp ) )
515 temp->nextaccess = nil;
516 temp = temptr;
519 updateassoc(); /* update association list */
521 updatelocks(); /* update locks */
523 /* update state attribution */
524 if (chgheadstate) {
525 /* change state of default branch or head */
526 if (Dbranch==nil) {
527 if (Head==nil)
528 warn("Can't change states in an empty tree");
529 else Head->state = headstate;
530 } else {
531 rcs_setstate(Dbranch->num,headstate); /* Can't set directly */
534 curstate = statelst;
535 while( curstate ) {
536 rcs_setstate(curstate->revno,curstate->status);
537 curstate = curstate->nextstatus;
540 cuthead = cuttail = nil;
541 if ( delrev && removerevs()) {
542 /* rebuild delta tree if some deltas are deleted */
543 if ( cuttail )
544 VOID genrevs(cuttail->num, (char *)nil,(char *)nil,
545 (char *)nil, gendeltas);
546 buildtree();
550 /* prepare for rewriting the RCS file */
551 newRCSfilename=mktempfile(RCSfilename,NEWRCSFILE);
552 oldumask = umask(0222); /* turn off write bits */
553 if ((frewrite=fopen(newRCSfilename, "w"))==NULL) {
554 VOID fclose(finptr);
555 error("Can't open file %s",newRCSfilename);
556 continue;
558 VOID umask(oldumask);
559 putadmin(frewrite);
560 if ( Head )
561 puttree(Head, frewrite);
562 putdesc(initflag,textflag,textfile,quietflag);
563 rewriteflag = false;
565 if ( Head) {
566 if (!delrev) {
567 /* no revision deleted */
568 fastcopy(finptr,frewrite);
569 } else {
570 if ( cuttail )
571 buildeltatext(gendeltas);
572 else
573 scanlogtext((struct hshentry *)nil,empty);
574 /* copy rest of delta text nodes that are not deleted */
577 ffclose(frewrite); frewrite = NULL;
578 if ( ! nerror ) { /* move temporary file to RCS file if no error */
579 ignoreints(); /* ignore interrupts */
580 if(rename(newRCSfilename,RCSfilename)<0) {
581 error("Can't create RCS file %s; saved in %s",
582 RCSfilename, newRCSfilename);
583 newRCSfilename[0] = '\0'; /* avoid deletion by cleanup */
584 restoreints();
585 VOID cleanup();
586 break;
588 newRCSfilename[0]='\0'; /* avoid re-unlinking by cleanup()*/
589 /* update mode */
590 result=0;
591 if (!initflag) /* preserve mode bits */
592 result=chmod(RCSfilename,RCSstat.st_mode & ~0222);
593 elsif (haveworkstat==0) /* initialization, and work file exists */
594 result=chmod(RCSfilename,workstat.st_mode & ~0222);
595 if (result<0) warn("Can't set mode of %s",RCSfilename);
597 restoreints(); /* catch them all again */
598 diagnose("done");
599 } else {
600 diagnose("%s aborted; %s unchanged.",cmdid,RCSfilename);
601 status = 1;
602 nerror = 0;
604 } while (cleanup(),
605 ++argv, --argc >=1);
607 exit(status);
608 } /* end of main (rcs) */
612 getassoclst(flag, sp)
613 int flag;
614 char * sp;
615 /* Function: associate a symbolic name to a revision or branch, */
616 /* and store in assoclst */
619 struct Symrev * pt;
620 char * temp, *temp2;
621 int c;
623 while( (c=(*++sp)) == ' ' || c == '\t' || c =='\n') ;
624 temp = sp;
625 temp2=checkid(sp, ':'); /* check for invalid symbolic name */
626 sp = temp2; c = *sp; *sp = '\0';
627 while( c == ' ' || c == '\t' || c == '\n') c = *++sp;
629 if ( c != ':' && c != '\0') {
630 error("Invalid string %s after option -n or -N",sp);
631 return;
634 pt = (struct Symrev *)talloc(sizeof(struct Symrev));
635 pt->ssymbol = temp;
636 pt->override = flag;
637 if (c == '\0') /* delete symbol */
638 pt->revno = nil;
639 else {
640 while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
641 if ( c == '\0' )
642 pt->revno = nil;
643 else
644 pt->revno = sp;
646 pt->nextsym = nil;
647 if (lastassoc)
648 lastassoc->nextsym = pt;
649 else
650 assoclst = pt;
651 lastassoc = pt;
652 return;
657 struct access * getaccessor( sp)
658 char *sp;
659 /* Function: get the accessor list of options -e and -a, */
660 /* and store in curpt */
664 struct access * curpt, * pt, *pre;
665 char *temp;
666 register c;
668 while( ( c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') ;
669 if ( c == '\0') {
670 error("Missing login name after option -a or -e");
671 return nil;
674 curpt = pt = nil;
675 while( c != '\0') {
676 temp=checkid(sp,',');
677 pt = (struct access *)talloc(sizeof(struct access));
678 pt->login = sp;
679 if ( curpt )
680 pre->nextaccess = pt;
681 else
682 curpt = pt;
683 pt->nextaccess = curpt;
684 pre = pt;
685 sp = temp; c = *sp; *sp = '\0';
686 while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
688 return pt;
693 getstates(sp)
694 char *sp;
695 /* Function: get one state attribute and the corresponding */
696 /* revision and store in statelst */
699 char *temp, *temp2;
700 struct Status *pt;
701 register c;
703 while( (c=(*++sp)) ==' ' || c == '\t' || c == '\n') ;
704 temp = sp;
705 temp2=checkid(sp,':'); /* check for invalid state attribute */
706 sp = temp2; c = *sp; *sp = '\0';
707 while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp;
709 if ( c == '\0' ) { /* change state of def. branch or Head */
710 chgheadstate = true;
711 headstate = temp;
712 return;
714 else if ( c != ':' ) {
715 error("Missing ':' after state in option -s");
716 return;
719 while( (c = *++sp) == ' ' || c == '\t' || c == '\n') ;
720 pt = (struct Status *)talloc(sizeof(struct Status));
721 pt->status = temp;
722 pt->revno = sp;
723 pt->nextstatus = nil;
724 if (laststate)
725 laststate->nextstatus = pt;
726 else
727 statelst = pt;
728 laststate = pt;
733 getrplaccess()
734 /* Function : get the accesslist of the 'accessfile' */
735 /* and place in rplaccessor */
737 register char *id, *nextp;
738 struct access *newaccess, *oldaccess;
740 if ( (finptr=fopen(accessfile, "r")) == NULL) {
741 faterror("Can't open file %s", accessfile);
743 Lexinit();
744 nextp = &accessorlst[0];
746 if ( ! getkey(Khead)) faterror("Missing head in %s", accessfile);
747 VOID getnum();
748 if ( ! getlex(SEMI) ) serror("Missing ';' after head in %s",accessfile);
750 if (getkey(Kbranch)) { /* optional */
751 Dbranch=getnum();
752 if (!getlex(SEMI)) serror("Missing ';' after branch list");
756 #ifdef COMPAT2
757 /* read suffix. Only in release 2 format */
758 if (getkey(Ksuffix)) {
759 if (nexttok==STRING) {
760 readstring(); nextlex(); /*through away the suffix*/
761 } elsif(nexttok==ID) {
762 nextlex();
764 if ( ! getlex(SEMI) ) serror("Missing ';' after suffix in %s",accessfile);
766 #endif
768 if (! getkey(Kaccess))fatserror("Missing access list in %s",accessfile);
769 oldaccess = nil;
770 while( id =getid() ) {
771 newaccess = (struct access *)talloc(sizeof(struct access));
772 newaccess->login = nextp;
773 newaccess->nextaccess = nil;
774 while( ( *nextp++ = *id++) != '\0') ;
775 if ( oldaccess )
776 oldaccess->nextaccess = newaccess;
777 else
778 rplaccessor = newaccess;
779 oldaccess = newaccess;
781 if ( ! getlex(SEMI))serror("Missing ';' after access list in %s",accessfile);
782 return;
787 getdelrev(sp)
788 char *sp;
789 /* Function: get revision range or branch to be deleted, */
790 /* and place in delrev */
792 int c;
793 struct delrevpair *pt;
795 if (delrev) free((char *)delrev);
797 pt = (struct delrevpair *)talloc(sizeof(struct delrevpair));
798 while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
800 if ( c == '<' || c == '-' ) { /* -o -rev or <rev */
801 while( (c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
802 pt->strt = sp; pt->code = 1;
803 while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
804 *sp = '\0';
805 pt->end = nil; delrev = pt;
806 return;
808 else {
809 pt->strt = sp;
810 while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
811 && c != '-' && c != '<' ) c = *++sp;
812 *sp = '\0';
813 while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp;
814 if ( c == '\0' ) { /* -o rev or branch */
815 pt->end = nil; pt->code = 0;
816 delrev = pt;
817 return;
819 if ( c != '-' && c != '<') {
820 faterror("Invalid range %s %s after -o", pt->strt, sp);
821 free((char *)pt);
822 return;
824 while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
825 if ( c == '\0') { /* -o rev- or rev< */
826 pt->end = nil; pt->code = 2;
827 delrev = pt;
828 return;
831 /* -o rev1-rev2 or rev1<rev2 */
832 pt->end = sp; pt->code = 3; delrev = pt;
833 while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
834 *sp = '\0';
840 scanlogtext(delta,func)
841 struct hshentry * delta; enum stringwork func;
842 /* Function: Scans delta text nodes up to and including the one given
843 * by delta, or up to last one present, if delta==nil.
844 * For the one given by delta (if delta!=nil), the log message is saved into
845 * curlogmsg and the text is processed according to parameter func.
846 * Assumes the initial lexeme must be read in first.
847 * Does not advance nexttok after it is finished, except if delta==nil.
849 { struct hshentry * nextdelta;
851 do {
852 rewriteflag = false;
853 nextlex();
854 if (!(nextdelta=getnum())) {
855 if(delta)
856 faterror("Can't find delta for revision %s", delta->num);
857 else return; /* no more delta text nodes */
859 if ( nextdelta->selector != DELETE) {
860 rewriteflag = true;
861 VOID fprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
863 if (!getkey(Klog) || nexttok!=STRING)
864 serror("Missing log entry");
865 elsif (delta==nextdelta) {
866 VOID savestring(curlogmsg,logsize);
867 delta->log=curlogmsg;
868 } else {readstring();
869 if (delta!=nil) delta->log="";
871 nextlex();
872 if (!getkey(Ktext) || nexttok!=STRING)
873 fatserror("Missing delta text");
875 if(delta==nextdelta)
876 /* got the one we're looking for */
877 switch (func) {
878 case copy: copystring();
879 break;
880 case edit: editstring((struct hshentry *)nil);
881 break;
882 default: faterror("Wrong scanlogtext");
884 else readstring(); /* skip over it */
886 } while (delta!=nextdelta);
891 releaselst(sourcelst)
892 struct access * sourcelst;
893 /* Function: release the storages whose address are in sourcelst */
896 struct access * pt;
898 pt = sourcelst;
899 while(pt) {
900 struct access *pn = pt->nextaccess;
901 free((char *)pt);
902 pt = pn;
908 struct Lockrev * rmnewlocklst(which)
909 struct Lockrev * which;
910 /* Function: remove lock to revision which->revno from newlocklst */
913 struct Lockrev * pt, *pre;
915 while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){
916 struct Lockrev *pn = newlocklst->nextrev;
917 free((char *)newlocklst);
918 newlocklst = pn;
921 pt = pre = newlocklst;
922 while( pt ) {
923 if ( ! strcmp(pt->revno, which->revno) ) {
924 pre->nextrev = pt->nextrev;
925 free((char *)pt);
926 pt = pre->nextrev;
928 else {
929 pre = pt;
930 pt = pt->nextrev;
933 return pre;
938 struct access * removeaccess( who, sourcelst,flag)
939 struct access * who, * sourcelst;
940 int flag;
941 /* Function: remove the accessor-- who from sourcelst */
944 struct access *pt, *pre;
946 pt = sourcelst;
947 while( pt && (! strcmp(who->login, pt->login) )) {
948 pre = pt->nextaccess;
949 free((char *)pt);
950 pt = pre;
951 flag = false;
953 pre = sourcelst = pt;
954 while( pt ) {
955 if ( ! strcmp(who->login, pt->login) ) {
956 pre->nextaccess = pt->nextaccess;
957 free((char *)pt);
958 pt = pre->nextaccess;
959 flag = false;
961 else {
962 pre = pt;
963 pt = pt->nextaccess;
966 if ( flag ) warn("Can't remove a nonexisting accessor %s",who->login);
967 return sourcelst;
972 int addnewaccess( who )
973 struct access * who;
974 /* Function: add new accessor-- who into AccessList */
977 struct access *pt, *pre;
979 pre = pt = AccessList;
981 while( pt ) {
982 if ( strcmp( who->login, pt->login) ) {
983 pre = pt;
984 pt = pt->nextaccess;
986 else
987 return 0;
989 if ( pre == pt )
990 AccessList = who;
991 else
992 pre->nextaccess = who;
993 return 1;
997 sendmail(Delta, who)
998 char * Delta, *who;
999 /* Function: mail to who, informing him that his lock on delta was
1000 * broken by caller. Ask first whether to go ahead. Return false on
1001 * error or if user decides not to break the lock.
1004 char * messagefile;
1005 int old1, old2, c, response;
1006 FILE * mailmess;
1009 VOID fprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
1010 VOID fprintf(stderr, "Do you want to break the lock? [ny](n): ");
1011 response=c=getchar();
1012 while (!(c==EOF || c=='\n')) c=getchar();/*skip to end of line*/
1013 if (response=='\n'||response=='n'||response=='N') return false;
1015 /* go ahead with breaking */
1016 messagefile=mktempfile("/tmp/", "RCSmailXXXXXX");
1017 if ( (mailmess = fopen(messagefile, "w")) == NULL) {
1018 faterror("Can't open file %s", messagefile);
1021 VOID fprintf(mailmess, "Subject: Broken lock on %s\n\n",bindex(RCSfilename,'/'));
1022 VOID fprintf(mailmess, "Your lock on revision %s of file %s\n",Delta, getfullRCSname());
1023 VOID fprintf(mailmess,"has been broken by %s for the following reason:\n",caller);
1024 VOID fputs("State the reason for breaking the lock:\n", stderr);
1025 VOID fputs("(terminate with ^D or single '.')\n>> ", stderr);
1027 old1 = '\n'; old2 = ' ';
1028 for (; ;) {
1029 c = getchar();
1030 if ( c == EOF ) {
1031 VOID putc('\n',stderr);
1032 VOID fprintf(mailmess, "%c\n", old1);
1033 break;
1035 else if ( c == '\n' && old1 == '.' && old2 == '\n')
1036 break;
1037 else {
1038 VOID fputc( old1, mailmess);
1039 old2 = old1; old1 = c;
1040 if (c== '\n') VOID fputs(">> ", stderr);
1043 ffclose(mailmess);
1045 /* ignore the exit status, even if delivermail unsuccessful */
1046 VOID run(messagefile,(char*)nil,
1047 _PATH_SENDMAIL,
1048 who,(char*)nil);
1049 VOID unlink(messagefile);
1050 return(true);
1055 static breaklock(who,delta)
1056 char * who; struct hshentry * delta;
1057 /* function: Finds the lock held by who on delta,
1058 * and removes it.
1059 * Sends mail if a lock different from the caller's is broken.
1060 * Prints an error message if there is no such lock or error.
1063 register struct lock * next, * trail;
1064 char * num;
1065 struct lock dummy;
1066 int whor, numr;
1068 num=delta->num;
1069 dummy.nextlock=next=Locks;
1070 trail = &dummy;
1071 while (next!=nil) {
1072 if (num != nil)
1073 numr = strcmp(num, next->delta->num);
1075 whor=strcmp(who,next->login);
1076 if (whor==0 && numr==0) break; /* exact match */
1077 if (numr==0 && whor !=0) {
1078 if (!sendmail( num, next->login)){
1079 diagnose("%s still locked by %s",num,next->login);
1080 return;
1081 } else break; /* continue after loop */
1083 trail=next;
1084 next=next->nextlock;
1086 if (next!=nil) {
1087 /*found one */
1088 diagnose("%s unlocked",next->delta->num);
1089 trail->nextlock=next->nextlock;
1090 next->delta->lockedby=nil;
1091 Locks=dummy.nextlock;
1092 } else {
1093 error("no lock set on revision %s", num);
1099 struct hshentry *searchcutpt(object, length, store)
1100 char * object;
1101 int length;
1102 struct hshentry * * store;
1103 /* Function: Search store and return entry with number being object. */
1104 /* cuttail = nil, if the entry is Head; otherwise, cuttail */
1105 /* is the entry point to the one with number being object */
1108 while( compartial( (*store++)->num, object, length) ) ;
1109 store--;
1111 if ( *store == Head)
1112 cuthead = nil;
1113 else
1114 cuthead = *(store -1);
1115 return *store;
1120 int branchpoint(strt, tail)
1121 struct hshentry *strt, *tail;
1122 /* Function: check whether the deltas between strt and tail */
1123 /* are locked or branch point, return 1 if any is */
1124 /* locked or branch point; otherwise, return 0 and */
1125 /* mark DELETE on selector */
1128 struct hshentry *pt;
1129 struct lock *lockpt;
1130 int flag;
1133 pt = strt;
1134 flag = false;
1135 while( pt != tail) {
1136 if ( pt->branches ){ /* a branch point */
1137 flag = true;
1138 error("Can't remove branch point %s", pt->num);
1140 lockpt = Locks;
1141 while(lockpt && lockpt->delta != pt)
1142 lockpt = lockpt->nextlock;
1143 if ( lockpt ) {
1144 flag = true;
1145 error("Can't remove locked revision %s",pt->num);
1147 pt = pt->next;
1150 if ( ! flag ) {
1151 pt = strt;
1152 while( pt != tail ) {
1153 pt->selector = DELETE;
1154 diagnose("deleting revision %s ",pt->num);
1155 pt = pt->next;
1158 return flag;
1163 removerevs()
1164 /* Function: get the revision range to be removed, and place the */
1165 /* first revision removed in delstrt, the revision before */
1166 /* delstrt in cuthead( nil, if delstrt is head), and the */
1167 /* revision after the last removed revision in cuttail(nil */
1168 /* if the last is a leaf */
1171 struct hshentry *target, *target2, * temp, *searchcutpt();
1172 int length, flag;
1174 flag = false;
1175 if ( ! expandsym(delrev->strt, &numrev[0]) ) return 0;
1176 target = genrevs(&numrev[0], (char *)nil, (char *)nil, (char *)nil, gendeltas);
1177 if ( ! target ) return 0;
1178 if ( cmpnum(target->num, &numrev[0]) ) flag = true;
1179 length = countnumflds( &numrev[0] );
1181 if ( delrev->code == 0 ) { /* -o rev or -o branch */
1182 if ( length % 2)
1183 temp=searchcutpt(target->num,length+1,gendeltas);
1184 else if (flag) {
1185 error("Revision %s does not exist", &numrev[0]);
1186 return 0;
1188 else
1189 temp = searchcutpt(&numrev[0],length,gendeltas);
1190 cuttail = target->next;
1191 if ( branchpoint(temp, cuttail) ) {
1192 cuttail = nil;
1193 return 0;
1195 delstrt = temp; /* first revision to be removed */
1196 return 1;
1199 if ( length % 2 ) { /* invalid branch after -o */
1200 error("Invalid branch range %s after -o", &numrev[0]);
1201 return 0;
1204 if ( delrev->code == 1 ) { /* -o -rev */
1205 if ( length > 2 ) {
1206 temp = searchcutpt( target->num, length-1, gendeltas);
1207 cuttail = target->next;
1209 else {
1210 temp = searchcutpt(target->num, length, gendeltas);
1211 cuttail = target;
1212 while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
1213 cuttail = cuttail->next;
1215 if ( branchpoint(temp, cuttail) ){
1216 cuttail = nil;
1217 return 0;
1219 delstrt = temp;
1220 return 1;
1223 if ( delrev->code == 2 ) { /* -o rev- */
1224 if ( length == 2 ) {
1225 temp = searchcutpt(target->num, 1,gendeltas);
1226 if ( flag)
1227 cuttail = target;
1228 else
1229 cuttail = target->next;
1231 else {
1232 if ( flag){
1233 cuthead = target;
1234 if ( !(temp = target->next) ) return 0;
1236 else
1237 temp = searchcutpt(target->num, length, gendeltas);
1238 getbranchno(temp->num, &numrev[0]); /* get branch number */
1239 target = genrevs(&numrev[0], (char *)nil, (char *)nil, (char *)nil, gendeltas);
1241 if ( branchpoint( temp, cuttail ) ) {
1242 cuttail = nil;
1243 return 0;
1245 delstrt = temp;
1246 return 1;
1249 /* -o rev1-rev2 */
1250 if ( ! expandsym(delrev->end, &numrev[0]) ) return 0;
1251 if ( length != countnumflds( &numrev[0] ) ) {
1252 error("Invalid revision range %s-%s", target->num, &numrev[0]);
1253 return 0;
1255 if ( length > 2 && compartial( &numrev[0], target->num, length-1) ) {
1256 error("Invalid revision range %s-%s", target->num, &numrev[0]);
1257 return 0;
1260 target2 = genrevs( &numrev[0], (char *)nil, (char *)nil, (char *)nil,gendeltas);
1261 if ( ! target2 ) return 0;
1263 if ( length > 2) { /* delete revisions on branches */
1264 if ( cmpnum(target->num, target2->num) > 0) {
1265 if ( cmpnum(target2->num, &numrev[0]) )
1266 flag = true;
1267 else
1268 flag = false;
1269 temp = target;
1270 target = target2;
1271 target2 = temp;
1273 if ( flag ) {
1274 if ( ! cmpnum(target->num, target2->num) ) {
1275 error("Revisions %s-%s don't exist", delrev->strt,delrev->end);
1276 return 0;
1278 cuthead = target;
1279 temp = target->next;
1281 else
1282 temp = searchcutpt(target->num, length, gendeltas);
1283 cuttail = target2->next;
1285 else { /* delete revisions on trunk */
1286 if ( cmpnum( target->num, target2->num) < 0 ) {
1287 temp = target;
1288 target = target2;
1289 target2 = temp;
1291 else
1292 if ( cmpnum(target2->num, &numrev[0]) )
1293 flag = true;
1294 else
1295 flag = false;
1296 if ( flag ) {
1297 if ( ! cmpnum(target->num, target2->num) ) {
1298 error("Revisions %s-%s don't exist", delrev->strt, delrev->end);
1299 return 0;
1301 cuttail = target2;
1303 else
1304 cuttail = target2->next;
1305 temp = searchcutpt(target->num, length, gendeltas);
1307 if ( branchpoint(temp, cuttail) ) {
1308 cuttail = nil;
1309 return 0;
1311 delstrt = temp;
1312 return 1;
1317 updateassoc()
1318 /* Function: add or delete(if revno is nil) association */
1319 /* which is stored in assoclst */
1322 struct Symrev * curassoc;
1323 struct assoc * pre, * pt;
1324 struct hshentry * target;
1326 /* add new associations */
1327 curassoc = assoclst;
1328 while( curassoc ) {
1329 if ( curassoc->revno == nil ) { /* delete symbol */
1330 pre = pt = Symbols;
1331 while( pt && strcmp(pt->symbol,curassoc->ssymbol) ) {
1332 pre = pt;
1333 pt = pt->nextassoc;
1335 if ( pt )
1336 if ( pre == pt )
1337 Symbols = pt->nextassoc;
1338 else
1339 pre->nextassoc = pt->nextassoc;
1340 else
1341 warn("Can't delete nonexisting symbol %s",curassoc->ssymbol);
1343 else if ( expandsym( curassoc->revno, &numrev[0] ) ) {
1344 /* add symbol */
1345 target = (struct hshentry *) talloc(sizeof(struct hshentry));
1346 target->num = &numrev[0];
1347 VOID addsymbol(target, curassoc->ssymbol, curassoc->override);
1349 curassoc = curassoc->nextsym;
1356 updatelocks()
1357 /* Function: remove lock for caller or first lock if unlockcaller==true;
1358 * remove locks which are stored in rmvlocklst,
1359 * add new locks which are stored in newlocklst,
1360 * add lock for Dbranch or Head if lockhead==true.
1363 struct hshentry *target;
1364 struct Lockrev *lockpt;
1366 if(unlockcaller == true) { /* find lock for caller */
1367 if ( Head ) {
1368 if (Locks) {
1369 target=findlock(caller,true);
1370 if (target==nil) {
1371 breaklock(caller, Locks->delta); /* remove most recent lock */
1372 } else {
1373 diagnose("%s unlocked",target->num);
1375 } else {
1376 warn("There are no locks set.");
1378 } else {
1379 warn("Can't unlock an empty tree");
1383 /* remove locks which are stored in rmvlocklst */
1384 lockpt = rmvlocklst;
1385 while( lockpt ) {
1386 if (expandsym(lockpt->revno, numrev)) {
1387 target = genrevs(numrev, (char *)nil, (char *)nil, (char *)nil, gendeltas);
1388 if ( target )
1389 if ( !(countnumflds(numrev)%2) && cmpnum(target->num,numrev))
1390 error("Can't unlock nonexisting revision %s",lockpt->revno);
1391 else
1392 breaklock(caller, target);
1393 /* breaklock does its own diagnose */
1395 lockpt = lockpt->nextrev;
1398 /* add new locks which stored in newlocklst */
1399 lockpt = newlocklst;
1400 while( lockpt ) {
1401 setlock(lockpt->revno,caller);
1402 lockpt = lockpt->nextrev;
1405 if ( lockhead == true) { /* lock default branch or head */
1406 if (Dbranch) {
1407 setlock(Dbranch->num,caller);
1408 } elsif ( Head) {
1409 if (addlock(Head, caller))
1410 diagnose("%s locked",Head->num);
1411 } else {
1412 warn("Can't lock an empty tree");
1420 setlock(rev,who)
1421 char * rev, * who;
1422 /* Function: Given a revision or branch number, finds the correponding
1423 * delta and locks it for who.
1426 struct lock *lpt;
1427 struct hshentry *target;
1429 if (expandsym(rev, &numrev[0]) ){
1430 target = genrevs(&numrev[0],(char *) nil,(char *) nil,
1431 (char *)nil, gendeltas);
1432 if ( target )
1433 if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num,&numrev[0]))
1434 error("Can't lock nonexisting revision %s",numrev);
1435 else
1436 if(lpt=addlock(target, who))
1437 diagnose("%s locked",lpt->delta->num);
1443 rcs_setstate(rev,status)
1444 char * rev, * status;
1445 /* Function: Given a revision or branch number, finds the corresponding delta
1446 * and sets its state to status.
1449 struct hshentry *target;
1451 if ( expandsym(rev, &numrev[0]) ) {
1452 target = genrevs(&numrev[0],(char *) nil, (char *)nil,
1453 (char *) nil, gendeltas);
1454 if ( target )
1455 if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num, &numrev[0]) )
1456 error("Can't set state of nonexisting revision %s to %s",
1457 numrev,status);
1458 else
1459 target->state = status;
1467 buildeltatext(deltas)
1468 struct hshentry ** deltas;
1469 /* Function: put the delta text on frewrite and make necessary */
1470 /* change to delta text */
1472 int i, c, exit_stats;
1474 cuttail->selector = DELETE;
1475 initeditfiles("/tmp/");
1476 scanlogtext(deltas[0], copy);
1477 i = 1;
1478 if ( cuthead ) {
1479 cutfilename=mktempfile("/tmp/", "RCScutXXXXXX");
1480 if ( (fcut = fopen(cutfilename, "w")) == NULL) {
1481 faterror("Can't open temporary file %s", cutfilename);
1484 while( deltas[i-1] != cuthead ) {
1485 scanlogtext(deltas[i++], edit);
1488 finishedit((struct hshentry *)nil); rewind(fcopy);
1489 while( (c = getc(fcopy)) != EOF) VOID putc(c, fcut);
1490 swapeditfiles(false);
1491 ffclose(fcut);
1494 while( deltas[i-1] != cuttail)
1495 scanlogtext(deltas[i++], edit);
1496 finishedit((struct hshentry *)nil); ffclose(fcopy);
1498 if ( cuthead ) {
1499 diffilename=mktempfile("/tmp/", "RCSdifXXXXXX");
1500 exit_stats = run((char*)nil,diffilename,
1501 DIFF,"-n",cutfilename,resultfile,(char*)nil);
1502 if (exit_stats != 0 && exit_stats != (1 << BYTESIZ))
1503 faterror ("diff failed");
1504 if(!putdtext(cuttail->num,curlogmsg,diffilename,frewrite)) return;
1506 else
1507 if (!putdtext(cuttail->num,curlogmsg,resultfile,frewrite)) return;
1509 scanlogtext((struct hshentry *)nil,empty); /* read the rest of the deltas */
1514 buildtree()
1515 /* Function: actually removes revisions whose selector field */
1516 /* is DELETE, and rebuilds the linkage of deltas. */
1517 /* asks for reconfirmation if deleting last revision*/
1519 int c, response;
1521 struct hshentry * Delta;
1522 struct branchhead *pt, *pre;
1524 if ( cuthead )
1525 if ( cuthead->next == delstrt )
1526 cuthead->next = cuttail;
1527 else {
1528 pre = pt = cuthead->branches;
1529 while( pt && pt->hsh != delstrt ) {
1530 pre = pt;
1531 pt = pt->nextbranch;
1533 if ( cuttail )
1534 pt->hsh = cuttail;
1535 else if ( pt == pre )
1536 cuthead->branches = pt->nextbranch;
1537 else
1538 pre->nextbranch = pt->nextbranch;
1540 else {
1541 if ( cuttail == nil && !quietflag) {
1542 VOID fprintf(stderr,"Do you really want to delete all revisions ?[ny](n): ");
1543 c = response = getchar();
1544 while( c != EOF && c != '\n') c = getchar();
1545 if ( response != 'y' && response != 'Y') {
1546 diagnose("No revision deleted");
1547 Delta = delstrt;
1548 while( Delta) {
1549 Delta->selector = 'S';
1550 Delta = Delta->next;
1552 return;
1555 Head = cuttail;
1557 return;