rcs.c revision 11894
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 * $Log: rcs.c,v $ 3211894Speter * Revision 5.21 1995/06/16 06:19:24 eggert 3311894Speter * Update FSF address. 348858Srgrimes * 3511894Speter * Revision 5.20 1995/06/01 16:23:43 eggert 3611894Speter * (main): Warn if no options were given. Punctuate messages properly. 3711894Speter * 3811894Speter * (sendmail): Rewind mailmess before flushing it. 3911894Speter * Output another warning if mail should work but fails. 4011894Speter * 4111894Speter * (buildeltatext): Pass "--binary" if -kb and if --binary makes a difference. 4211894Speter * 4311894Speter * Revision 5.19 1994/03/17 14:05:48 eggert 4411894Speter * Use ORCSerror to clean up after a fatal error. Remove lint. 4511894Speter * Specify subprocess input via file descriptor, not file name. Remove lint. 4611894Speter * Flush stderr after prompt. 4711894Speter * 4811894Speter * Revision 5.18 1993/11/09 17:40:15 eggert 4911894Speter * -V now prints version on stdout and exits. Don't print usage twice. 5011894Speter * 5111894Speter * Revision 5.17 1993/11/03 17:42:27 eggert 5211894Speter * Add -z. Don't lose track of -m or -t when there are no other changes. 5311894Speter * Don't discard ignored phrases. Improve quality of diagnostics. 5411894Speter * 5511894Speter * Revision 5.16 1992/07/28 16:12:44 eggert 5611894Speter * rcs -l now asks whether you want to break the lock. 5711894Speter * Add -V. Set RCS file's mode and time at right moment. 5811894Speter * 5911894Speter * Revision 5.15 1992/02/17 23:02:20 eggert 6011894Speter * Add -T. 6111894Speter * 6211894Speter * Revision 5.14 1992/01/27 16:42:53 eggert 6311894Speter * Add -M. Avoid invoking umask(); it's one less thing to configure. 6411894Speter * Add support for bad_creat0. lint -> RCS_lint 6511894Speter * 6611894Speter * Revision 5.13 1992/01/06 02:42:34 eggert 6711894Speter * Avoid changing RCS file in common cases where no change can occur. 6811894Speter * 699Sjkh * Revision 5.12 1991/11/20 17:58:08 eggert 709Sjkh * Don't read the delta tree from a nonexistent RCS file. 719Sjkh * 729Sjkh * Revision 5.11 1991/10/07 17:32:46 eggert 739Sjkh * Remove lint. 749Sjkh * 759Sjkh * Revision 5.10 1991/08/19 23:17:54 eggert 769Sjkh * Add -m, -r$, piece tables. Revision separator is `:', not `-'. Tune. 779Sjkh * 789Sjkh * Revision 5.9 1991/04/21 11:58:18 eggert 799Sjkh * Add -x, RCSINIT, MS-DOS support. 809Sjkh * 819Sjkh * Revision 5.8 1991/02/25 07:12:38 eggert 829Sjkh * strsave -> str_save (DG/UX name clash) 839Sjkh * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability 849Sjkh * 859Sjkh * Revision 5.7 1990/12/18 17:19:21 eggert 869Sjkh * Fix bug with multiple -n and -N options. 879Sjkh * 889Sjkh * Revision 5.6 1990/12/04 05:18:40 eggert 899Sjkh * Use -I for prompts and -q for diagnostics. 909Sjkh * 919Sjkh * Revision 5.5 1990/11/11 00:06:35 eggert 929Sjkh * Fix `rcs -e' core dump. 939Sjkh * 949Sjkh * Revision 5.4 1990/11/01 05:03:33 eggert 959Sjkh * Add -I and new -t behavior. Permit arbitrary data in logs. 969Sjkh * 979Sjkh * Revision 5.3 1990/10/04 06:30:16 eggert 989Sjkh * Accumulate exit status across files. 999Sjkh * 1009Sjkh * Revision 5.2 1990/09/04 08:02:17 eggert 1019Sjkh * Standardize yes-or-no procedure. 1029Sjkh * 1039Sjkh * Revision 5.1 1990/08/29 07:13:51 eggert 1049Sjkh * Remove unused setuid support. Clean old log messages too. 1059Sjkh * 1069Sjkh * Revision 5.0 1990/08/22 08:12:42 eggert 1079Sjkh * Don't lose names when applying -a option to multiple files. 1089Sjkh * Remove compile-time limits; use malloc instead. Add setuid support. 1099Sjkh * Permit dates past 1999/12/31. Make lock and temp files faster and safer. 1109Sjkh * Ansify and Posixate. Add -V. Fix umask bug. Make linting easier. Tune. 1119Sjkh * Yield proper exit status. Check diff's output. 1129Sjkh * 1139Sjkh * Revision 4.11 89/05/01 15:12:06 narten 1149Sjkh * changed copyright header to reflect current distribution rules 1158858Srgrimes * 1169Sjkh * Revision 4.10 88/11/08 16:01:54 narten 1179Sjkh * didn't install previous patch correctly 1188858Srgrimes * 1199Sjkh * Revision 4.9 88/11/08 13:56:01 narten 1209Sjkh * removed include <sysexits.h> (not needed) 1219Sjkh * minor fix for -A option 1228858Srgrimes * 1239Sjkh * Revision 4.8 88/08/09 19:12:27 eggert 1249Sjkh * Don't access freed storage. 1259Sjkh * Use execv(), not system(); yield proper exit status; remove lint. 1268858Srgrimes * 1279Sjkh * Revision 4.7 87/12/18 11:37:17 narten 1289Sjkh * lint cleanups (Guy Harris) 1298858Srgrimes * 1309Sjkh * Revision 4.6 87/10/18 10:28:48 narten 1318858Srgrimes * Updating verison numbers. Changes relative to 1.1 are actually 1329Sjkh * relative to 4.3 1338858Srgrimes * 1349Sjkh * Revision 1.4 87/09/24 13:58:52 narten 1358858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf 1369Sjkh * warnings) 1378858Srgrimes * 1389Sjkh * Revision 1.3 87/03/27 14:21:55 jenkins 1399Sjkh * Port to suns 1408858Srgrimes * 1419Sjkh * Revision 1.2 85/12/17 13:59:09 albitz 1429Sjkh * Changed setstate to rcs_setstate because of conflict with random.o. 1438858Srgrimes * 1449Sjkh * Revision 4.3 83/12/15 12:27:33 wft 1459Sjkh * rcs -u now breaks most recent lock if it can't find a lock by the caller. 1468858Srgrimes * 1479Sjkh * Revision 4.2 83/12/05 10:18:20 wft 1489Sjkh * Added conditional compilation for sending mail. 1499Sjkh * Alternatives: V4_2BSD, V6, USG, and other. 1508858Srgrimes * 1519Sjkh * Revision 4.1 83/05/10 16:43:02 wft 1529Sjkh * Simplified breaklock(); added calls to findlock() and getcaller(). 1539Sjkh * Added option -b (default branch). Updated -s and -w for -b. 1549Sjkh * Removed calls to stat(); now done by pairfilenames(). 1559Sjkh * Replaced most catchints() calls with restoreints(). 1569Sjkh * Removed check for exit status of delivermail(). 1579Sjkh * Directed all interactive output to stderr. 1588858Srgrimes * 1599Sjkh * Revision 3.9.1.1 83/12/02 22:08:51 wft 1609Sjkh * Added conditional compilation for 4.2 sendmail and 4.1 delivermail. 1618858Srgrimes * 1629Sjkh * Revision 3.9 83/02/15 15:38:39 wft 1639Sjkh * Added call to fastcopy() to copy remainder of RCS file. 1649Sjkh * 1659Sjkh * Revision 3.8 83/01/18 17:37:51 wft 1669Sjkh * Changed sendmail(): now uses delivermail, and asks whether to break the lock. 1679Sjkh * 1689Sjkh * Revision 3.7 83/01/15 18:04:25 wft 1699Sjkh * Removed putree(); replaced with puttree() in rcssyn.c. 1709Sjkh * Combined putdellog() and scanlogtext(); deleted putdellog(). 1719Sjkh * Cleaned up diagnostics and error messages. Fixed problem with 1729Sjkh * mutilated files in case of deletions in 2 files in a single command. 1739Sjkh * Changed marking of selector from 'D' to DELETE. 1749Sjkh * 1759Sjkh * Revision 3.6 83/01/14 15:37:31 wft 1769Sjkh * Added ignoring of interrupts while new RCS file is renamed; 1779Sjkh * Avoids deletion of RCS files by interrupts. 1789Sjkh * 1799Sjkh * Revision 3.5 82/12/10 21:11:39 wft 1809Sjkh * Removed unused variables, fixed checking of return code from diff, 1819Sjkh * introduced variant COMPAT2 for skipping Suffix on -A files. 1829Sjkh * 1839Sjkh * Revision 3.4 82/12/04 13:18:20 wft 1849Sjkh * Replaced getdelta() with gettree(), changed breaklock to update 1859Sjkh * field lockedby, added some diagnostics. 1869Sjkh * 1879Sjkh * Revision 3.3 82/12/03 17:08:04 wft 1889Sjkh * Replaced getlogin() with getpwuid(), flcose() with ffclose(), 1899Sjkh * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x). 1909Sjkh * fixed -u for missing revno. Disambiguated structure members. 1919Sjkh * 1929Sjkh * Revision 3.2 82/10/18 21:05:07 wft 1939Sjkh * rcs -i now generates a file mode given by the umask minus write permission; 1949Sjkh * otherwise, rcs keeps the mode, but removes write permission. 1959Sjkh * I added a check for write error, fixed call to getlogin(), replaced 1969Sjkh * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed 1979Sjkh * conflicting, long identifiers. 1989Sjkh * 1999Sjkh * Revision 3.1 82/10/13 16:11:07 wft 2009Sjkh * fixed type of variables receiving from getc() (char -> int). 2019Sjkh */ 2029Sjkh 2039Sjkh 2049Sjkh#include "rcsbase.h" 2059Sjkh 2069Sjkhstruct Lockrev { 2079Sjkh char const *revno; 2089Sjkh struct Lockrev * nextrev; 2099Sjkh}; 2109Sjkh 2119Sjkhstruct Symrev { 2129Sjkh char const *revno; 2139Sjkh char const *ssymbol; 2149Sjkh int override; 2159Sjkh struct Symrev * nextsym; 2169Sjkh}; 2179Sjkh 2189Sjkhstruct Message { 2199Sjkh char const *revno; 2209Sjkh struct cbuf message; 2219Sjkh struct Message *nextmessage; 2229Sjkh}; 2239Sjkh 2249Sjkhstruct Status { 2259Sjkh char const *revno; 2269Sjkh char const *status; 2279Sjkh struct Status * nextstatus; 2289Sjkh}; 2299Sjkh 2309Sjkhenum changeaccess {append, erase}; 2319Sjkhstruct chaccess { 2329Sjkh char const *login; 2339Sjkh enum changeaccess command; 2349Sjkh struct chaccess *nextchaccess; 2359Sjkh}; 2369Sjkh 2379Sjkhstruct delrevpair { 2389Sjkh char const *strt; 2399Sjkh char const *end; 2409Sjkh int code; 2419Sjkh}; 2429Sjkh 24311894Speterstatic int branchpoint P((struct hshentry*,struct hshentry*)); 24411894Speterstatic int breaklock P((struct hshentry const*)); 2459Sjkhstatic int buildeltatext P((struct hshentries const*)); 24611894Speterstatic int doaccess P((void)); 24711894Speterstatic int doassoc P((void)); 24811894Speterstatic int dolocks P((void)); 24911894Speterstatic int domessages P((void)); 25011894Speterstatic int rcs_setstate P((char const*,char const*)); 2519Sjkhstatic int removerevs P((void)); 2529Sjkhstatic int sendmail P((char const*,char const*)); 25311894Speterstatic int setlock P((char const*)); 25411894Speterstatic struct Lockrev **rmnewlocklst P((char const*)); 25511894Speterstatic struct hshentry *searchcutpt P((char const*,int,struct hshentries*)); 2569Sjkhstatic void buildtree P((void)); 2579Sjkhstatic void cleanup P((void)); 2589Sjkhstatic void getaccessor P((char*,enum changeaccess)); 2599Sjkhstatic void getassoclst P((int,char*)); 2609Sjkhstatic void getchaccess P((char const*,enum changeaccess)); 2619Sjkhstatic void getdelrev P((char*)); 2629Sjkhstatic void getmessage P((char*)); 2639Sjkhstatic void getstates P((char*)); 2649Sjkhstatic void scanlogtext P((struct hshentry*,int)); 2659Sjkh 2669Sjkhstatic struct buf numrev; 2679Sjkhstatic char const *headstate; 2689Sjkhstatic int chgheadstate, exitstatus, lockhead, unlockcaller; 26911894Speterstatic int suppress_mail; 2709Sjkhstatic struct Lockrev *newlocklst, *rmvlocklst; 27111894Speterstatic struct Message *messagelst, **nextmessage; 27211894Speterstatic struct Status *statelst, **nextstate; 27311894Speterstatic struct Symrev *assoclst, **nextassoc; 2749Sjkhstatic struct chaccess *chaccess, **nextchaccess; 2759Sjkhstatic struct delrevpair delrev; 2769Sjkhstatic struct hshentry *cuthead, *cuttail, *delstrt; 2779Sjkhstatic struct hshentries *gendeltas; 2789Sjkh 27911894SpetermainProg(rcsId, "rcs", "$Id: rcs.c,v 5.21 1995/06/16 06:19:24 eggert Exp $") 2809Sjkh{ 2819Sjkh static char const cmdusage[] = 28211894Speter "\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 ..."; 2839Sjkh 2849Sjkh char *a, **newargv, *textfile; 2859Sjkh char const *branchsym, *commsyml; 28611894Speter int branchflag, changed, expmode, initflag; 28711894Speter int strictlock, strict_selected, textflag; 28811894Speter int keepRCStime, Ttimeflag; 28911894Speter size_t commsymlen; 2909Sjkh struct buf branchnum; 29111894Speter struct Lockrev *lockpt; 29211894Speter struct Lockrev **curlock, **rmvlock; 2939Sjkh struct Status * curstate; 2949Sjkh 2959Sjkh nosetid(); 2969Sjkh 29711894Speter nextassoc = &assoclst; 2989Sjkh nextchaccess = &chaccess; 29911894Speter nextmessage = &messagelst; 30011894Speter nextstate = &statelst; 30111894Speter branchsym = commsyml = textfile = 0; 3029Sjkh branchflag = strictlock = false; 3039Sjkh bufautobegin(&branchnum); 30411894Speter commsymlen = 0; 30511894Speter curlock = &newlocklst; 30611894Speter rmvlock = &rmvlocklst; 3079Sjkh expmode = -1; 3089Sjkh suffixes = X_DEFAULT; 3099Sjkh initflag= textflag = false; 3109Sjkh strict_selected = 0; 31111894Speter Ttimeflag = false; 3129Sjkh 3139Sjkh /* preprocessing command options */ 31411894Speter if (1 < argc && argv[1][0] != '-') 31511894Speter warn("No options were given; this usage is obsolescent."); 31611894Speter 3179Sjkh argc = getRCSINIT(argc, argv, &newargv); 3189Sjkh argv = newargv; 3199Sjkh while (a = *++argv, 0<--argc && *a++=='-') { 3209Sjkh switch (*a++) { 3219Sjkh 3229Sjkh case 'i': /* initial version */ 3239Sjkh initflag = true; 3249Sjkh break; 3259Sjkh 3269Sjkh case 'b': /* change default branch */ 3279Sjkh if (branchflag) redefined('b'); 3289Sjkh branchflag= true; 3299Sjkh branchsym = a; 3309Sjkh break; 3319Sjkh 3329Sjkh case 'c': /* change comment symbol */ 3339Sjkh if (commsyml) redefined('c'); 3349Sjkh commsyml = a; 33511894Speter commsymlen = strlen(a); 3369Sjkh break; 3379Sjkh 3389Sjkh case 'a': /* add new accessor */ 3399Sjkh getaccessor(*argv+1, append); 3409Sjkh break; 3419Sjkh 3429Sjkh case 'A': /* append access list according to accessfile */ 3439Sjkh if (!*a) { 34411894Speter error("missing pathname after -A"); 3459Sjkh break; 3469Sjkh } 3479Sjkh *argv = a; 34811894Speter if (0 < pairnames(1,argv,rcsreadopen,true,false)) { 3499Sjkh while (AccessList) { 3509Sjkh getchaccess(str_save(AccessList->login),append); 3519Sjkh AccessList = AccessList->nextaccess; 3529Sjkh } 3539Sjkh Izclose(&finptr); 3549Sjkh } 3559Sjkh break; 3569Sjkh 3579Sjkh case 'e': /* remove accessors */ 3589Sjkh getaccessor(*argv+1, erase); 3599Sjkh break; 3609Sjkh 3619Sjkh case 'l': /* lock a revision if it is unlocked */ 3629Sjkh if (!*a) { 3639Sjkh /* Lock head or default branch. */ 3649Sjkh lockhead = true; 3659Sjkh break; 3669Sjkh } 36711894Speter *curlock = lockpt = talloc(struct Lockrev); 3689Sjkh lockpt->revno = a; 36911894Speter lockpt->nextrev = 0; 37011894Speter curlock = &lockpt->nextrev; 3719Sjkh break; 3729Sjkh 3739Sjkh case 'u': /* release lock of a locked revision */ 3749Sjkh if (!*a) { 3759Sjkh unlockcaller=true; 3769Sjkh break; 3779Sjkh } 37811894Speter *rmvlock = lockpt = talloc(struct Lockrev); 3799Sjkh lockpt->revno = a; 38011894Speter lockpt->nextrev = 0; 38111894Speter rmvlock = &lockpt->nextrev; 38211894Speter curlock = rmnewlocklst(lockpt->revno); 3839Sjkh break; 3849Sjkh 3859Sjkh case 'L': /* set strict locking */ 38611894Speter if (strict_selected) { 3879Sjkh if (!strictlock) /* Already selected -U? */ 38811894Speter warn("-U overridden by -L"); 3899Sjkh } 3909Sjkh strictlock = true; 39111894Speter strict_selected = true; 3929Sjkh break; 3939Sjkh 3949Sjkh case 'U': /* release strict locking */ 39511894Speter if (strict_selected) { 3969Sjkh if (strictlock) /* Already selected -L? */ 39711894Speter warn("-L overridden by -U"); 3989Sjkh } 39911894Speter strict_selected = true; 4009Sjkh break; 4019Sjkh 4029Sjkh case 'n': /* add new association: error, if name exists */ 4039Sjkh if (!*a) { 4049Sjkh error("missing symbolic name after -n"); 4059Sjkh break; 4069Sjkh } 4079Sjkh getassoclst(false, (*argv)+1); 4089Sjkh break; 4099Sjkh 4109Sjkh case 'N': /* add or change association */ 4119Sjkh if (!*a) { 4129Sjkh error("missing symbolic name after -N"); 4139Sjkh break; 4149Sjkh } 4159Sjkh getassoclst(true, (*argv)+1); 4169Sjkh break; 4179Sjkh 4189Sjkh case 'm': /* change log message */ 4199Sjkh getmessage(a); 4209Sjkh break; 4219Sjkh 42211894Speter case 'M': /* do not send mail */ 42311894Speter suppress_mail = true; 42411894Speter break; 42511894Speter 4269Sjkh case 'o': /* delete revisions */ 4279Sjkh if (delrev.strt) redefined('o'); 4289Sjkh if (!*a) { 4299Sjkh error("missing revision range after -o"); 4309Sjkh break; 4319Sjkh } 4329Sjkh getdelrev( (*argv)+1 ); 4339Sjkh break; 4349Sjkh 4359Sjkh case 's': /* change state attribute of a revision */ 4369Sjkh if (!*a) { 4379Sjkh error("state missing after -s"); 4389Sjkh break; 4399Sjkh } 4409Sjkh getstates( (*argv)+1); 4419Sjkh break; 4429Sjkh 4439Sjkh case 't': /* change descriptive text */ 4449Sjkh textflag=true; 4459Sjkh if (*a) { 4469Sjkh if (textfile) redefined('t'); 4479Sjkh textfile = a; 4489Sjkh } 4499Sjkh break; 4509Sjkh 45111894Speter case 'T': /* do not update last-mod time for minor changes */ 45211894Speter if (*a) 45311894Speter goto unknown; 45411894Speter Ttimeflag = true; 45511894Speter break; 45611894Speter 4579Sjkh case 'I': 4589Sjkh interactiveflag = true; 4599Sjkh break; 4609Sjkh 4619Sjkh case 'q': 4629Sjkh quietflag = true; 4639Sjkh break; 4649Sjkh 4659Sjkh case 'x': 4669Sjkh suffixes = a; 4679Sjkh break; 4689Sjkh 4699Sjkh case 'V': 4709Sjkh setRCSversion(*argv); 4719Sjkh break; 4729Sjkh 47311894Speter case 'z': 47411894Speter zone_set(a); 47511894Speter break; 47611894Speter 4779Sjkh case 'k': /* set keyword expand mode */ 4789Sjkh if (0 <= expmode) redefined('k'); 4799Sjkh if (0 <= (expmode = str2expmode(a))) 4809Sjkh break; 4819Sjkh /* fall into */ 4829Sjkh default: 48311894Speter unknown: 48411894Speter error("unknown option: %s%s", *argv, cmdusage); 4859Sjkh }; 4869Sjkh } /* end processing of options */ 4879Sjkh 48811894Speter /* Now handle all pathnames. */ 48911894Speter if (nerror) cleanup(); 49011894Speter else if (argc < 1) faterror("no input file%s", cmdusage); 49111894Speter else for (; 0 < argc; cleanup(), ++argv, --argc) { 4929Sjkh 4939Sjkh ffree(); 4949Sjkh 4959Sjkh if ( initflag ) { 49611894Speter switch (pairnames(argc, argv, rcswriteopen, false, false)) { 4979Sjkh case -1: break; /* not exist; ok */ 4989Sjkh case 0: continue; /* error */ 49911894Speter case 1: rcserror("already exists"); 5009Sjkh continue; 5019Sjkh } 5029Sjkh } 5039Sjkh else { 50411894Speter switch (pairnames(argc, argv, rcswriteopen, true, false)) { 5059Sjkh case -1: continue; /* not exist */ 5069Sjkh case 0: continue; /* errors */ 5079Sjkh case 1: break; /* file exists; ok*/ 5089Sjkh } 5099Sjkh } 5109Sjkh 5119Sjkh 51211894Speter /* 51311894Speter * RCSname contains the name of the RCS file, and 51411894Speter * workname contains the name of the working file. 5159Sjkh * if !initflag, finptr contains the file descriptor for the 5169Sjkh * RCS file. The admin node is initialized. 5179Sjkh */ 5189Sjkh 51911894Speter diagnose("RCS file: %s\n", RCSname); 5209Sjkh 52111894Speter changed = initflag | textflag; 52211894Speter keepRCStime = Ttimeflag; 52311894Speter if (!initflag) { 5249Sjkh if (!checkaccesslist()) continue; 5259Sjkh gettree(); /* Read the delta tree. */ 5269Sjkh } 5279Sjkh 5289Sjkh /* update admin. node */ 52911894Speter if (strict_selected) { 53011894Speter changed |= StrictLocks ^ strictlock; 53111894Speter StrictLocks = strictlock; 53211894Speter } 53311894Speter if ( 53411894Speter commsyml && 53511894Speter ( 53611894Speter commsymlen != Comment.size || 53711894Speter memcmp(commsyml, Comment.string, commsymlen) != 0 53811894Speter ) 53911894Speter ) { 5409Sjkh Comment.string = commsyml; 5419Sjkh Comment.size = strlen(commsyml); 54211894Speter changed = true; 5439Sjkh } 54411894Speter if (0 <= expmode && Expand != expmode) { 54511894Speter Expand = expmode; 54611894Speter changed = true; 54711894Speter } 5489Sjkh 5499Sjkh /* update default branch */ 5509Sjkh if (branchflag && expandsym(branchsym, &branchnum)) { 5519Sjkh if (countnumflds(branchnum.string)) { 55211894Speter if (cmpnum(Dbranch, branchnum.string) != 0) { 55311894Speter Dbranch = branchnum.string; 55411894Speter changed = true; 55511894Speter } 5569Sjkh } else 55711894Speter if (Dbranch) { 55811894Speter Dbranch = 0; 55911894Speter changed = true; 56011894Speter } 56111894Speter } 5629Sjkh 56311894Speter changed |= doaccess(); /* Update access list. */ 5649Sjkh 56511894Speter changed |= doassoc(); /* Update association list. */ 5669Sjkh 56711894Speter changed |= dolocks(); /* Update locks. */ 5689Sjkh 56911894Speter changed |= domessages(); /* Update log messages. */ 5709Sjkh 5719Sjkh /* update state attribution */ 5729Sjkh if (chgheadstate) { 5739Sjkh /* change state of default branch or head */ 57411894Speter if (!Dbranch) { 57511894Speter if (!Head) 57611894Speter rcswarn("can't change states in an empty tree"); 57711894Speter else if (strcmp(Head->state, headstate) != 0) { 57811894Speter Head->state = headstate; 57911894Speter changed = true; 58011894Speter } 58111894Speter } else 58211894Speter changed |= rcs_setstate(Dbranch,headstate); 5839Sjkh } 58411894Speter for (curstate = statelst; curstate; curstate = curstate->nextstatus) 58511894Speter changed |= rcs_setstate(curstate->revno,curstate->status); 5869Sjkh 58711894Speter cuthead = cuttail = 0; 5889Sjkh if (delrev.strt && removerevs()) { 5899Sjkh /* rebuild delta tree if some deltas are deleted */ 5909Sjkh if ( cuttail ) 59111894Speter VOID genrevs( 59211894Speter cuttail->num, (char *)0, (char *)0, (char *)0, 59311894Speter &gendeltas 59411894Speter ); 5959Sjkh buildtree(); 59611894Speter changed = true; 59711894Speter keepRCStime = false; 5989Sjkh } 5999Sjkh 6009Sjkh if (nerror) 6019Sjkh continue; 6029Sjkh 60311894Speter putadmin(); 6049Sjkh if ( Head ) 6059Sjkh puttree(Head, frewrite); 6069Sjkh putdesc(textflag,textfile); 6079Sjkh 6089Sjkh if ( Head) { 60911894Speter if (delrev.strt || messagelst) { 6109Sjkh if (!cuttail || buildeltatext(gendeltas)) { 6119Sjkh advise_access(finptr, MADV_SEQUENTIAL); 61211894Speter scanlogtext((struct hshentry *)0, false); 6139Sjkh /* copy rest of delta text nodes that are not deleted */ 61411894Speter changed = true; 6159Sjkh } 6169Sjkh } 6179Sjkh } 6189Sjkh 61911894Speter if (initflag) { 62011894Speter /* Adjust things for donerewrite's sake. */ 62111894Speter if (stat(workname, &RCSstat) != 0) { 62211894Speter# if bad_creat0 62311894Speter mode_t m = umask(0); 62411894Speter (void) umask(m); 62511894Speter RCSstat.st_mode = (S_IRUSR|S_IRGRP|S_IROTH) & ~m; 62611894Speter# else 62711894Speter changed = -1; 62811894Speter# endif 62911894Speter } 63011894Speter RCSstat.st_nlink = 0; 63111894Speter keepRCStime = false; 63211894Speter } 63311894Speter if (donerewrite(changed, 63411894Speter keepRCStime ? RCSstat.st_mtime : (time_t)-1 63511894Speter ) != 0) 63611894Speter break; 63711894Speter 63811894Speter diagnose("done\n"); 63911894Speter } 64011894Speter 6419Sjkh tempunlink(); 6429Sjkh exitmain(exitstatus); 6439Sjkh} /* end of main (rcs) */ 6449Sjkh 6459Sjkh static void 6469Sjkhcleanup() 6479Sjkh{ 6489Sjkh if (nerror) exitstatus = EXIT_FAILURE; 6499Sjkh Izclose(&finptr); 6509Sjkh Ozclose(&fcopy); 65111894Speter ORCSclose(); 6529Sjkh dirtempunlink(); 6539Sjkh} 6549Sjkh 65511894Speter void 6569Sjkhexiterr() 6579Sjkh{ 65811894Speter ORCSerror(); 6599Sjkh dirtempunlink(); 6609Sjkh tempunlink(); 6619Sjkh _exit(EXIT_FAILURE); 6629Sjkh} 6639Sjkh 6649Sjkh 6659Sjkh static void 6669Sjkhgetassoclst(flag, sp) 6679Sjkhint flag; 6689Sjkhchar * sp; 6699Sjkh/* Function: associate a symbolic name to a revision or branch, */ 6709Sjkh/* and store in assoclst */ 6719Sjkh 6729Sjkh{ 6739Sjkh struct Symrev * pt; 6749Sjkh char const *temp; 6759Sjkh int c; 6769Sjkh 67711894Speter while ((c = *++sp) == ' ' || c == '\t' || c =='\n') 67811894Speter continue; 6799Sjkh temp = sp; 68011894Speter sp = checksym(sp, ':'); /* check for invalid symbolic name */ 6819Sjkh c = *sp; *sp = '\0'; 6829Sjkh while( c == ' ' || c == '\t' || c == '\n') c = *++sp; 6839Sjkh 6849Sjkh if ( c != ':' && c != '\0') { 6859Sjkh error("invalid string %s after option -n or -N",sp); 6869Sjkh return; 6879Sjkh } 6889Sjkh 6899Sjkh pt = talloc(struct Symrev); 6909Sjkh pt->ssymbol = temp; 6919Sjkh pt->override = flag; 6929Sjkh if (c == '\0') /* delete symbol */ 69311894Speter pt->revno = 0; 6949Sjkh else { 69511894Speter while ((c = *++sp) == ' ' || c == '\n' || c == '\t') 69611894Speter continue; 6979Sjkh pt->revno = sp; 6989Sjkh } 69911894Speter pt->nextsym = 0; 70011894Speter *nextassoc = pt; 70111894Speter nextassoc = &pt->nextsym; 7029Sjkh} 7039Sjkh 7049Sjkh 7059Sjkh static void 7069Sjkhgetchaccess(login, command) 7079Sjkh char const *login; 7089Sjkh enum changeaccess command; 7099Sjkh{ 7109Sjkh register struct chaccess *pt; 7119Sjkh 71211894Speter pt = talloc(struct chaccess); 7139Sjkh pt->login = login; 7149Sjkh pt->command = command; 71511894Speter pt->nextchaccess = 0; 71611894Speter *nextchaccess = pt; 7179Sjkh nextchaccess = &pt->nextchaccess; 7189Sjkh} 7199Sjkh 7209Sjkh 7219Sjkh 7229Sjkh static void 7239Sjkhgetaccessor(opt, command) 7249Sjkh char *opt; 7259Sjkh enum changeaccess command; 7269Sjkh/* Function: get the accessor list of options -e and -a, */ 7279Sjkh/* and store in chaccess */ 7289Sjkh 7299Sjkh 7309Sjkh{ 7319Sjkh register c; 7329Sjkh register char *sp; 7339Sjkh 7349Sjkh sp = opt; 73511894Speter while ((c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') 73611894Speter continue; 7379Sjkh if ( c == '\0') { 7389Sjkh if (command == erase && sp-opt == 1) { 73911894Speter getchaccess((char*)0, command); 7409Sjkh return; 7419Sjkh } 7429Sjkh error("missing login name after option -a or -e"); 7439Sjkh return; 7449Sjkh } 7459Sjkh 7469Sjkh while( c != '\0') { 7479Sjkh getchaccess(sp, command); 7489Sjkh sp = checkid(sp,','); 7499Sjkh c = *sp; *sp = '\0'; 7509Sjkh while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp); 7519Sjkh } 7529Sjkh} 7539Sjkh 7549Sjkh 7559Sjkh static void 7569Sjkhgetmessage(option) 7579Sjkh char *option; 7589Sjkh{ 7599Sjkh struct Message *pt; 7609Sjkh struct cbuf cb; 7619Sjkh char *m; 7629Sjkh 7639Sjkh if (!(m = strchr(option, ':'))) { 7649Sjkh error("-m option lacks revision number"); 7659Sjkh return; 7669Sjkh } 7679Sjkh *m++ = 0; 7689Sjkh cb = cleanlogmsg(m, strlen(m)); 7699Sjkh if (!cb.size) { 7709Sjkh error("-m option lacks log message"); 7719Sjkh return; 7729Sjkh } 7739Sjkh pt = talloc(struct Message); 7749Sjkh pt->revno = option; 7759Sjkh pt->message = cb; 7769Sjkh pt->nextmessage = 0; 77711894Speter *nextmessage = pt; 77811894Speter nextmessage = &pt->nextmessage; 7799Sjkh} 7809Sjkh 7819Sjkh 7829Sjkh static void 7839Sjkhgetstates(sp) 7849Sjkhchar *sp; 7859Sjkh/* Function: get one state attribute and the corresponding */ 7869Sjkh/* revision and store in statelst */ 7879Sjkh 7889Sjkh{ 7899Sjkh char const *temp; 7909Sjkh struct Status *pt; 7919Sjkh register c; 7929Sjkh 79311894Speter while ((c = *++sp) ==' ' || c == '\t' || c == '\n') 79411894Speter continue; 7959Sjkh temp = sp; 7969Sjkh sp = checkid(sp,':'); /* check for invalid state attribute */ 7979Sjkh c = *sp; *sp = '\0'; 7989Sjkh while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp; 7999Sjkh 8009Sjkh if ( c == '\0' ) { /* change state of def. branch or Head */ 8019Sjkh chgheadstate = true; 8029Sjkh headstate = temp; 8039Sjkh return; 8049Sjkh } 8059Sjkh else if ( c != ':' ) { 8069Sjkh error("missing ':' after state in option -s"); 8079Sjkh return; 8089Sjkh } 8099Sjkh 81011894Speter while ((c = *++sp) == ' ' || c == '\t' || c == '\n') 81111894Speter continue; 8129Sjkh pt = talloc(struct Status); 8139Sjkh pt->status = temp; 8149Sjkh pt->revno = sp; 81511894Speter pt->nextstatus = 0; 81611894Speter *nextstate = pt; 81711894Speter nextstate = &pt->nextstatus; 8189Sjkh} 8199Sjkh 8209Sjkh 8219Sjkh 8229Sjkh static void 8239Sjkhgetdelrev(sp) 8249Sjkhchar *sp; 8259Sjkh/* Function: get revision range or branch to be deleted, */ 8269Sjkh/* and place in delrev */ 8279Sjkh{ 8289Sjkh int c; 8299Sjkh struct delrevpair *pt; 8309Sjkh int separator; 8319Sjkh 8329Sjkh pt = &delrev; 83311894Speter while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t') 83411894Speter continue; 8359Sjkh 8369Sjkh /* Support old ambiguous '-' syntax; this will go away. */ 8379Sjkh if (strchr(sp,':')) 8389Sjkh separator = ':'; 8399Sjkh else { 8409Sjkh if (strchr(sp,'-') && VERSION(5) <= RCSversion) 8419Sjkh warn("`-' is obsolete in `-o%s'; use `:' instead", sp); 8429Sjkh separator = '-'; 8439Sjkh } 8449Sjkh 8459Sjkh if (c == separator) { /* -o:rev */ 84611894Speter while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t') 84711894Speter continue; 8489Sjkh pt->strt = sp; pt->code = 1; 8499Sjkh while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp); 8509Sjkh *sp = '\0'; 85111894Speter pt->end = 0; 8529Sjkh return; 8539Sjkh } 8549Sjkh else { 8559Sjkh pt->strt = sp; 8569Sjkh while( c != ' ' && c != '\n' && c != '\t' && c != '\0' 8579Sjkh && c != separator ) c = *++sp; 8589Sjkh *sp = '\0'; 8599Sjkh while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp; 8609Sjkh if ( c == '\0' ) { /* -o rev or branch */ 86111894Speter pt->code = 0; 86211894Speter pt->end = 0; 8639Sjkh return; 8649Sjkh } 8659Sjkh if (c != separator) { 86611894Speter error("invalid range %s %s after -o", pt->strt, sp); 8679Sjkh } 86811894Speter while ((c = *++sp) == ' ' || c == '\n' || c == '\t') 86911894Speter continue; 8709Sjkh if (!c) { /* -orev: */ 87111894Speter pt->code = 2; 87211894Speter pt->end = 0; 8739Sjkh return; 8749Sjkh } 8759Sjkh } 8769Sjkh /* -orev1:rev2 */ 8779Sjkh pt->end = sp; pt->code = 3; 8789Sjkh while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp; 8799Sjkh *sp = '\0'; 8809Sjkh} 8819Sjkh 8829Sjkh 8839Sjkh 8849Sjkh 8859Sjkh static void 8869Sjkhscanlogtext(delta,edit) 8879Sjkh struct hshentry *delta; 8889Sjkh int edit; 8899Sjkh/* Function: Scans delta text nodes up to and including the one given 89011894Speter * by delta, or up to last one present, if !delta. 89111894Speter * For the one given by delta (if delta), the log message is saved into 8929Sjkh * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied. 8939Sjkh * Assumes the initial lexeme must be read in first. 89411894Speter * Does not advance nexttok after it is finished, except if !delta. 8959Sjkh */ 8969Sjkh{ 8979Sjkh struct hshentry const *nextdelta; 8989Sjkh struct cbuf cb; 8999Sjkh 9009Sjkh for (;;) { 9019Sjkh foutptr = 0; 9029Sjkh if (eoflex()) { 9039Sjkh if(delta) 90411894Speter rcsfaterror("can't find delta for revision %s", 90511894Speter delta->num 90611894Speter ); 9079Sjkh return; /* no more delta text nodes */ 9089Sjkh } 9099Sjkh nextlex(); 9109Sjkh if (!(nextdelta=getnum())) 91111894Speter fatserror("delta number corrupted"); 9129Sjkh if (nextdelta->selector) { 9139Sjkh foutptr = frewrite; 9149Sjkh aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog); 9159Sjkh } 9169Sjkh getkeystring(Klog); 9179Sjkh if (nextdelta == cuttail) { 9189Sjkh cb = savestring(&curlogbuf); 9199Sjkh if (!delta->log.string) 9209Sjkh delta->log = cleanlogmsg(curlogbuf.string, cb.size); 92111894Speter nextlex(); 92211894Speter delta->igtext = getphrases(Ktext); 92311894Speter } else { 92411894Speter if (nextdelta->log.string && nextdelta->selector) { 92511894Speter foutptr = 0; 92611894Speter readstring(); 92711894Speter foutptr = frewrite; 92811894Speter putstring(foutptr, false, nextdelta->log, true); 92911894Speter afputc(nextc, foutptr); 93011894Speter } else 93111894Speter readstring(); 93211894Speter ignorephrases(Ktext); 93311894Speter } 9349Sjkh getkeystring(Ktext); 9359Sjkh 9369Sjkh if (delta==nextdelta) 9379Sjkh break; 9389Sjkh readstring(); /* skip over it */ 9399Sjkh 9409Sjkh } 9419Sjkh /* got the one we're looking for */ 9429Sjkh if (edit) 94311894Speter editstring((struct hshentry*)0); 9449Sjkh else 9459Sjkh enterstring(); 9469Sjkh} 9479Sjkh 9489Sjkh 9499Sjkh 95011894Speter static struct Lockrev ** 9519Sjkhrmnewlocklst(which) 95211894Speter char const *which; 95311894Speter/* Remove lock to revision WHICH from newlocklst. */ 9549Sjkh{ 95511894Speter struct Lockrev *pt, **pre; 9569Sjkh 95711894Speter pre = &newlocklst; 95811894Speter while ((pt = *pre)) 95911894Speter if (strcmp(pt->revno, which) != 0) 96011894Speter pre = &pt->nextrev; 96111894Speter else { 96211894Speter *pre = pt->nextrev; 9639Sjkh tfree(pt); 96411894Speter } 9659Sjkh return pre; 9669Sjkh} 9679Sjkh 9689Sjkh 9699Sjkh 97011894Speter static int 9719Sjkhdoaccess() 9729Sjkh{ 9739Sjkh register struct chaccess *ch; 9749Sjkh register struct access **p, *t; 97511894Speter register int changed = false; 9769Sjkh 9779Sjkh for (ch = chaccess; ch; ch = ch->nextchaccess) { 9789Sjkh switch (ch->command) { 9799Sjkh case erase: 98011894Speter if (!ch->login) { 98111894Speter if (AccessList) { 98211894Speter AccessList = 0; 98311894Speter changed = true; 98411894Speter } 98511894Speter } else 98611894Speter for (p = &AccessList; (t = *p); p = &t->nextaccess) 98711894Speter if (strcmp(ch->login, t->login) == 0) { 9889Sjkh *p = t->nextaccess; 98911894Speter changed = true; 99011894Speter break; 99111894Speter } 9929Sjkh break; 9939Sjkh case append: 9949Sjkh for (p = &AccessList; ; p = &t->nextaccess) 9959Sjkh if (!(t = *p)) { 9969Sjkh *p = t = ftalloc(struct access); 9979Sjkh t->login = ch->login; 99811894Speter t->nextaccess = 0; 99911894Speter changed = true; 10009Sjkh break; 10019Sjkh } else if (strcmp(ch->login, t->login) == 0) 10029Sjkh break; 10039Sjkh break; 10049Sjkh } 10059Sjkh } 100611894Speter return changed; 10079Sjkh} 10089Sjkh 10099Sjkh 10109Sjkh static int 10119Sjkhsendmail(Delta, who) 10129Sjkh char const *Delta, *who; 10139Sjkh/* Function: mail to who, informing him that his lock on delta was 10149Sjkh * broken by caller. Ask first whether to go ahead. Return false on 10159Sjkh * error or if user decides not to break the lock. 10169Sjkh */ 10179Sjkh{ 10189Sjkh#ifdef SENDMAIL 10199Sjkh char const *messagefile; 102011894Speter int old1, old2, c, status; 10219Sjkh FILE * mailmess; 10229Sjkh#endif 10239Sjkh 10249Sjkh 10259Sjkh aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who); 102611894Speter if (suppress_mail) 102711894Speter return true; 10289Sjkh if (!yesorno(false, "Do you want to break the lock? [ny](n): ")) 10299Sjkh return false; 10309Sjkh 10319Sjkh /* go ahead with breaking */ 10329Sjkh#ifdef SENDMAIL 10339Sjkh messagefile = maketemp(0); 103411894Speter if (!(mailmess = fopenSafer(messagefile, "w+"))) { 10359Sjkh efaterror(messagefile); 10369Sjkh } 10379Sjkh 10389Sjkh 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", 103911894Speter basefilename(RCSname), Delta, getfullRCSname(), getcaller() 10409Sjkh ); 10419Sjkh aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr); 104211894Speter eflush(); 10439Sjkh 10449Sjkh old1 = '\n'; old2 = ' '; 10459Sjkh for (; ;) { 10469Sjkh c = getcstdin(); 10479Sjkh if (feof(stdin)) { 10489Sjkh aprintf(mailmess, "%c\n", old1); 10499Sjkh break; 10509Sjkh } 10519Sjkh else if ( c == '\n' && old1 == '.' && old2 == '\n') 10529Sjkh break; 10539Sjkh else { 10549Sjkh afputc(old1, mailmess); 10559Sjkh old2 = old1; old1 = c; 105611894Speter if (c == '\n') { 105711894Speter aputs(">> ", stderr); 105811894Speter eflush(); 105911894Speter } 10609Sjkh } 10619Sjkh } 106211894Speter Orewind(mailmess); 106311894Speter aflush(mailmess); 106411894Speter status = run(fileno(mailmess), (char*)0, SENDMAIL, who, (char*)0); 10659Sjkh Ozclose(&mailmess); 106611894Speter if (status == 0) 106711894Speter return true; 106811894Speter warn("Mail failed."); 10699Sjkh#endif 107011894Speter warn("Mail notification of broken locks is not available."); 107111894Speter warn("Please tell `%s' why you broke the lock.", who); 10729Sjkh return(true); 10739Sjkh} 10749Sjkh 10759Sjkh 10769Sjkh 107711894Speter static int 10789Sjkhbreaklock(delta) 10799Sjkh struct hshentry const *delta; 10809Sjkh/* function: Finds the lock held by caller on delta, 10819Sjkh * and removes it. 10829Sjkh * Sends mail if a lock different from the caller's is broken. 10839Sjkh * Prints an error message if there is no such lock or error. 10849Sjkh */ 10859Sjkh{ 108611894Speter register struct rcslock *next, **trail; 10879Sjkh char const *num; 10889Sjkh 10899Sjkh num=delta->num; 109011894Speter for (trail = &Locks; (next = *trail); trail = &next->nextlock) 10919Sjkh if (strcmp(num, next->delta->num) == 0) { 10929Sjkh if ( 10939Sjkh strcmp(getcaller(),next->login) != 0 10949Sjkh && !sendmail(num, next->login) 10959Sjkh ) { 109611894Speter rcserror("revision %s still locked by %s", 109711894Speter num, next->login 109811894Speter ); 109911894Speter return false; 11009Sjkh } 110111894Speter diagnose("%s unlocked\n", next->delta->num); 110211894Speter *trail = next->nextlock; 110311894Speter next->delta->lockedby = 0; 110411894Speter return true; 11059Sjkh } 110611894Speter rcserror("no lock set on revision %s", num); 110711894Speter return false; 11089Sjkh} 11099Sjkh 11109Sjkh 11119Sjkh 11129Sjkh static struct hshentry * 11139Sjkhsearchcutpt(object, length, store) 11149Sjkh char const *object; 111511894Speter int length; 11169Sjkh struct hshentries *store; 11179Sjkh/* Function: Search store and return entry with number being object. */ 111811894Speter/* cuttail = 0, if the entry is Head; otherwise, cuttail */ 11199Sjkh/* is the entry point to the one with number being object */ 11209Sjkh 11219Sjkh{ 112211894Speter cuthead = 0; 11239Sjkh while (compartial(store->first->num, object, length)) { 11249Sjkh cuthead = store->first; 11259Sjkh store = store->rest; 11269Sjkh } 11279Sjkh return store->first; 11289Sjkh} 11299Sjkh 11309Sjkh 11319Sjkh 11329Sjkh static int 11339Sjkhbranchpoint(strt, tail) 11349Sjkhstruct hshentry *strt, *tail; 11359Sjkh/* Function: check whether the deltas between strt and tail */ 11369Sjkh/* are locked or branch point, return 1 if any is */ 11379Sjkh/* locked or branch point; otherwise, return 0 and */ 11389Sjkh/* mark deleted */ 11399Sjkh 11409Sjkh{ 11419Sjkh struct hshentry *pt; 114211894Speter struct rcslock const *lockpt; 11439Sjkh 114411894Speter for (pt = strt; pt != tail; pt = pt->next) { 11459Sjkh if ( pt->branches ){ /* a branch point */ 114611894Speter rcserror("can't remove branch point %s", pt->num); 114711894Speter return true; 11489Sjkh } 114911894Speter for (lockpt = Locks; lockpt; lockpt = lockpt->nextlock) 115011894Speter if (lockpt->delta == pt) { 115111894Speter rcserror("can't remove locked revision %s", pt->num); 115211894Speter return true; 115311894Speter } 115411894Speter pt->selector = false; 115511894Speter diagnose("deleting revision %s\n",pt->num); 11569Sjkh } 115711894Speter return false; 11589Sjkh} 11599Sjkh 11609Sjkh 11619Sjkh 11629Sjkh static int 11639Sjkhremoverevs() 11649Sjkh/* Function: get the revision range to be removed, and place the */ 11659Sjkh/* first revision removed in delstrt, the revision before */ 116611894Speter/* delstrt in cuthead (0, if delstrt is head), and the */ 116711894Speter/* revision after the last removed revision in cuttail (0 */ 11689Sjkh/* if the last is a leaf */ 11699Sjkh 11709Sjkh{ 11719Sjkh struct hshentry *target, *target2, *temp; 117211894Speter int length; 117311894Speter int cmp; 11749Sjkh 11759Sjkh if (!expandsym(delrev.strt, &numrev)) return 0; 117611894Speter target = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas); 11779Sjkh if ( ! target ) return 0; 117811894Speter cmp = cmpnum(target->num, numrev.string); 11799Sjkh length = countnumflds(numrev.string); 11809Sjkh 11819Sjkh if (delrev.code == 0) { /* -o rev or -o branch */ 11829Sjkh if (length & 1) 11839Sjkh temp=searchcutpt(target->num,length+1,gendeltas); 118411894Speter else if (cmp) { 118511894Speter rcserror("Revision %s doesn't exist.", numrev.string); 11869Sjkh return 0; 11879Sjkh } 11889Sjkh else 11899Sjkh temp = searchcutpt(numrev.string, length, gendeltas); 11909Sjkh cuttail = target->next; 11919Sjkh if ( branchpoint(temp, cuttail) ) { 119211894Speter cuttail = 0; 11939Sjkh return 0; 11949Sjkh } 11959Sjkh delstrt = temp; /* first revision to be removed */ 11969Sjkh return 1; 11979Sjkh } 11989Sjkh 11999Sjkh if (length & 1) { /* invalid branch after -o */ 120011894Speter rcserror("invalid branch range %s after -o", numrev.string); 12019Sjkh return 0; 12029Sjkh } 12039Sjkh 12049Sjkh if (delrev.code == 1) { /* -o -rev */ 12059Sjkh if ( length > 2 ) { 12069Sjkh temp = searchcutpt( target->num, length-1, gendeltas); 12079Sjkh cuttail = target->next; 12089Sjkh } 12099Sjkh else { 12109Sjkh temp = searchcutpt(target->num, length, gendeltas); 12119Sjkh cuttail = target; 12129Sjkh while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) ) 12139Sjkh cuttail = cuttail->next; 12149Sjkh } 12159Sjkh if ( branchpoint(temp, cuttail) ){ 121611894Speter cuttail = 0; 12179Sjkh return 0; 12189Sjkh } 12199Sjkh delstrt = temp; 12209Sjkh return 1; 12219Sjkh } 12229Sjkh 12239Sjkh if (delrev.code == 2) { /* -o rev- */ 12249Sjkh if ( length == 2 ) { 12259Sjkh temp = searchcutpt(target->num, 1,gendeltas); 122611894Speter if (cmp) 12279Sjkh cuttail = target; 12289Sjkh else 12299Sjkh cuttail = target->next; 12309Sjkh } 12319Sjkh else { 123211894Speter if (cmp) { 12339Sjkh cuthead = target; 12349Sjkh if ( !(temp = target->next) ) return 0; 12359Sjkh } 12369Sjkh else 12379Sjkh temp = searchcutpt(target->num, length, gendeltas); 12389Sjkh getbranchno(temp->num, &numrev); /* get branch number */ 123911894Speter VOID genrevs(numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas); 12409Sjkh } 12419Sjkh if ( branchpoint( temp, cuttail ) ) { 124211894Speter cuttail = 0; 12439Sjkh return 0; 12449Sjkh } 12459Sjkh delstrt = temp; 12469Sjkh return 1; 12479Sjkh } 12489Sjkh 12499Sjkh /* -o rev1-rev2 */ 12509Sjkh if (!expandsym(delrev.end, &numrev)) return 0; 12519Sjkh if ( 12529Sjkh length != countnumflds(numrev.string) 125311894Speter || (length>2 && compartial(numrev.string, target->num, length-1)) 12549Sjkh ) { 125511894Speter rcserror("invalid revision range %s-%s", 125611894Speter target->num, numrev.string 125711894Speter ); 12589Sjkh return 0; 12599Sjkh } 12609Sjkh 126111894Speter target2 = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas); 12629Sjkh if ( ! target2 ) return 0; 12639Sjkh 12649Sjkh if ( length > 2) { /* delete revisions on branches */ 12659Sjkh if ( cmpnum(target->num, target2->num) > 0) { 126611894Speter cmp = cmpnum(target2->num, numrev.string); 12679Sjkh temp = target; 12689Sjkh target = target2; 12699Sjkh target2 = temp; 12709Sjkh } 127111894Speter if (cmp) { 12729Sjkh if ( ! cmpnum(target->num, target2->num) ) { 127311894Speter rcserror("Revisions %s-%s don't exist.", 127411894Speter delrev.strt, delrev.end 127511894Speter ); 12769Sjkh return 0; 12779Sjkh } 12789Sjkh cuthead = target; 12799Sjkh temp = target->next; 12809Sjkh } 12819Sjkh else 12829Sjkh temp = searchcutpt(target->num, length, gendeltas); 12839Sjkh cuttail = target2->next; 12849Sjkh } 12859Sjkh else { /* delete revisions on trunk */ 12869Sjkh if ( cmpnum( target->num, target2->num) < 0 ) { 12879Sjkh temp = target; 12889Sjkh target = target2; 12899Sjkh target2 = temp; 12909Sjkh } 12919Sjkh else 129211894Speter cmp = cmpnum(target2->num, numrev.string); 129311894Speter if (cmp) { 12949Sjkh if ( ! cmpnum(target->num, target2->num) ) { 129511894Speter rcserror("Revisions %s-%s don't exist.", 129611894Speter delrev.strt, delrev.end 129711894Speter ); 12989Sjkh return 0; 12999Sjkh } 13009Sjkh cuttail = target2; 13019Sjkh } 13029Sjkh else 13039Sjkh cuttail = target2->next; 13049Sjkh temp = searchcutpt(target->num, length, gendeltas); 13059Sjkh } 13069Sjkh if ( branchpoint(temp, cuttail) ) { 130711894Speter cuttail = 0; 13089Sjkh return 0; 13099Sjkh } 13109Sjkh delstrt = temp; 13119Sjkh return 1; 13129Sjkh} 13139Sjkh 13149Sjkh 13159Sjkh 131611894Speter static int 13179Sjkhdoassoc() 131811894Speter/* Add or delete (if !revno) association that is stored in assoclst. */ 13199Sjkh{ 13209Sjkh char const *p; 132111894Speter int changed = false; 13229Sjkh struct Symrev const *curassoc; 132311894Speter struct assoc **pre, *pt; 13249Sjkh 13259Sjkh /* add new associations */ 132611894Speter for (curassoc = assoclst; curassoc; curassoc = curassoc->nextsym) { 132711894Speter char const *ssymbol = curassoc->ssymbol; 132811894Speter 132911894Speter if (!curassoc->revno) { /* delete symbol */ 133011894Speter for (pre = &Symbols; ; pre = &pt->nextassoc) 133111894Speter if (!(pt = *pre)) { 133211894Speter rcswarn("can't delete nonexisting symbol %s", ssymbol); 133311894Speter break; 133411894Speter } else if (strcmp(pt->symbol, ssymbol) == 0) { 133511894Speter *pre = pt->nextassoc; 133611894Speter changed = true; 133711894Speter break; 133811894Speter } 13399Sjkh } 13409Sjkh else { 13419Sjkh if (curassoc->revno[0]) { 13429Sjkh p = 0; 13439Sjkh if (expandsym(curassoc->revno, &numrev)) 13449Sjkh p = fstr_save(numrev.string); 13459Sjkh } else if (!(p = tiprev())) 134611894Speter rcserror("no latest revision to associate with symbol %s", 134711894Speter ssymbol 13489Sjkh ); 13499Sjkh if (p) 135011894Speter changed |= addsymbol(p, ssymbol, curassoc->override); 13519Sjkh } 13529Sjkh } 135311894Speter return changed; 13549Sjkh} 13559Sjkh 13569Sjkh 13579Sjkh 135811894Speter static int 13599Sjkhdolocks() 13609Sjkh/* Function: remove lock for caller or first lock if unlockcaller is set; 13619Sjkh * remove locks which are stored in rmvlocklst, 13629Sjkh * add new locks which are stored in newlocklst, 13639Sjkh * add lock for Dbranch or Head if lockhead is set. 13649Sjkh */ 13659Sjkh{ 13669Sjkh struct Lockrev const *lockpt; 13679Sjkh struct hshentry *target; 136811894Speter int changed = false; 13699Sjkh 13709Sjkh if (unlockcaller) { /* find lock for caller */ 13719Sjkh if ( Head ) { 13729Sjkh if (Locks) { 13739Sjkh switch (findlock(true, &target)) { 13749Sjkh case 0: 137511894Speter /* remove most recent lock */ 137611894Speter changed |= breaklock(Locks->delta); 13779Sjkh break; 13789Sjkh case 1: 13799Sjkh diagnose("%s unlocked\n",target->num); 138011894Speter changed = true; 13819Sjkh break; 13829Sjkh } 13839Sjkh } else { 138411894Speter rcswarn("No locks are set."); 13859Sjkh } 13869Sjkh } else { 138711894Speter rcswarn("can't unlock an empty tree"); 13889Sjkh } 13899Sjkh } 13909Sjkh 13919Sjkh /* remove locks which are stored in rmvlocklst */ 139211894Speter for (lockpt = rmvlocklst; lockpt; lockpt = lockpt->nextrev) 13939Sjkh if (expandsym(lockpt->revno, &numrev)) { 139411894Speter target = genrevs(numrev.string, (char *)0, (char *)0, (char *)0, &gendeltas); 13959Sjkh if ( target ) 13969Sjkh if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 139711894Speter rcserror("can't unlock nonexisting revision %s", 139811894Speter lockpt->revno 139911894Speter ); 14009Sjkh else 140111894Speter changed |= breaklock(target); 14029Sjkh /* breaklock does its own diagnose */ 14039Sjkh } 14049Sjkh 14059Sjkh /* add new locks which stored in newlocklst */ 140611894Speter for (lockpt = newlocklst; lockpt; lockpt = lockpt->nextrev) 140711894Speter changed |= setlock(lockpt->revno); 14089Sjkh 140911894Speter if (lockhead) /* lock default branch or head */ 141011894Speter if (Dbranch) 141111894Speter changed |= setlock(Dbranch); 141211894Speter else if (Head) 141311894Speter changed |= setlock(Head->num); 141411894Speter else 141511894Speter rcswarn("can't lock an empty tree"); 141611894Speter return changed; 14179Sjkh} 14189Sjkh 14199Sjkh 14209Sjkh 142111894Speter static int 14229Sjkhsetlock(rev) 14239Sjkh char const *rev; 14249Sjkh/* Function: Given a revision or branch number, finds the corresponding 14259Sjkh * delta and locks it for caller. 14269Sjkh */ 14279Sjkh{ 14289Sjkh struct hshentry *target; 142911894Speter int r; 14309Sjkh 14319Sjkh if (expandsym(rev, &numrev)) { 143211894Speter target = genrevs(numrev.string, (char*)0, (char*)0, 143311894Speter (char*)0, &gendeltas); 14349Sjkh if ( target ) 14359Sjkh if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 143611894Speter rcserror("can't lock nonexisting revision %s", 143711894Speter numrev.string 143811894Speter ); 143911894Speter else { 144011894Speter if ((r = addlock(target, false)) < 0 && breaklock(target)) 144111894Speter r = addlock(target, true); 144211894Speter if (0 <= r) { 144311894Speter if (r) 144411894Speter diagnose("%s locked\n", target->num); 144511894Speter return r; 144611894Speter } 144711894Speter } 144811894Speter } 144911894Speter return 0; 14509Sjkh} 14519Sjkh 14529Sjkh 145311894Speter static int 14549Sjkhdomessages() 14559Sjkh{ 14569Sjkh struct hshentry *target; 14579Sjkh struct Message *p; 145811894Speter int changed = false; 14599Sjkh 14609Sjkh for (p = messagelst; p; p = p->nextmessage) 14619Sjkh if ( 14629Sjkh expandsym(p->revno, &numrev) && 14639Sjkh (target = genrevs( 14649Sjkh numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas 14659Sjkh )) 146611894Speter ) { 146711894Speter /* 146811894Speter * We can't check the old log -- it's much later in the file. 146911894Speter * We pessimistically assume that it changed. 147011894Speter */ 14719Sjkh target->log = p->message; 147211894Speter changed = true; 147311894Speter } 147411894Speter return changed; 14759Sjkh} 14769Sjkh 14779Sjkh 147811894Speter static int 14799Sjkhrcs_setstate(rev,status) 14809Sjkh char const *rev, *status; 14819Sjkh/* Function: Given a revision or branch number, finds the corresponding delta 14829Sjkh * and sets its state to status. 14839Sjkh */ 14849Sjkh{ 14859Sjkh struct hshentry *target; 14869Sjkh 14879Sjkh if (expandsym(rev, &numrev)) { 148811894Speter target = genrevs(numrev.string, (char*)0, (char*)0, 148911894Speter (char*)0, &gendeltas); 14909Sjkh if ( target ) 14919Sjkh if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 149211894Speter rcserror("can't set state of nonexisting revision %s", 149311894Speter numrev.string 149411894Speter ); 149511894Speter else if (strcmp(target->state, status) != 0) { 14969Sjkh target->state = status; 149711894Speter return true; 149811894Speter } 149911894Speter } 150011894Speter return false; 15019Sjkh} 15029Sjkh 15039Sjkh 15049Sjkh 15059Sjkh 15069Sjkh 15079Sjkh static int 15089Sjkhbuildeltatext(deltas) 15099Sjkh struct hshentries const *deltas; 15109Sjkh/* Function: put the delta text on frewrite and make necessary */ 15119Sjkh/* change to delta text */ 15129Sjkh{ 15139Sjkh register FILE *fcut; /* temporary file to rebuild delta tree */ 151411894Speter char const *cutname; 15159Sjkh 151611894Speter fcut = 0; 15179Sjkh cuttail->selector = false; 15189Sjkh scanlogtext(deltas->first, false); 15199Sjkh if ( cuthead ) { 152011894Speter cutname = maketemp(3); 152111894Speter if (!(fcut = fopenSafer(cutname, FOPEN_WPLUS_WORK))) { 152211894Speter efaterror(cutname); 15239Sjkh } 15249Sjkh 15259Sjkh while (deltas->first != cuthead) { 15269Sjkh deltas = deltas->rest; 15279Sjkh scanlogtext(deltas->first, true); 15289Sjkh } 15299Sjkh 15309Sjkh snapshotedit(fcut); 153111894Speter Orewind(fcut); 153211894Speter aflush(fcut); 15339Sjkh } 15349Sjkh 15359Sjkh while (deltas->first != cuttail) 15369Sjkh scanlogtext((deltas = deltas->rest)->first, true); 153711894Speter finishedit((struct hshentry*)0, (FILE*)0, true); 15389Sjkh Ozclose(&fcopy); 15399Sjkh 154011894Speter if (fcut) { 154111894Speter char const *diffname = maketemp(0); 154211894Speter char const *diffv[6 + !!OPEN_O_BINARY]; 154311894Speter char const **diffp = diffv; 154411894Speter *++diffp = DIFF; 154511894Speter *++diffp = DIFFFLAGS; 154611894Speter# if OPEN_O_BINARY 154711894Speter if (Expand == BINARY_EXPAND) 154811894Speter *++diffp == "--binary"; 154911894Speter# endif 155011894Speter *++diffp = "-"; 155111894Speter *++diffp = resultname; 155211894Speter *++diffp = 0; 155311894Speter switch (runv(fileno(fcut), diffname, diffv)) { 15549Sjkh case DIFF_FAILURE: case DIFF_SUCCESS: break; 155511894Speter default: rcsfaterror("diff failed"); 15569Sjkh } 155711894Speter Ofclose(fcut); 155811894Speter return putdtext(cuttail,diffname,frewrite,true); 15599Sjkh } else 156011894Speter return putdtext(cuttail,resultname,frewrite,false); 15619Sjkh} 15629Sjkh 15639Sjkh 15649Sjkh 15659Sjkh static void 15669Sjkhbuildtree() 15679Sjkh/* Function: actually removes revisions whose selector field */ 15689Sjkh/* is false, and rebuilds the linkage of deltas. */ 15699Sjkh/* asks for reconfirmation if deleting last revision*/ 15709Sjkh{ 15719Sjkh struct hshentry * Delta; 15729Sjkh struct branchhead *pt, *pre; 15739Sjkh 15749Sjkh if ( cuthead ) 15759Sjkh if ( cuthead->next == delstrt ) 15769Sjkh cuthead->next = cuttail; 15779Sjkh else { 15789Sjkh pre = pt = cuthead->branches; 15799Sjkh while( pt && pt->hsh != delstrt ) { 15809Sjkh pre = pt; 15819Sjkh pt = pt->nextbranch; 15829Sjkh } 15839Sjkh if ( cuttail ) 15849Sjkh pt->hsh = cuttail; 15859Sjkh else if ( pt == pre ) 15869Sjkh cuthead->branches = pt->nextbranch; 15879Sjkh else 15889Sjkh pre->nextbranch = pt->nextbranch; 15899Sjkh } 15909Sjkh else { 159111894Speter if (!cuttail && !quietflag) { 15929Sjkh if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) { 159311894Speter rcserror("No revision deleted"); 15949Sjkh Delta = delstrt; 15959Sjkh while( Delta) { 15969Sjkh Delta->selector = true; 15979Sjkh Delta = Delta->next; 15989Sjkh } 15999Sjkh return; 16009Sjkh } 16019Sjkh } 16029Sjkh Head = cuttail; 16039Sjkh } 16049Sjkh return; 16059Sjkh} 16069Sjkh 160711894Speter#if RCS_lint 16089Sjkh/* This lets us lint everything all at once. */ 16099Sjkh 16109Sjkhchar const cmdid[] = ""; 16119Sjkh 16129Sjkh#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();} 16139Sjkh 16149Sjkh int 16159Sjkhmain(argc, argv) 16169Sjkh int argc; 16179Sjkh char **argv; 16189Sjkh{ 16199Sjkh go(ciId, ciExit); 16209Sjkh go(coId, coExit); 16219Sjkh go(identId, identExit); 16229Sjkh go(mergeId, mergeExit); 16239Sjkh go(rcsId, exiterr); 16249Sjkh go(rcscleanId, rcscleanExit); 16259Sjkh go(rcsdiffId, rdiffExit); 16269Sjkh go(rcsmergeId, rmergeExit); 16279Sjkh go(rlogId, rlogExit); 16289Sjkh return 0; 16299Sjkh} 16309Sjkh#endif 1631