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