2 * RCS create/change operation
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";
8 /* Copyright (C) 1982, 1988, 1989 Walter Tichy
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
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
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
61 * Revision 1.4 87/09/24 13:58:52 narten
62 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
65 * Revision 1.3 87/03/27 14:21:55 jenkins
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
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>
139 static char rcsbaseid
[] = RCSBASE
;
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
[];
150 extern char Ksuffix
[];
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 */
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
];
189 struct Lockrev
* nextrev
;
196 struct Symrev
* nextsym
;
202 struct Status
* nextstatus
;
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
;
220 int lockhead
,unlockcaller
,chgheadstate
,branchflag
,commentflag
;
222 enum stringwork
{copy
, edit
, empty
}; /* expand and edit_expand not needed */
231 struct access
*removeaccess(), * getaccessor();
232 struct Lockrev
*rmnewlocklst();
233 struct Lockrev
*curlock
, * rmvlock
, *lockpt
;
234 struct Status
* curstate
;
235 struct access
*temp
, *temptr
;
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;
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 */
266 case 'b': /* change default branch */
267 if (branchflag
)warn("Redfinition of option -b");
269 branchsym
= (*argv
)+2;
272 case 'c': /* change comment symbol */
273 if (commentflag
)warn("Redefinition of option -c");
275 commsyml
= (*argv
)+2;
278 case 'a': /* add new accessor */
279 if ( (*argv
)[2] == '\0') {
280 error("Login name missing after -a");
282 if ( (temp
= getaccessor((*argv
)+1)) ) {
284 curaccess
->nextaccess
= temp
->nextaccess
;
286 newaccessor
= temp
->nextaccess
;
287 temp
->nextaccess
= nil
;
292 case 'A': /* append access list according to accessfile */
293 if ( (*argv
)[2] == '\0') {
294 error("Missing file name after -A");
297 if ( accessfile
) warn("Redefinition of option -A");
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
;
310 case 'e': /* remove accessors */
311 if ( (*argv
)[2] == '\0' ) {
312 delaccessflag
= true;
315 if ( (temp
= getaccessor((*argv
)+1)) ) {
317 rmaccess
->nextaccess
= temp
->nextaccess
;
319 rmvaccessor
= temp
->nextaccess
;
320 temptr
= temp
->nextaccess
;
321 temp
->nextaccess
= nil
;
324 newaccessor
= removeaccess(temptr
,newaccessor
,false);
325 temptr
= temptr
->nextaccess
;
327 curaccess
= temp
= newaccessor
;
330 temp
= temp
->nextaccess
;
335 case 'l': /* lock a revision if it is unlocked */
336 if ( (*argv
)[2] == '\0'){ /* lock head or def. branch */
340 lockpt
= (struct Lockrev
*)talloc(sizeof(struct Lockrev
));
341 lockpt
->revno
= (*argv
)+2;
342 lockpt
->nextrev
= nil
;
344 curlock
->nextrev
= lockpt
;
350 case 'u': /* release lock of a locked revision */
351 if ( (*argv
)[2] == '\0'){ /* unlock head */
355 lockpt
= (struct Lockrev
*)talloc(sizeof(struct Lockrev
));
356 lockpt
->revno
= (*argv
)+2;
357 lockpt
->nextrev
= nil
;
359 rmvlock
->nextrev
= lockpt
;
364 curlock
= rmnewlocklst(lockpt
);
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");
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");
384 case 'n': /* add new association: error, if name exists */
385 if ( (*argv
)[2] == '\0') {
386 error("Missing symbolic name after -n");
389 getassoclst(false, (*argv
)+1);
392 case 'N': /* add or change association */
393 if ( (*argv
)[2] == '\0') {
394 error("Missing symbolic name after -N");
397 getassoclst(true, (*argv
)+1);
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");
406 getdelrev( (*argv
)+1 );
409 case 's': /* change state attribute of a revision */
410 if ( (*argv
)[2] == '\0') {
411 error("State missing after -s");
414 getstates( (*argv
)+1);
417 case 't': /* change descriptive text */
419 if ((*argv
)[2]!='\0'){
420 if (textfile
!=nil
)warn("Redefinition of -t option");
421 textfile
= (*argv
)+2;
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
);
438 if (accessfile
) /* get replacement for access list */
441 diagnose("%s aborted",cmdid
);
445 /* now handle all filenames */
448 finptr
=frewrite
=NULL
;
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
);
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
;
495 /* update access list */
496 if ( delaccessflag
) AccessList
= nil
;
500 temptr
= temp
->nextaccess
;
501 if ( addnewaccess(temp
) )
502 temp
->nextaccess
= nil
;
507 while(temp
) { /* remove accessors from accesslist */
508 AccessList
= removeaccess(temp
, AccessList
,true);
509 temp
= temp
->nextaccess
;
512 while( temp
) { /* add new accessors */
513 temptr
= temp
->nextaccess
;
514 if ( addnewaccess( temp
) )
515 temp
->nextaccess
= nil
;
519 updateassoc(); /* update association list */
521 updatelocks(); /* update locks */
523 /* update state attribution */
525 /* change state of default branch or head */
528 warn("Can't change states in an empty tree");
529 else Head
->state
= headstate
;
531 rcs_setstate(Dbranch
->num
,headstate
); /* Can't set directly */
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 */
544 VOID
genrevs(cuttail
->num
, (char *)nil
,(char *)nil
,
545 (char *)nil
, gendeltas
);
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
) {
555 error("Can't open file %s",newRCSfilename
);
558 VOID
umask(oldumask
);
561 puttree(Head
, frewrite
);
562 putdesc(initflag
,textflag
,textfile
,quietflag
);
567 /* no revision deleted */
568 fastcopy(finptr
,frewrite
);
571 buildeltatext(gendeltas
);
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 */
588 newRCSfilename
[0]='\0'; /* avoid re-unlinking by cleanup()*/
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 */
600 diagnose("%s aborted; %s unchanged.",cmdid
,RCSfilename
);
608 } /* end of main (rcs) */
612 getassoclst(flag
, sp
)
615 /* Function: associate a symbolic name to a revision or branch, */
616 /* and store in assoclst */
623 while( (c
=(*++sp
)) == ' ' || c
== '\t' || c
=='\n') ;
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
);
634 pt
= (struct Symrev
*)talloc(sizeof(struct Symrev
));
637 if (c
== '\0') /* delete symbol */
640 while( (c
= *++sp
) == ' ' || c
== '\n' || c
== '\t') ;
648 lastassoc
->nextsym
= pt
;
657 struct access
* getaccessor( sp
)
659 /* Function: get the accessor list of options -e and -a, */
660 /* and store in curpt */
664 struct access
* curpt
, * pt
, *pre
;
668 while( ( c
= *++sp
) == ' ' || c
== '\n' || c
== '\t' || c
== ',') ;
670 error("Missing login name after option -a or -e");
676 temp
=checkid(sp
,',');
677 pt
= (struct access
*)talloc(sizeof(struct access
));
680 pre
->nextaccess
= pt
;
683 pt
->nextaccess
= curpt
;
685 sp
= temp
; c
= *sp
; *sp
= '\0';
686 while( c
== ' ' || c
== '\n' || c
== '\t'|| c
== ',')c
=(*++sp
);
695 /* Function: get one state attribute and the corresponding */
696 /* revision and store in statelst */
703 while( (c
=(*++sp
)) ==' ' || c
== '\t' || c
== '\n') ;
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 */
714 else if ( c
!= ':' ) {
715 error("Missing ':' after state in option -s");
719 while( (c
= *++sp
) == ' ' || c
== '\t' || c
== '\n') ;
720 pt
= (struct Status
*)talloc(sizeof(struct Status
));
723 pt
->nextstatus
= nil
;
725 laststate
->nextstatus
= pt
;
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
);
744 nextp
= &accessorlst
[0];
746 if ( ! getkey(Khead
)) faterror("Missing head in %s", accessfile
);
748 if ( ! getlex(SEMI
) ) serror("Missing ';' after head in %s",accessfile
);
750 if (getkey(Kbranch
)) { /* optional */
752 if (!getlex(SEMI
)) serror("Missing ';' after branch list");
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
) {
764 if ( ! getlex(SEMI
) ) serror("Missing ';' after suffix in %s",accessfile
);
768 if (! getkey(Kaccess
))fatserror("Missing access list in %s",accessfile
);
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') ;
776 oldaccess
->nextaccess
= newaccess
;
778 rplaccessor
= newaccess
;
779 oldaccess
= newaccess
;
781 if ( ! getlex(SEMI
))serror("Missing ';' after access list in %s",accessfile
);
789 /* Function: get revision range or branch to be deleted, */
790 /* and place in delrev */
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
);
805 pt
->end
= nil
; delrev
= pt
;
810 while( c
!= ' ' && c
!= '\n' && c
!= '\t' && c
!= '\0'
811 && c
!= '-' && c
!= '<' ) c
= *++sp
;
813 while( c
== ' ' || c
== '\n' || c
== '\t' ) c
= *++sp
;
814 if ( c
== '\0' ) { /* -o rev or branch */
815 pt
->end
= nil
; pt
->code
= 0;
819 if ( c
!= '-' && c
!= '<') {
820 faterror("Invalid range %s %s after -o", pt
->strt
, sp
);
824 while( (c
= *++sp
) == ' ' || c
== '\n' || c
== '\t') ;
825 if ( c
== '\0') { /* -o rev- or rev< */
826 pt
->end
= nil
; pt
->code
= 2;
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
;
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
;
854 if (!(nextdelta
=getnum())) {
856 faterror("Can't find delta for revision %s", delta
->num
);
857 else return; /* no more delta text nodes */
859 if ( nextdelta
->selector
!= DELETE
) {
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
="";
872 if (!getkey(Ktext
) || nexttok
!=STRING
)
873 fatserror("Missing delta text");
876 /* got the one we're looking for */
878 case copy
: copystring();
880 case edit
: editstring((struct hshentry
*)nil
);
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 */
900 struct access
*pn
= pt
->nextaccess
;
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
);
921 pt
= pre
= newlocklst
;
923 if ( ! strcmp(pt
->revno
, which
->revno
) ) {
924 pre
->nextrev
= pt
->nextrev
;
938 struct access
* removeaccess( who
, sourcelst
,flag
)
939 struct access
* who
, * sourcelst
;
941 /* Function: remove the accessor-- who from sourcelst */
944 struct access
*pt
, *pre
;
947 while( pt
&& (! strcmp(who
->login
, pt
->login
) )) {
948 pre
= pt
->nextaccess
;
953 pre
= sourcelst
= pt
;
955 if ( ! strcmp(who
->login
, pt
->login
) ) {
956 pre
->nextaccess
= pt
->nextaccess
;
958 pt
= pre
->nextaccess
;
966 if ( flag
) warn("Can't remove a nonexisting accessor %s",who
->login
);
972 int addnewaccess( who
)
974 /* Function: add new accessor-- who into AccessList */
977 struct access
*pt
, *pre
;
979 pre
= pt
= AccessList
;
982 if ( strcmp( who
->login
, pt
->login
) ) {
992 pre
->nextaccess
= 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.
1005 int old1
, old2
, c
, response
;
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
= ' ';
1031 VOID
putc('\n',stderr
);
1032 VOID
fprintf(mailmess
, "%c\n", old1
);
1035 else if ( c
== '\n' && old1
== '.' && old2
== '\n')
1038 VOID
fputc( old1
, mailmess
);
1039 old2
= old1
; old1
= c
;
1040 if (c
== '\n') VOID
fputs(">> ", stderr
);
1045 /* ignore the exit status, even if delivermail unsuccessful */
1046 VOID
run(messagefile
,(char*)nil
,
1049 VOID
unlink(messagefile
);
1055 static breaklock(who
,delta
)
1056 char * who
; struct hshentry
* delta
;
1057 /* function: Finds the lock held by who on delta,
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
;
1069 dummy
.nextlock
=next
=Locks
;
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
);
1081 } else break; /* continue after loop */
1084 next
=next
->nextlock
;
1088 diagnose("%s unlocked",next
->delta
->num
);
1089 trail
->nextlock
=next
->nextlock
;
1090 next
->delta
->lockedby
=nil
;
1091 Locks
=dummy
.nextlock
;
1093 error("no lock set on revision %s", num
);
1099 struct hshentry
*searchcutpt(object
, length
, store
)
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
) ) ;
1111 if ( *store
== Head
)
1114 cuthead
= *(store
-1);
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
;
1135 while( pt
!= tail
) {
1136 if ( pt
->branches
){ /* a branch point */
1138 error("Can't remove branch point %s", pt
->num
);
1141 while(lockpt
&& lockpt
->delta
!= pt
)
1142 lockpt
= lockpt
->nextlock
;
1145 error("Can't remove locked revision %s",pt
->num
);
1152 while( pt
!= tail
) {
1153 pt
->selector
= DELETE
;
1154 diagnose("deleting revision %s ",pt
->num
);
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();
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 */
1183 temp
=searchcutpt(target
->num
,length
+1,gendeltas
);
1185 error("Revision %s does not exist", &numrev
[0]);
1189 temp
= searchcutpt(&numrev
[0],length
,gendeltas
);
1190 cuttail
= target
->next
;
1191 if ( branchpoint(temp
, cuttail
) ) {
1195 delstrt
= temp
; /* first revision to be removed */
1199 if ( length
% 2 ) { /* invalid branch after -o */
1200 error("Invalid branch range %s after -o", &numrev
[0]);
1204 if ( delrev
->code
== 1 ) { /* -o -rev */
1206 temp
= searchcutpt( target
->num
, length
-1, gendeltas
);
1207 cuttail
= target
->next
;
1210 temp
= searchcutpt(target
->num
, length
, gendeltas
);
1212 while( cuttail
&& ! cmpnumfld(target
->num
,cuttail
->num
,1) )
1213 cuttail
= cuttail
->next
;
1215 if ( branchpoint(temp
, cuttail
) ){
1223 if ( delrev
->code
== 2 ) { /* -o rev- */
1224 if ( length
== 2 ) {
1225 temp
= searchcutpt(target
->num
, 1,gendeltas
);
1229 cuttail
= target
->next
;
1234 if ( !(temp
= target
->next
) ) return 0;
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
) ) {
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]);
1255 if ( length
> 2 && compartial( &numrev
[0], target
->num
, length
-1) ) {
1256 error("Invalid revision range %s-%s", target
->num
, &numrev
[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]) )
1274 if ( ! cmpnum(target
->num
, target2
->num
) ) {
1275 error("Revisions %s-%s don't exist", delrev
->strt
,delrev
->end
);
1279 temp
= target
->next
;
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 ) {
1292 if ( cmpnum(target2
->num
, &numrev
[0]) )
1297 if ( ! cmpnum(target
->num
, target2
->num
) ) {
1298 error("Revisions %s-%s don't exist", delrev
->strt
, delrev
->end
);
1304 cuttail
= target2
->next
;
1305 temp
= searchcutpt(target
->num
, length
, gendeltas
);
1307 if ( branchpoint(temp
, cuttail
) ) {
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
;
1329 if ( curassoc
->revno
== nil
) { /* delete symbol */
1331 while( pt
&& strcmp(pt
->symbol
,curassoc
->ssymbol
) ) {
1337 Symbols
= pt
->nextassoc
;
1339 pre
->nextassoc
= pt
->nextassoc
;
1341 warn("Can't delete nonexisting symbol %s",curassoc
->ssymbol
);
1343 else if ( expandsym( curassoc
->revno
, &numrev
[0] ) ) {
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
;
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 */
1369 target
=findlock(caller
,true);
1371 breaklock(caller
, Locks
->delta
); /* remove most recent lock */
1373 diagnose("%s unlocked",target
->num
);
1376 warn("There are no locks set.");
1379 warn("Can't unlock an empty tree");
1383 /* remove locks which are stored in rmvlocklst */
1384 lockpt
= rmvlocklst
;
1386 if (expandsym(lockpt
->revno
, numrev
)) {
1387 target
= genrevs(numrev
, (char *)nil
, (char *)nil
, (char *)nil
, gendeltas
);
1389 if ( !(countnumflds(numrev
)%2) && cmpnum(target
->num
,numrev
))
1390 error("Can't unlock nonexisting revision %s",lockpt
->revno
);
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
;
1401 setlock(lockpt
->revno
,caller
);
1402 lockpt
= lockpt
->nextrev
;
1405 if ( lockhead
== true) { /* lock default branch or head */
1407 setlock(Dbranch
->num
,caller
);
1409 if (addlock(Head
, caller
))
1410 diagnose("%s locked",Head
->num
);
1412 warn("Can't lock an empty tree");
1422 /* Function: Given a revision or branch number, finds the correponding
1423 * delta and locks it for who.
1427 struct hshentry
*target
;
1429 if (expandsym(rev
, &numrev
[0]) ){
1430 target
= genrevs(&numrev
[0],(char *) nil
,(char *) nil
,
1431 (char *)nil
, gendeltas
);
1433 if ( !(countnumflds(&numrev
[0])%2) && cmpnum(target
->num
,&numrev
[0]))
1434 error("Can't lock nonexisting revision %s",numrev
);
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
);
1455 if ( !(countnumflds(&numrev
[0])%2) && cmpnum(target
->num
, &numrev
[0]) )
1456 error("Can't set state of nonexisting revision %s to %s",
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
);
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);
1494 while( deltas
[i
-1] != cuttail
)
1495 scanlogtext(deltas
[i
++], edit
);
1496 finishedit((struct hshentry
*)nil
); ffclose(fcopy
);
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;
1507 if (!putdtext(cuttail
->num
,curlogmsg
,resultfile
,frewrite
)) return;
1509 scanlogtext((struct hshentry
*)nil
,empty
); /* read the rest of the deltas */
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*/
1521 struct hshentry
* Delta
;
1522 struct branchhead
*pt
, *pre
;
1525 if ( cuthead
->next
== delstrt
)
1526 cuthead
->next
= cuttail
;
1528 pre
= pt
= cuthead
->branches
;
1529 while( pt
&& pt
->hsh
!= delstrt
) {
1531 pt
= pt
->nextbranch
;
1535 else if ( pt
== pre
)
1536 cuthead
->branches
= pt
->nextbranch
;
1538 pre
->nextbranch
= pt
->nextbranch
;
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");
1549 Delta
->selector
= 'S';
1550 Delta
= Delta
->next
;