ci.c revision 9
19Sjkh/* Copyright (C) 1982, 1988, 1989 Walter Tichy
29Sjkh   Copyright 1990, 1991 by Paul Eggert
39Sjkh   Distributed under license by the Free Software Foundation, Inc.
49Sjkh
59SjkhThis file is part of RCS.
69Sjkh
79SjkhRCS is free software; you can redistribute it and/or modify
89Sjkhit under the terms of the GNU General Public License as published by
99Sjkhthe Free Software Foundation; either version 2, or (at your option)
109Sjkhany later version.
119Sjkh
129SjkhRCS is distributed in the hope that it will be useful,
139Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of
149SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
159SjkhGNU General Public License for more details.
169Sjkh
179SjkhYou should have received a copy of the GNU General Public License
189Sjkhalong with RCS; see the file COPYING.  If not, write to
199Sjkhthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
209Sjkh
219SjkhReport problems and direct all questions to:
229Sjkh
239Sjkh    rcs-bugs@cs.purdue.edu
249Sjkh
259Sjkh*/
269Sjkh
279Sjkh/*
289Sjkh *                     RCS checkin operation
299Sjkh */
309Sjkh/*******************************************************************
319Sjkh *                       check revisions into RCS files
329Sjkh *******************************************************************
339Sjkh */
349Sjkh
359Sjkh
369Sjkh
379Sjkh/* $Log: ci.c,v $
389Sjkh * Revision 5.21  1991/11/20  17:58:07  eggert
399Sjkh * Don't read the delta tree from a nonexistent RCS file.
409Sjkh *
419Sjkh * Revision 5.20  1991/10/07  17:32:46  eggert
429Sjkh * Fix log bugs.  Remove lint.
439Sjkh *
449Sjkh * Revision 5.19  1991/09/26  23:10:30  eggert
459Sjkh * Plug file descriptor leak.
469Sjkh *
479Sjkh * Revision 5.18  1991/09/18  07:29:10  eggert
489Sjkh * Work around a common ftruncate() bug.
499Sjkh *
509Sjkh * Revision 5.17  1991/09/10  22:15:46  eggert
519Sjkh * Fix test for redirected stdin.
529Sjkh *
539Sjkh * Revision 5.16  1991/08/19  23:17:54  eggert
549Sjkh * When there are no changes, revert to previous revision instead of aborting.
559Sjkh * Add piece tables, -M, -r$.  Tune.
569Sjkh *
579Sjkh * Revision 5.15  1991/04/21  11:58:14  eggert
589Sjkh * Ensure that working file is newer than RCS file after ci -[lu].
599Sjkh * Add -x, RCSINIT, MS-DOS support.
609Sjkh *
619Sjkh * Revision 5.14  1991/02/28  19:18:47  eggert
629Sjkh * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first.
639Sjkh * Fix ci -ko -l mode bug.  Open work file at most once.
649Sjkh *
659Sjkh * Revision 5.13  1991/02/25  07:12:33  eggert
669Sjkh * getdate -> getcurdate (SVR4 name clash)
679Sjkh *
689Sjkh * Revision 5.12  1990/12/31  01:00:12  eggert
699Sjkh * Don't use uninitialized storage when handling -{N,n}.
709Sjkh *
719Sjkh * Revision 5.11  1990/12/04  05:18:36  eggert
729Sjkh * Use -I for prompts and -q for diagnostics.
739Sjkh *
749Sjkh * Revision 5.10  1990/11/05  20:30:10  eggert
759Sjkh * Don't remove working file when aborting due to no changes.
769Sjkh *
779Sjkh * Revision 5.9  1990/11/01  05:03:23  eggert
789Sjkh * Add -I and new -t behavior.  Permit arbitrary data in logs.
799Sjkh *
809Sjkh * Revision 5.8  1990/10/04  06:30:09  eggert
819Sjkh * Accumulate exit status across files.
829Sjkh *
839Sjkh * Revision 5.7  1990/09/25  20:11:46  hammer
849Sjkh * fixed another small typo
859Sjkh *
869Sjkh * Revision 5.6  1990/09/24  21:48:50  hammer
879Sjkh * added cleanups from Paul Eggert.
889Sjkh *
899Sjkh * Revision 5.5  1990/09/21  06:16:38  hammer
909Sjkh * made it handle multiple -{N,n}'s.  Also, made it treat re-directed stdin
919Sjkh * the same as the terminal
929Sjkh *
939Sjkh * Revision 5.4  1990/09/20  02:38:51  eggert
949Sjkh * ci -k now checks dates more thoroughly.
959Sjkh *
969Sjkh * Revision 5.3  1990/09/11  02:41:07  eggert
979Sjkh * Fix revision bug with `ci -k file1 file2'.
989Sjkh *
999Sjkh * Revision 5.2  1990/09/04  08:02:10  eggert
1009Sjkh * Permit adjacent revisions with identical time stamps (possible on fast hosts).
1019Sjkh * Improve incomplete line handling.  Standardize yes-or-no procedure.
1029Sjkh *
1039Sjkh * Revision 5.1  1990/08/29  07:13:44  eggert
1049Sjkh * Expand locker value like co.  Clean old log messages too.
1059Sjkh *
1069Sjkh * Revision 5.0  1990/08/22  08:10:00  eggert
1079Sjkh * Don't require a final newline.
1089Sjkh * Make lock and temp files faster and safer.
1099Sjkh * Remove compile-time limits; use malloc instead.
1109Sjkh * Permit dates past 1999/12/31.  Switch to GMT.
1119Sjkh * Add setuid support.  Don't pass +args to diff.  Check diff's output.
1129Sjkh * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
1139Sjkh * Check diff's output.
1149Sjkh *
1159Sjkh * Revision 4.9  89/05/01  15:10:54  narten
1169Sjkh * changed copyright header to reflect current distribution rules
1179Sjkh *
1189Sjkh * Revision 4.8  88/11/08  13:38:23  narten
1199Sjkh * changes from root@seismo.CSS.GOV (Super User)
1209Sjkh * -d with no arguments uses the mod time of the file it is checking in
1219Sjkh *
1229Sjkh * Revision 4.7  88/08/09  19:12:07  eggert
1239Sjkh * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one.
1249Sjkh * Use execv(), not system(); allow cc -R; remove lint.
1259Sjkh * isatty(fileno(stdin)) -> ttystdin()
1269Sjkh *
1279Sjkh * Revision 4.6  87/12/18  11:34:41  narten
1289Sjkh * lint cleanups (from Guy Harris)
1299Sjkh *
1309Sjkh * Revision 4.5  87/10/18  10:18:48  narten
1319Sjkh * Updating version numbers. Changes relative to revision 1.1 are actually
1329Sjkh * relative to 4.3
1339Sjkh *
1349Sjkh * Revision 1.3  87/09/24  13:57:19  narten
1359Sjkh * Sources now pass through lint (if you ignore printf/sprintf/fprintf
1369Sjkh * warnings)
1379Sjkh *
1389Sjkh * Revision 1.2  87/03/27  14:21:33  jenkins
1399Sjkh * Port to suns
1409Sjkh *
1419Sjkh * Revision 4.3  83/12/15  12:28:54  wft
1429Sjkh * ci -u and ci -l now set mode of working file properly.
1439Sjkh *
1449Sjkh * Revision 4.2  83/12/05  13:40:54  wft
1459Sjkh * Merged with 3.9.1.1: added calls to clearerr(stdin).
1469Sjkh * made rewriteflag external.
1479Sjkh *
1489Sjkh * Revision 4.1  83/05/10  17:03:06  wft
1499Sjkh * Added option -d and -w, and updated assingment of date, etc. to new delta.
1509Sjkh * Added handling of default branches.
1519Sjkh * Option -k generates std. log message; fixed undef. pointer in reading of log.
1529Sjkh * Replaced getlock() with findlock(), link--unlink with rename(),
1539Sjkh * getpwuid() with getcaller().
1549Sjkh * Moved all revision number generation to new routine addelta().
1559Sjkh * Removed calls to stat(); now done by pairfilenames().
1569Sjkh * Changed most calls to catchints() with restoreints().
1579Sjkh * Directed all interactive messages to stderr.
1589Sjkh *
1599Sjkh * Revision 3.9.1.1  83/10/19  04:21:03  lepreau
1609Sjkh * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
1619Sjkh *
1629Sjkh * Revision 3.9  83/02/15  15:25:44  wft
1639Sjkh * 4.2 prerelease
1649Sjkh *
1659Sjkh * Revision 3.9  83/02/15  15:25:44  wft
1669Sjkh * Added call to fastcopy() to copy remainder of RCS file.
1679Sjkh *
1689Sjkh * Revision 3.8  83/01/14  15:34:05  wft
1699Sjkh * Added ignoring of interrupts while new RCS file is renamed;
1709Sjkh * Avoids deletion of RCS files by interrupts.
1719Sjkh *
1729Sjkh * Revision 3.7  82/12/10  16:09:20  wft
1739Sjkh * Corrected checking of return code from diff.
1749Sjkh *
1759Sjkh * Revision 3.6  82/12/08  21:34:49  wft
1769Sjkh * Using DATEFORM to prepare date of checked-in revision;
1779Sjkh * Fixed return from addbranch().
1789Sjkh *
1799Sjkh * Revision 3.5  82/12/04  18:32:42  wft
1809Sjkh * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated
1819Sjkh * field lockedby in removelock(), moved getlogmsg() before calling diff.
1829Sjkh *
1839Sjkh * Revision 3.4  82/12/02  13:27:13  wft
1849Sjkh * added option -k.
1859Sjkh *
1869Sjkh * Revision 3.3  82/11/28  20:53:31  wft
1879Sjkh * Added mustcheckin() to check for redundant checkins.
1889Sjkh * Added xpandfile() to do keyword expansion for -u and -l;
1899Sjkh * -m appends linefeed to log message if necessary.
1909Sjkh * getlogmsg() suppresses prompt if stdin is not a terminal.
1919Sjkh * Replaced keeplock with lockflag, fclose() with ffclose(),
1929Sjkh * %02d with %.2d, getlogin() with getpwuid().
1939Sjkh *
1949Sjkh * Revision 3.2  82/10/18  20:57:23  wft
1959Sjkh * An RCS file inherits its mode during the first ci from the working file,
1969Sjkh * otherwise it stays the same, except that write permission is removed.
1979Sjkh * Fixed ci -l, added ci -u (both do an implicit co after the ci).
1989Sjkh * Fixed call to getlogin(), added call to getfullRCSname(), added check
1999Sjkh * for write error.
2009Sjkh * Changed conflicting identifiers.
2019Sjkh *
2029Sjkh * Revision 3.1  82/10/13  16:04:59  wft
2039Sjkh * fixed type of variables receiving from getc() (char -> int).
2049Sjkh * added include file dbm.h for getting BYTESIZ. This is used
2059Sjkh * to check the return code from diff portably.
2069Sjkh */
2079Sjkh
2089Sjkh#include "rcsbase.h"
2099Sjkh
2109Sjkhstruct Symrev {
2119Sjkh       char const *ssymbol;
2129Sjkh       int override;
2139Sjkh       struct Symrev * nextsym;
2149Sjkh};
2159Sjkh
2169Sjkhstatic char const *getcurdate P((void));
2179Sjkhstatic int addbranch P((struct hshentry*,struct buf*));
2189Sjkhstatic int addelta P((void));
2199Sjkhstatic int addsyms P((char const*));
2209Sjkhstatic int fixwork P((mode_t,char const*));
2219Sjkhstatic int removelock P((struct hshentry*));
2229Sjkhstatic int xpandfile P((RILE*,char const*,struct hshentry const*,char const**));
2239Sjkhstatic struct cbuf getlogmsg P((void));
2249Sjkhstatic void cleanup P((void));
2259Sjkhstatic void incnum P((char const*,struct buf*));
2269Sjkhstatic void addassoclst P((int, char *));
2279Sjkh
2289Sjkhstatic FILE *exfile;
2299Sjkhstatic RILE *workptr;			/* working file pointer		*/
2309Sjkhstatic struct buf newdelnum;		/* new revision number		*/
2319Sjkhstatic struct cbuf msg;
2329Sjkhstatic int exitstatus;
2339Sjkhstatic int forceciflag;			/* forces check in		*/
2349Sjkhstatic int keepflag, keepworkingfile, rcsinitflag;
2359Sjkhstatic struct hshentries *gendeltas;	/* deltas to be generated	*/
2369Sjkhstatic struct hshentry *targetdelta;	/* old delta to be generated	*/
2379Sjkhstatic struct hshentry newdelta;	/* new delta to be inserted	*/
2389Sjkhstatic struct stat workstat;
2399Sjkhstatic struct Symrev *assoclst, *lastassoc;
2409Sjkh
2419SjkhmainProg(ciId, "ci", "$Id: ci.c,v 5.21 1991/11/20 17:58:07 eggert Exp $")
2429Sjkh{
2439Sjkh	static char const cmdusage[] =
2449Sjkh		"\nci usage: ci -{fklqru}[rev] -mmsg -{nN}name -sstate -t[textfile] -Vn file ...";
2459Sjkh	static char const default_state[] = DEFAULTSTATE;
2469Sjkh
2479Sjkh	char altdate[datesize];
2489Sjkh	char olddate[datesize];
2499Sjkh	char newdatebuf[datesize], targetdatebuf[datesize];
2509Sjkh	char *a, **newargv, *textfile;
2519Sjkh	char const *author, *krev, *rev, *state;
2529Sjkh	char const *diffilename, *expfilename;
2539Sjkh	char const *workdiffname, *newworkfilename;
2549Sjkh	char const *mtime;
2559Sjkh	int lockflag, lockthis, mtimeflag, removedlock;
2569Sjkh	int r;
2579Sjkh	int changedRCS, changework, newhead;
2589Sjkh	int usestatdate; /* Use mod time of file for -d.  */
2599Sjkh	mode_t newworkmode; /* mode for working file */
2609Sjkh	struct hshentry *workdelta;
2619Sjkh
2629Sjkh	setrid();
2639Sjkh
2649Sjkh	author = rev = state = textfile = nil;
2659Sjkh	lockflag = false;
2669Sjkh	mtimeflag = false;
2679Sjkh	altdate[0]= '\0'; /* empty alternate date for -d */
2689Sjkh	usestatdate=false;
2699Sjkh	suffixes = X_DEFAULT;
2709Sjkh
2719Sjkh	argc = getRCSINIT(argc, argv, &newargv);
2729Sjkh	argv = newargv;
2739Sjkh	while (a = *++argv,  0<--argc && *a++=='-') {
2749Sjkh		switch (*a++) {
2759Sjkh
2769Sjkh                case 'r':
2779Sjkh			keepworkingfile = lockflag = false;
2789Sjkh		revno:
2799Sjkh			if (*a) {
2809Sjkh				if (rev) warn("redefinition of revision number");
2819Sjkh				rev = a;
2829Sjkh                        }
2839Sjkh                        break;
2849Sjkh
2859Sjkh                case 'l':
2869Sjkh                        keepworkingfile=lockflag=true;
2879Sjkh                        goto revno;
2889Sjkh
2899Sjkh                case 'u':
2909Sjkh                        keepworkingfile=true; lockflag=false;
2919Sjkh                        goto revno;
2929Sjkh
2939Sjkh		case 'I':
2949Sjkh			interactiveflag = true;
2959Sjkh			goto revno;
2969Sjkh
2979Sjkh                case 'q':
2989Sjkh                        quietflag=true;
2999Sjkh                        goto revno;
3009Sjkh
3019Sjkh                case 'f':
3029Sjkh                        forceciflag=true;
3039Sjkh                        goto revno;
3049Sjkh
3059Sjkh                case 'k':
3069Sjkh                        keepflag=true;
3079Sjkh                        goto revno;
3089Sjkh
3099Sjkh                case 'm':
3109Sjkh			if (msg.size) redefined('m');
3119Sjkh			msg = cleanlogmsg(a, strlen(a));
3129Sjkh			if (!msg.size)
3139Sjkh				warn("missing message for -m option");
3149Sjkh                        break;
3159Sjkh
3169Sjkh                case 'n':
3179Sjkh			if (!*a) {
3189Sjkh                                error("missing symbolic name after -n");
3199Sjkh				break;
3209Sjkh            		}
3219Sjkh			checksid(a);
3229Sjkh			addassoclst(false, a);
3239Sjkh		        break;
3249Sjkh
3259Sjkh		case 'N':
3269Sjkh			if (!*a) {
3279Sjkh                                error("missing symbolic name after -N");
3289Sjkh				break;
3299Sjkh            		}
3309Sjkh			checksid(a);
3319Sjkh			addassoclst(true, a);
3329Sjkh		        break;
3339Sjkh
3349Sjkh                case 's':
3359Sjkh			if (*a) {
3369Sjkh				if (state) redefined('s');
3379Sjkh				checksid(a);
3389Sjkh				state = a;
3399Sjkh			} else
3409Sjkh				warn("missing state for -s option");
3419Sjkh                        break;
3429Sjkh
3439Sjkh                case 't':
3449Sjkh			if (*a) {
3459Sjkh				if (textfile) redefined('t');
3469Sjkh				textfile = a;
3479Sjkh                        }
3489Sjkh                        break;
3499Sjkh
3509Sjkh		case 'd':
3519Sjkh			if (altdate[0] || usestatdate)
3529Sjkh				redefined('d');
3539Sjkh			altdate[0] = 0;
3549Sjkh			if (!(usestatdate = !*a))
3559Sjkh				str2date(a, altdate);
3569Sjkh                        break;
3579Sjkh
3589Sjkh		case 'M':
3599Sjkh			mtimeflag = true;
3609Sjkh			goto revno;
3619Sjkh
3629Sjkh		case 'w':
3639Sjkh			if (*a) {
3649Sjkh				if (author) redefined('w');
3659Sjkh				checksid(a);
3669Sjkh				author = a;
3679Sjkh			} else
3689Sjkh				warn("missing author for -w option");
3699Sjkh                        break;
3709Sjkh
3719Sjkh		case 'x':
3729Sjkh			suffixes = a;
3739Sjkh			break;
3749Sjkh
3759Sjkh		case 'V':
3769Sjkh			setRCSversion(*argv);
3779Sjkh			break;
3789Sjkh
3799Sjkh
3809Sjkh
3819Sjkh                default:
3829Sjkh			faterror("unknown option: %s%s", *argv, cmdusage);
3839Sjkh                };
3849Sjkh        }  /* end processing of options */
3859Sjkh
3869Sjkh	if (argc<1) faterror("no input file%s", cmdusage);
3879Sjkh
3889Sjkh        /* now handle all filenames */
3899Sjkh        do {
3909Sjkh        targetdelta=nil;
3919Sjkh	ffree();
3929Sjkh
3939Sjkh	switch (pairfilenames(argc, argv, rcswriteopen, false, false)) {
3949Sjkh
3959Sjkh        case -1:                /* New RCS file */
3969Sjkh#		if has_setuid && has_getuid
3979Sjkh		    if (euid() != ruid()) {
3989Sjkh			error("setuid initial checkin prohibited; use `rcs -i -a' first");
3999Sjkh			continue;
4009Sjkh		    }
4019Sjkh#		endif
4029Sjkh		rcsinitflag = true;
4039Sjkh                break;
4049Sjkh
4059Sjkh        case 0:                 /* Error */
4069Sjkh                continue;
4079Sjkh
4089Sjkh        case 1:                 /* Normal checkin with prev . RCS file */
4099Sjkh		rcsinitflag = !Head;
4109Sjkh        }
4119Sjkh
4129Sjkh        /* now RCSfilename contains the name of the RCS file, and
4139Sjkh         * workfilename contains the name of the working file.
4149Sjkh	 * If the RCS file exists, finptr contains the file descriptor for the
4159Sjkh         * RCS file. The admin node is initialized.
4169Sjkh	 * RCSstat is set.
4179Sjkh         */
4189Sjkh
4199Sjkh	diagnose("%s  <--  %s\n", RCSfilename,workfilename);
4209Sjkh
4219Sjkh	if (!(workptr = Iopen(workfilename, FOPEN_R_WORK, &workstat))) {
4229Sjkh		eerror(workfilename);
4239Sjkh		continue;
4249Sjkh	}
4259Sjkh	if (finptr && !checkaccesslist()) continue; /* give up */
4269Sjkh
4279Sjkh	krev = rev;
4289Sjkh        if (keepflag) {
4299Sjkh                /* get keyword values from working file */
4309Sjkh		if (!getoldkeys(workptr)) continue;
4319Sjkh		if (!rev  &&  !*(krev = prevrev.string)) {
4329Sjkh			error("can't find a revision number in %s",workfilename);
4339Sjkh                        continue;
4349Sjkh                }
4359Sjkh		if (!*prevdate.string && *altdate=='\0' && usestatdate==false)
4369Sjkh			warn("can't find a date in %s", workfilename);
4379Sjkh		if (!*prevauthor.string && !author)
4389Sjkh			warn("can't find an author in %s", workfilename);
4399Sjkh		if (!*prevstate.string && !state)
4409Sjkh			warn("can't find a state in %s", workfilename);
4419Sjkh        } /* end processing keepflag */
4429Sjkh
4439Sjkh	/* Read the delta tree.  */
4449Sjkh	if (finptr)
4459Sjkh	    gettree();
4469Sjkh
4479Sjkh        /* expand symbolic revision number */
4489Sjkh	if (!fexpandsym(krev, &newdelnum, workptr))
4499Sjkh	    continue;
4509Sjkh
4519Sjkh        /* splice new delta into tree */
4529Sjkh	if ((removedlock = addelta()) < 0)
4539Sjkh	    continue;
4549Sjkh
4559Sjkh	newdelta.num = newdelnum.string;
4569Sjkh        newdelta.branches=nil;
4579Sjkh        newdelta.lockedby=nil; /*might be changed by addlock() */
4589Sjkh	newdelta.selector = true;
4599Sjkh	/* set author */
4609Sjkh	if (author!=nil)
4619Sjkh		newdelta.author=author;     /* set author given by -w         */
4629Sjkh	else if (keepflag && *prevauthor.string)
4639Sjkh		newdelta.author=prevauthor.string; /* preserve old author if possible*/
4649Sjkh	else    newdelta.author=getcaller();/* otherwise use caller's id      */
4659Sjkh	newdelta.state = default_state;
4669Sjkh	if (state!=nil)
4679Sjkh		newdelta.state=state;       /* set state given by -s          */
4689Sjkh	else if (keepflag && *prevstate.string)
4699Sjkh		newdelta.state=prevstate.string;   /* preserve old state if possible */
4709Sjkh	if (usestatdate) {
4719Sjkh	    time2date(workstat.st_mtime, altdate);
4729Sjkh	}
4739Sjkh	if (*altdate!='\0')
4749Sjkh		newdelta.date=altdate;      /* set date given by -d           */
4759Sjkh	else if (keepflag && *prevdate.string) {
4769Sjkh		/* Preserve old date if possible.  */
4779Sjkh		str2date(prevdate.string, olddate);
4789Sjkh		newdelta.date = olddate;
4799Sjkh	} else
4809Sjkh		newdelta.date = getcurdate();  /* use current date */
4819Sjkh	/* now check validity of date -- needed because of -d and -k          */
4829Sjkh	if (targetdelta!=nil &&
4839Sjkh	    cmpnum(newdelta.date,targetdelta->date) < 0) {
4849Sjkh		error("Date %s precedes %s in existing revision %s.",
4859Sjkh			date2str(newdelta.date, newdatebuf),
4869Sjkh			date2str(targetdelta->date, targetdatebuf),
4879Sjkh			targetdelta->num
4889Sjkh		);
4899Sjkh		continue;
4909Sjkh	}
4919Sjkh
4929Sjkh
4939Sjkh	if (lockflag  &&  addlock(&newdelta) < 0) continue;
4949Sjkh	if (!addsyms(newdelta.num))
4959Sjkh	    continue;
4969Sjkh
4979Sjkh
4989Sjkh        putadmin(frewrite);
4999Sjkh        puttree(Head,frewrite);
5009Sjkh	putdesc(false,textfile);
5019Sjkh
5029Sjkh	changework = Expand != OLD_EXPAND;
5039Sjkh	lockthis = lockflag;
5049Sjkh	workdelta = &newdelta;
5059Sjkh
5069Sjkh        /* build rest of file */
5079Sjkh	if (rcsinitflag) {
5089Sjkh		diagnose("initial revision: %s\n", newdelnum.string);
5099Sjkh                /* get logmessage */
5109Sjkh                newdelta.log=getlogmsg();
5119Sjkh		if (!putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false)) continue;
5129Sjkh		RCSstat.st_mode = workstat.st_mode;
5139Sjkh		changedRCS = true;
5149Sjkh        } else {
5159Sjkh		diffilename = maketemp(0);
5169Sjkh		workdiffname = workfilename;
5179Sjkh		if (workdiffname[0] == '+') {
5189Sjkh		    /* Some diffs have options with leading '+'.  */
5199Sjkh		    char *dp = ftnalloc(char, strlen(workfilename)+3);
5209Sjkh		    workdiffname = dp;
5219Sjkh		    *dp++ = '.';
5229Sjkh		    *dp++ = SLASH;
5239Sjkh		    VOID strcpy(dp, workfilename);
5249Sjkh		}
5259Sjkh		newhead  =  Head == &newdelta;
5269Sjkh		if (!newhead)
5279Sjkh			foutptr = frewrite;
5289Sjkh		expfilename = buildrevision(
5299Sjkh			gendeltas, targetdelta, (FILE*)0, false
5309Sjkh		);
5319Sjkh		if (
5329Sjkh		    !forceciflag  &&
5339Sjkh		    (changework = rcsfcmp(
5349Sjkh			workptr, &workstat, expfilename, targetdelta
5359Sjkh		    )) <= 0
5369Sjkh		) {
5379Sjkh		    diagnose("file is unchanged; reverting to previous revision %s\n",
5389Sjkh			targetdelta->num
5399Sjkh		    );
5409Sjkh		    if (removedlock < lockflag) {
5419Sjkh			diagnose("previous revision was not locked; ignoring -l option\n");
5429Sjkh			lockthis = 0;
5439Sjkh		    }
5449Sjkh		    if (!(changedRCS  =
5459Sjkh			    lockflag < removedlock
5469Sjkh			||  assoclst
5479Sjkh			||	newdelta.state != default_state
5489Sjkh			    &&	strcmp(newdelta.state, targetdelta->state) != 0
5499Sjkh		    ))
5509Sjkh			workdelta = targetdelta;
5519Sjkh		    else {
5529Sjkh			/*
5539Sjkh			 * We have started to build the wrong new RCS file.
5549Sjkh			 * Start over from the beginning.
5559Sjkh			 */
5569Sjkh			long hwm = ftell(frewrite);
5579Sjkh			int bad_truncate;
5589Sjkh			if (fseek(frewrite, 0L, SEEK_SET) != 0)
5599Sjkh			    Oerror();
5609Sjkh#			if !has_ftruncate
5619Sjkh			    bad_truncate = 1;
5629Sjkh#			else
5639Sjkh			    /*
5649Sjkh			     * Work around a common ftruncate() bug.
5659Sjkh			     * We can't rely on has_truncate, because we might
5669Sjkh			     * be using a filesystem exported to us via NFS.
5679Sjkh			     */
5689Sjkh			    bad_truncate = ftruncate(fileno(frewrite),(off_t)0);
5699Sjkh			    if (bad_truncate  &&  errno != EACCES)
5709Sjkh				Oerror();
5719Sjkh#			endif
5729Sjkh			Irewind(finptr);
5739Sjkh			Lexinit();
5749Sjkh			getadmin();
5759Sjkh			gettree();
5769Sjkh			if (!(workdelta = genrevs(
5779Sjkh			    targetdelta->num, (char*)0, (char*)0, (char*)0,
5789Sjkh			    &gendeltas
5799Sjkh			)))
5809Sjkh			    continue;
5819Sjkh			workdelta->log = targetdelta->log;
5829Sjkh			if (newdelta.state != default_state)
5839Sjkh			    workdelta->state = newdelta.state;
5849Sjkh			if (removedlock && removelock(workdelta)<0)
5859Sjkh			    continue;
5869Sjkh			if (!addsyms(workdelta->num))
5879Sjkh			    continue;
5889Sjkh			if (!dorewrite(true, true))
5899Sjkh			    continue;
5909Sjkh			fastcopy(finptr, frewrite);
5919Sjkh			if (bad_truncate)
5929Sjkh			    while (ftell(frewrite) < hwm)
5939Sjkh				/* White out any earlier mistake with '\n's.  */
5949Sjkh				/* This is unlikely.  */
5959Sjkh				afputc('\n', frewrite);
5969Sjkh		    }
5979Sjkh		} else {
5989Sjkh		    diagnose("new revision: %s; previous revision: %s\n",
5999Sjkh			newdelnum.string, targetdelta->num
6009Sjkh		    );
6019Sjkh		    newdelta.log = getlogmsg();
6029Sjkh		    switch (run((char*)0, diffilename,
6039Sjkh			DIFF DIFF_FLAGS,
6049Sjkh			newhead ? workdiffname : expfilename,
6059Sjkh			newhead ? expfilename : workdiffname,
6069Sjkh			(char*)0
6079Sjkh		    )) {
6089Sjkh			case DIFF_FAILURE: case DIFF_SUCCESS: break;
6099Sjkh			default: faterror("diff failed");
6109Sjkh		    }
6119Sjkh		    if (newhead) {
6129Sjkh			Irewind(workptr);
6139Sjkh			if (!putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false)) continue;
6149Sjkh			if (!putdtext(targetdelta->num,targetdelta->log,diffilename,frewrite,true)) continue;
6159Sjkh		    } else
6169Sjkh			if (!putdtext(newdelnum.string,newdelta.log,diffilename,frewrite,true)) continue;
6179Sjkh		    changedRCS = true;
6189Sjkh                }
6199Sjkh        }
6209Sjkh	if (!donerewrite(changedRCS))
6219Sjkh		continue;
6229Sjkh
6239Sjkh        if (!keepworkingfile) {
6249Sjkh		Izclose(&workptr);
6259Sjkh		r = un_link(workfilename); /* Get rid of old file */
6269Sjkh        } else {
6279Sjkh		newworkmode = WORKMODE(RCSstat.st_mode,
6289Sjkh			!   (Expand==VAL_EXPAND  ||  lockthis < StrictLocks)
6299Sjkh		);
6309Sjkh		mtime = mtimeflag ? workdelta->date : (char const*)0;
6319Sjkh
6329Sjkh		/* Expand if it might change or if we can't fix mode, time.  */
6339Sjkh		if (changework  ||  (r=fixwork(newworkmode,mtime)) != 0) {
6349Sjkh		    Irewind(workptr);
6359Sjkh		    /* Expand keywords in file.  */
6369Sjkh		    locker_expansion = lockthis;
6379Sjkh		    switch (xpandfile(
6389Sjkh			workptr, workfilename,
6399Sjkh			workdelta, &newworkfilename
6409Sjkh		    )) {
6419Sjkh			default:
6429Sjkh			    continue;
6439Sjkh
6449Sjkh			case 0:
6459Sjkh			    /*
6469Sjkh			     * No expansion occurred; try to reuse working file
6479Sjkh			     * unless we already tried and failed.
6489Sjkh			     */
6499Sjkh			    if (changework)
6509Sjkh				if ((r=fixwork(newworkmode,mtime)) == 0)
6519Sjkh				    break;
6529Sjkh			    /* fall into */
6539Sjkh			case 1:
6549Sjkh			    if (!(r = setfiledate(newworkfilename,mtime))) {
6559Sjkh				Izclose(&workptr);
6569Sjkh				ignoreints();
6579Sjkh				r = chnamemod(&exfile, newworkfilename, workfilename, newworkmode);
6589Sjkh				keepdirtemp(newworkfilename);
6599Sjkh				restoreints();
6609Sjkh			    }
6619Sjkh		    }
6629Sjkh		}
6639Sjkh        }
6649Sjkh	if (r != 0) {
6659Sjkh	    eerror(workfilename);
6669Sjkh	    continue;
6679Sjkh	}
6689Sjkh	diagnose("done\n");
6699Sjkh
6709Sjkh        } while (cleanup(),
6719Sjkh                 ++argv, --argc >=1);
6729Sjkh
6739Sjkh	tempunlink();
6749Sjkh	exitmain(exitstatus);
6759Sjkh}       /* end of main (ci) */
6769Sjkh
6779Sjkh	static void
6789Sjkhcleanup()
6799Sjkh{
6809Sjkh	if (nerror) exitstatus = EXIT_FAILURE;
6819Sjkh	Izclose(&finptr);
6829Sjkh	Izclose(&workptr);
6839Sjkh	Ozclose(&exfile);
6849Sjkh	Ozclose(&fcopy);
6859Sjkh	Ozclose(&frewrite);
6869Sjkh	dirtempunlink();
6879Sjkh}
6889Sjkh
6899Sjkh#if lint
6909Sjkh#	define exiterr ciExit
6919Sjkh#endif
6929Sjkh	exiting void
6939Sjkhexiterr()
6949Sjkh{
6959Sjkh	dirtempunlink();
6969Sjkh	tempunlink();
6979Sjkh	_exit(EXIT_FAILURE);
6989Sjkh}
6999Sjkh
7009Sjkh/*****************************************************************/
7019Sjkh/* the rest are auxiliary routines                               */
7029Sjkh
7039Sjkh
7049Sjkh	static int
7059Sjkhaddelta()
7069Sjkh/* Function: Appends a delta to the delta tree, whose number is
7079Sjkh * given by newdelnum.  Updates Head, newdelnum, newdelnumlength,
7089Sjkh * and the links in newdelta.
7099Sjkh * Return -1 on error, 1 if a lock is removed, 0 otherwise.
7109Sjkh */
7119Sjkh{
7129Sjkh	register char *tp;
7139Sjkh	register unsigned i;
7149Sjkh	int removedlock;
7159Sjkh	unsigned newdnumlength;  /* actual length of new rev. num. */
7169Sjkh
7179Sjkh	newdnumlength = countnumflds(newdelnum.string);
7189Sjkh
7199Sjkh	if (rcsinitflag) {
7209Sjkh                /* this covers non-existing RCS file and a file initialized with rcs -i */
7219Sjkh		if ((newdnumlength==0)&&(Dbranch!=nil)) {
7229Sjkh			bufscpy(&newdelnum, Dbranch);
7239Sjkh			newdnumlength = countnumflds(Dbranch);
7249Sjkh		}
7259Sjkh		if (newdnumlength==0) bufscpy(&newdelnum, "1.1");
7269Sjkh		else if (newdnumlength==1) bufscat(&newdelnum, ".1");
7279Sjkh		else if (newdnumlength>2) {
7289Sjkh		    error("Branch point doesn't exist for %s.",newdelnum.string);
7299Sjkh		    return -1;
7309Sjkh                } /* newdnumlength == 2 is OK;  */
7319Sjkh                Head = &newdelta;
7329Sjkh                newdelta.next=nil;
7339Sjkh		return 0;
7349Sjkh        }
7359Sjkh        if (newdnumlength==0) {
7369Sjkh                /* derive new revision number from locks */
7379Sjkh		switch (findlock(true, &targetdelta)) {
7389Sjkh
7399Sjkh		  default:
7409Sjkh		    /* found two or more old locks */
7419Sjkh		    return -1;
7429Sjkh
7439Sjkh		  case 1:
7449Sjkh                    /* found an old lock */
7459Sjkh                    /* check whether locked revision exists */
7469Sjkh		    if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas))
7479Sjkh			return -1;
7489Sjkh                    if (targetdelta==Head) {
7499Sjkh                        /* make new head */
7509Sjkh                        newdelta.next=Head;
7519Sjkh                        Head= &newdelta;
7529Sjkh		    } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) {
7539Sjkh                        /* new tip revision on side branch */
7549Sjkh                        targetdelta->next= &newdelta;
7559Sjkh                        newdelta.next = nil;
7569Sjkh                    } else {
7579Sjkh                        /* middle revision; start a new branch */
7589Sjkh			bufscpy(&newdelnum, "");
7599Sjkh			return addbranch(targetdelta,&newdelnum);
7609Sjkh                    }
7619Sjkh		    incnum(targetdelta->num, &newdelnum);
7629Sjkh		    return 1; /* successful use of existing lock */
7639Sjkh
7649Sjkh		  case 0:
7659Sjkh                    /* no existing lock; try Dbranch */
7669Sjkh                    /* update newdelnum */
7679Sjkh		    if (StrictLocks || !myself(RCSstat.st_uid)) {
7689Sjkh			error("no lock set by %s",getcaller());
7699Sjkh			return -1;
7709Sjkh                    }
7719Sjkh                    if (Dbranch) {
7729Sjkh			bufscpy(&newdelnum, Dbranch);
7739Sjkh                    } else {
7749Sjkh			incnum(Head->num, &newdelnum);
7759Sjkh                    }
7769Sjkh		    newdnumlength = countnumflds(newdelnum.string);
7779Sjkh                    /* now fall into next statement */
7789Sjkh                }
7799Sjkh        }
7809Sjkh        if (newdnumlength<=2) {
7819Sjkh                /* add new head per given number */
7829Sjkh                if(newdnumlength==1) {
7839Sjkh                    /* make a two-field number out of it*/
7849Sjkh		    if (cmpnumfld(newdelnum.string,Head->num,1)==0)
7859Sjkh			incnum(Head->num, &newdelnum);
7869Sjkh		    else
7879Sjkh			bufscat(&newdelnum, ".1");
7889Sjkh                }
7899Sjkh		if (cmpnum(newdelnum.string,Head->num) <= 0) {
7909Sjkh                    error("deltanumber %s too low; must be higher than %s",
7919Sjkh			  newdelnum.string, Head->num);
7929Sjkh		    return -1;
7939Sjkh                }
7949Sjkh		targetdelta = Head;
7959Sjkh		if (0 <= (removedlock = removelock(Head))) {
7969Sjkh		    if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas))
7979Sjkh			return -1;
7989Sjkh		    newdelta.next = Head;
7999Sjkh		    Head = &newdelta;
8009Sjkh		}
8019Sjkh		return removedlock;
8029Sjkh        } else {
8039Sjkh                /* put new revision on side branch */
8049Sjkh                /*first, get branch point */
8059Sjkh		tp = newdelnum.string;
8069Sjkh		for (i = newdnumlength - (newdnumlength&1 ^ 1);  (--i);  )
8079Sjkh			while (*tp++ != '.')
8089Sjkh				;
8099Sjkh		*--tp = 0; /* Kill final dot to get old delta temporarily. */
8109Sjkh		if (!(targetdelta=genrevs(newdelnum.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas)))
8119Sjkh		    return -1;
8129Sjkh		if (cmpnum(targetdelta->num, newdelnum.string) != 0) {
8139Sjkh		    error("can't find branchpoint %s", newdelnum.string);
8149Sjkh		    return -1;
8159Sjkh                }
8169Sjkh		*tp = '.'; /* Restore final dot. */
8179Sjkh		return addbranch(targetdelta,&newdelnum);
8189Sjkh        }
8199Sjkh}
8209Sjkh
8219Sjkh
8229Sjkh
8239Sjkh	static int
8249Sjkhaddbranch(branchpoint,num)
8259Sjkh	struct hshentry *branchpoint;
8269Sjkh	struct buf *num;
8279Sjkh/* adds a new branch and branch delta at branchpoint.
8289Sjkh * If num is the null string, appends the new branch, incrementing
8299Sjkh * the highest branch number (initially 1), and setting the level number to 1.
8309Sjkh * the new delta and branchhead are in globals newdelta and newbranch, resp.
8319Sjkh * the new number is placed into num.
8329Sjkh * Return -1 on error, 1 if a lock is removed, 0 otherwise.
8339Sjkh */
8349Sjkh{
8359Sjkh	struct branchhead *bhead, **btrail;
8369Sjkh	struct buf branchnum;
8379Sjkh	int removedlock, result;
8389Sjkh	unsigned field, numlength;
8399Sjkh	static struct branchhead newbranch;  /* new branch to be inserted */
8409Sjkh
8419Sjkh	numlength = countnumflds(num->string);
8429Sjkh
8439Sjkh        if (branchpoint->branches==nil) {
8449Sjkh                /* start first branch */
8459Sjkh                branchpoint->branches = &newbranch;
8469Sjkh                if (numlength==0) {
8479Sjkh			bufscpy(num, branchpoint->num);
8489Sjkh			bufscat(num, ".1.1");
8499Sjkh		} else if (numlength&1)
8509Sjkh			bufscat(num, ".1");
8519Sjkh                newbranch.nextbranch=nil;
8529Sjkh
8539Sjkh	} else if (numlength==0) {
8549Sjkh                /* append new branch to the end */
8559Sjkh                bhead=branchpoint->branches;
8569Sjkh                while (bhead->nextbranch) bhead=bhead->nextbranch;
8579Sjkh                bhead->nextbranch = &newbranch;
8589Sjkh		bufautobegin(&branchnum);
8599Sjkh		getbranchno(bhead->hsh->num, &branchnum);
8609Sjkh		incnum(branchnum.string, num);
8619Sjkh		bufautoend(&branchnum);
8629Sjkh		bufscat(num, ".1");
8639Sjkh                newbranch.nextbranch=nil;
8649Sjkh        } else {
8659Sjkh                /* place the branch properly */
8669Sjkh		field = numlength - (numlength&1 ^ 1);
8679Sjkh                /* field of branch number */
8689Sjkh		btrail = &branchpoint->branches;
8699Sjkh		while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) {
8709Sjkh			btrail = &(*btrail)->nextbranch;
8719Sjkh			if (!*btrail) {
8729Sjkh				result = -1;
8739Sjkh				break;
8749Sjkh			}
8759Sjkh                }
8769Sjkh		if (result < 0) {
8779Sjkh                        /* insert/append new branchhead */
8789Sjkh			newbranch.nextbranch = *btrail;
8799Sjkh			*btrail = &newbranch;
8809Sjkh			if (numlength&1) bufscat(num, ".1");
8819Sjkh                } else {
8829Sjkh                        /* branch exists; append to end */
8839Sjkh			bufautobegin(&branchnum);
8849Sjkh			getbranchno(num->string, &branchnum);
8859Sjkh			targetdelta=genrevs(branchnum.string,(char*)nil,
8869Sjkh					    (char*)nil,(char*)nil,&gendeltas);
8879Sjkh			bufautoend(&branchnum);
8889Sjkh			if (!targetdelta)
8899Sjkh			    return -1;
8909Sjkh			if (cmpnum(num->string,targetdelta->num) <= 0) {
8919Sjkh                                error("deltanumber %s too low; must be higher than %s",
8929Sjkh				      num->string,targetdelta->num);
8939Sjkh				return -1;
8949Sjkh                        }
8959Sjkh			if (0 <= (removedlock = removelock(targetdelta))) {
8969Sjkh			    if (numlength&1)
8979Sjkh				incnum(targetdelta->num,num);
8989Sjkh			    targetdelta->next = &newdelta;
8999Sjkh			    newdelta.next = 0;
9009Sjkh			}
9019Sjkh			return removedlock;
9029Sjkh			/* Don't do anything to newbranch.  */
9039Sjkh                }
9049Sjkh        }
9059Sjkh        newbranch.hsh = &newdelta;
9069Sjkh        newdelta.next=nil;
9079Sjkh	return 0;
9089Sjkh}
9099Sjkh
9109Sjkh	static int
9119Sjkhaddsyms(num)
9129Sjkh	char const *num;
9139Sjkh{
9149Sjkh	register struct Symrev *p;
9159Sjkh
9169Sjkh	for (p = assoclst;  p;  p = p->nextsym)
9179Sjkh		if (!addsymbol(num, p->ssymbol, p->override))
9189Sjkh			return false;
9199Sjkh	return true;
9209Sjkh}
9219Sjkh
9229Sjkh
9239Sjkh	static void
9249Sjkhincnum(onum,nnum)
9259Sjkh	char const *onum;
9269Sjkh	struct buf *nnum;
9279Sjkh/* Increment the last field of revision number onum by one and
9289Sjkh * place the result into nnum.
9299Sjkh */
9309Sjkh{
9319Sjkh	register char *tp, *np;
9329Sjkh	register size_t l;
9339Sjkh
9349Sjkh	l = strlen(onum);
9359Sjkh	bufalloc(nnum, l+2);
9369Sjkh	np = tp = nnum->string;
9379Sjkh	VOID strcpy(np, onum);
9389Sjkh	for (tp = np + l;  np != tp;  )
9399Sjkh		if (isdigit(*--tp)) {
9409Sjkh			if (*tp != '9') {
9419Sjkh				++*tp;
9429Sjkh				return;
9439Sjkh			}
9449Sjkh			*tp = '0';
9459Sjkh		} else {
9469Sjkh			tp++;
9479Sjkh			break;
9489Sjkh		}
9499Sjkh	/* We changed 999 to 000; now change it to 1000.  */
9509Sjkh	*tp = '1';
9519Sjkh	tp = np + l;
9529Sjkh	*tp++ = '0';
9539Sjkh	*tp = 0;
9549Sjkh}
9559Sjkh
9569Sjkh
9579Sjkh
9589Sjkh	static int
9599Sjkhremovelock(delta)
9609Sjkhstruct hshentry * delta;
9619Sjkh/* function: Finds the lock held by caller on delta,
9629Sjkh * removes it, and returns nonzero if successful.
9639Sjkh * Print an error message and return -1 if there is no such lock.
9649Sjkh * An exception is if !StrictLocks, and caller is the owner of
9659Sjkh * the RCS file. If caller does not have a lock in this case,
9669Sjkh * return 0; return 1 if a lock is actually removed.
9679Sjkh */
9689Sjkh{
9699Sjkh	register struct lock *next, **trail;
9709Sjkh	char const *num;
9719Sjkh
9729Sjkh        num=delta->num;
9739Sjkh	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
9749Sjkh	    if (next->delta == delta)
9759Sjkh		if (strcmp(getcaller(), next->login) == 0) {
9769Sjkh		    /* We found a lock on delta by caller; delete it.  */
9779Sjkh		    *trail = next->nextlock;
9789Sjkh		    delta->lockedby = 0;
9799Sjkh		    return 1;
9809Sjkh		} else {
9819Sjkh                    error("revision %s locked by %s",num,next->login);
9829Sjkh		    return -1;
9839Sjkh                }
9849Sjkh	if (!StrictLocks && myself(RCSstat.st_uid))
9859Sjkh	    return 0;
9869Sjkh	error("no lock set by %s for revision %s", getcaller(), num);
9879Sjkh	return -1;
9889Sjkh}
9899Sjkh
9909Sjkh
9919Sjkh
9929Sjkh	static char const *
9939Sjkhgetcurdate()
9949Sjkh/* Return a pointer to the current date.  */
9959Sjkh{
9969Sjkh	static char buffer[datesize]; /* date buffer */
9979Sjkh	time_t t;
9989Sjkh
9999Sjkh	if (!buffer[0]) {
10009Sjkh		t = time((time_t *)0);
10019Sjkh		if (t == -1)
10029Sjkh			faterror("time not available");
10039Sjkh		time2date(t, buffer);
10049Sjkh	}
10059Sjkh        return buffer;
10069Sjkh}
10079Sjkh
10089Sjkh	static int
10099Sjkh#if has_prototypes
10109Sjkhfixwork(mode_t newworkmode, char const *mtime)
10119Sjkh  /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
10129Sjkh#else
10139Sjkh  fixwork(newworkmode, mtime)
10149Sjkh	mode_t newworkmode;
10159Sjkh	char const *mtime;
10169Sjkh#endif
10179Sjkh{
10189Sjkh	int r;
10199Sjkh	return
10209Sjkh			1 < workstat.st_nlink
10219Sjkh		    ||	newworkmode&S_IWUSR && !myself(workstat.st_uid)
10229Sjkh		?   -1
10239Sjkh	    :
10249Sjkh			workstat.st_mode != newworkmode
10259Sjkh		    &&
10269Sjkh			(r =
10279Sjkh#			    if has_fchmod
10289Sjkh				fchmod(Ifileno(workptr), newworkmode)
10299Sjkh#			    else
10309Sjkh				chmod(workfilename, newworkmode)
10319Sjkh#			    endif
10329Sjkh			) != 0
10339Sjkh		?   r
10349Sjkh	    :
10359Sjkh		setfiledate(workfilename, mtime);
10369Sjkh}
10379Sjkh
10389Sjkh	static int
10399Sjkhxpandfile(unexfile, dir, delta, exfilename)
10409Sjkh	RILE *unexfile;
10419Sjkh	char const *dir;
10429Sjkh	struct hshentry const *delta;
10439Sjkh	char const **exfilename;
10449Sjkh/*
10459Sjkh * Read unexfile and copy it to a
10469Sjkh * file in dir, performing keyword substitution with data from delta.
10479Sjkh * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
10489Sjkh * If successful, stores the stream descriptor into *EXFILEP
10499Sjkh * and its name into *EXFILENAME.
10509Sjkh */
10519Sjkh{
10529Sjkh	char const *targetfname;
10539Sjkh	int e, r;
10549Sjkh
10559Sjkh	targetfname = makedirtemp(dir, 1);
10569Sjkh	if (!(exfile = fopen(targetfname, FOPEN_W_WORK))) {
10579Sjkh		eerror(targetfname);
10589Sjkh		error("can't expand working file");
10599Sjkh		return -1;
10609Sjkh        }
10619Sjkh	r = 0;
10629Sjkh	if (Expand == OLD_EXPAND)
10639Sjkh		fastcopy(unexfile,exfile);
10649Sjkh	else {
10659Sjkh		for (;;) {
10669Sjkh			e = expandline(unexfile,exfile,delta,false,(FILE*)nil);
10679Sjkh			if (e < 0)
10689Sjkh				break;
10699Sjkh			r |= e;
10709Sjkh			if (e <= 1)
10719Sjkh				break;
10729Sjkh		}
10739Sjkh	}
10749Sjkh	*exfilename = targetfname;
10759Sjkh	aflush(exfile);
10769Sjkh	return r & 1;
10779Sjkh}
10789Sjkh
10799Sjkh
10809Sjkh
10819Sjkh
10829Sjkh/* --------------------- G E T L O G M S G --------------------------------*/
10839Sjkh
10849Sjkh
10859Sjkh	static struct cbuf
10869Sjkhgetlogmsg()
10879Sjkh/* Obtain and yield a log message.
10889Sjkh * If a log message is given with -m, yield that message.
10899Sjkh * If this is the initial revision, yield a standard log message.
10909Sjkh * Otherwise, reads a character string from the terminal.
10919Sjkh * Stops after reading EOF or a single '.' on a
10929Sjkh * line. getlogmsg prompts the first time it is called for the
10939Sjkh * log message; during all later calls it asks whether the previous
10949Sjkh * log message can be reused.
10959Sjkh */
10969Sjkh{
10979Sjkh	static char const
10989Sjkh		emptych[] = EMPTYLOG,
10999Sjkh		initialch[] = "Initial revision";
11009Sjkh	static struct cbuf const
11019Sjkh		emptylog = { emptych, sizeof(emptych)-sizeof(char) },
11029Sjkh		initiallog = { initialch, sizeof(initialch)-sizeof(char) };
11039Sjkh	static struct buf logbuf;
11049Sjkh	static struct cbuf logmsg;
11059Sjkh
11069Sjkh	register char *tp;
11079Sjkh	register size_t i;
11089Sjkh	char const *caller;
11099Sjkh
11109Sjkh	if (msg.size) return msg;
11119Sjkh
11129Sjkh	if (keepflag) {
11139Sjkh		/* generate std. log message */
11149Sjkh		caller = getcaller();
11159Sjkh		i = sizeof(ciklog)+strlen(caller)+3;
11169Sjkh		bufalloc(&logbuf, i+datesize);
11179Sjkh		tp = logbuf.string;
11189Sjkh		VOID sprintf(tp, "%s%s at ", ciklog, caller);
11199Sjkh		VOID date2str(getcurdate(), tp+i);
11209Sjkh		logmsg.string = tp;
11219Sjkh		logmsg.size = strlen(tp);
11229Sjkh		return logmsg;
11239Sjkh	}
11249Sjkh
11259Sjkh	if (!targetdelta && (
11269Sjkh		cmpnum(newdelnum.string,"1.1")==0 ||
11279Sjkh		cmpnum(newdelnum.string,"1.0")==0
11289Sjkh	))
11299Sjkh		return initiallog;
11309Sjkh
11319Sjkh	if (logmsg.size) {
11329Sjkh                /*previous log available*/
11339Sjkh	    if (yesorno(true, "reuse log message of previous file? [yn](y): "))
11349Sjkh		return logmsg;
11359Sjkh        }
11369Sjkh
11379Sjkh        /* now read string from stdin */
11389Sjkh	logmsg = getsstdin("m", "log message", "", &logbuf);
11399Sjkh
11409Sjkh        /* now check whether the log message is not empty */
11419Sjkh	if (logmsg.size)
11429Sjkh		return logmsg;
11439Sjkh	return emptylog;
11449Sjkh}
11459Sjkh
11469Sjkh/*  Make a linked list of Symbolic names  */
11479Sjkh
11489Sjkh        static void
11499Sjkhaddassoclst(flag, sp)
11509Sjkhint  flag;
11519Sjkhchar * sp;
11529Sjkh{
11539Sjkh        struct Symrev *pt;
11549Sjkh
11559Sjkh	pt = talloc(struct Symrev);
11569Sjkh	pt->ssymbol = sp;
11579Sjkh	pt->override = flag;
11589Sjkh	pt->nextsym = nil;
11599Sjkh	if (lastassoc)
11609Sjkh	        lastassoc->nextsym = pt;
11619Sjkh	else
11629Sjkh	        assoclst = pt;
11639Sjkh	lastassoc = pt;
11649Sjkh	return;
11659Sjkh}
1166