111894Speter/* Change RCS file attributes. */ 211894Speter 311894Speter/* Copyright 1982, 1988, 1989 Walter Tichy 411894Speter Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert 59Sjkh Distributed under license by the Free Software Foundation, Inc. 69Sjkh 79SjkhThis file is part of RCS. 89Sjkh 99SjkhRCS is free software; you can redistribute it and/or modify 109Sjkhit under the terms of the GNU General Public License as published by 119Sjkhthe Free Software Foundation; either version 2, or (at your option) 129Sjkhany later version. 139Sjkh 149SjkhRCS is distributed in the hope that it will be useful, 159Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of 169SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 179SjkhGNU General Public License for more details. 189Sjkh 199SjkhYou should have received a copy of the GNU General Public License 2011894Speteralong with RCS; see the file COPYING. 2111894SpeterIf not, write to the Free Software Foundation, 2211894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 239Sjkh 249SjkhReport problems and direct all questions to: 259Sjkh 269Sjkh rcs-bugs@cs.purdue.edu 279Sjkh 289Sjkh*/ 299Sjkh 3011894Speter/* 3111894Speter * Revision 5.21 1995/06/16 06:19:24 eggert 3211894Speter * Update FSF address. 338858Srgrimes * 3411894Speter * Revision 5.20 1995/06/01 16:23:43 eggert 3511894Speter * (main): Warn if no options were given. Punctuate messages properly. 3611894Speter * 3711894Speter * (sendmail): Rewind mailmess before flushing it. 3811894Speter * Output another warning if mail should work but fails. 3911894Speter * 4011894Speter * (buildeltatext): Pass "--binary" if -kb and if --binary makes a difference. 4111894Speter * 4211894Speter * Revision 5.19 1994/03/17 14:05:48 eggert 4311894Speter * Use ORCSerror to clean up after a fatal error. Remove lint. 4411894Speter * Specify subprocess input via file descriptor, not file name. Remove lint. 4511894Speter * Flush stderr after prompt. 4611894Speter * 4711894Speter * Revision 5.18 1993/11/09 17:40:15 eggert 4811894Speter * -V now prints version on stdout and exits. Don't print usage twice. 4911894Speter * 5011894Speter * Revision 5.17 1993/11/03 17:42:27 eggert 5111894Speter * Add -z. Don't lose track of -m or -t when there are no other changes. 5211894Speter * Don't discard ignored phrases. Improve quality of diagnostics. 5311894Speter * 5411894Speter * Revision 5.16 1992/07/28 16:12:44 eggert 5511894Speter * rcs -l now asks whether you want to break the lock. 5611894Speter * Add -V. Set RCS file's mode and time at right moment. 5711894Speter * 5811894Speter * Revision 5.15 1992/02/17 23:02:20 eggert 5911894Speter * Add -T. 6011894Speter * 6111894Speter * Revision 5.14 1992/01/27 16:42:53 eggert 6211894Speter * Add -M. Avoid invoking umask(); it's one less thing to configure. 6311894Speter * Add support for bad_creat0. lint -> RCS_lint 6411894Speter * 6511894Speter * Revision 5.13 1992/01/06 02:42:34 eggert 6611894Speter * Avoid changing RCS file in common cases where no change can occur. 6711894Speter * 689Sjkh * Revision 5.12 1991/11/20 17:58:08 eggert 699Sjkh * Don't read the delta tree from a nonexistent RCS file. 709Sjkh * 719Sjkh * Revision 5.11 1991/10/07 17:32:46 eggert 729Sjkh * Remove lint. 739Sjkh * 749Sjkh * Revision 5.10 1991/08/19 23:17:54 eggert 759Sjkh * Add -m, -r$, piece tables. Revision separator is `:', not `-'. Tune. 769Sjkh * 779Sjkh * Revision 5.9 1991/04/21 11:58:18 eggert 789Sjkh * Add -x, RCSINIT, MS-DOS support. 799Sjkh * 809Sjkh * Revision 5.8 1991/02/25 07:12:38 eggert 819Sjkh * strsave -> str_save (DG/UX name clash) 829Sjkh * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability 839Sjkh * 849Sjkh * Revision 5.7 1990/12/18 17:19:21 eggert 859Sjkh * Fix bug with multiple -n and -N options. 869Sjkh * 879Sjkh * Revision 5.6 1990/12/04 05:18:40 eggert 889Sjkh * Use -I for prompts and -q for diagnostics. 899Sjkh * 909Sjkh * Revision 5.5 1990/11/11 00:06:35 eggert 919Sjkh * Fix `rcs -e' core dump. 929Sjkh * 939Sjkh * Revision 5.4 1990/11/01 05:03:33 eggert 949Sjkh * Add -I and new -t behavior. Permit arbitrary data in logs. 959Sjkh * 969Sjkh * Revision 5.3 1990/10/04 06:30:16 eggert 979Sjkh * Accumulate exit status across files. 989Sjkh * 999Sjkh * Revision 5.2 1990/09/04 08:02:17 eggert 1009Sjkh * Standardize yes-or-no procedure. 1019Sjkh * 1029Sjkh * Revision 5.1 1990/08/29 07:13:51 eggert 1039Sjkh * Remove unused setuid support. Clean old log messages too. 1049Sjkh * 1059Sjkh * Revision 5.0 1990/08/22 08:12:42 eggert 1069Sjkh * Don't lose names when applying -a option to multiple files. 1079Sjkh * Remove compile-time limits; use malloc instead. Add setuid support. 1089Sjkh * Permit dates past 1999/12/31. Make lock and temp files faster and safer. 1099Sjkh * Ansify and Posixate. Add -V. Fix umask bug. Make linting easier. Tune. 1109Sjkh * Yield proper exit status. Check diff's output. 1119Sjkh * 1129Sjkh * Revision 4.11 89/05/01 15:12:06 narten 1139Sjkh * changed copyright header to reflect current distribution rules 1148858Srgrimes * 1159Sjkh * Revision 4.10 88/11/08 16:01:54 narten 1169Sjkh * didn't install previous patch correctly 1178858Srgrimes * 1189Sjkh * Revision 4.9 88/11/08 13:56:01 narten 1199Sjkh * removed include <sysexits.h> (not needed) 1209Sjkh * minor fix for -A option 1218858Srgrimes * 1229Sjkh * Revision 4.8 88/08/09 19:12:27 eggert 1239Sjkh * Don't access freed storage. 1249Sjkh * Use execv(), not system(); yield proper exit status; remove lint. 1258858Srgrimes * 1269Sjkh * Revision 4.7 87/12/18 11:37:17 narten 1279Sjkh * lint cleanups (Guy Harris) 1288858Srgrimes * 1299Sjkh * Revision 4.6 87/10/18 10:28:48 narten 1308858Srgrimes * Updating verison numbers. Changes relative to 1.1 are actually 1319Sjkh * relative to 4.3 1328858Srgrimes * 1339Sjkh * Revision 1.4 87/09/24 13:58:52 narten 1348858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf 1359Sjkh * warnings) 1368858Srgrimes * 1379Sjkh * Revision 1.3 87/03/27 14:21:55 jenkins 1389Sjkh * Port to suns 1398858Srgrimes * 1409Sjkh * Revision 1.2 85/12/17 13:59:09 albitz 1419Sjkh * Changed setstate to rcs_setstate because of conflict with random.o. 1428858Srgrimes * 1439Sjkh * Revision 4.3 83/12/15 12:27:33 wft 1449Sjkh * rcs -u now breaks most recent lock if it can't find a lock by the caller. 1458858Srgrimes * 1469Sjkh * Revision 4.2 83/12/05 10:18:20 wft 1479Sjkh * Added conditional compilation for sending mail. 1489Sjkh * Alternatives: V4_2BSD, V6, USG, and other. 1498858Srgrimes * 1509Sjkh * Revision 4.1 83/05/10 16:43:02 wft 1519Sjkh * Simplified breaklock(); added calls to findlock() and getcaller(). 1529Sjkh * Added option -b (default branch). Updated -s and -w for -b. 1539Sjkh * Removed calls to stat(); now done by pairfilenames(). 1549Sjkh * Replaced most catchints() calls with restoreints(). 1559Sjkh * Removed check for exit status of delivermail(). 1569Sjkh * Directed all interactive output to stderr. 1578858Srgrimes * 1589Sjkh * Revision 3.9.1.1 83/12/02 22:08:51 wft 1599Sjkh * Added conditional compilation for 4.2 sendmail and 4.1 delivermail. 1608858Srgrimes * 1619Sjkh * Revision 3.9 83/02/15 15:38:39 wft 1629Sjkh * Added call to fastcopy() to copy remainder of RCS file. 1639Sjkh * 1649Sjkh * Revision 3.8 83/01/18 17:37:51 wft 1659Sjkh * Changed sendmail(): now uses delivermail, and asks whether to break the lock. 1669Sjkh * 1679Sjkh * Revision 3.7 83/01/15 18:04:25 wft 1689Sjkh * Removed putree(); replaced with puttree() in rcssyn.c. 1699Sjkh * Combined putdellog() and scanlogtext(); deleted putdellog(). 1709Sjkh * Cleaned up diagnostics and error messages. Fixed problem with 1719Sjkh * mutilated files in case of deletions in 2 files in a single command. 1729Sjkh * Changed marking of selector from 'D' to DELETE. 1739Sjkh * 1749Sjkh * Revision 3.6 83/01/14 15:37:31 wft 1759Sjkh * Added ignoring of interrupts while new RCS file is renamed; 1769Sjkh * Avoids deletion of RCS files by interrupts. 1779Sjkh * 1789Sjkh * Revision 3.5 82/12/10 21:11:39 wft 1799Sjkh * Removed unused variables, fixed checking of return code from diff, 1809Sjkh * introduced variant COMPAT2 for skipping Suffix on -A files. 1819Sjkh * 1829Sjkh * Revision 3.4 82/12/04 13:18:20 wft 1839Sjkh * Replaced getdelta() with gettree(), changed breaklock to update 1849Sjkh * field lockedby, added some diagnostics. 1859Sjkh * 1869Sjkh * Revision 3.3 82/12/03 17:08:04 wft 1879Sjkh * Replaced getlogin() with getpwuid(), flcose() with ffclose(), 1889Sjkh * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x). 1899Sjkh * fixed -u for missing revno. Disambiguated structure members. 1909Sjkh * 1919Sjkh * Revision 3.2 82/10/18 21:05:07 wft 1929Sjkh * rcs -i now generates a file mode given by the umask minus write permission; 1939Sjkh * otherwise, rcs keeps the mode, but removes write permission. 1949Sjkh * I added a check for write error, fixed call to getlogin(), replaced 1959Sjkh * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed 1969Sjkh * conflicting, long identifiers. 1979Sjkh * 1989Sjkh * Revision 3.1 82/10/13 16:11:07 wft 1999Sjkh * fixed type of variables receiving from getc() (char -> int). 2009Sjkh */ 2019Sjkh 2029Sjkh 2039Sjkh#include "rcsbase.h" 2049Sjkh 2059Sjkhstruct Lockrev { 2069Sjkh char const *revno; 2079Sjkh struct Lockrev * nextrev; 2089Sjkh}; 2099Sjkh 2109Sjkhstruct Symrev { 2119Sjkh char const *revno; 2129Sjkh char const *ssymbol; 2139Sjkh int override; 2149Sjkh struct Symrev * nextsym; 2159Sjkh}; 2169Sjkh 2179Sjkhstruct Message { 2189Sjkh char const *revno; 2199Sjkh struct cbuf message; 2209Sjkh struct Message *nextmessage; 2219Sjkh}; 2229Sjkh 2239Sjkhstruct Status { 2249Sjkh char const *revno; 2259Sjkh char const *status; 2269Sjkh struct Status * nextstatus; 2279Sjkh}; 2289Sjkh 2299Sjkhenum changeaccess {append, erase}; 2309Sjkhstruct chaccess { 2319Sjkh char const *login; 2329Sjkh enum changeaccess command; 2339Sjkh struct chaccess *nextchaccess; 2349Sjkh}; 2359Sjkh 2369Sjkhstruct delrevpair { 2379Sjkh char const *strt; 2389Sjkh char const *end; 2399Sjkh int code; 2409Sjkh}; 2419Sjkh 24211894Speterstatic int branchpoint P((struct hshentry*,struct hshentry*)); 24311894Speterstatic int breaklock P((struct hshentry const*)); 2449Sjkhstatic int buildeltatext P((struct hshentries const*)); 24511894Speterstatic int doaccess P((void)); 24611894Speterstatic int doassoc P((void)); 24711894Speterstatic int dolocks P((void)); 24811894Speterstatic int domessages P((void)); 24911894Speterstatic int rcs_setstate P((char const*,char const*)); 2509Sjkhstatic int removerevs P((void)); 2519Sjkhstatic int sendmail P((char const*,char const*)); 25211894Speterstatic int setlock P((char const*)); 25311894Speterstatic struct Lockrev **rmnewlocklst P((char const*)); 25411894Speterstatic struct hshentry *searchcutpt P((char const*,int,struct hshentries*)); 2559Sjkhstatic void buildtree P((void)); 2569Sjkhstatic void cleanup P((void)); 2579Sjkhstatic void getaccessor P((char*,enum changeaccess)); 2589Sjkhstatic void getassoclst P((int,char*)); 2599Sjkhstatic void getchaccess P((char const*,enum changeaccess)); 2609Sjkhstatic void getdelrev P((char*)); 2619Sjkhstatic void getmessage P((char*)); 2629Sjkhstatic void getstates P((char*)); 2639Sjkhstatic void scanlogtext P((struct hshentry*,int)); 2649Sjkh 2659Sjkhstatic struct buf numrev; 2669Sjkhstatic char const *headstate; 2679Sjkhstatic int chgheadstate, exitstatus, lockhead, unlockcaller; 26811894Speterstatic int suppress_mail; 2699Sjkhstatic struct Lockrev *newlocklst, *rmvlocklst; 27011894Speterstatic struct Message *messagelst, **nextmessage; 27111894Speterstatic struct Status *statelst, **nextstate; 27211894Speterstatic struct Symrev *assoclst, **nextassoc; 2739Sjkhstatic struct chaccess *chaccess, **nextchaccess; 2749Sjkhstatic struct delrevpair delrev; 2759Sjkhstatic struct hshentry *cuthead, *cuttail, *delstrt; 2769Sjkhstatic struct hshentries *gendeltas; 2779Sjkh 27850472SpetermainProg(rcsId, "rcs", "$FreeBSD$") 2799Sjkh{ 2809Sjkh static char const cmdusage[] = 28111894Speter "\nrcs usage: rcs -{ae}logins -Afile -{blu}[rev] -cstring -{iILqTU} -ksubst -mrev:msg -{nN}name[:[rev]] -orange -sstate[:rev] -t[text] -Vn -xsuff -zzone file ..."; 2829Sjkh 2839Sjkh char *a, **newargv, *textfile; 2849Sjkh char const *branchsym, *commsyml; 28511894Speter int branchflag, changed, expmode, initflag; 28611894Speter int strictlock, strict_selected, textflag; 28711894Speter int keepRCStime, Ttimeflag; 28811894Speter size_t commsymlen; 2899Sjkh struct buf branchnum; 29011894Speter struct Lockrev *lockpt; 29111894Speter struct Lockrev **curlock, **rmvlock; 2929Sjkh struct Status * curstate; 2939Sjkh 2949Sjkh nosetid(); 2959Sjkh 29611894Speter nextassoc = &assoclst; 2979Sjkh nextchaccess = &chaccess; 29811894Speter nextmessage = &messagelst; 29911894Speter nextstate = &statelst; 30011894Speter branchsym = commsyml = textfile = 0; 3019Sjkh branchflag = strictlock = false; 3029Sjkh bufautobegin(&branchnum); 30311894Speter commsymlen = 0; 30411894Speter curlock = &newlocklst; 30511894Speter rmvlock = &rmvlocklst; 3069Sjkh expmode = -1; 3079Sjkh suffixes = X_DEFAULT; 3089Sjkh initflag= textflag = false; 3099Sjkh strict_selected = 0; 31011894Speter Ttimeflag = false; 3119Sjkh 3129Sjkh /* preprocessing command options */ 31311894Speter if (1 < argc && argv[1][0] != '-') 31411894Speter warn("No options were given; this usage is obsolescent."); 31511894Speter 3169Sjkh argc = getRCSINIT(argc, argv, &newargv); 3179Sjkh argv = newargv; 3189Sjkh while (a = *++argv, 0<--argc && *a++=='-') { 3199Sjkh switch (*a++) { 3209Sjkh 3219Sjkh case 'i': /* initial version */ 3229Sjkh initflag = true; 3239Sjkh break; 3249Sjkh 3259Sjkh case 'b': /* change default branch */ 3269Sjkh if (branchflag) redefined('b'); 3279Sjkh branchflag= true; 3289Sjkh branchsym = a; 3299Sjkh break; 3309Sjkh 3319Sjkh case 'c': /* change comment symbol */ 3329Sjkh if (commsyml) redefined('c'); 3339Sjkh commsyml = a; 33411894Speter commsymlen = strlen(a); 3359Sjkh break; 3369Sjkh 3379Sjkh case 'a': /* add new accessor */ 3389Sjkh getaccessor(*argv+1, append); 3399Sjkh break; 3409Sjkh 3419Sjkh case 'A': /* append access list according to accessfile */ 3429Sjkh if (!*a) { 34311894Speter error("missing pathname after -A"); 3449Sjkh break; 3459Sjkh } 3469Sjkh *argv = a; 34711894Speter if (0 < pairnames(1,argv,rcsreadopen,true,false)) { 3489Sjkh while (AccessList) { 3499Sjkh getchaccess(str_save(AccessList->login),append); 3509Sjkh AccessList = AccessList->nextaccess; 3519Sjkh } 3529Sjkh Izclose(&finptr); 3539Sjkh } 3549Sjkh break; 3559Sjkh 3569Sjkh case 'e': /* remove accessors */ 3579Sjkh getaccessor(*argv+1, erase); 3589Sjkh break; 3599Sjkh 3609Sjkh case 'l': /* lock a revision if it is unlocked */ 3619Sjkh if (!*a) { 3629Sjkh /* Lock head or default branch. */ 3639Sjkh lockhead = true; 3649Sjkh break; 3659Sjkh } 36611894Speter *curlock = lockpt = talloc(struct Lockrev); 3679Sjkh lockpt->revno = a; 36811894Speter lockpt->nextrev = 0; 36911894Speter curlock = &lockpt->nextrev; 3709Sjkh break; 3719Sjkh 3729Sjkh case 'u': /* release lock of a locked revision */ 3739Sjkh if (!*a) { 3749Sjkh unlockcaller=true; 3759Sjkh break; 3769Sjkh } 37711894Speter *rmvlock = lockpt = talloc(struct Lockrev); 3789Sjkh lockpt->revno = a; 37911894Speter lockpt->nextrev = 0; 38011894Speter rmvlock = &lockpt->nextrev; 38111894Speter curlock = rmnewlocklst(lockpt->revno); 3829Sjkh break; 3839Sjkh 3849Sjkh case 'L': /* set strict locking */ 38511894Speter if (strict_selected) { 3869Sjkh if (!strictlock) /* Already selected -U? */ 38711894Speter warn("-U overridden by -L"); 3889Sjkh } 3899Sjkh strictlock = true; 39011894Speter strict_selected = true; 3919Sjkh break; 3929Sjkh 3939Sjkh case 'U': /* release strict locking */ 39411894Speter if (strict_selected) { 3959Sjkh if (strictlock) /* Already selected -L? */ 39611894Speter warn("-L overridden by -U"); 3979Sjkh } 39811894Speter strict_selected = true; 3999Sjkh break; 4009Sjkh 4019Sjkh case 'n': /* add new association: error, if name exists */ 4029Sjkh if (!*a) { 4039Sjkh error("missing symbolic name after -n"); 4049Sjkh break; 4059Sjkh } 4069Sjkh getassoclst(false, (*argv)+1); 4079Sjkh break; 4089Sjkh 4099Sjkh case 'N': /* add or change association */ 4109Sjkh if (!*a) { 4119Sjkh error("missing symbolic name after -N"); 4129Sjkh break; 4139Sjkh } 4149Sjkh getassoclst(true, (*argv)+1); 4159Sjkh break; 4169Sjkh 4179Sjkh case 'm': /* change log message */ 4189Sjkh getmessage(a); 4199Sjkh break; 4209Sjkh 42111894Speter case 'M': /* do not send mail */ 42211894Speter suppress_mail = true; 42311894Speter break; 42411894Speter 4259Sjkh case 'o': /* delete revisions */ 4269Sjkh if (delrev.strt) redefined('o'); 4279Sjkh if (!*a) { 4289Sjkh error("missing revision range after -o"); 4299Sjkh break; 4309Sjkh } 4319Sjkh getdelrev( (*argv)+1 ); 4329Sjkh break; 4339Sjkh 4349Sjkh case 's': /* change state attribute of a revision */ 4359Sjkh if (!*a) { 4369Sjkh error("state missing after -s"); 4379Sjkh break; 4389Sjkh } 4399Sjkh getstates( (*argv)+1); 4409Sjkh break; 4419Sjkh 4429Sjkh case 't': /* change descriptive text */ 4439Sjkh textflag=true; 4449Sjkh if (*a) { 4459Sjkh if (textfile) redefined('t'); 4469Sjkh textfile = a; 4479Sjkh } 4489Sjkh break; 4499Sjkh 45011894Speter case 'T': /* do not update last-mod time for minor changes */ 45111894Speter if (*a) 45211894Speter goto unknown; 45311894Speter Ttimeflag = true; 45411894Speter break; 45511894Speter 4569Sjkh case 'I': 4579Sjkh interactiveflag = true; 4589Sjkh break; 4599Sjkh 4609Sjkh case 'q': 4619Sjkh quietflag = true; 4629Sjkh break; 4639Sjkh 4649Sjkh case 'x': 4659Sjkh suffixes = a; 4669Sjkh break; 4679Sjkh 4689Sjkh case 'V': 4699Sjkh setRCSversion(*argv); 4709Sjkh break; 4719Sjkh 47211894Speter case 'z': 47311894Speter zone_set(a); 47411894Speter break; 47511894Speter 4769Sjkh case 'k': /* set keyword expand mode */ 4779Sjkh if (0 <= expmode) redefined('k'); 4789Sjkh if (0 <= (expmode = str2expmode(a))) 4799Sjkh break; 4809Sjkh /* fall into */ 4819Sjkh default: 48211894Speter unknown: 48311894Speter error("unknown option: %s%s", *argv, cmdusage); 4849Sjkh }; 4859Sjkh } /* end processing of options */ 4869Sjkh 48711894Speter /* Now handle all pathnames. */ 48811894Speter if (nerror) cleanup(); 48911894Speter else if (argc < 1) faterror("no input file%s", cmdusage); 49011894Speter else for (; 0 < argc; cleanup(), ++argv, --argc) { 4919Sjkh 4929Sjkh ffree(); 4939Sjkh 4949Sjkh if ( initflag ) { 49511894Speter switch (pairnames(argc, argv, rcswriteopen, false, false)) { 4969Sjkh case -1: break; /* not exist; ok */ 4979Sjkh case 0: continue; /* error */ 49811894Speter case 1: rcserror("already exists"); 4999Sjkh continue; 5009Sjkh } 5019Sjkh } 5029Sjkh else { 50311894Speter switch (pairnames(argc, argv, rcswriteopen, true, false)) { 5049Sjkh case -1: continue; /* not exist */ 5059Sjkh case 0: continue; /* errors */ 5069Sjkh case 1: break; /* file exists; ok*/ 5079Sjkh } 5089Sjkh } 5099Sjkh 5109Sjkh 51111894Speter /* 51211894Speter * RCSname contains the name of the RCS file, and 51311894Speter * workname contains the name of the working file. 5149Sjkh * if !initflag, finptr contains the file descriptor for the 5159Sjkh * RCS file. The admin node is initialized. 5169Sjkh */ 5179Sjkh 51811894Speter diagnose("RCS file: %s\n", RCSname); 5199Sjkh 52011894Speter changed = initflag | textflag; 52111894Speter keepRCStime = Ttimeflag; 52211894Speter if (!initflag) { 5239Sjkh if (!checkaccesslist()) continue; 5249Sjkh gettree(); /* Read the delta tree. */ 5259Sjkh } 5269Sjkh 5279Sjkh /* update admin. node */ 52811894Speter if (strict_selected) { 52911894Speter changed |= StrictLocks ^ strictlock; 53011894Speter StrictLocks = strictlock; 53111894Speter } 53211894Speter if ( 53311894Speter commsyml && 53411894Speter ( 53511894Speter commsymlen != Comment.size || 53611894Speter memcmp(commsyml, Comment.string, commsymlen) != 0 53711894Speter ) 53811894Speter ) { 5399Sjkh Comment.string = commsyml; 5409Sjkh Comment.size = strlen(commsyml); 54111894Speter changed = true; 5429Sjkh } 54311894Speter if (0 <= expmode && Expand != expmode) { 54411894Speter Expand = expmode; 54511894Speter changed = true; 54611894Speter } 5479Sjkh 5489Sjkh /* update default branch */ 5499Sjkh if (branchflag && expandsym(branchsym, &branchnum)) { 5509Sjkh if (countnumflds(branchnum.string)) { 55111894Speter if (cmpnum(Dbranch, branchnum.string) != 0) { 55211894Speter Dbranch = branchnum.string; 55311894Speter changed = true; 55411894Speter } 5559Sjkh } else 55611894Speter if (Dbranch) { 55711894Speter Dbranch = 0; 55811894Speter changed = true; 55911894Speter } 56011894Speter } 5619Sjkh 56211894Speter changed |= doaccess(); /* Update access list. */ 5639Sjkh 56411894Speter changed |= doassoc(); /* Update association list. */ 5659Sjkh 56611894Speter changed |= dolocks(); /* Update locks. */ 5679Sjkh 56811894Speter changed |= domessages(); /* Update log messages. */ 5699Sjkh 5709Sjkh /* update state attribution */ 5719Sjkh if (chgheadstate) { 5729Sjkh /* change state of default branch or head */ 57311894Speter if (!Dbranch) { 57411894Speter if (!Head) 57511894Speter rcswarn("can't change states in an empty tree"); 57611894Speter else if (strcmp(Head->state, headstate) != 0) { 57711894Speter Head->state = headstate; 57811894Speter changed = true; 57911894Speter } 58011894Speter } else 58111894Speter changed |= rcs_setstate(Dbranch,headstate); 5829Sjkh } 58311894Speter for (curstate = statelst; curstate; curstate = curstate->nextstatus) 58411894Speter changed |= rcs_setstate(curstate->revno,curstate->status); 5859Sjkh 58611894Speter cuthead = cuttail = 0; 5879Sjkh if (delrev.strt && removerevs()) { 5889Sjkh /* rebuild delta tree if some deltas are deleted */ 5899Sjkh if ( cuttail ) 59011894Speter VOID genrevs( 59111894Speter cuttail->num, (char *)0, (char *)0, (char *)0, 59211894Speter &gendeltas 59311894Speter ); 5949Sjkh buildtree(); 59511894Speter changed = true; 59611894Speter keepRCStime = false; 5979Sjkh } 5989Sjkh 5999Sjkh if (nerror) 6009Sjkh continue; 6019Sjkh 60211894Speter putadmin(); 6039Sjkh if ( Head ) 6049Sjkh puttree(Head, frewrite); 6059Sjkh putdesc(textflag,textfile); 6069Sjkh 6079Sjkh if ( Head) { 60811894Speter if (delrev.strt || messagelst) { 6099Sjkh if (!cuttail || buildeltatext(gendeltas)) { 6109Sjkh advise_access(finptr, MADV_SEQUENTIAL); 61111894Speter scanlogtext((struct hshentry *)0, false); 6129Sjkh /* copy rest of delta text nodes that are not deleted */ 61311894Speter changed = true; 6149Sjkh } 6159Sjkh } 6169Sjkh } 6179Sjkh 61811894Speter if (initflag) { 61911894Speter /* Adjust things for donerewrite's sake. */ 62011894Speter if (stat(workname, &RCSstat) != 0) { 62111894Speter# if bad_creat0 62211894Speter mode_t m = umask(0); 62311894Speter (void) umask(m); 62411894Speter RCSstat.st_mode = (S_IRUSR|S_IRGRP|S_IROTH) & ~m; 62511894Speter# else 62611894Speter changed = -1; 62711894Speter# endif 62811894Speter } 62911894Speter RCSstat.st_nlink = 0; 63011894Speter keepRCStime = false; 63111894Speter } 63211894Speter if (donerewrite(changed, 63311894Speter keepRCStime ? RCSstat.st_mtime : (time_t)-1 63411894Speter ) != 0) 63511894Speter break; 63611894Speter 63711894Speter diagnose("done\n"); 63811894Speter } 63911894Speter 6409Sjkh tempunlink(); 6419Sjkh exitmain(exitstatus); 6429Sjkh} /* end of main (rcs) */ 6439Sjkh 6449Sjkh static void 6459Sjkhcleanup() 6469Sjkh{ 6479Sjkh if (nerror) exitstatus = EXIT_FAILURE; 6489Sjkh Izclose(&finptr); 6499Sjkh Ozclose(&fcopy); 65011894Speter ORCSclose(); 6519Sjkh dirtempunlink(); 6529Sjkh} 6539Sjkh 65411894Speter void 6559Sjkhexiterr() 6569Sjkh{ 65711894Speter ORCSerror(); 6589Sjkh dirtempunlink(); 6599Sjkh tempunlink(); 6609Sjkh _exit(EXIT_FAILURE); 6619Sjkh} 6629Sjkh 6639Sjkh 6649Sjkh static void 6659Sjkhgetassoclst(flag, sp) 6669Sjkhint flag; 6679Sjkhchar * sp; 6689Sjkh/* Function: associate a symbolic name to a revision or branch, */ 6699Sjkh/* and store in assoclst */ 6709Sjkh 6719Sjkh{ 6729Sjkh struct Symrev * pt; 6739Sjkh char const *temp; 6749Sjkh int c; 6759Sjkh 67611894Speter while ((c = *++sp) == ' ' || c == '\t' || c =='\n') 67711894Speter continue; 6789Sjkh temp = sp; 67911894Speter sp = checksym(sp, ':'); /* check for invalid symbolic name */ 6809Sjkh c = *sp; *sp = '\0'; 6819Sjkh while( c == ' ' || c == '\t' || c == '\n') c = *++sp; 6829Sjkh 6839Sjkh if ( c != ':' && c != '\0') { 6849Sjkh error("invalid string %s after option -n or -N",sp); 6859Sjkh return; 6869Sjkh } 6879Sjkh 6889Sjkh pt = talloc(struct Symrev); 6899Sjkh pt->ssymbol = temp; 6909Sjkh pt->override = flag; 6919Sjkh if (c == '\0') /* delete symbol */ 69211894Speter pt->revno = 0; 6939Sjkh else { 69411894Speter while ((c = *++sp) == ' ' || c == '\n' || c == '\t') 69511894Speter continue; 6969Sjkh pt->revno = sp; 6979Sjkh } 69811894Speter pt->nextsym = 0; 69911894Speter *nextassoc = pt; 70011894Speter nextassoc = &pt->nextsym; 7019Sjkh} 7029Sjkh 7039Sjkh 7049Sjkh static void 7059Sjkhgetchaccess(login, command) 7069Sjkh char const *login; 7079Sjkh enum changeaccess command; 7089Sjkh{ 7099Sjkh register struct chaccess *pt; 7109Sjkh 71111894Speter pt = talloc(struct chaccess); 7129Sjkh pt->login = login; 7139Sjkh pt->command = command; 71411894Speter pt->nextchaccess = 0; 71511894Speter *nextchaccess = pt; 7169Sjkh nextchaccess = &pt->nextchaccess; 7179Sjkh} 7189Sjkh 7199Sjkh 7209Sjkh 7219Sjkh static void 7229Sjkhgetaccessor(opt, command) 7239Sjkh char *opt; 7249Sjkh enum changeaccess command; 7259Sjkh/* Function: get the accessor list of options -e and -a, */ 7269Sjkh/* and store in chaccess */ 7279Sjkh 7289Sjkh 7299Sjkh{ 7309Sjkh register c; 7319Sjkh register char *sp; 7329Sjkh 7339Sjkh sp = opt; 73411894Speter while ((c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') 73511894Speter continue; 7369Sjkh if ( c == '\0') { 7379Sjkh if (command == erase && sp-opt == 1) { 73811894Speter getchaccess((char*)0, command); 7399Sjkh return; 7409Sjkh } 7419Sjkh error("missing login name after option -a or -e"); 7429Sjkh return; 7439Sjkh } 7449Sjkh 7459Sjkh while( c != '\0') { 7469Sjkh getchaccess(sp, command); 7479Sjkh sp = checkid(sp,','); 7489Sjkh c = *sp; *sp = '\0'; 7499Sjkh while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp); 7509Sjkh } 7519Sjkh} 7529Sjkh 7539Sjkh 7549Sjkh static void 7559Sjkhgetmessage(option) 7569Sjkh char *option; 7579Sjkh{ 7589Sjkh struct Message *pt; 7599Sjkh struct cbuf cb; 7609Sjkh char *m; 7619Sjkh 7629Sjkh if (!(m = strchr(option, ':'))) { 7639Sjkh error("-m option lacks revision number"); 7649Sjkh return; 7659Sjkh } 7669Sjkh *m++ = 0; 7679Sjkh cb = cleanlogmsg(m, strlen(m)); 7689Sjkh if (!cb.size) { 7699Sjkh error("-m option lacks log message"); 7709Sjkh return; 7719Sjkh } 7729Sjkh pt = talloc(struct Message); 7739Sjkh pt->revno = option; 7749Sjkh pt->message = cb; 7759Sjkh pt->nextmessage = 0; 77611894Speter *nextmessage = pt; 77711894Speter nextmessage = &pt->nextmessage; 7789Sjkh} 7799Sjkh 7809Sjkh 7819Sjkh static void 7829Sjkhgetstates(sp) 7839Sjkhchar *sp; 7849Sjkh/* Function: get one state attribute and the corresponding */ 7859Sjkh/* revision and store in statelst */ 7869Sjkh 7879Sjkh{ 7889Sjkh char const *temp; 7899Sjkh struct Status *pt; 7909Sjkh register c; 7919Sjkh 79211894Speter while ((c = *++sp) ==' ' || c == '\t' || c == '\n') 79311894Speter continue; 7949Sjkh temp = sp; 7959Sjkh sp = checkid(sp,':'); /* check for invalid state attribute */ 7969Sjkh c = *sp; *sp = '\0'; 7979Sjkh while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp; 7989Sjkh 7999Sjkh if ( c == '\0' ) { /* change state of def. branch or Head */ 8009Sjkh chgheadstate = true; 8019Sjkh headstate = temp; 8029Sjkh return; 8039Sjkh } 8049Sjkh else if ( c != ':' ) { 8059Sjkh error("missing ':' after state in option -s"); 8069Sjkh return; 8079Sjkh } 8089Sjkh 80911894Speter while ((c = *++sp) == ' ' || c == '\t' || c == '\n') 81011894Speter continue; 8119Sjkh pt = talloc(struct Status); 8129Sjkh pt->status = temp; 8139Sjkh pt->revno = sp; 81411894Speter pt->nextstatus = 0; 81511894Speter *nextstate = pt; 81611894Speter nextstate = &pt->nextstatus; 8179Sjkh} 8189Sjkh 8199Sjkh 8209Sjkh 8219Sjkh static void 8229Sjkhgetdelrev(sp) 8239Sjkhchar *sp; 8249Sjkh/* Function: get revision range or branch to be deleted, */ 8259Sjkh/* and place in delrev */ 8269Sjkh{ 8279Sjkh int c; 8289Sjkh struct delrevpair *pt; 8299Sjkh int separator; 8309Sjkh 8319Sjkh pt = &delrev; 83211894Speter while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t') 83311894Speter continue; 8349Sjkh 8359Sjkh /* Support old ambiguous '-' syntax; this will go away. */ 8369Sjkh if (strchr(sp,':')) 8379Sjkh separator = ':'; 8389Sjkh else { 8399Sjkh if (strchr(sp,'-') && VERSION(5) <= RCSversion) 8409Sjkh warn("`-' is obsolete in `-o%s'; use `:' instead", sp); 8419Sjkh separator = '-'; 8429Sjkh } 8439Sjkh 8449Sjkh if (c == separator) { /* -o:rev */ 84511894Speter while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t') 84611894Speter continue; 8479Sjkh pt->strt = sp; pt->code = 1; 8489Sjkh while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp); 8499Sjkh *sp = '\0'; 85011894Speter pt->end = 0; 8519Sjkh return; 8529Sjkh } 8539Sjkh else { 8549Sjkh pt->strt = sp; 8559Sjkh while( c != ' ' && c != '\n' && c != '\t' && c != '\0' 8569Sjkh && c != separator ) c = *++sp; 8579Sjkh *sp = '\0'; 8589Sjkh while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp; 8599Sjkh if ( c == '\0' ) { /* -o rev or branch */ 86011894Speter pt->code = 0; 86111894Speter pt->end = 0; 8629Sjkh return; 8639Sjkh } 8649Sjkh if (c != separator) { 86511894Speter error("invalid range %s %s after -o", pt->strt, sp); 8669Sjkh } 86711894Speter while ((c = *++sp) == ' ' || c == '\n' || c == '\t') 86811894Speter continue; 8699Sjkh if (!c) { /* -orev: */ 87011894Speter pt->code = 2; 87111894Speter pt->end = 0; 8729Sjkh return; 8739Sjkh } 8749Sjkh } 8759Sjkh /* -orev1:rev2 */ 8769Sjkh pt->end = sp; pt->code = 3; 8779Sjkh while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp; 8789Sjkh *sp = '\0'; 8799Sjkh} 8809Sjkh 8819Sjkh 8829Sjkh 8839Sjkh 8849Sjkh static void 8859Sjkhscanlogtext(delta,edit) 8869Sjkh struct hshentry *delta; 8879Sjkh int edit; 8889Sjkh/* Function: Scans delta text nodes up to and including the one given 88911894Speter * by delta, or up to last one present, if !delta. 89011894Speter * For the one given by delta (if delta), the log message is saved into 8919Sjkh * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied. 8929Sjkh * Assumes the initial lexeme must be read in first. 89311894Speter * Does not advance nexttok after it is finished, except if !delta. 8949Sjkh */ 8959Sjkh{ 8969Sjkh struct hshentry const *nextdelta; 8979Sjkh struct cbuf cb; 8989Sjkh 8999Sjkh for (;;) { 9009Sjkh foutptr = 0; 9019Sjkh if (eoflex()) { 9029Sjkh if(delta) 90311894Speter rcsfaterror("can't find delta for revision %s", 90411894Speter delta->num 90511894Speter ); 9069Sjkh return; /* no more delta text nodes */ 9079Sjkh } 9089Sjkh nextlex(); 9099Sjkh if (!(nextdelta=getnum())) 91011894Speter fatserror("delta number corrupted"); 9119Sjkh if (nextdelta->selector) { 9129Sjkh foutptr = frewrite; 9139Sjkh aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog); 9149Sjkh } 9159Sjkh getkeystring(Klog); 9169Sjkh if (nextdelta == cuttail) { 9179Sjkh cb = savestring(&curlogbuf); 9189Sjkh if (!delta->log.string) 9199Sjkh delta->log = cleanlogmsg(curlogbuf.string, cb.size); 92011894Speter nextlex(); 92111894Speter delta->igtext = getphrases(Ktext); 92211894Speter } else { 92311894Speter if (nextdelta->log.string && nextdelta->selector) { 92411894Speter foutptr = 0; 92511894Speter readstring(); 92611894Speter foutptr = frewrite; 92711894Speter putstring(foutptr, false, nextdelta->log, true); 92811894Speter afputc(nextc, foutptr); 92911894Speter } else 93011894Speter readstring(); 93111894Speter ignorephrases(Ktext); 93211894Speter } 9339Sjkh getkeystring(Ktext); 9349Sjkh 9359Sjkh if (delta==nextdelta) 9369Sjkh break; 9379Sjkh readstring(); /* skip over it */ 9389Sjkh 9399Sjkh } 9409Sjkh /* got the one we're looking for */ 9419Sjkh if (edit) 94211894Speter editstring((struct hshentry*)0); 9439Sjkh else 9449Sjkh enterstring(); 9459Sjkh} 9469Sjkh 9479Sjkh 9489Sjkh 94911894Speter static struct Lockrev ** 9509Sjkhrmnewlocklst(which) 95111894Speter char const *which; 95211894Speter/* Remove lock to revision WHICH from newlocklst. */ 9539Sjkh{ 95411894Speter struct Lockrev *pt, **pre; 9559Sjkh 95611894Speter pre = &newlocklst; 95711894Speter while ((pt = *pre)) 95811894Speter if (strcmp(pt->revno, which) != 0) 95911894Speter pre = &pt->nextrev; 96011894Speter else { 96111894Speter *pre = pt->nextrev; 9629Sjkh tfree(pt); 96311894Speter } 9649Sjkh return pre; 9659Sjkh} 9669Sjkh 9679Sjkh 9689Sjkh 96911894Speter static int 9709Sjkhdoaccess() 9719Sjkh{ 9729Sjkh register struct chaccess *ch; 9739Sjkh register struct access **p, *t; 97411894Speter register int changed = false; 9759Sjkh 9769Sjkh for (ch = chaccess; ch; ch = ch->nextchaccess) { 9779Sjkh switch (ch->command) { 9789Sjkh case erase: 97911894Speter if (!ch->login) { 98011894Speter if (AccessList) { 98111894Speter AccessList = 0; 98211894Speter changed = true; 98311894Speter } 98411894Speter } else 98511894Speter for (p = &AccessList; (t = *p); p = &t->nextaccess) 98611894Speter if (strcmp(ch->login, t->login) == 0) { 9879Sjkh *p = t->nextaccess; 98811894Speter changed = true; 98911894Speter break; 99011894Speter } 9919Sjkh break; 9929Sjkh case append: 9939Sjkh for (p = &AccessList; ; p = &t->nextaccess) 9949Sjkh if (!(t = *p)) { 9959Sjkh *p = t = ftalloc(struct access); 9969Sjkh t->login = ch->login; 99711894Speter t->nextaccess = 0; 99811894Speter changed = true; 9999Sjkh break; 10009Sjkh } else if (strcmp(ch->login, t->login) == 0) 10019Sjkh break; 10029Sjkh break; 10039Sjkh } 10049Sjkh } 100511894Speter return changed; 10069Sjkh} 10079Sjkh 10089Sjkh 10099Sjkh static int 10109Sjkhsendmail(Delta, who) 10119Sjkh char const *Delta, *who; 10129Sjkh/* Function: mail to who, informing him that his lock on delta was 10139Sjkh * broken by caller. Ask first whether to go ahead. Return false on 10149Sjkh * error or if user decides not to break the lock. 10159Sjkh */ 10169Sjkh{ 10179Sjkh#ifdef SENDMAIL 10189Sjkh char const *messagefile; 101911894Speter int old1, old2, c, status; 10209Sjkh FILE * mailmess; 10219Sjkh#endif 10229Sjkh 10239Sjkh 10249Sjkh aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who); 102511894Speter if (suppress_mail) 102611894Speter return true; 10279Sjkh if (!yesorno(false, "Do you want to break the lock? [ny](n): ")) 10289Sjkh return false; 10299Sjkh 10309Sjkh /* go ahead with breaking */ 10319Sjkh#ifdef SENDMAIL 10329Sjkh messagefile = maketemp(0); 103311894Speter if (!(mailmess = fopenSafer(messagefile, "w+"))) { 10349Sjkh efaterror(messagefile); 10359Sjkh } 10369Sjkh 10379Sjkh aprintf(mailmess, "Subject: Broken lock on %s\n\nYour lock on revision %s of file %s\nhas been broken by %s for the following reason:\n", 103811894Speter basefilename(RCSname), Delta, getfullRCSname(), getcaller() 10399Sjkh ); 10409Sjkh aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr); 104111894Speter eflush(); 10429Sjkh 10439Sjkh old1 = '\n'; old2 = ' '; 10449Sjkh for (; ;) { 10459Sjkh c = getcstdin(); 10469Sjkh if (feof(stdin)) { 10479Sjkh aprintf(mailmess, "%c\n", old1); 10489Sjkh break; 10499Sjkh } 10509Sjkh else if ( c == '\n' && old1 == '.' && old2 == '\n') 10519Sjkh break; 10529Sjkh else { 10539Sjkh afputc(old1, mailmess); 10549Sjkh old2 = old1; old1 = c; 105511894Speter if (c == '\n') { 105611894Speter aputs(">> ", stderr); 105711894Speter eflush(); 105811894Speter } 10599Sjkh } 10609Sjkh } 106111894Speter Orewind(mailmess); 106211894Speter aflush(mailmess); 106311894Speter status = run(fileno(mailmess), (char*)0, SENDMAIL, who, (char*)0); 10649Sjkh Ozclose(&mailmess); 106511894Speter if (status == 0) 106611894Speter return true; 106711894Speter warn("Mail failed."); 10689Sjkh#endif 106911894Speter warn("Mail notification of broken locks is not available."); 107011894Speter warn("Please tell `%s' why you broke the lock.", who); 10719Sjkh return(true); 10729Sjkh} 10739Sjkh 10749Sjkh 10759Sjkh 107611894Speter static int 10779Sjkhbreaklock(delta) 10789Sjkh struct hshentry const *delta; 10799Sjkh/* function: Finds the lock held by caller on delta, 10809Sjkh * and removes it. 10819Sjkh * Sends mail if a lock different from the caller's is broken. 10829Sjkh * Prints an error message if there is no such lock or error. 10839Sjkh */ 10849Sjkh{ 108511894Speter register struct rcslock *next, **trail; 10869Sjkh char const *num; 10879Sjkh 10889Sjkh num=delta->num; 108911894Speter for (trail = &Locks; (next = *trail); trail = &next->nextlock) 10909Sjkh if (strcmp(num, next->delta->num) == 0) { 10919Sjkh if ( 10929Sjkh strcmp(getcaller(),next->login) != 0 10939Sjkh && !sendmail(num, next->login) 10949Sjkh ) { 109511894Speter rcserror("revision %s still locked by %s", 109611894Speter num, next->login 109711894Speter ); 109811894Speter return false; 10999Sjkh } 110011894Speter diagnose("%s unlocked\n", next->delta->num); 110111894Speter *trail = next->nextlock; 110211894Speter next->delta->lockedby = 0; 110311894Speter return true; 11049Sjkh } 110511894Speter rcserror("no lock set on revision %s", num); 110611894Speter return false; 11079Sjkh} 11089Sjkh 11099Sjkh 11109Sjkh 11119Sjkh static struct hshentry * 11129Sjkhsearchcutpt(object, length, store) 11139Sjkh char const *object; 111411894Speter int length; 11159Sjkh struct hshentries *store; 11169Sjkh/* Function: Search store and return entry with number being object. */ 111711894Speter/* cuttail = 0, if the entry is Head; otherwise, cuttail */ 11189Sjkh/* is the entry point to the one with number being object */ 11199Sjkh 11209Sjkh{ 112111894Speter cuthead = 0; 11229Sjkh while (compartial(store->first->num, object, length)) { 11239Sjkh cuthead = store->first; 11249Sjkh store = store->rest; 11259Sjkh } 11269Sjkh return store->first; 11279Sjkh} 11289Sjkh 11299Sjkh 11309Sjkh 11319Sjkh static int 11329Sjkhbranchpoint(strt, tail) 11339Sjkhstruct hshentry *strt, *tail; 11349Sjkh/* Function: check whether the deltas between strt and tail */ 11359Sjkh/* are locked or branch point, return 1 if any is */ 11369Sjkh/* locked or branch point; otherwise, return 0 and */ 11379Sjkh/* mark deleted */ 11389Sjkh 11399Sjkh{ 11409Sjkh struct hshentry *pt; 114111894Speter struct rcslock const *lockpt; 11429Sjkh 114311894Speter for (pt = strt; pt != tail; pt = pt->next) { 11449Sjkh if ( pt->branches ){ /* a branch point */ 114511894Speter rcserror("can't remove branch point %s", pt->num); 114611894Speter return true; 11479Sjkh } 114811894Speter for (lockpt = Locks; lockpt; lockpt = lockpt->nextlock) 114911894Speter if (lockpt->delta == pt) { 115011894Speter rcserror("can't remove locked revision %s", pt->num); 115111894Speter return true; 115211894Speter } 115311894Speter pt->selector = false; 115411894Speter diagnose("deleting revision %s\n",pt->num); 11559Sjkh } 115611894Speter return false; 11579Sjkh} 11589Sjkh 11599Sjkh 11609Sjkh 11619Sjkh static int 11629Sjkhremoverevs() 11639Sjkh/* Function: get the revision range to be removed, and place the */ 11649Sjkh/* first revision removed in delstrt, the revision before */ 116511894Speter/* delstrt in cuthead (0, if delstrt is head), and the */ 116611894Speter/* revision after the last removed revision in cuttail (0 */ 11679Sjkh/* if the last is a leaf */ 11689Sjkh 11699Sjkh{ 11709Sjkh struct hshentry *target, *target2, *temp; 117111894Speter int length; 117211894Speter int cmp; 11739Sjkh 11749Sjkh if (!expandsym(delrev.strt, &numrev)) return 0; 117511894Speter target = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas); 11769Sjkh if ( ! target ) return 0; 117711894Speter cmp = cmpnum(target->num, numrev.string); 11789Sjkh length = countnumflds(numrev.string); 11799Sjkh 11809Sjkh if (delrev.code == 0) { /* -o rev or -o branch */ 11819Sjkh if (length & 1) 11829Sjkh temp=searchcutpt(target->num,length+1,gendeltas); 118311894Speter else if (cmp) { 118411894Speter rcserror("Revision %s doesn't exist.", numrev.string); 11859Sjkh return 0; 11869Sjkh } 11879Sjkh else 11889Sjkh temp = searchcutpt(numrev.string, length, gendeltas); 11899Sjkh cuttail = target->next; 11909Sjkh if ( branchpoint(temp, cuttail) ) { 119111894Speter cuttail = 0; 11929Sjkh return 0; 11939Sjkh } 11949Sjkh delstrt = temp; /* first revision to be removed */ 11959Sjkh return 1; 11969Sjkh } 11979Sjkh 11989Sjkh if (length & 1) { /* invalid branch after -o */ 119911894Speter rcserror("invalid branch range %s after -o", numrev.string); 12009Sjkh return 0; 12019Sjkh } 12029Sjkh 12039Sjkh if (delrev.code == 1) { /* -o -rev */ 12049Sjkh if ( length > 2 ) { 12059Sjkh temp = searchcutpt( target->num, length-1, gendeltas); 12069Sjkh cuttail = target->next; 12079Sjkh } 12089Sjkh else { 12099Sjkh temp = searchcutpt(target->num, length, gendeltas); 12109Sjkh cuttail = target; 12119Sjkh while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) ) 12129Sjkh cuttail = cuttail->next; 12139Sjkh } 12149Sjkh if ( branchpoint(temp, cuttail) ){ 121511894Speter cuttail = 0; 12169Sjkh return 0; 12179Sjkh } 12189Sjkh delstrt = temp; 12199Sjkh return 1; 12209Sjkh } 12219Sjkh 12229Sjkh if (delrev.code == 2) { /* -o rev- */ 12239Sjkh if ( length == 2 ) { 12249Sjkh temp = searchcutpt(target->num, 1,gendeltas); 122511894Speter if (cmp) 12269Sjkh cuttail = target; 12279Sjkh else 12289Sjkh cuttail = target->next; 12299Sjkh } 12309Sjkh else { 123111894Speter if (cmp) { 12329Sjkh cuthead = target; 12339Sjkh if ( !(temp = target->next) ) return 0; 12349Sjkh } 12359Sjkh else 12369Sjkh temp = searchcutpt(target->num, length, gendeltas); 12379Sjkh getbranchno(temp->num, &numrev); /* get branch number */ 123811894Speter VOID genrevs(numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas); 12399Sjkh } 12409Sjkh if ( branchpoint( temp, cuttail ) ) { 124111894Speter cuttail = 0; 12429Sjkh return 0; 12439Sjkh } 12449Sjkh delstrt = temp; 12459Sjkh return 1; 12469Sjkh } 12479Sjkh 12489Sjkh /* -o rev1-rev2 */ 12499Sjkh if (!expandsym(delrev.end, &numrev)) return 0; 12509Sjkh if ( 12519Sjkh length != countnumflds(numrev.string) 125211894Speter || (length>2 && compartial(numrev.string, target->num, length-1)) 12539Sjkh ) { 125411894Speter rcserror("invalid revision range %s-%s", 125511894Speter target->num, numrev.string 125611894Speter ); 12579Sjkh return 0; 12589Sjkh } 12599Sjkh 126011894Speter target2 = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas); 12619Sjkh if ( ! target2 ) return 0; 12629Sjkh 12639Sjkh if ( length > 2) { /* delete revisions on branches */ 12649Sjkh if ( cmpnum(target->num, target2->num) > 0) { 126511894Speter cmp = cmpnum(target2->num, numrev.string); 12669Sjkh temp = target; 12679Sjkh target = target2; 12689Sjkh target2 = temp; 12699Sjkh } 127011894Speter if (cmp) { 12719Sjkh if ( ! cmpnum(target->num, target2->num) ) { 127211894Speter rcserror("Revisions %s-%s don't exist.", 127311894Speter delrev.strt, delrev.end 127411894Speter ); 12759Sjkh return 0; 12769Sjkh } 12779Sjkh cuthead = target; 12789Sjkh temp = target->next; 12799Sjkh } 12809Sjkh else 12819Sjkh temp = searchcutpt(target->num, length, gendeltas); 12829Sjkh cuttail = target2->next; 12839Sjkh } 12849Sjkh else { /* delete revisions on trunk */ 12859Sjkh if ( cmpnum( target->num, target2->num) < 0 ) { 12869Sjkh temp = target; 12879Sjkh target = target2; 12889Sjkh target2 = temp; 12899Sjkh } 12909Sjkh else 129111894Speter cmp = cmpnum(target2->num, numrev.string); 129211894Speter if (cmp) { 12939Sjkh if ( ! cmpnum(target->num, target2->num) ) { 129411894Speter rcserror("Revisions %s-%s don't exist.", 129511894Speter delrev.strt, delrev.end 129611894Speter ); 12979Sjkh return 0; 12989Sjkh } 12999Sjkh cuttail = target2; 13009Sjkh } 13019Sjkh else 13029Sjkh cuttail = target2->next; 13039Sjkh temp = searchcutpt(target->num, length, gendeltas); 13049Sjkh } 13059Sjkh if ( branchpoint(temp, cuttail) ) { 130611894Speter cuttail = 0; 13079Sjkh return 0; 13089Sjkh } 13099Sjkh delstrt = temp; 13109Sjkh return 1; 13119Sjkh} 13129Sjkh 13139Sjkh 13149Sjkh 131511894Speter static int 13169Sjkhdoassoc() 131711894Speter/* Add or delete (if !revno) association that is stored in assoclst. */ 13189Sjkh{ 13199Sjkh char const *p; 132011894Speter int changed = false; 13219Sjkh struct Symrev const *curassoc; 132211894Speter struct assoc **pre, *pt; 13239Sjkh 13249Sjkh /* add new associations */ 132511894Speter for (curassoc = assoclst; curassoc; curassoc = curassoc->nextsym) { 132611894Speter char const *ssymbol = curassoc->ssymbol; 132711894Speter 132811894Speter if (!curassoc->revno) { /* delete symbol */ 132911894Speter for (pre = &Symbols; ; pre = &pt->nextassoc) 133011894Speter if (!(pt = *pre)) { 133111894Speter rcswarn("can't delete nonexisting symbol %s", ssymbol); 133211894Speter break; 133311894Speter } else if (strcmp(pt->symbol, ssymbol) == 0) { 133411894Speter *pre = pt->nextassoc; 133511894Speter changed = true; 133611894Speter break; 133711894Speter } 13389Sjkh } 13399Sjkh else { 13409Sjkh if (curassoc->revno[0]) { 13419Sjkh p = 0; 13429Sjkh if (expandsym(curassoc->revno, &numrev)) 13439Sjkh p = fstr_save(numrev.string); 13449Sjkh } else if (!(p = tiprev())) 134511894Speter rcserror("no latest revision to associate with symbol %s", 134611894Speter ssymbol 13479Sjkh ); 13489Sjkh if (p) 134911894Speter changed |= addsymbol(p, ssymbol, curassoc->override); 13509Sjkh } 13519Sjkh } 135211894Speter return changed; 13539Sjkh} 13549Sjkh 13559Sjkh 13569Sjkh 135711894Speter static int 13589Sjkhdolocks() 13599Sjkh/* Function: remove lock for caller or first lock if unlockcaller is set; 13609Sjkh * remove locks which are stored in rmvlocklst, 13619Sjkh * add new locks which are stored in newlocklst, 13629Sjkh * add lock for Dbranch or Head if lockhead is set. 13639Sjkh */ 13649Sjkh{ 13659Sjkh struct Lockrev const *lockpt; 13669Sjkh struct hshentry *target; 136711894Speter int changed = false; 13689Sjkh 13699Sjkh if (unlockcaller) { /* find lock for caller */ 13709Sjkh if ( Head ) { 13719Sjkh if (Locks) { 13729Sjkh switch (findlock(true, &target)) { 13739Sjkh case 0: 137411894Speter /* remove most recent lock */ 137511894Speter changed |= breaklock(Locks->delta); 13769Sjkh break; 13779Sjkh case 1: 13789Sjkh diagnose("%s unlocked\n",target->num); 137911894Speter changed = true; 13809Sjkh break; 13819Sjkh } 13829Sjkh } else { 138311894Speter rcswarn("No locks are set."); 13849Sjkh } 13859Sjkh } else { 138611894Speter rcswarn("can't unlock an empty tree"); 13879Sjkh } 13889Sjkh } 13899Sjkh 13909Sjkh /* remove locks which are stored in rmvlocklst */ 139111894Speter for (lockpt = rmvlocklst; lockpt; lockpt = lockpt->nextrev) 13929Sjkh if (expandsym(lockpt->revno, &numrev)) { 139311894Speter target = genrevs(numrev.string, (char *)0, (char *)0, (char *)0, &gendeltas); 13949Sjkh if ( target ) 13959Sjkh if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 139611894Speter rcserror("can't unlock nonexisting revision %s", 139711894Speter lockpt->revno 139811894Speter ); 13999Sjkh else 140011894Speter changed |= breaklock(target); 14019Sjkh /* breaklock does its own diagnose */ 14029Sjkh } 14039Sjkh 14049Sjkh /* add new locks which stored in newlocklst */ 140511894Speter for (lockpt = newlocklst; lockpt; lockpt = lockpt->nextrev) 140611894Speter changed |= setlock(lockpt->revno); 14079Sjkh 140811894Speter if (lockhead) /* lock default branch or head */ 140911894Speter if (Dbranch) 141011894Speter changed |= setlock(Dbranch); 141111894Speter else if (Head) 141211894Speter changed |= setlock(Head->num); 141311894Speter else 141411894Speter rcswarn("can't lock an empty tree"); 141511894Speter return changed; 14169Sjkh} 14179Sjkh 14189Sjkh 14199Sjkh 142011894Speter static int 14219Sjkhsetlock(rev) 14229Sjkh char const *rev; 14239Sjkh/* Function: Given a revision or branch number, finds the corresponding 14249Sjkh * delta and locks it for caller. 14259Sjkh */ 14269Sjkh{ 14279Sjkh struct hshentry *target; 142811894Speter int r; 14299Sjkh 14309Sjkh if (expandsym(rev, &numrev)) { 143111894Speter target = genrevs(numrev.string, (char*)0, (char*)0, 143211894Speter (char*)0, &gendeltas); 14339Sjkh if ( target ) 14349Sjkh if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 143511894Speter rcserror("can't lock nonexisting revision %s", 143611894Speter numrev.string 143711894Speter ); 143811894Speter else { 143911894Speter if ((r = addlock(target, false)) < 0 && breaklock(target)) 144011894Speter r = addlock(target, true); 144111894Speter if (0 <= r) { 144211894Speter if (r) 144311894Speter diagnose("%s locked\n", target->num); 144411894Speter return r; 144511894Speter } 144611894Speter } 144711894Speter } 144811894Speter return 0; 14499Sjkh} 14509Sjkh 14519Sjkh 145211894Speter static int 14539Sjkhdomessages() 14549Sjkh{ 14559Sjkh struct hshentry *target; 14569Sjkh struct Message *p; 145711894Speter int changed = false; 14589Sjkh 14599Sjkh for (p = messagelst; p; p = p->nextmessage) 14609Sjkh if ( 14619Sjkh expandsym(p->revno, &numrev) && 14629Sjkh (target = genrevs( 14639Sjkh numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas 14649Sjkh )) 146511894Speter ) { 146611894Speter /* 146711894Speter * We can't check the old log -- it's much later in the file. 146811894Speter * We pessimistically assume that it changed. 146911894Speter */ 14709Sjkh target->log = p->message; 147111894Speter changed = true; 147211894Speter } 147311894Speter return changed; 14749Sjkh} 14759Sjkh 14769Sjkh 147711894Speter static int 14789Sjkhrcs_setstate(rev,status) 14799Sjkh char const *rev, *status; 14809Sjkh/* Function: Given a revision or branch number, finds the corresponding delta 14819Sjkh * and sets its state to status. 14829Sjkh */ 14839Sjkh{ 14849Sjkh struct hshentry *target; 14859Sjkh 14869Sjkh if (expandsym(rev, &numrev)) { 148711894Speter target = genrevs(numrev.string, (char*)0, (char*)0, 148811894Speter (char*)0, &gendeltas); 14899Sjkh if ( target ) 14909Sjkh if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 149111894Speter rcserror("can't set state of nonexisting revision %s", 149211894Speter numrev.string 149311894Speter ); 149411894Speter else if (strcmp(target->state, status) != 0) { 14959Sjkh target->state = status; 149611894Speter return true; 149711894Speter } 149811894Speter } 149911894Speter return false; 15009Sjkh} 15019Sjkh 15029Sjkh 15039Sjkh 15049Sjkh 15059Sjkh 15069Sjkh static int 15079Sjkhbuildeltatext(deltas) 15089Sjkh struct hshentries const *deltas; 15099Sjkh/* Function: put the delta text on frewrite and make necessary */ 15109Sjkh/* change to delta text */ 15119Sjkh{ 15129Sjkh register FILE *fcut; /* temporary file to rebuild delta tree */ 151311894Speter char const *cutname; 15149Sjkh 151511894Speter fcut = 0; 15169Sjkh cuttail->selector = false; 15179Sjkh scanlogtext(deltas->first, false); 15189Sjkh if ( cuthead ) { 151911894Speter cutname = maketemp(3); 152011894Speter if (!(fcut = fopenSafer(cutname, FOPEN_WPLUS_WORK))) { 152111894Speter efaterror(cutname); 15229Sjkh } 15239Sjkh 15249Sjkh while (deltas->first != cuthead) { 15259Sjkh deltas = deltas->rest; 15269Sjkh scanlogtext(deltas->first, true); 15279Sjkh } 15289Sjkh 15299Sjkh snapshotedit(fcut); 153011894Speter Orewind(fcut); 153111894Speter aflush(fcut); 15329Sjkh } 15339Sjkh 15349Sjkh while (deltas->first != cuttail) 15359Sjkh scanlogtext((deltas = deltas->rest)->first, true); 153611894Speter finishedit((struct hshentry*)0, (FILE*)0, true); 15379Sjkh Ozclose(&fcopy); 15389Sjkh 153911894Speter if (fcut) { 154011894Speter char const *diffname = maketemp(0); 154111894Speter char const *diffv[6 + !!OPEN_O_BINARY]; 154211894Speter char const **diffp = diffv; 154311894Speter *++diffp = DIFF; 154411894Speter *++diffp = DIFFFLAGS; 154511894Speter# if OPEN_O_BINARY 154611894Speter if (Expand == BINARY_EXPAND) 154711894Speter *++diffp == "--binary"; 154811894Speter# endif 154911894Speter *++diffp = "-"; 155011894Speter *++diffp = resultname; 155111894Speter *++diffp = 0; 155211894Speter switch (runv(fileno(fcut), diffname, diffv)) { 15539Sjkh case DIFF_FAILURE: case DIFF_SUCCESS: break; 155411894Speter default: rcsfaterror("diff failed"); 15559Sjkh } 155611894Speter Ofclose(fcut); 155711894Speter return putdtext(cuttail,diffname,frewrite,true); 15589Sjkh } else 155911894Speter return putdtext(cuttail,resultname,frewrite,false); 15609Sjkh} 15619Sjkh 15629Sjkh 15639Sjkh 15649Sjkh static void 15659Sjkhbuildtree() 15669Sjkh/* Function: actually removes revisions whose selector field */ 15679Sjkh/* is false, and rebuilds the linkage of deltas. */ 15689Sjkh/* asks for reconfirmation if deleting last revision*/ 15699Sjkh{ 15709Sjkh struct hshentry * Delta; 15719Sjkh struct branchhead *pt, *pre; 15729Sjkh 15739Sjkh if ( cuthead ) 15749Sjkh if ( cuthead->next == delstrt ) 15759Sjkh cuthead->next = cuttail; 15769Sjkh else { 15779Sjkh pre = pt = cuthead->branches; 15789Sjkh while( pt && pt->hsh != delstrt ) { 15799Sjkh pre = pt; 15809Sjkh pt = pt->nextbranch; 15819Sjkh } 15829Sjkh if ( cuttail ) 15839Sjkh pt->hsh = cuttail; 15849Sjkh else if ( pt == pre ) 15859Sjkh cuthead->branches = pt->nextbranch; 15869Sjkh else 15879Sjkh pre->nextbranch = pt->nextbranch; 15889Sjkh } 15899Sjkh else { 159011894Speter if (!cuttail && !quietflag) { 15919Sjkh if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) { 159211894Speter rcserror("No revision deleted"); 15939Sjkh Delta = delstrt; 15949Sjkh while( Delta) { 15959Sjkh Delta->selector = true; 15969Sjkh Delta = Delta->next; 15979Sjkh } 15989Sjkh return; 15999Sjkh } 16009Sjkh } 16019Sjkh Head = cuttail; 16029Sjkh } 16039Sjkh return; 16049Sjkh} 16059Sjkh 160611894Speter#if RCS_lint 16079Sjkh/* This lets us lint everything all at once. */ 16089Sjkh 16099Sjkhchar const cmdid[] = ""; 16109Sjkh 16119Sjkh#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();} 16129Sjkh 16139Sjkh int 16149Sjkhmain(argc, argv) 16159Sjkh int argc; 16169Sjkh char **argv; 16179Sjkh{ 16189Sjkh go(ciId, ciExit); 16199Sjkh go(coId, coExit); 16209Sjkh go(identId, identExit); 16219Sjkh go(mergeId, mergeExit); 16229Sjkh go(rcsId, exiterr); 16239Sjkh go(rcscleanId, rcscleanExit); 16249Sjkh go(rcsdiffId, rdiffExit); 16259Sjkh go(rcsmergeId, rmergeExit); 16269Sjkh go(rlogId, rlogExit); 16279Sjkh return 0; 16289Sjkh} 16299Sjkh#endif 1630