rcs.c revision 9
19Sjkh/* 29Sjkh * RCS create/change operation 39Sjkh */ 49Sjkh/* Copyright (C) 1982, 1988, 1989 Walter Tichy 59Sjkh Copyright 1990, 1991 by Paul Eggert 69Sjkh Distributed under license by the Free Software Foundation, Inc. 79Sjkh 89SjkhThis file is part of RCS. 99Sjkh 109SjkhRCS is free software; you can redistribute it and/or modify 119Sjkhit under the terms of the GNU General Public License as published by 129Sjkhthe Free Software Foundation; either version 2, or (at your option) 139Sjkhany later version. 149Sjkh 159SjkhRCS is distributed in the hope that it will be useful, 169Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of 179SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 189SjkhGNU General Public License for more details. 199Sjkh 209SjkhYou should have received a copy of the GNU General Public License 219Sjkhalong with RCS; see the file COPYING. If not, write to 229Sjkhthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 239Sjkh 249SjkhReport problems and direct all questions to: 259Sjkh 269Sjkh rcs-bugs@cs.purdue.edu 279Sjkh 289Sjkh*/ 299Sjkh 309Sjkh 319Sjkh 329Sjkh 339Sjkh/* $Log: rcs.c,v $ 349Sjkh * Revision 5.12 1991/11/20 17:58:08 eggert 359Sjkh * Don't read the delta tree from a nonexistent RCS file. 369Sjkh * 379Sjkh * Revision 5.11 1991/10/07 17:32:46 eggert 389Sjkh * Remove lint. 399Sjkh * 409Sjkh * Revision 5.10 1991/08/19 23:17:54 eggert 419Sjkh * Add -m, -r$, piece tables. Revision separator is `:', not `-'. Tune. 429Sjkh * 439Sjkh * Revision 5.9 1991/04/21 11:58:18 eggert 449Sjkh * Add -x, RCSINIT, MS-DOS support. 459Sjkh * 469Sjkh * Revision 5.8 1991/02/25 07:12:38 eggert 479Sjkh * strsave -> str_save (DG/UX name clash) 489Sjkh * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability 499Sjkh * 509Sjkh * Revision 5.7 1990/12/18 17:19:21 eggert 519Sjkh * Fix bug with multiple -n and -N options. 529Sjkh * 539Sjkh * Revision 5.6 1990/12/04 05:18:40 eggert 549Sjkh * Use -I for prompts and -q for diagnostics. 559Sjkh * 569Sjkh * Revision 5.5 1990/11/11 00:06:35 eggert 579Sjkh * Fix `rcs -e' core dump. 589Sjkh * 599Sjkh * Revision 5.4 1990/11/01 05:03:33 eggert 609Sjkh * Add -I and new -t behavior. Permit arbitrary data in logs. 619Sjkh * 629Sjkh * Revision 5.3 1990/10/04 06:30:16 eggert 639Sjkh * Accumulate exit status across files. 649Sjkh * 659Sjkh * Revision 5.2 1990/09/04 08:02:17 eggert 669Sjkh * Standardize yes-or-no procedure. 679Sjkh * 689Sjkh * Revision 5.1 1990/08/29 07:13:51 eggert 699Sjkh * Remove unused setuid support. Clean old log messages too. 709Sjkh * 719Sjkh * Revision 5.0 1990/08/22 08:12:42 eggert 729Sjkh * Don't lose names when applying -a option to multiple files. 739Sjkh * Remove compile-time limits; use malloc instead. Add setuid support. 749Sjkh * Permit dates past 1999/12/31. Make lock and temp files faster and safer. 759Sjkh * Ansify and Posixate. Add -V. Fix umask bug. Make linting easier. Tune. 769Sjkh * Yield proper exit status. Check diff's output. 779Sjkh * 789Sjkh * Revision 4.11 89/05/01 15:12:06 narten 799Sjkh * changed copyright header to reflect current distribution rules 809Sjkh * 819Sjkh * Revision 4.10 88/11/08 16:01:54 narten 829Sjkh * didn't install previous patch correctly 839Sjkh * 849Sjkh * Revision 4.9 88/11/08 13:56:01 narten 859Sjkh * removed include <sysexits.h> (not needed) 869Sjkh * minor fix for -A option 879Sjkh * 889Sjkh * Revision 4.8 88/08/09 19:12:27 eggert 899Sjkh * Don't access freed storage. 909Sjkh * Use execv(), not system(); yield proper exit status; remove lint. 919Sjkh * 929Sjkh * Revision 4.7 87/12/18 11:37:17 narten 939Sjkh * lint cleanups (Guy Harris) 949Sjkh * 959Sjkh * Revision 4.6 87/10/18 10:28:48 narten 969Sjkh * Updating verison numbers. Changes relative to 1.1 are actually 979Sjkh * relative to 4.3 989Sjkh * 999Sjkh * Revision 1.4 87/09/24 13:58:52 narten 1009Sjkh * Sources now pass through lint (if you ignore printf/sprintf/fprintf 1019Sjkh * warnings) 1029Sjkh * 1039Sjkh * Revision 1.3 87/03/27 14:21:55 jenkins 1049Sjkh * Port to suns 1059Sjkh * 1069Sjkh * Revision 1.2 85/12/17 13:59:09 albitz 1079Sjkh * Changed setstate to rcs_setstate because of conflict with random.o. 1089Sjkh * 1099Sjkh * Revision 4.3 83/12/15 12:27:33 wft 1109Sjkh * rcs -u now breaks most recent lock if it can't find a lock by the caller. 1119Sjkh * 1129Sjkh * Revision 4.2 83/12/05 10:18:20 wft 1139Sjkh * Added conditional compilation for sending mail. 1149Sjkh * Alternatives: V4_2BSD, V6, USG, and other. 1159Sjkh * 1169Sjkh * Revision 4.1 83/05/10 16:43:02 wft 1179Sjkh * Simplified breaklock(); added calls to findlock() and getcaller(). 1189Sjkh * Added option -b (default branch). Updated -s and -w for -b. 1199Sjkh * Removed calls to stat(); now done by pairfilenames(). 1209Sjkh * Replaced most catchints() calls with restoreints(). 1219Sjkh * Removed check for exit status of delivermail(). 1229Sjkh * Directed all interactive output to stderr. 1239Sjkh * 1249Sjkh * Revision 3.9.1.1 83/12/02 22:08:51 wft 1259Sjkh * Added conditional compilation for 4.2 sendmail and 4.1 delivermail. 1269Sjkh * 1279Sjkh * Revision 3.9 83/02/15 15:38:39 wft 1289Sjkh * Added call to fastcopy() to copy remainder of RCS file. 1299Sjkh * 1309Sjkh * Revision 3.8 83/01/18 17:37:51 wft 1319Sjkh * Changed sendmail(): now uses delivermail, and asks whether to break the lock. 1329Sjkh * 1339Sjkh * Revision 3.7 83/01/15 18:04:25 wft 1349Sjkh * Removed putree(); replaced with puttree() in rcssyn.c. 1359Sjkh * Combined putdellog() and scanlogtext(); deleted putdellog(). 1369Sjkh * Cleaned up diagnostics and error messages. Fixed problem with 1379Sjkh * mutilated files in case of deletions in 2 files in a single command. 1389Sjkh * Changed marking of selector from 'D' to DELETE. 1399Sjkh * 1409Sjkh * Revision 3.6 83/01/14 15:37:31 wft 1419Sjkh * Added ignoring of interrupts while new RCS file is renamed; 1429Sjkh * Avoids deletion of RCS files by interrupts. 1439Sjkh * 1449Sjkh * Revision 3.5 82/12/10 21:11:39 wft 1459Sjkh * Removed unused variables, fixed checking of return code from diff, 1469Sjkh * introduced variant COMPAT2 for skipping Suffix on -A files. 1479Sjkh * 1489Sjkh * Revision 3.4 82/12/04 13:18:20 wft 1499Sjkh * Replaced getdelta() with gettree(), changed breaklock to update 1509Sjkh * field lockedby, added some diagnostics. 1519Sjkh * 1529Sjkh * Revision 3.3 82/12/03 17:08:04 wft 1539Sjkh * Replaced getlogin() with getpwuid(), flcose() with ffclose(), 1549Sjkh * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x). 1559Sjkh * fixed -u for missing revno. Disambiguated structure members. 1569Sjkh * 1579Sjkh * Revision 3.2 82/10/18 21:05:07 wft 1589Sjkh * rcs -i now generates a file mode given by the umask minus write permission; 1599Sjkh * otherwise, rcs keeps the mode, but removes write permission. 1609Sjkh * I added a check for write error, fixed call to getlogin(), replaced 1619Sjkh * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed 1629Sjkh * conflicting, long identifiers. 1639Sjkh * 1649Sjkh * Revision 3.1 82/10/13 16:11:07 wft 1659Sjkh * fixed type of variables receiving from getc() (char -> int). 1669Sjkh */ 1679Sjkh 1689Sjkh 1699Sjkh#include "rcsbase.h" 1709Sjkh 1719Sjkhstruct Lockrev { 1729Sjkh char const *revno; 1739Sjkh struct Lockrev * nextrev; 1749Sjkh}; 1759Sjkh 1769Sjkhstruct Symrev { 1779Sjkh char const *revno; 1789Sjkh char const *ssymbol; 1799Sjkh int override; 1809Sjkh struct Symrev * nextsym; 1819Sjkh}; 1829Sjkh 1839Sjkhstruct Message { 1849Sjkh char const *revno; 1859Sjkh struct cbuf message; 1869Sjkh struct Message *nextmessage; 1879Sjkh}; 1889Sjkh 1899Sjkhstruct Status { 1909Sjkh char const *revno; 1919Sjkh char const *status; 1929Sjkh struct Status * nextstatus; 1939Sjkh}; 1949Sjkh 1959Sjkhenum changeaccess {append, erase}; 1969Sjkhstruct chaccess { 1979Sjkh char const *login; 1989Sjkh enum changeaccess command; 1999Sjkh struct chaccess *nextchaccess; 2009Sjkh}; 2019Sjkh 2029Sjkhstruct delrevpair { 2039Sjkh char const *strt; 2049Sjkh char const *end; 2059Sjkh int code; 2069Sjkh}; 2079Sjkh 2089Sjkhstatic int buildeltatext P((struct hshentries const*)); 2099Sjkhstatic int removerevs P((void)); 2109Sjkhstatic int sendmail P((char const*,char const*)); 2119Sjkhstatic struct Lockrev *rmnewlocklst P((struct Lockrev const*)); 2129Sjkhstatic void breaklock P((struct hshentry const*)); 2139Sjkhstatic void buildtree P((void)); 2149Sjkhstatic void cleanup P((void)); 2159Sjkhstatic void doaccess P((void)); 2169Sjkhstatic void doassoc P((void)); 2179Sjkhstatic void dolocks P((void)); 2189Sjkhstatic void domessages P((void)); 2199Sjkhstatic void getaccessor P((char*,enum changeaccess)); 2209Sjkhstatic void getassoclst P((int,char*)); 2219Sjkhstatic void getchaccess P((char const*,enum changeaccess)); 2229Sjkhstatic void getdelrev P((char*)); 2239Sjkhstatic void getmessage P((char*)); 2249Sjkhstatic void getstates P((char*)); 2259Sjkhstatic void rcs_setstate P((char const*,char const*)); 2269Sjkhstatic void scanlogtext P((struct hshentry*,int)); 2279Sjkhstatic void setlock P((char const*)); 2289Sjkh 2299Sjkhstatic struct buf numrev; 2309Sjkhstatic char const *headstate; 2319Sjkhstatic int chgheadstate, exitstatus, lockhead, unlockcaller; 2329Sjkhstatic struct Lockrev *newlocklst, *rmvlocklst; 2339Sjkhstatic struct Message *messagelst, *lastmessage; 2349Sjkhstatic struct Status *statelst, *laststate; 2359Sjkhstatic struct Symrev *assoclst, *lastassoc; 2369Sjkhstatic struct chaccess *chaccess, **nextchaccess; 2379Sjkhstatic struct delrevpair delrev; 2389Sjkhstatic struct hshentry *cuthead, *cuttail, *delstrt; 2399Sjkhstatic struct hshentries *gendeltas; 2409Sjkh 2419SjkhmainProg(rcsId, "rcs", "$Id: rcs.c,v 5.12 1991/11/20 17:58:08 eggert Exp $") 2429Sjkh{ 2439Sjkh static char const cmdusage[] = 2449Sjkh "\nrcs usage: rcs -{ae}logins -Afile -{blu}[rev] -cstring -{iLU} -{nNs}name[:rev] -orange -t[file] -Vn file ..."; 2459Sjkh 2469Sjkh char *a, **newargv, *textfile; 2479Sjkh char const *branchsym, *commsyml; 2489Sjkh int branchflag, expmode, initflag; 2499Sjkh int e, r, strictlock, strict_selected, textflag; 2509Sjkh mode_t defaultRCSmode; /* default mode for new RCS files */ 2519Sjkh mode_t RCSmode; 2529Sjkh struct buf branchnum; 2539Sjkh struct stat workstat; 2549Sjkh struct Lockrev *curlock, * rmvlock, *lockpt; 2559Sjkh struct Status * curstate; 2569Sjkh 2579Sjkh nosetid(); 2589Sjkh 2599Sjkh nextchaccess = &chaccess; 2609Sjkh branchsym = commsyml = textfile = nil; 2619Sjkh branchflag = strictlock = false; 2629Sjkh bufautobegin(&branchnum); 2639Sjkh curlock = rmvlock = nil; 2649Sjkh defaultRCSmode = 0; 2659Sjkh expmode = -1; 2669Sjkh suffixes = X_DEFAULT; 2679Sjkh initflag= textflag = false; 2689Sjkh strict_selected = 0; 2699Sjkh 2709Sjkh /* preprocessing command options */ 2719Sjkh argc = getRCSINIT(argc, argv, &newargv); 2729Sjkh argv = newargv; 2739Sjkh while (a = *++argv, 0<--argc && *a++=='-') { 2749Sjkh switch (*a++) { 2759Sjkh 2769Sjkh case 'i': /* initial version */ 2779Sjkh initflag = true; 2789Sjkh break; 2799Sjkh 2809Sjkh case 'b': /* change default branch */ 2819Sjkh if (branchflag) redefined('b'); 2829Sjkh branchflag= true; 2839Sjkh branchsym = a; 2849Sjkh break; 2859Sjkh 2869Sjkh case 'c': /* change comment symbol */ 2879Sjkh if (commsyml) redefined('c'); 2889Sjkh commsyml = a; 2899Sjkh break; 2909Sjkh 2919Sjkh case 'a': /* add new accessor */ 2929Sjkh getaccessor(*argv+1, append); 2939Sjkh break; 2949Sjkh 2959Sjkh case 'A': /* append access list according to accessfile */ 2969Sjkh if (!*a) { 2979Sjkh error("missing file name after -A"); 2989Sjkh break; 2999Sjkh } 3009Sjkh *argv = a; 3019Sjkh if (0 < pairfilenames(1,argv,rcsreadopen,true,false)) { 3029Sjkh while (AccessList) { 3039Sjkh getchaccess(str_save(AccessList->login),append); 3049Sjkh AccessList = AccessList->nextaccess; 3059Sjkh } 3069Sjkh Izclose(&finptr); 3079Sjkh } 3089Sjkh break; 3099Sjkh 3109Sjkh case 'e': /* remove accessors */ 3119Sjkh getaccessor(*argv+1, erase); 3129Sjkh break; 3139Sjkh 3149Sjkh case 'l': /* lock a revision if it is unlocked */ 3159Sjkh if (!*a) { 3169Sjkh /* Lock head or default branch. */ 3179Sjkh lockhead = true; 3189Sjkh break; 3199Sjkh } 3209Sjkh lockpt = talloc(struct Lockrev); 3219Sjkh lockpt->revno = a; 3229Sjkh lockpt->nextrev = nil; 3239Sjkh if ( curlock ) 3249Sjkh curlock->nextrev = lockpt; 3259Sjkh else 3269Sjkh newlocklst = lockpt; 3279Sjkh curlock = lockpt; 3289Sjkh break; 3299Sjkh 3309Sjkh case 'u': /* release lock of a locked revision */ 3319Sjkh if (!*a) { 3329Sjkh unlockcaller=true; 3339Sjkh break; 3349Sjkh } 3359Sjkh lockpt = talloc(struct Lockrev); 3369Sjkh lockpt->revno = a; 3379Sjkh lockpt->nextrev = nil; 3389Sjkh if (rmvlock) 3399Sjkh rmvlock->nextrev = lockpt; 3409Sjkh else 3419Sjkh rmvlocklst = lockpt; 3429Sjkh rmvlock = lockpt; 3439Sjkh 3449Sjkh curlock = rmnewlocklst(lockpt); 3459Sjkh break; 3469Sjkh 3479Sjkh case 'L': /* set strict locking */ 3489Sjkh if (strict_selected++) { /* Already selected L or U? */ 3499Sjkh if (!strictlock) /* Already selected -U? */ 3509Sjkh warn("-L overrides -U."); 3519Sjkh } 3529Sjkh strictlock = true; 3539Sjkh break; 3549Sjkh 3559Sjkh case 'U': /* release strict locking */ 3569Sjkh if (strict_selected++) { /* Already selected L or U? */ 3579Sjkh if (strictlock) /* Already selected -L? */ 3589Sjkh warn("-L overrides -U."); 3599Sjkh } 3609Sjkh else 3619Sjkh strictlock = false; 3629Sjkh break; 3639Sjkh 3649Sjkh case 'n': /* add new association: error, if name exists */ 3659Sjkh if (!*a) { 3669Sjkh error("missing symbolic name after -n"); 3679Sjkh break; 3689Sjkh } 3699Sjkh getassoclst(false, (*argv)+1); 3709Sjkh break; 3719Sjkh 3729Sjkh case 'N': /* add or change association */ 3739Sjkh if (!*a) { 3749Sjkh error("missing symbolic name after -N"); 3759Sjkh break; 3769Sjkh } 3779Sjkh getassoclst(true, (*argv)+1); 3789Sjkh break; 3799Sjkh 3809Sjkh case 'm': /* change log message */ 3819Sjkh getmessage(a); 3829Sjkh break; 3839Sjkh 3849Sjkh case 'o': /* delete revisions */ 3859Sjkh if (delrev.strt) redefined('o'); 3869Sjkh if (!*a) { 3879Sjkh error("missing revision range after -o"); 3889Sjkh break; 3899Sjkh } 3909Sjkh getdelrev( (*argv)+1 ); 3919Sjkh break; 3929Sjkh 3939Sjkh case 's': /* change state attribute of a revision */ 3949Sjkh if (!*a) { 3959Sjkh error("state missing after -s"); 3969Sjkh break; 3979Sjkh } 3989Sjkh getstates( (*argv)+1); 3999Sjkh break; 4009Sjkh 4019Sjkh case 't': /* change descriptive text */ 4029Sjkh textflag=true; 4039Sjkh if (*a) { 4049Sjkh if (textfile) redefined('t'); 4059Sjkh textfile = a; 4069Sjkh } 4079Sjkh break; 4089Sjkh 4099Sjkh case 'I': 4109Sjkh interactiveflag = true; 4119Sjkh break; 4129Sjkh 4139Sjkh case 'q': 4149Sjkh quietflag = true; 4159Sjkh break; 4169Sjkh 4179Sjkh case 'x': 4189Sjkh suffixes = a; 4199Sjkh break; 4209Sjkh 4219Sjkh case 'V': 4229Sjkh setRCSversion(*argv); 4239Sjkh break; 4249Sjkh 4259Sjkh case 'k': /* set keyword expand mode */ 4269Sjkh if (0 <= expmode) redefined('k'); 4279Sjkh if (0 <= (expmode = str2expmode(a))) 4289Sjkh break; 4299Sjkh /* fall into */ 4309Sjkh default: 4319Sjkh faterror("unknown option: %s%s", *argv, cmdusage); 4329Sjkh }; 4339Sjkh } /* end processing of options */ 4349Sjkh 4359Sjkh if (argc<1) faterror("no input file%s", cmdusage); 4369Sjkh if (nerror) { 4379Sjkh diagnose("%s aborted\n",cmdid); 4389Sjkh exitmain(EXIT_FAILURE); 4399Sjkh } 4409Sjkh if (initflag) { 4419Sjkh defaultRCSmode = umask((mode_t)0); 4429Sjkh VOID umask(defaultRCSmode); 4439Sjkh defaultRCSmode = (S_IRUSR|S_IRGRP|S_IROTH) & ~defaultRCSmode; 4449Sjkh } 4459Sjkh 4469Sjkh /* now handle all filenames */ 4479Sjkh do { 4489Sjkh ffree(); 4499Sjkh 4509Sjkh if ( initflag ) { 4519Sjkh switch (pairfilenames(argc, argv, rcswriteopen, false, false)) { 4529Sjkh case -1: break; /* not exist; ok */ 4539Sjkh case 0: continue; /* error */ 4549Sjkh case 1: error("file %s exists already", RCSfilename); 4559Sjkh continue; 4569Sjkh } 4579Sjkh } 4589Sjkh else { 4599Sjkh switch (pairfilenames(argc, argv, rcswriteopen, true, false)) { 4609Sjkh case -1: continue; /* not exist */ 4619Sjkh case 0: continue; /* errors */ 4629Sjkh case 1: break; /* file exists; ok*/ 4639Sjkh } 4649Sjkh } 4659Sjkh 4669Sjkh 4679Sjkh /* now RCSfilename contains the name of the RCS file, and 4689Sjkh * workfilename contains the name of the working file. 4699Sjkh * if !initflag, finptr contains the file descriptor for the 4709Sjkh * RCS file. The admin node is initialized. 4719Sjkh */ 4729Sjkh 4739Sjkh diagnose("RCS file: %s\n", RCSfilename); 4749Sjkh 4759Sjkh RCSmode = defaultRCSmode; 4769Sjkh if (initflag) { 4779Sjkh if (stat(workfilename, &workstat) == 0) 4789Sjkh RCSmode = workstat.st_mode; 4799Sjkh } else { 4809Sjkh if (!checkaccesslist()) continue; 4819Sjkh gettree(); /* Read the delta tree. */ 4829Sjkh RCSmode = RCSstat.st_mode; 4839Sjkh } 4849Sjkh RCSmode &= ~(S_IWUSR|S_IWGRP|S_IWOTH); 4859Sjkh 4869Sjkh /* update admin. node */ 4879Sjkh if (strict_selected) StrictLocks = strictlock; 4889Sjkh if (commsyml) { 4899Sjkh Comment.string = commsyml; 4909Sjkh Comment.size = strlen(commsyml); 4919Sjkh } 4929Sjkh if (0 <= expmode) Expand = expmode; 4939Sjkh 4949Sjkh /* update default branch */ 4959Sjkh if (branchflag && expandsym(branchsym, &branchnum)) { 4969Sjkh if (countnumflds(branchnum.string)) { 4979Sjkh Dbranch = branchnum.string; 4989Sjkh } else 4999Sjkh Dbranch = nil; 5009Sjkh } 5019Sjkh 5029Sjkh doaccess(); /* Update access list. */ 5039Sjkh 5049Sjkh doassoc(); /* Update association list. */ 5059Sjkh 5069Sjkh dolocks(); /* Update locks. */ 5079Sjkh 5089Sjkh domessages(); /* Update log messages. */ 5099Sjkh 5109Sjkh /* update state attribution */ 5119Sjkh if (chgheadstate) { 5129Sjkh /* change state of default branch or head */ 5139Sjkh if (Dbranch==nil) { 5149Sjkh if (Head==nil) 5159Sjkh warn("can't change states in an empty tree"); 5169Sjkh else Head->state = headstate; 5179Sjkh } else { 5189Sjkh rcs_setstate(Dbranch,headstate); /* Can't set directly */ 5199Sjkh } 5209Sjkh } 5219Sjkh curstate = statelst; 5229Sjkh while( curstate ) { 5239Sjkh rcs_setstate(curstate->revno,curstate->status); 5249Sjkh curstate = curstate->nextstatus; 5259Sjkh } 5269Sjkh 5279Sjkh cuthead = cuttail = nil; 5289Sjkh if (delrev.strt && removerevs()) { 5299Sjkh /* rebuild delta tree if some deltas are deleted */ 5309Sjkh if ( cuttail ) 5319Sjkh VOID genrevs(cuttail->num, (char *)nil,(char *)nil, 5329Sjkh (char *)nil, &gendeltas); 5339Sjkh buildtree(); 5349Sjkh } 5359Sjkh 5369Sjkh if (nerror) 5379Sjkh continue; 5389Sjkh 5399Sjkh putadmin(frewrite); 5409Sjkh if ( Head ) 5419Sjkh puttree(Head, frewrite); 5429Sjkh putdesc(textflag,textfile); 5439Sjkh 5449Sjkh if ( Head) { 5459Sjkh if (!delrev.strt && !messagelst) { 5469Sjkh /* No revision was deleted and no message was changed. */ 5479Sjkh fastcopy(finptr, frewrite); 5489Sjkh } else { 5499Sjkh if (!cuttail || buildeltatext(gendeltas)) { 5509Sjkh advise_access(finptr, MADV_SEQUENTIAL); 5519Sjkh scanlogtext((struct hshentry *)nil, false); 5529Sjkh /* copy rest of delta text nodes that are not deleted */ 5539Sjkh } 5549Sjkh } 5559Sjkh } 5569Sjkh Izclose(&finptr); 5579Sjkh if ( ! nerror ) { /* move temporary file to RCS file if no error */ 5589Sjkh /* update mode */ 5599Sjkh ignoreints(); 5609Sjkh r = chnamemod(&frewrite, newRCSfilename, RCSfilename, RCSmode); 5619Sjkh e = errno; 5629Sjkh keepdirtemp(newRCSfilename); 5639Sjkh restoreints(); 5649Sjkh if (r != 0) { 5659Sjkh enerror(e, RCSfilename); 5669Sjkh error("saved in %s", newRCSfilename); 5679Sjkh dirtempunlink(); 5689Sjkh break; 5699Sjkh } 5709Sjkh diagnose("done\n"); 5719Sjkh } else { 5729Sjkh diagnose("%s aborted; %s unchanged.\n",cmdid,RCSfilename); 5739Sjkh } 5749Sjkh } while (cleanup(), 5759Sjkh ++argv, --argc >=1); 5769Sjkh 5779Sjkh tempunlink(); 5789Sjkh exitmain(exitstatus); 5799Sjkh} /* end of main (rcs) */ 5809Sjkh 5819Sjkh static void 5829Sjkhcleanup() 5839Sjkh{ 5849Sjkh if (nerror) exitstatus = EXIT_FAILURE; 5859Sjkh Izclose(&finptr); 5869Sjkh Ozclose(&fcopy); 5879Sjkh Ozclose(&frewrite); 5889Sjkh dirtempunlink(); 5899Sjkh} 5909Sjkh 5919Sjkh exiting void 5929Sjkhexiterr() 5939Sjkh{ 5949Sjkh dirtempunlink(); 5959Sjkh tempunlink(); 5969Sjkh _exit(EXIT_FAILURE); 5979Sjkh} 5989Sjkh 5999Sjkh 6009Sjkh static void 6019Sjkhgetassoclst(flag, sp) 6029Sjkhint flag; 6039Sjkhchar * sp; 6049Sjkh/* Function: associate a symbolic name to a revision or branch, */ 6059Sjkh/* and store in assoclst */ 6069Sjkh 6079Sjkh{ 6089Sjkh struct Symrev * pt; 6099Sjkh char const *temp; 6109Sjkh int c; 6119Sjkh 6129Sjkh while( (c=(*++sp)) == ' ' || c == '\t' || c =='\n') ; 6139Sjkh temp = sp; 6149Sjkh sp = checkid(sp, ':'); /* check for invalid symbolic name */ 6159Sjkh c = *sp; *sp = '\0'; 6169Sjkh while( c == ' ' || c == '\t' || c == '\n') c = *++sp; 6179Sjkh 6189Sjkh if ( c != ':' && c != '\0') { 6199Sjkh error("invalid string %s after option -n or -N",sp); 6209Sjkh return; 6219Sjkh } 6229Sjkh 6239Sjkh pt = talloc(struct Symrev); 6249Sjkh pt->ssymbol = temp; 6259Sjkh pt->override = flag; 6269Sjkh if (c == '\0') /* delete symbol */ 6279Sjkh pt->revno = nil; 6289Sjkh else { 6299Sjkh while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ; 6309Sjkh pt->revno = sp; 6319Sjkh } 6329Sjkh pt->nextsym = nil; 6339Sjkh if (lastassoc) 6349Sjkh lastassoc->nextsym = pt; 6359Sjkh else 6369Sjkh assoclst = pt; 6379Sjkh lastassoc = pt; 6389Sjkh return; 6399Sjkh} 6409Sjkh 6419Sjkh 6429Sjkh static void 6439Sjkhgetchaccess(login, command) 6449Sjkh char const *login; 6459Sjkh enum changeaccess command; 6469Sjkh{ 6479Sjkh register struct chaccess *pt; 6489Sjkh 6499Sjkh *nextchaccess = pt = talloc(struct chaccess); 6509Sjkh pt->login = login; 6519Sjkh pt->command = command; 6529Sjkh pt->nextchaccess = nil; 6539Sjkh nextchaccess = &pt->nextchaccess; 6549Sjkh} 6559Sjkh 6569Sjkh 6579Sjkh 6589Sjkh static void 6599Sjkhgetaccessor(opt, command) 6609Sjkh char *opt; 6619Sjkh enum changeaccess command; 6629Sjkh/* Function: get the accessor list of options -e and -a, */ 6639Sjkh/* and store in chaccess */ 6649Sjkh 6659Sjkh 6669Sjkh{ 6679Sjkh register c; 6689Sjkh register char *sp; 6699Sjkh 6709Sjkh sp = opt; 6719Sjkh while( ( c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') ; 6729Sjkh if ( c == '\0') { 6739Sjkh if (command == erase && sp-opt == 1) { 6749Sjkh getchaccess((char const*)nil, command); 6759Sjkh return; 6769Sjkh } 6779Sjkh error("missing login name after option -a or -e"); 6789Sjkh return; 6799Sjkh } 6809Sjkh 6819Sjkh while( c != '\0') { 6829Sjkh getchaccess(sp, command); 6839Sjkh sp = checkid(sp,','); 6849Sjkh c = *sp; *sp = '\0'; 6859Sjkh while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp); 6869Sjkh } 6879Sjkh} 6889Sjkh 6899Sjkh 6909Sjkh static void 6919Sjkhgetmessage(option) 6929Sjkh char *option; 6939Sjkh{ 6949Sjkh struct Message *pt; 6959Sjkh struct cbuf cb; 6969Sjkh char *m; 6979Sjkh 6989Sjkh if (!(m = strchr(option, ':'))) { 6999Sjkh error("-m option lacks revision number"); 7009Sjkh return; 7019Sjkh } 7029Sjkh *m++ = 0; 7039Sjkh cb = cleanlogmsg(m, strlen(m)); 7049Sjkh if (!cb.size) { 7059Sjkh error("-m option lacks log message"); 7069Sjkh return; 7079Sjkh } 7089Sjkh pt = talloc(struct Message); 7099Sjkh pt->revno = option; 7109Sjkh pt->message = cb; 7119Sjkh pt->nextmessage = 0; 7129Sjkh if (lastmessage) 7139Sjkh lastmessage->nextmessage = pt; 7149Sjkh else 7159Sjkh messagelst = pt; 7169Sjkh lastmessage = pt; 7179Sjkh} 7189Sjkh 7199Sjkh 7209Sjkh static void 7219Sjkhgetstates(sp) 7229Sjkhchar *sp; 7239Sjkh/* Function: get one state attribute and the corresponding */ 7249Sjkh/* revision and store in statelst */ 7259Sjkh 7269Sjkh{ 7279Sjkh char const *temp; 7289Sjkh struct Status *pt; 7299Sjkh register c; 7309Sjkh 7319Sjkh while( (c=(*++sp)) ==' ' || c == '\t' || c == '\n') ; 7329Sjkh temp = sp; 7339Sjkh sp = checkid(sp,':'); /* check for invalid state attribute */ 7349Sjkh c = *sp; *sp = '\0'; 7359Sjkh while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp; 7369Sjkh 7379Sjkh if ( c == '\0' ) { /* change state of def. branch or Head */ 7389Sjkh chgheadstate = true; 7399Sjkh headstate = temp; 7409Sjkh return; 7419Sjkh } 7429Sjkh else if ( c != ':' ) { 7439Sjkh error("missing ':' after state in option -s"); 7449Sjkh return; 7459Sjkh } 7469Sjkh 7479Sjkh while( (c = *++sp) == ' ' || c == '\t' || c == '\n') ; 7489Sjkh pt = talloc(struct Status); 7499Sjkh pt->status = temp; 7509Sjkh pt->revno = sp; 7519Sjkh pt->nextstatus = nil; 7529Sjkh if (laststate) 7539Sjkh laststate->nextstatus = pt; 7549Sjkh else 7559Sjkh statelst = pt; 7569Sjkh laststate = pt; 7579Sjkh} 7589Sjkh 7599Sjkh 7609Sjkh 7619Sjkh static void 7629Sjkhgetdelrev(sp) 7639Sjkhchar *sp; 7649Sjkh/* Function: get revision range or branch to be deleted, */ 7659Sjkh/* and place in delrev */ 7669Sjkh{ 7679Sjkh int c; 7689Sjkh struct delrevpair *pt; 7699Sjkh int separator; 7709Sjkh 7719Sjkh pt = &delrev; 7729Sjkh while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ; 7739Sjkh 7749Sjkh /* Support old ambiguous '-' syntax; this will go away. */ 7759Sjkh if (strchr(sp,':')) 7769Sjkh separator = ':'; 7779Sjkh else { 7789Sjkh if (strchr(sp,'-') && VERSION(5) <= RCSversion) 7799Sjkh warn("`-' is obsolete in `-o%s'; use `:' instead", sp); 7809Sjkh separator = '-'; 7819Sjkh } 7829Sjkh 7839Sjkh if (c == separator) { /* -o:rev */ 7849Sjkh while( (c = (*++sp)) == ' ' || c == '\n' || c == '\t') ; 7859Sjkh pt->strt = sp; pt->code = 1; 7869Sjkh while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp); 7879Sjkh *sp = '\0'; 7889Sjkh pt->end = nil; 7899Sjkh return; 7909Sjkh } 7919Sjkh else { 7929Sjkh pt->strt = sp; 7939Sjkh while( c != ' ' && c != '\n' && c != '\t' && c != '\0' 7949Sjkh && c != separator ) c = *++sp; 7959Sjkh *sp = '\0'; 7969Sjkh while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp; 7979Sjkh if ( c == '\0' ) { /* -o rev or branch */ 7989Sjkh pt->end = nil; pt->code = 0; 7999Sjkh return; 8009Sjkh } 8019Sjkh if (c != separator) { 8029Sjkh faterror("invalid range %s %s after -o", pt->strt, sp); 8039Sjkh } 8049Sjkh while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ; 8059Sjkh if (!c) { /* -orev: */ 8069Sjkh pt->end = nil; pt->code = 2; 8079Sjkh return; 8089Sjkh } 8099Sjkh } 8109Sjkh /* -orev1:rev2 */ 8119Sjkh pt->end = sp; pt->code = 3; 8129Sjkh while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp; 8139Sjkh *sp = '\0'; 8149Sjkh} 8159Sjkh 8169Sjkh 8179Sjkh 8189Sjkh 8199Sjkh static void 8209Sjkhscanlogtext(delta,edit) 8219Sjkh struct hshentry *delta; 8229Sjkh int edit; 8239Sjkh/* Function: Scans delta text nodes up to and including the one given 8249Sjkh * by delta, or up to last one present, if delta==nil. 8259Sjkh * For the one given by delta (if delta!=nil), the log message is saved into 8269Sjkh * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied. 8279Sjkh * Assumes the initial lexeme must be read in first. 8289Sjkh * Does not advance nexttok after it is finished, except if delta==nil. 8299Sjkh */ 8309Sjkh{ 8319Sjkh struct hshentry const *nextdelta; 8329Sjkh struct cbuf cb; 8339Sjkh 8349Sjkh for (;;) { 8359Sjkh foutptr = 0; 8369Sjkh if (eoflex()) { 8379Sjkh if(delta) 8389Sjkh faterror("can't find delta for revision %s", delta->num); 8399Sjkh return; /* no more delta text nodes */ 8409Sjkh } 8419Sjkh nextlex(); 8429Sjkh if (!(nextdelta=getnum())) 8439Sjkh faterror("delta number corrupted"); 8449Sjkh if (nextdelta->selector) { 8459Sjkh foutptr = frewrite; 8469Sjkh aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog); 8479Sjkh } 8489Sjkh getkeystring(Klog); 8499Sjkh if (nextdelta == cuttail) { 8509Sjkh cb = savestring(&curlogbuf); 8519Sjkh if (!delta->log.string) 8529Sjkh delta->log = cleanlogmsg(curlogbuf.string, cb.size); 8539Sjkh } else if (nextdelta->log.string && nextdelta->selector) { 8549Sjkh foutptr = 0; 8559Sjkh readstring(); 8569Sjkh foutptr = frewrite; 8579Sjkh putstring(foutptr, false, nextdelta->log, true); 8589Sjkh afputc(nextc, foutptr); 8599Sjkh } else {readstring(); 8609Sjkh } 8619Sjkh nextlex(); 8629Sjkh while (nexttok==ID && strcmp(NextString,Ktext)!=0) 8639Sjkh ignorephrase(); 8649Sjkh getkeystring(Ktext); 8659Sjkh 8669Sjkh if (delta==nextdelta) 8679Sjkh break; 8689Sjkh readstring(); /* skip over it */ 8699Sjkh 8709Sjkh } 8719Sjkh /* got the one we're looking for */ 8729Sjkh if (edit) 8739Sjkh editstring((struct hshentry *)nil); 8749Sjkh else 8759Sjkh enterstring(); 8769Sjkh} 8779Sjkh 8789Sjkh 8799Sjkh 8809Sjkh static struct Lockrev * 8819Sjkhrmnewlocklst(which) 8829Sjkh struct Lockrev const *which; 8839Sjkh/* Function: remove lock to revision which->revno from newlocklst */ 8849Sjkh 8859Sjkh{ 8869Sjkh struct Lockrev * pt, *pre; 8879Sjkh 8889Sjkh while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){ 8899Sjkh struct Lockrev *pn = newlocklst->nextrev; 8909Sjkh tfree(newlocklst); 8919Sjkh newlocklst = pn; 8929Sjkh } 8939Sjkh 8949Sjkh pt = pre = newlocklst; 8959Sjkh while( pt ) { 8969Sjkh if ( ! strcmp(pt->revno, which->revno) ) { 8979Sjkh pre->nextrev = pt->nextrev; 8989Sjkh tfree(pt); 8999Sjkh pt = pre->nextrev; 9009Sjkh } 9019Sjkh else { 9029Sjkh pre = pt; 9039Sjkh pt = pt->nextrev; 9049Sjkh } 9059Sjkh } 9069Sjkh return pre; 9079Sjkh} 9089Sjkh 9099Sjkh 9109Sjkh 9119Sjkh static void 9129Sjkhdoaccess() 9139Sjkh{ 9149Sjkh register struct chaccess *ch; 9159Sjkh register struct access **p, *t; 9169Sjkh 9179Sjkh for (ch = chaccess; ch; ch = ch->nextchaccess) { 9189Sjkh switch (ch->command) { 9199Sjkh case erase: 9209Sjkh if (!ch->login) 9219Sjkh AccessList = nil; 9229Sjkh else 9239Sjkh for (p = &AccessList; (t = *p); ) 9249Sjkh if (strcmp(ch->login, t->login) == 0) 9259Sjkh *p = t->nextaccess; 9269Sjkh else 9279Sjkh p = &t->nextaccess; 9289Sjkh break; 9299Sjkh case append: 9309Sjkh for (p = &AccessList; ; p = &t->nextaccess) 9319Sjkh if (!(t = *p)) { 9329Sjkh *p = t = ftalloc(struct access); 9339Sjkh t->login = ch->login; 9349Sjkh t->nextaccess = nil; 9359Sjkh break; 9369Sjkh } else if (strcmp(ch->login, t->login) == 0) 9379Sjkh break; 9389Sjkh break; 9399Sjkh } 9409Sjkh } 9419Sjkh} 9429Sjkh 9439Sjkh 9449Sjkh static int 9459Sjkhsendmail(Delta, who) 9469Sjkh char const *Delta, *who; 9479Sjkh/* Function: mail to who, informing him that his lock on delta was 9489Sjkh * broken by caller. Ask first whether to go ahead. Return false on 9499Sjkh * error or if user decides not to break the lock. 9509Sjkh */ 9519Sjkh{ 9529Sjkh#ifdef SENDMAIL 9539Sjkh char const *messagefile; 9549Sjkh int old1, old2, c; 9559Sjkh FILE * mailmess; 9569Sjkh#endif 9579Sjkh 9589Sjkh 9599Sjkh aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who); 9609Sjkh if (!yesorno(false, "Do you want to break the lock? [ny](n): ")) 9619Sjkh return false; 9629Sjkh 9639Sjkh /* go ahead with breaking */ 9649Sjkh#ifdef SENDMAIL 9659Sjkh messagefile = maketemp(0); 9669Sjkh if (!(mailmess = fopen(messagefile, "w"))) { 9679Sjkh efaterror(messagefile); 9689Sjkh } 9699Sjkh 9709Sjkh 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", 9719Sjkh basename(RCSfilename), Delta, getfullRCSname(), getcaller() 9729Sjkh ); 9739Sjkh aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr); 9749Sjkh 9759Sjkh old1 = '\n'; old2 = ' '; 9769Sjkh for (; ;) { 9779Sjkh c = getcstdin(); 9789Sjkh if (feof(stdin)) { 9799Sjkh aprintf(mailmess, "%c\n", old1); 9809Sjkh break; 9819Sjkh } 9829Sjkh else if ( c == '\n' && old1 == '.' && old2 == '\n') 9839Sjkh break; 9849Sjkh else { 9859Sjkh afputc(old1, mailmess); 9869Sjkh old2 = old1; old1 = c; 9879Sjkh if (c=='\n') aputs(">> ", stderr); 9889Sjkh } 9899Sjkh } 9909Sjkh Ozclose(&mailmess); 9919Sjkh 9929Sjkh if (run(messagefile, (char*)nil, SENDMAIL, who, (char*)nil)) 9939Sjkh warn("Mail may have failed."), 9949Sjkh#else 9959Sjkh warn("Mail notification of broken locks is not available."), 9969Sjkh#endif 9979Sjkh warn("Please tell `%s' why you broke the lock.", who); 9989Sjkh return(true); 9999Sjkh} 10009Sjkh 10019Sjkh 10029Sjkh 10039Sjkh static void 10049Sjkhbreaklock(delta) 10059Sjkh struct hshentry const *delta; 10069Sjkh/* function: Finds the lock held by caller on delta, 10079Sjkh * and removes it. 10089Sjkh * Sends mail if a lock different from the caller's is broken. 10099Sjkh * Prints an error message if there is no such lock or error. 10109Sjkh */ 10119Sjkh{ 10129Sjkh register struct lock * next, * trail; 10139Sjkh char const *num; 10149Sjkh struct lock dummy; 10159Sjkh 10169Sjkh num=delta->num; 10179Sjkh dummy.nextlock=next=Locks; 10189Sjkh trail = &dummy; 10199Sjkh while (next!=nil) { 10209Sjkh if (strcmp(num, next->delta->num) == 0) { 10219Sjkh if ( 10229Sjkh strcmp(getcaller(),next->login) != 0 10239Sjkh && !sendmail(num, next->login) 10249Sjkh ) { 10259Sjkh error("%s still locked by %s", num, next->login); 10269Sjkh return; 10279Sjkh } 10289Sjkh break; /* exact match */ 10299Sjkh } 10309Sjkh trail=next; 10319Sjkh next=next->nextlock; 10329Sjkh } 10339Sjkh if (next!=nil) { 10349Sjkh /*found one */ 10359Sjkh diagnose("%s unlocked\n",next->delta->num); 10369Sjkh trail->nextlock=next->nextlock; 10379Sjkh next->delta->lockedby=nil; 10389Sjkh Locks=dummy.nextlock; 10399Sjkh } else { 10409Sjkh error("no lock set on revision %s", num); 10419Sjkh } 10429Sjkh} 10439Sjkh 10449Sjkh 10459Sjkh 10469Sjkh static struct hshentry * 10479Sjkhsearchcutpt(object, length, store) 10489Sjkh char const *object; 10499Sjkh unsigned length; 10509Sjkh struct hshentries *store; 10519Sjkh/* Function: Search store and return entry with number being object. */ 10529Sjkh/* cuttail = nil, if the entry is Head; otherwise, cuttail */ 10539Sjkh/* is the entry point to the one with number being object */ 10549Sjkh 10559Sjkh{ 10569Sjkh cuthead = nil; 10579Sjkh while (compartial(store->first->num, object, length)) { 10589Sjkh cuthead = store->first; 10599Sjkh store = store->rest; 10609Sjkh } 10619Sjkh return store->first; 10629Sjkh} 10639Sjkh 10649Sjkh 10659Sjkh 10669Sjkh static int 10679Sjkhbranchpoint(strt, tail) 10689Sjkhstruct hshentry *strt, *tail; 10699Sjkh/* Function: check whether the deltas between strt and tail */ 10709Sjkh/* are locked or branch point, return 1 if any is */ 10719Sjkh/* locked or branch point; otherwise, return 0 and */ 10729Sjkh/* mark deleted */ 10739Sjkh 10749Sjkh{ 10759Sjkh struct hshentry *pt; 10769Sjkh struct lock const *lockpt; 10779Sjkh int flag; 10789Sjkh 10799Sjkh 10809Sjkh pt = strt; 10819Sjkh flag = false; 10829Sjkh while( pt != tail) { 10839Sjkh if ( pt->branches ){ /* a branch point */ 10849Sjkh flag = true; 10859Sjkh error("can't remove branch point %s", pt->num); 10869Sjkh } 10879Sjkh lockpt = Locks; 10889Sjkh while(lockpt && lockpt->delta != pt) 10899Sjkh lockpt = lockpt->nextlock; 10909Sjkh if ( lockpt ) { 10919Sjkh flag = true; 10929Sjkh error("can't remove locked revision %s",pt->num); 10939Sjkh } 10949Sjkh pt = pt->next; 10959Sjkh } 10969Sjkh 10979Sjkh if ( ! flag ) { 10989Sjkh pt = strt; 10999Sjkh while( pt != tail ) { 11009Sjkh pt->selector = false; 11019Sjkh diagnose("deleting revision %s\n",pt->num); 11029Sjkh pt = pt->next; 11039Sjkh } 11049Sjkh } 11059Sjkh return flag; 11069Sjkh} 11079Sjkh 11089Sjkh 11099Sjkh 11109Sjkh static int 11119Sjkhremoverevs() 11129Sjkh/* Function: get the revision range to be removed, and place the */ 11139Sjkh/* first revision removed in delstrt, the revision before */ 11149Sjkh/* delstrt in cuthead( nil, if delstrt is head), and the */ 11159Sjkh/* revision after the last removed revision in cuttail(nil */ 11169Sjkh/* if the last is a leaf */ 11179Sjkh 11189Sjkh{ 11199Sjkh struct hshentry *target, *target2, *temp; 11209Sjkh unsigned length; 11219Sjkh int flag; 11229Sjkh 11239Sjkh flag = false; 11249Sjkh if (!expandsym(delrev.strt, &numrev)) return 0; 11259Sjkh target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas); 11269Sjkh if ( ! target ) return 0; 11279Sjkh if (cmpnum(target->num, numrev.string)) flag = true; 11289Sjkh length = countnumflds(numrev.string); 11299Sjkh 11309Sjkh if (delrev.code == 0) { /* -o rev or -o branch */ 11319Sjkh if (length & 1) 11329Sjkh temp=searchcutpt(target->num,length+1,gendeltas); 11339Sjkh else if (flag) { 11349Sjkh error("Revision %s doesn't exist.", numrev.string); 11359Sjkh return 0; 11369Sjkh } 11379Sjkh else 11389Sjkh temp = searchcutpt(numrev.string, length, gendeltas); 11399Sjkh cuttail = target->next; 11409Sjkh if ( branchpoint(temp, cuttail) ) { 11419Sjkh cuttail = nil; 11429Sjkh return 0; 11439Sjkh } 11449Sjkh delstrt = temp; /* first revision to be removed */ 11459Sjkh return 1; 11469Sjkh } 11479Sjkh 11489Sjkh if (length & 1) { /* invalid branch after -o */ 11499Sjkh error("invalid branch range %s after -o", numrev.string); 11509Sjkh return 0; 11519Sjkh } 11529Sjkh 11539Sjkh if (delrev.code == 1) { /* -o -rev */ 11549Sjkh if ( length > 2 ) { 11559Sjkh temp = searchcutpt( target->num, length-1, gendeltas); 11569Sjkh cuttail = target->next; 11579Sjkh } 11589Sjkh else { 11599Sjkh temp = searchcutpt(target->num, length, gendeltas); 11609Sjkh cuttail = target; 11619Sjkh while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) ) 11629Sjkh cuttail = cuttail->next; 11639Sjkh } 11649Sjkh if ( branchpoint(temp, cuttail) ){ 11659Sjkh cuttail = nil; 11669Sjkh return 0; 11679Sjkh } 11689Sjkh delstrt = temp; 11699Sjkh return 1; 11709Sjkh } 11719Sjkh 11729Sjkh if (delrev.code == 2) { /* -o rev- */ 11739Sjkh if ( length == 2 ) { 11749Sjkh temp = searchcutpt(target->num, 1,gendeltas); 11759Sjkh if ( flag) 11769Sjkh cuttail = target; 11779Sjkh else 11789Sjkh cuttail = target->next; 11799Sjkh } 11809Sjkh else { 11819Sjkh if ( flag){ 11829Sjkh cuthead = target; 11839Sjkh if ( !(temp = target->next) ) return 0; 11849Sjkh } 11859Sjkh else 11869Sjkh temp = searchcutpt(target->num, length, gendeltas); 11879Sjkh getbranchno(temp->num, &numrev); /* get branch number */ 11889Sjkh target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas); 11899Sjkh } 11909Sjkh if ( branchpoint( temp, cuttail ) ) { 11919Sjkh cuttail = nil; 11929Sjkh return 0; 11939Sjkh } 11949Sjkh delstrt = temp; 11959Sjkh return 1; 11969Sjkh } 11979Sjkh 11989Sjkh /* -o rev1-rev2 */ 11999Sjkh if (!expandsym(delrev.end, &numrev)) return 0; 12009Sjkh if ( 12019Sjkh length != countnumflds(numrev.string) 12029Sjkh || length>2 && compartial(numrev.string, target->num, length-1) 12039Sjkh ) { 12049Sjkh error("invalid revision range %s-%s", target->num, numrev.string); 12059Sjkh return 0; 12069Sjkh } 12079Sjkh 12089Sjkh target2 = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas); 12099Sjkh if ( ! target2 ) return 0; 12109Sjkh 12119Sjkh if ( length > 2) { /* delete revisions on branches */ 12129Sjkh if ( cmpnum(target->num, target2->num) > 0) { 12139Sjkh if (cmpnum(target2->num, numrev.string)) 12149Sjkh flag = true; 12159Sjkh else 12169Sjkh flag = false; 12179Sjkh temp = target; 12189Sjkh target = target2; 12199Sjkh target2 = temp; 12209Sjkh } 12219Sjkh if ( flag ) { 12229Sjkh if ( ! cmpnum(target->num, target2->num) ) { 12239Sjkh error("Revisions %s-%s don't exist.", delrev.strt,delrev.end); 12249Sjkh return 0; 12259Sjkh } 12269Sjkh cuthead = target; 12279Sjkh temp = target->next; 12289Sjkh } 12299Sjkh else 12309Sjkh temp = searchcutpt(target->num, length, gendeltas); 12319Sjkh cuttail = target2->next; 12329Sjkh } 12339Sjkh else { /* delete revisions on trunk */ 12349Sjkh if ( cmpnum( target->num, target2->num) < 0 ) { 12359Sjkh temp = target; 12369Sjkh target = target2; 12379Sjkh target2 = temp; 12389Sjkh } 12399Sjkh else 12409Sjkh if (cmpnum(target2->num, numrev.string)) 12419Sjkh flag = true; 12429Sjkh else 12439Sjkh flag = false; 12449Sjkh if ( flag ) { 12459Sjkh if ( ! cmpnum(target->num, target2->num) ) { 12469Sjkh error("Revisions %s-%s don't exist.", delrev.strt, delrev.end); 12479Sjkh return 0; 12489Sjkh } 12499Sjkh cuttail = target2; 12509Sjkh } 12519Sjkh else 12529Sjkh cuttail = target2->next; 12539Sjkh temp = searchcutpt(target->num, length, gendeltas); 12549Sjkh } 12559Sjkh if ( branchpoint(temp, cuttail) ) { 12569Sjkh cuttail = nil; 12579Sjkh return 0; 12589Sjkh } 12599Sjkh delstrt = temp; 12609Sjkh return 1; 12619Sjkh} 12629Sjkh 12639Sjkh 12649Sjkh 12659Sjkh static void 12669Sjkhdoassoc() 12679Sjkh/* Function: add or delete(if revno is nil) association */ 12689Sjkh/* which is stored in assoclst */ 12699Sjkh 12709Sjkh{ 12719Sjkh char const *p; 12729Sjkh struct Symrev const *curassoc; 12739Sjkh struct assoc * pre, * pt; 12749Sjkh 12759Sjkh /* add new associations */ 12769Sjkh curassoc = assoclst; 12779Sjkh while( curassoc ) { 12789Sjkh if ( curassoc->revno == nil ) { /* delete symbol */ 12799Sjkh pre = pt = Symbols; 12809Sjkh while( pt && strcmp(pt->symbol,curassoc->ssymbol) ) { 12819Sjkh pre = pt; 12829Sjkh pt = pt->nextassoc; 12839Sjkh } 12849Sjkh if ( pt ) 12859Sjkh if ( pre == pt ) 12869Sjkh Symbols = pt->nextassoc; 12879Sjkh else 12889Sjkh pre->nextassoc = pt->nextassoc; 12899Sjkh else 12909Sjkh warn("can't delete nonexisting symbol %s",curassoc->ssymbol); 12919Sjkh } 12929Sjkh else { 12939Sjkh if (curassoc->revno[0]) { 12949Sjkh p = 0; 12959Sjkh if (expandsym(curassoc->revno, &numrev)) 12969Sjkh p = fstr_save(numrev.string); 12979Sjkh } else if (!(p = tiprev())) 12989Sjkh error("no latest revision to associate with symbol %s", 12999Sjkh curassoc->ssymbol 13009Sjkh ); 13019Sjkh if (p) 13029Sjkh VOID addsymbol(p, curassoc->ssymbol, curassoc->override); 13039Sjkh } 13049Sjkh curassoc = curassoc->nextsym; 13059Sjkh } 13069Sjkh 13079Sjkh} 13089Sjkh 13099Sjkh 13109Sjkh 13119Sjkh static void 13129Sjkhdolocks() 13139Sjkh/* Function: remove lock for caller or first lock if unlockcaller is set; 13149Sjkh * remove locks which are stored in rmvlocklst, 13159Sjkh * add new locks which are stored in newlocklst, 13169Sjkh * add lock for Dbranch or Head if lockhead is set. 13179Sjkh */ 13189Sjkh{ 13199Sjkh struct Lockrev const *lockpt; 13209Sjkh struct hshentry *target; 13219Sjkh 13229Sjkh if (unlockcaller) { /* find lock for caller */ 13239Sjkh if ( Head ) { 13249Sjkh if (Locks) { 13259Sjkh switch (findlock(true, &target)) { 13269Sjkh case 0: 13279Sjkh breaklock(Locks->delta); /* remove most recent lock */ 13289Sjkh break; 13299Sjkh case 1: 13309Sjkh diagnose("%s unlocked\n",target->num); 13319Sjkh break; 13329Sjkh } 13339Sjkh } else { 13349Sjkh warn("No locks are set."); 13359Sjkh } 13369Sjkh } else { 13379Sjkh warn("can't unlock an empty tree"); 13389Sjkh } 13399Sjkh } 13409Sjkh 13419Sjkh /* remove locks which are stored in rmvlocklst */ 13429Sjkh lockpt = rmvlocklst; 13439Sjkh while( lockpt ) { 13449Sjkh if (expandsym(lockpt->revno, &numrev)) { 13459Sjkh target = genrevs(numrev.string, (char *)nil, (char *)nil, (char *)nil, &gendeltas); 13469Sjkh if ( target ) 13479Sjkh if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 13489Sjkh error("can't unlock nonexisting revision %s",lockpt->revno); 13499Sjkh else 13509Sjkh breaklock(target); 13519Sjkh /* breaklock does its own diagnose */ 13529Sjkh } 13539Sjkh lockpt = lockpt->nextrev; 13549Sjkh } 13559Sjkh 13569Sjkh /* add new locks which stored in newlocklst */ 13579Sjkh lockpt = newlocklst; 13589Sjkh while( lockpt ) { 13599Sjkh setlock(lockpt->revno); 13609Sjkh lockpt = lockpt->nextrev; 13619Sjkh } 13629Sjkh 13639Sjkh if (lockhead) { /* lock default branch or head */ 13649Sjkh if (Dbranch) { 13659Sjkh setlock(Dbranch); 13669Sjkh } else if (Head) { 13679Sjkh if (0 <= addlock(Head)) 13689Sjkh diagnose("%s locked\n",Head->num); 13699Sjkh } else { 13709Sjkh warn("can't lock an empty tree"); 13719Sjkh } 13729Sjkh } 13739Sjkh 13749Sjkh} 13759Sjkh 13769Sjkh 13779Sjkh 13789Sjkh static void 13799Sjkhsetlock(rev) 13809Sjkh char const *rev; 13819Sjkh/* Function: Given a revision or branch number, finds the corresponding 13829Sjkh * delta and locks it for caller. 13839Sjkh */ 13849Sjkh{ 13859Sjkh struct hshentry *target; 13869Sjkh 13879Sjkh if (expandsym(rev, &numrev)) { 13889Sjkh target = genrevs(numrev.string, (char*)nil, (char*)nil, 13899Sjkh (char*)nil, &gendeltas); 13909Sjkh if ( target ) 13919Sjkh if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 13929Sjkh error("can't lock nonexisting revision %s", numrev.string); 13939Sjkh else 13949Sjkh if (0 <= addlock(target)) 13959Sjkh diagnose("%s locked\n", target->num); 13969Sjkh } 13979Sjkh} 13989Sjkh 13999Sjkh 14009Sjkh static void 14019Sjkhdomessages() 14029Sjkh{ 14039Sjkh struct hshentry *target; 14049Sjkh struct Message *p; 14059Sjkh 14069Sjkh for (p = messagelst; p; p = p->nextmessage) 14079Sjkh if ( 14089Sjkh expandsym(p->revno, &numrev) && 14099Sjkh (target = genrevs( 14109Sjkh numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas 14119Sjkh )) 14129Sjkh ) 14139Sjkh target->log = p->message; 14149Sjkh} 14159Sjkh 14169Sjkh 14179Sjkh static void 14189Sjkhrcs_setstate(rev,status) 14199Sjkh char const *rev, *status; 14209Sjkh/* Function: Given a revision or branch number, finds the corresponding delta 14219Sjkh * and sets its state to status. 14229Sjkh */ 14239Sjkh{ 14249Sjkh struct hshentry *target; 14259Sjkh 14269Sjkh if (expandsym(rev, &numrev)) { 14279Sjkh target = genrevs(numrev.string, (char*)nil, (char*)nil, 14289Sjkh (char*)nil, &gendeltas); 14299Sjkh if ( target ) 14309Sjkh if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 14319Sjkh error("can't set state of nonexisting revision %s to %s", 14329Sjkh numrev.string, status); 14339Sjkh else 14349Sjkh target->state = status; 14359Sjkh } 14369Sjkh} 14379Sjkh 14389Sjkh 14399Sjkh 14409Sjkh 14419Sjkh 14429Sjkh static int 14439Sjkhbuildeltatext(deltas) 14449Sjkh struct hshentries const *deltas; 14459Sjkh/* Function: put the delta text on frewrite and make necessary */ 14469Sjkh/* change to delta text */ 14479Sjkh{ 14489Sjkh register FILE *fcut; /* temporary file to rebuild delta tree */ 14499Sjkh char const *cutfilename, *diffilename; 14509Sjkh 14519Sjkh cutfilename = nil; 14529Sjkh cuttail->selector = false; 14539Sjkh scanlogtext(deltas->first, false); 14549Sjkh if ( cuthead ) { 14559Sjkh cutfilename = maketemp(3); 14569Sjkh if (!(fcut = fopen(cutfilename, FOPEN_W_WORK))) { 14579Sjkh efaterror(cutfilename); 14589Sjkh } 14599Sjkh 14609Sjkh while (deltas->first != cuthead) { 14619Sjkh deltas = deltas->rest; 14629Sjkh scanlogtext(deltas->first, true); 14639Sjkh } 14649Sjkh 14659Sjkh snapshotedit(fcut); 14669Sjkh Ofclose(fcut); 14679Sjkh } 14689Sjkh 14699Sjkh while (deltas->first != cuttail) 14709Sjkh scanlogtext((deltas = deltas->rest)->first, true); 14719Sjkh finishedit((struct hshentry *)nil, (FILE*)0, true); 14729Sjkh Ozclose(&fcopy); 14739Sjkh 14749Sjkh if ( cuthead ) { 14759Sjkh diffilename = maketemp(0); 14769Sjkh switch (run((char*)nil,diffilename, 14779Sjkh DIFF DIFF_FLAGS, cutfilename, resultfile, (char*)nil 14789Sjkh )) { 14799Sjkh case DIFF_FAILURE: case DIFF_SUCCESS: break; 14809Sjkh default: faterror ("diff failed"); 14819Sjkh } 14829Sjkh return putdtext(cuttail->num,cuttail->log,diffilename,frewrite,true); 14839Sjkh } else 14849Sjkh return putdtext(cuttail->num,cuttail->log,resultfile,frewrite,false); 14859Sjkh} 14869Sjkh 14879Sjkh 14889Sjkh 14899Sjkh static void 14909Sjkhbuildtree() 14919Sjkh/* Function: actually removes revisions whose selector field */ 14929Sjkh/* is false, and rebuilds the linkage of deltas. */ 14939Sjkh/* asks for reconfirmation if deleting last revision*/ 14949Sjkh{ 14959Sjkh struct hshentry * Delta; 14969Sjkh struct branchhead *pt, *pre; 14979Sjkh 14989Sjkh if ( cuthead ) 14999Sjkh if ( cuthead->next == delstrt ) 15009Sjkh cuthead->next = cuttail; 15019Sjkh else { 15029Sjkh pre = pt = cuthead->branches; 15039Sjkh while( pt && pt->hsh != delstrt ) { 15049Sjkh pre = pt; 15059Sjkh pt = pt->nextbranch; 15069Sjkh } 15079Sjkh if ( cuttail ) 15089Sjkh pt->hsh = cuttail; 15099Sjkh else if ( pt == pre ) 15109Sjkh cuthead->branches = pt->nextbranch; 15119Sjkh else 15129Sjkh pre->nextbranch = pt->nextbranch; 15139Sjkh } 15149Sjkh else { 15159Sjkh if ( cuttail == nil && !quietflag) { 15169Sjkh if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) { 15179Sjkh error("No revision deleted"); 15189Sjkh Delta = delstrt; 15199Sjkh while( Delta) { 15209Sjkh Delta->selector = true; 15219Sjkh Delta = Delta->next; 15229Sjkh } 15239Sjkh return; 15249Sjkh } 15259Sjkh } 15269Sjkh Head = cuttail; 15279Sjkh } 15289Sjkh return; 15299Sjkh} 15309Sjkh 15319Sjkh#if lint 15329Sjkh/* This lets us lint everything all at once. */ 15339Sjkh 15349Sjkhchar const cmdid[] = ""; 15359Sjkh 15369Sjkh#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();} 15379Sjkh 15389Sjkh int 15399Sjkhmain(argc, argv) 15409Sjkh int argc; 15419Sjkh char **argv; 15429Sjkh{ 15439Sjkh go(ciId, ciExit); 15449Sjkh go(coId, coExit); 15459Sjkh go(identId, identExit); 15469Sjkh go(mergeId, mergeExit); 15479Sjkh go(rcsId, exiterr); 15489Sjkh go(rcscleanId, rcscleanExit); 15499Sjkh go(rcsdiffId, rdiffExit); 15509Sjkh go(rcsmergeId, rmergeExit); 15519Sjkh go(rlogId, rlogExit); 15529Sjkh return 0; 15539Sjkh} 15549Sjkh#endif 1555