home *** CD-ROM | disk | FTP | other *** search
- /* Copyright (C) 1982, 1988, 1989 Walter Tichy
- Copyright 1990, 1991, 1992 by Paul Eggert
- Distributed under license by the Free Software Foundation, Inc.
-
- This file is part of RCS.
-
- RCS is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
-
- RCS is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with RCS; see the file COPYING. If not, write to
- the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
-
- Report problems and direct all questions to:
-
- rcs-bugs@cs.purdue.edu
-
- */
-
- /*
- * RCS checkin operation
- */
- /*******************************************************************
- * check revisions into RCS files
- *******************************************************************
- */
-
-
-
- /* $Log: ci.c,v $
- * Revision 5.25 1992/07/28 16:12:44 eggert
- * Add -i, -j, -V. Check that working and RCS files are distinct.
- *
- * Revision 5.24 1992/02/17 23:02:06 eggert
- * `-rREV' now just specifies a revision REV; only bare `-r' reverts to default.
- * Add -T.
- *
- * Revision 5.23 1992/01/27 16:42:51 eggert
- * Always unlock branchpoint if caller has a lock.
- * Add support for bad_chmod_close, bad_creat0. lint -> RCS_lint
- *
- * Revision 5.22 1992/01/06 02:42:34 eggert
- * Invoke utime() before chmod() to keep some buggy systems happy.
- *
- * Revision 5.21 1991/11/20 17:58:07 eggert
- * Don't read the delta tree from a nonexistent RCS file.
- *
- * Revision 5.20 1991/10/07 17:32:46 eggert
- * Fix log bugs. Remove lint.
- *
- * Revision 5.19 1991/09/26 23:10:30 eggert
- * Plug file descriptor leak.
- *
- * Revision 5.18 1991/09/18 07:29:10 eggert
- * Work around a common ftruncate() bug.
- *
- * Revision 5.17 1991/09/10 22:15:46 eggert
- * Fix test for redirected stdin.
- *
- * Revision 5.16 1991/08/19 23:17:54 eggert
- * When there are no changes, revert to previous revision instead of aborting.
- * Add piece tables, -M, -r$. Tune.
- *
- * Revision 5.15 1991/04/21 11:58:14 eggert
- * Ensure that working file is newer than RCS file after ci -[lu].
- * Add -x, RCSINIT, MS-DOS support.
- *
- * Revision 5.14 1991/02/28 19:18:47 eggert
- * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first.
- * Fix ci -ko -l mode bug. Open work file at most once.
- *
- * Revision 5.13 1991/02/25 07:12:33 eggert
- * getdate -> getcurdate (SVR4 name clash)
- *
- * Revision 5.12 1990/12/31 01:00:12 eggert
- * Don't use uninitialized storage when handling -{N,n}.
- *
- * Revision 5.11 1990/12/04 05:18:36 eggert
- * Use -I for prompts and -q for diagnostics.
- *
- * Revision 5.10 1990/11/05 20:30:10 eggert
- * Don't remove working file when aborting due to no changes.
- *
- * Revision 5.9 1990/11/01 05:03:23 eggert
- * Add -I and new -t behavior. Permit arbitrary data in logs.
- *
- * Revision 5.8 1990/10/04 06:30:09 eggert
- * Accumulate exit status across files.
- *
- * Revision 5.7 1990/09/25 20:11:46 hammer
- * fixed another small typo
- *
- * Revision 5.6 1990/09/24 21:48:50 hammer
- * added cleanups from Paul Eggert.
- *
- * Revision 5.5 1990/09/21 06:16:38 hammer
- * made it handle multiple -{N,n}'s. Also, made it treat re-directed stdin
- * the same as the terminal
- *
- * Revision 5.4 1990/09/20 02:38:51 eggert
- * ci -k now checks dates more thoroughly.
- *
- * Revision 5.3 1990/09/11 02:41:07 eggert
- * Fix revision bug with `ci -k file1 file2'.
- *
- * Revision 5.2 1990/09/04 08:02:10 eggert
- * Permit adjacent revisions with identical time stamps (possible on fast hosts).
- * Improve incomplete line handling. Standardize yes-or-no procedure.
- *
- * Revision 5.1 1990/08/29 07:13:44 eggert
- * Expand locker value like co. Clean old log messages too.
- *
- * Revision 5.0 1990/08/22 08:10:00 eggert
- * Don't require a final newline.
- * Make lock and temp files faster and safer.
- * Remove compile-time limits; use malloc instead.
- * Permit dates past 1999/12/31. Switch to GMT.
- * Add setuid support. Don't pass +args to diff. Check diff's output.
- * Ansify and Posixate. Add -k, -V. Remove snooping. Tune.
- * Check diff's output.
- *
- * Revision 4.9 89/05/01 15:10:54 narten
- * changed copyright header to reflect current distribution rules
- *
- * Revision 4.8 88/11/08 13:38:23 narten
- * changes from root@seismo.CSS.GOV (Super User)
- * -d with no arguments uses the mod time of the file it is checking in
- *
- * Revision 4.7 88/08/09 19:12:07 eggert
- * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one.
- * Use execv(), not system(); allow cc -R; remove lint.
- * isatty(fileno(stdin)) -> ttystdin()
- *
- * Revision 4.6 87/12/18 11:34:41 narten
- * lint cleanups (from Guy Harris)
- *
- * Revision 4.5 87/10/18 10:18:48 narten
- * Updating version numbers. Changes relative to revision 1.1 are actually
- * relative to 4.3
- *
- * Revision 1.3 87/09/24 13:57:19 narten
- * Sources now pass through lint (if you ignore printf/sprintf/fprintf
- * warnings)
- *
- * Revision 1.2 87/03/27 14:21:33 jenkins
- * Port to suns
- *
- * Revision 4.3 83/12/15 12:28:54 wft
- * ci -u and ci -l now set mode of working file properly.
- *
- * Revision 4.2 83/12/05 13:40:54 wft
- * Merged with 3.9.1.1: added calls to clearerr(stdin).
- * made rewriteflag external.
- *
- * Revision 4.1 83/05/10 17:03:06 wft
- * Added option -d and -w, and updated assingment of date, etc. to new delta.
- * Added handling of default branches.
- * Option -k generates std. log message; fixed undef. pointer in reading of log.
- * Replaced getlock() with findlock(), link--unlink with rename(),
- * getpwuid() with getcaller().
- * Moved all revision number generation to new routine addelta().
- * Removed calls to stat(); now done by pairfilenames().
- * Changed most calls to catchints() with restoreints().
- * Directed all interactive messages to stderr.
- *
- * Revision 3.9.1.1 83/10/19 04:21:03 lepreau
- * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
- *
- * Revision 3.9 83/02/15 15:25:44 wft
- * 4.2 prerelease
- *
- * Revision 3.9 83/02/15 15:25:44 wft
- * Added call to fastcopy() to copy remainder of RCS file.
- *
- * Revision 3.8 83/01/14 15:34:05 wft
- * Added ignoring of interrupts while new RCS file is renamed;
- * Avoids deletion of RCS files by interrupts.
- *
- * Revision 3.7 82/12/10 16:09:20 wft
- * Corrected checking of return code from diff.
- *
- * Revision 3.6 82/12/08 21:34:49 wft
- * Using DATEFORM to prepare date of checked-in revision;
- * Fixed return from addbranch().
- *
- * Revision 3.5 82/12/04 18:32:42 wft
- * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated
- * field lockedby in removelock(), moved getlogmsg() before calling diff.
- *
- * Revision 3.4 82/12/02 13:27:13 wft
- * added option -k.
- *
- * Revision 3.3 82/11/28 20:53:31 wft
- * Added mustcheckin() to check for redundant checkins.
- * Added xpandfile() to do keyword expansion for -u and -l;
- * -m appends linefeed to log message if necessary.
- * getlogmsg() suppresses prompt if stdin is not a terminal.
- * Replaced keeplock with lockflag, fclose() with ffclose(),
- * %02d with %.2d, getlogin() with getpwuid().
- *
- * Revision 3.2 82/10/18 20:57:23 wft
- * An RCS file inherits its mode during the first ci from the working file,
- * otherwise it stays the same, except that write permission is removed.
- * Fixed ci -l, added ci -u (both do an implicit co after the ci).
- * Fixed call to getlogin(), added call to getfullRCSname(), added check
- * for write error.
- * Changed conflicting identifiers.
- *
- * Revision 3.1 82/10/13 16:04:59 wft
- * fixed type of variables receiving from getc() (char -> int).
- * added include file dbm.h for getting BYTESIZ. This is used
- * to check the return code from diff portably.
- */
-
- #include "rcsbase.h"
-
- struct Symrev {
- char const *ssymbol;
- int override;
- struct Symrev * nextsym;
- };
-
- static char const *getcurdate P((void));
- static int addbranch P((struct hshentry*,struct buf*));
- static int addelta P((void));
- static int addsyms P((char const*));
- static int fixwork P((mode_t,time_t));
- static int removelock P((struct hshentry*));
- static int xpandfile P((RILE*,struct hshentry const*,char const**,int));
- static struct cbuf getlogmsg P((void));
- static void cleanup P((void));
- static void incnum P((char const*,struct buf*));
- static void addassoclst P((int, char *));
-
- static FILE *exfile;
- static RILE *workptr; /* working file pointer */
- static struct buf newdelnum; /* new revision number */
- static struct cbuf msg;
- static int exitstatus;
- static int forceciflag; /* forces check in */
- static int keepflag, keepworkingfile, rcsinitflag;
- static struct hshentries *gendeltas; /* deltas to be generated */
- static struct hshentry *targetdelta; /* old delta to be generated */
- static struct hshentry newdelta; /* new delta to be inserted */
- static struct stat workstat;
- static struct Symrev *assoclst, *lastassoc;
-
- mainProg(ciId, "ci", "$Id: ci.c,v 5.25 1992/07/28 16:12:44 eggert Exp $")
- {
- static char const cmdusage[] =
- "\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -Vn -wwho -xsuff file ...";
- static char const default_state[] = DEFAULTSTATE;
-
- char altdate[datesize];
- char olddate[datesize];
- char newdatebuf[datesize], targetdatebuf[datesize];
- char *a, **newargv, *textfile;
- char const *author, *krev, *rev, *state;
- char const *diffname, *expname;
- char const *workdiffname, *newworkname;
- int initflag, mustread;
- int keepRCStime, lockflag, lockthis, mtimeflag, removedlock, Ttimeflag;
- int argc_min, r;
- int changedRCS, changework, dolog, newhead;
- int usestatdate; /* Use mod time of file for -d. */
- mode_t newworkmode; /* mode for working file */
- time_t mtime;
- struct hshentry *workdelta;
-
- setrid();
-
- argc_min = 1;
- author = rev = state = textfile = nil;
- initflag = lockflag = mustread = false;
- mtimeflag = false;
- Ttimeflag = false;
- altdate[0]= '\0'; /* empty alternate date for -d */
- usestatdate=false;
- suffixes = X_DEFAULT;
-
- argc = getRCSINIT(argc, argv, &newargv);
- argv = newargv;
- while (a = *++argv, 0<--argc && *a++=='-') {
- switch (*a++) {
-
- case 'r':
- if (*a)
- goto revno;
- keepworkingfile = lockflag = false;
- break;
-
- case 'l':
- keepworkingfile = lockflag = true;
- revno:
- if (*a) {
- if (rev) warn("redefinition of revision number");
- rev = a;
- }
- break;
-
- case 'u':
- keepworkingfile=true; lockflag=false;
- goto revno;
-
- case 'i':
- initflag = true;
- goto revno;
-
- case 'j':
- mustread = true;
- goto revno;
-
- case 'I':
- interactiveflag = true;
- goto revno;
-
- case 'q':
- quietflag=true;
- goto revno;
-
- case 'f':
- forceciflag=true;
- goto revno;
-
- case 'k':
- keepflag=true;
- goto revno;
-
- case 'm':
- if (msg.size) redefined('m');
- msg = cleanlogmsg(a, strlen(a));
- if (!msg.size)
- warn("missing message for -m option");
- break;
-
- case 'n':
- if (!*a) {
- error("missing symbolic name after -n");
- break;
- }
- checkssym(a);
- addassoclst(false, a);
- break;
-
- case 'N':
- if (!*a) {
- error("missing symbolic name after -N");
- break;
- }
- checkssym(a);
- addassoclst(true, a);
- break;
-
- case 's':
- if (*a) {
- if (state) redefined('s');
- checksid(a);
- state = a;
- } else
- warn("missing state for -s option");
- break;
-
- case 't':
- if (*a) {
- if (textfile) redefined('t');
- textfile = a;
- }
- break;
-
- case 'd':
- if (altdate[0] || usestatdate)
- redefined('d');
- altdate[0] = 0;
- if (!(usestatdate = !*a))
- str2date(a, altdate);
- break;
-
- case 'M':
- mtimeflag = true;
- goto revno;
-
- case 'w':
- if (*a) {
- if (author) redefined('w');
- checksid(a);
- author = a;
- } else
- warn("missing author for -w option");
- break;
-
- case 'x':
- suffixes = a;
- break;
-
- case 'V':
- argc_min = setRCSversion(*argv);
- break;
-
- case 'T':
- if (!*a) {
- Ttimeflag = true;
- break;
- }
- /* fall into */
- default:
- faterror("unknown option: %s%s", *argv, cmdusage);
- };
- } /* end processing of options */
-
- if (argc < argc_min) faterror("no input file%s", cmdusage);
-
- /* Handle all pathnames. */
- for (; 0 < argc; cleanup(), ++argv, --argc) {
- targetdelta=nil;
- ffree();
-
- switch (pairnames(argc, argv, rcswriteopen, mustread, false)) {
-
- case -1: /* New RCS file */
- # if has_setuid && has_getuid
- if (euid() != ruid()) {
- error("setuid initial checkin prohibited; use `rcs -i -a' first");
- continue;
- }
- # endif
- rcsinitflag = true;
- break;
-
- case 0: /* Error */
- continue;
-
- case 1: /* Normal checkin with prev . RCS file */
- if (initflag) {
- error("file %s exists already", RCSname);
- continue;
- }
- rcsinitflag = !Head;
- }
-
- /*
- * RCSname contains the name of the RCS file, and
- * workname contains the name of the working file.
- * If the RCS file exists, finptr contains the file descriptor for the
- * RCS file, and RCSstat is set. The admin node is initialized.
- */
-
- diagnose("%s <-- %s\n", RCSname, workname);
-
- if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) {
- eerror(workname);
- continue;
- }
-
- if (finptr) {
- if (same_file(RCSstat, workstat, 0)) {
- error("RCS and working files are the same");
- continue;
- }
- if (!checkaccesslist())
- continue;
- }
-
- krev = rev;
- if (keepflag) {
- /* get keyword values from working file */
- if (!getoldkeys(workptr)) continue;
- if (!rev && !*(krev = prevrev.string)) {
- error("can't find a revision number in %s", workname);
- continue;
- }
- if (!*prevdate.string && *altdate=='\0' && usestatdate==false)
- warn("can't find a date in %s", workname);
- if (!*prevauthor.string && !author)
- warn("can't find an author in %s", workname);
- if (!*prevstate.string && !state)
- warn("can't find a state in %s", workname);
- } /* end processing keepflag */
-
- /* Read the delta tree. */
- if (finptr)
- gettree();
-
- /* expand symbolic revision number */
- if (!fexpandsym(krev, &newdelnum, workptr))
- continue;
-
- /* splice new delta into tree */
- if ((removedlock = addelta()) < 0)
- continue;
-
- newdelta.num = newdelnum.string;
- newdelta.branches=nil;
- newdelta.lockedby=nil; /*might be changed by addlock() */
- newdelta.selector = true;
- /* set author */
- if (author!=nil)
- newdelta.author=author; /* set author given by -w */
- else if (keepflag && *prevauthor.string)
- newdelta.author=prevauthor.string; /* preserve old author if possible*/
- else newdelta.author=getcaller();/* otherwise use caller's id */
- newdelta.state = default_state;
- if (state!=nil)
- newdelta.state=state; /* set state given by -s */
- else if (keepflag && *prevstate.string)
- newdelta.state=prevstate.string; /* preserve old state if possible */
- if (usestatdate) {
- time2date(workstat.st_mtime, altdate);
- }
- if (*altdate!='\0')
- newdelta.date=altdate; /* set date given by -d */
- else if (keepflag && *prevdate.string) {
- /* Preserve old date if possible. */
- str2date(prevdate.string, olddate);
- newdelta.date = olddate;
- } else
- newdelta.date = getcurdate(); /* use current date */
- /* now check validity of date -- needed because of -d and -k */
- if (targetdelta!=nil &&
- cmpnum(newdelta.date,targetdelta->date) < 0) {
- error("Date %s precedes %s in existing revision %s.",
- date2str(newdelta.date, newdatebuf),
- date2str(targetdelta->date, targetdatebuf),
- targetdelta->num
- );
- continue;
- }
-
-
- if (lockflag && addlock(&newdelta, true) < 0) continue;
- if (!addsyms(newdelta.num))
- continue;
-
-
- putadmin();
- puttree(Head,frewrite);
- putdesc(false,textfile);
-
- changework = Expand != OLD_EXPAND;
- dolog = true;
- keepRCStime = false;
- lockthis = lockflag;
- workdelta = &newdelta;
-
- /* build rest of file */
- if (rcsinitflag) {
- diagnose("initial revision: %s\n", newdelnum.string);
- /* get logmessage */
- newdelta.log=getlogmsg();
- putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false);
- RCSstat.st_mode = workstat.st_mode;
- RCSstat.st_nlink = 0;
- changedRCS = true;
- } else {
- diffname = maketemp(0);
- workdiffname = workname;
- if (workdiffname[0] == '+') {
- /* Some diffs have options with leading '+'. */
- char *dp = ftnalloc(char, strlen(workname)+3);
- workdiffname = dp;
- *dp++ = '.';
- *dp++ = SLASH;
- VOID strcpy(dp, workname);
- }
- newhead = Head == &newdelta;
- if (!newhead)
- foutptr = frewrite;
- expname = buildrevision(
- gendeltas, targetdelta, (FILE*)0, false
- );
- if (
- !forceciflag &&
- strcmp(newdelta.state, targetdelta->state) == 0 &&
- (changework = rcsfcmp(
- workptr, &workstat, expname, targetdelta
- )) <= 0
- ) {
- diagnose("file is unchanged; reverting to previous revision %s\n",
- targetdelta->num
- );
- if (removedlock < lockflag) {
- diagnose("previous revision was not locked; ignoring -l option\n");
- lockthis = 0;
- }
- dolog = false;
- if (! (changedRCS = lockflag<removedlock || assoclst))
- workdelta = targetdelta;
- else {
- /*
- * We have started to build the wrong new RCS file.
- * Start over from the beginning.
- */
- long hwm = ftell(frewrite);
- int bad_truncate;
- if (fseek(frewrite, 0L, SEEK_SET) != 0)
- Oerror();
- # if !has_ftruncate
- bad_truncate = 1;
- # else
- /*
- * Work around a common ftruncate() bug.
- * We can't rely on has_truncate, because we might
- * be using a filesystem exported to us via NFS.
- */
- bad_truncate = ftruncate(fileno(frewrite),(off_t)0);
- if (bad_truncate && errno != EACCES)
- Oerror();
- # endif
- Irewind(finptr);
- Lexinit();
- getadmin();
- gettree();
- if (!(workdelta = genrevs(
- targetdelta->num, (char*)0, (char*)0, (char*)0,
- &gendeltas
- )))
- continue;
- workdelta->log = targetdelta->log;
- if (newdelta.state != default_state)
- workdelta->state = newdelta.state;
- if (removedlock && removelock(workdelta)<0)
- continue;
- if (!addsyms(workdelta->num))
- continue;
- if (dorewrite(true, true) != 0)
- continue;
- fastcopy(finptr, frewrite);
- if (bad_truncate)
- while (ftell(frewrite) < hwm)
- /* White out any earlier mistake with '\n's. */
- /* This is unlikely. */
- afputc('\n', frewrite);
- keepRCStime = Ttimeflag;
- }
- } else {
- diagnose("new revision: %s; previous revision: %s\n",
- newdelnum.string, targetdelta->num
- );
- newdelta.log = getlogmsg();
- switch (run((char*)0, diffname,
- DIFF DIFF_FLAGS,
- newhead ? workdiffname : expname,
- newhead ? expname : workdiffname,
- (char*)0
- )) {
- case DIFF_FAILURE: case DIFF_SUCCESS: break;
- default: faterror("diff failed");
- }
- if (newhead) {
- Irewind(workptr);
- putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false);
- if (!putdtext(targetdelta->num,targetdelta->log,diffname,frewrite,true)) continue;
- } else
- if (!putdtext(newdelnum.string,newdelta.log,diffname,frewrite,true)) continue;
- changedRCS = true;
- }
- }
- if (donerewrite(changedRCS, keepRCStime) != 0)
- continue;
-
- if (!keepworkingfile) {
- Izclose(&workptr);
- r = un_link(workname); /* Get rid of old file */
- } else {
- newworkmode = WORKMODE(RCSstat.st_mode,
- ! (Expand==VAL_EXPAND || lockthis < StrictLocks)
- );
- mtime = mtimeflag ? date2time(workdelta->date) : (time_t)-1;
-
- /* Expand if it might change or if we can't fix mode, time. */
- if (changework || (r=fixwork(newworkmode,mtime)) != 0) {
- Irewind(workptr);
- /* Expand keywords in file. */
- locker_expansion = lockthis;
- switch (xpandfile(
- workptr, workdelta, &newworkname, dolog
- )) {
- default:
- continue;
-
- case 0:
- /*
- * No expansion occurred; try to reuse working file
- * unless we already tried and failed.
- */
- if (changework)
- if ((r=fixwork(newworkmode,mtime)) == 0)
- break;
- /* fall into */
- case 1:
- Izclose(&workptr);
- ignoreints();
- r = chnamemod(&exfile, newworkname,
- workname, 1, newworkmode, mtime
- );
- keepdirtemp(newworkname);
- restoreints();
- }
- }
- }
- if (r != 0) {
- eerror(workname);
- continue;
- }
- diagnose("done\n");
-
- }
-
- tempunlink();
- exitmain(exitstatus);
- } /* end of main (ci) */
-
- static void
- cleanup()
- {
- if (nerror) exitstatus = EXIT_FAILURE;
- Izclose(&finptr);
- Izclose(&workptr);
- Ozclose(&exfile);
- Ozclose(&fcopy);
- ORCSclose();
- dirtempunlink();
- }
-
- #if RCS_lint
- # define exiterr ciExit
- #endif
- exiting void
- exiterr()
- {
- dirtempunlink();
- tempunlink();
- _exit(EXIT_FAILURE);
- }
-
- /*****************************************************************/
- /* the rest are auxiliary routines */
-
-
- static int
- addelta()
- /* Function: Appends a delta to the delta tree, whose number is
- * given by newdelnum. Updates Head, newdelnum, newdelnumlength,
- * and the links in newdelta.
- * Return -1 on error, 1 if a lock is removed, 0 otherwise.
- */
- {
- register char *tp;
- register int i;
- int removedlock;
- int newdnumlength; /* actual length of new rev. num. */
-
- newdnumlength = countnumflds(newdelnum.string);
-
- if (rcsinitflag) {
- /* this covers non-existing RCS file and a file initialized with rcs -i */
- if ((newdnumlength==0)&&(Dbranch!=nil)) {
- bufscpy(&newdelnum, Dbranch);
- newdnumlength = countnumflds(Dbranch);
- }
- if (newdnumlength==0) bufscpy(&newdelnum, "1.1");
- else if (newdnumlength==1) bufscat(&newdelnum, ".1");
- else if (newdnumlength>2) {
- error("Branch point doesn't exist for %s.",newdelnum.string);
- return -1;
- } /* newdnumlength == 2 is OK; */
- Head = &newdelta;
- newdelta.next=nil;
- return 0;
- }
- if (newdnumlength==0) {
- /* derive new revision number from locks */
- switch (findlock(true, &targetdelta)) {
-
- default:
- /* found two or more old locks */
- return -1;
-
- case 1:
- /* found an old lock */
- /* check whether locked revision exists */
- if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas))
- return -1;
- if (targetdelta==Head) {
- /* make new head */
- newdelta.next=Head;
- Head= &newdelta;
- } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) {
- /* new tip revision on side branch */
- targetdelta->next= &newdelta;
- newdelta.next = nil;
- } else {
- /* middle revision; start a new branch */
- bufscpy(&newdelnum, "");
- return addbranch(targetdelta,&newdelnum);
- }
- incnum(targetdelta->num, &newdelnum);
- return 1; /* successful use of existing lock */
-
- case 0:
- /* no existing lock; try Dbranch */
- /* update newdelnum */
- if (StrictLocks || !myself(RCSstat.st_uid)) {
- error("no lock set by %s",getcaller());
- return -1;
- }
- if (Dbranch) {
- bufscpy(&newdelnum, Dbranch);
- } else {
- incnum(Head->num, &newdelnum);
- }
- newdnumlength = countnumflds(newdelnum.string);
- /* now fall into next statement */
- }
- }
- if (newdnumlength<=2) {
- /* add new head per given number */
- if(newdnumlength==1) {
- /* make a two-field number out of it*/
- if (cmpnumfld(newdelnum.string,Head->num,1)==0)
- incnum(Head->num, &newdelnum);
- else
- bufscat(&newdelnum, ".1");
- }
- if (cmpnum(newdelnum.string,Head->num) <= 0) {
- error("deltanumber %s too low; must be higher than %s",
- newdelnum.string, Head->num);
- return -1;
- }
- targetdelta = Head;
- if (0 <= (removedlock = removelock(Head))) {
- if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas))
- return -1;
- newdelta.next = Head;
- Head = &newdelta;
- }
- return removedlock;
- } else {
- /* put new revision on side branch */
- /*first, get branch point */
- tp = newdelnum.string;
- for (i = newdnumlength - (newdnumlength&1 ^ 1); (--i); )
- while (*tp++ != '.')
- continue;
- *--tp = 0; /* Kill final dot to get old delta temporarily. */
- if (!(targetdelta=genrevs(newdelnum.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas)))
- return -1;
- if (cmpnum(targetdelta->num, newdelnum.string) != 0) {
- error("can't find branchpoint %s", newdelnum.string);
- return -1;
- }
- *tp = '.'; /* Restore final dot. */
- return addbranch(targetdelta,&newdelnum);
- }
- }
-
-
-
- static int
- addbranch(branchpoint,num)
- struct hshentry *branchpoint;
- struct buf *num;
- /* adds a new branch and branch delta at branchpoint.
- * If num is the null string, appends the new branch, incrementing
- * the highest branch number (initially 1), and setting the level number to 1.
- * the new delta and branchhead are in globals newdelta and newbranch, resp.
- * the new number is placed into num.
- * Return -1 on error, 1 if a lock is removed, 0 otherwise.
- */
- {
- struct branchhead *bhead, **btrail;
- struct buf branchnum;
- int removedlock, result;
- int field, numlength;
- static struct branchhead newbranch; /* new branch to be inserted */
-
- numlength = countnumflds(num->string);
-
- if (branchpoint->branches==nil) {
- /* start first branch */
- branchpoint->branches = &newbranch;
- if (numlength==0) {
- bufscpy(num, branchpoint->num);
- bufscat(num, ".1.1");
- } else if (numlength&1)
- bufscat(num, ".1");
- newbranch.nextbranch=nil;
-
- } else if (numlength==0) {
- /* append new branch to the end */
- bhead=branchpoint->branches;
- while (bhead->nextbranch) bhead=bhead->nextbranch;
- bhead->nextbranch = &newbranch;
- bufautobegin(&branchnum);
- getbranchno(bhead->hsh->num, &branchnum);
- incnum(branchnum.string, num);
- bufautoend(&branchnum);
- bufscat(num, ".1");
- newbranch.nextbranch=nil;
- } else {
- /* place the branch properly */
- field = numlength - (numlength&1 ^ 1);
- /* field of branch number */
- btrail = &branchpoint->branches;
- while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) {
- btrail = &(*btrail)->nextbranch;
- if (!*btrail) {
- result = -1;
- break;
- }
- }
- if (result < 0) {
- /* insert/append new branchhead */
- newbranch.nextbranch = *btrail;
- *btrail = &newbranch;
- if (numlength&1) bufscat(num, ".1");
- } else {
- /* branch exists; append to end */
- bufautobegin(&branchnum);
- getbranchno(num->string, &branchnum);
- targetdelta=genrevs(branchnum.string,(char*)nil,
- (char*)nil,(char*)nil,&gendeltas);
- bufautoend(&branchnum);
- if (!targetdelta)
- return -1;
- if (cmpnum(num->string,targetdelta->num) <= 0) {
- error("deltanumber %s too low; must be higher than %s",
- num->string,targetdelta->num);
- return -1;
- }
- if (0 <= (removedlock = removelock(targetdelta))) {
- if (numlength&1)
- incnum(targetdelta->num,num);
- targetdelta->next = &newdelta;
- newdelta.next = 0;
- }
- return removedlock;
- /* Don't do anything to newbranch. */
- }
- }
- newbranch.hsh = &newdelta;
- newdelta.next=nil;
- if (branchpoint->lockedby)
- if (strcmp(branchpoint->lockedby, getcaller()) == 0)
- return removelock(branchpoint); /* This returns 1. */
- return 0;
- }
-
- static int
- addsyms(num)
- char const *num;
- {
- register struct Symrev *p;
-
- for (p = assoclst; p; p = p->nextsym)
- if (addsymbol(num, p->ssymbol, p->override) < 0)
- return false;
- return true;
- }
-
-
- static void
- incnum(onum,nnum)
- char const *onum;
- struct buf *nnum;
- /* Increment the last field of revision number onum by one and
- * place the result into nnum.
- */
- {
- register char *tp, *np;
- register size_t l;
-
- l = strlen(onum);
- bufalloc(nnum, l+2);
- np = tp = nnum->string;
- VOID strcpy(np, onum);
- for (tp = np + l; np != tp; )
- if (isdigit(*--tp)) {
- if (*tp != '9') {
- ++*tp;
- return;
- }
- *tp = '0';
- } else {
- tp++;
- break;
- }
- /* We changed 999 to 000; now change it to 1000. */
- *tp = '1';
- tp = np + l;
- *tp++ = '0';
- *tp = 0;
- }
-
-
-
- static int
- removelock(delta)
- struct hshentry * delta;
- /* function: Finds the lock held by caller on delta,
- * removes it, and returns nonzero if successful.
- * Print an error message and return -1 if there is no such lock.
- * An exception is if !StrictLocks, and caller is the owner of
- * the RCS file. If caller does not have a lock in this case,
- * return 0; return 1 if a lock is actually removed.
- */
- {
- register struct lock *next, **trail;
- char const *num;
-
- num=delta->num;
- for (trail = &Locks; (next = *trail); trail = &next->nextlock)
- if (next->delta == delta)
- if (strcmp(getcaller(), next->login) == 0) {
- /* We found a lock on delta by caller; delete it. */
- *trail = next->nextlock;
- delta->lockedby = 0;
- return 1;
- } else {
- error("revision %s locked by %s",num,next->login);
- return -1;
- }
- if (!StrictLocks && myself(RCSstat.st_uid))
- return 0;
- error("no lock set by %s for revision %s", getcaller(), num);
- return -1;
- }
-
-
-
- static char const *
- getcurdate()
- /* Return a pointer to the current date. */
- {
- static char buffer[datesize]; /* date buffer */
-
- if (!buffer[0])
- time2date(now(), buffer);
- return buffer;
- }
-
- static int
- #if has_prototypes
- fixwork(mode_t newworkmode, time_t mtime)
- /* The `#if has_prototypes' is needed because mode_t might promote to int. */
- #else
- fixwork(newworkmode, mtime)
- mode_t newworkmode;
- time_t mtime;
- #endif
- {
- return
- 1 < workstat.st_nlink
- || newworkmode&S_IWUSR && !myself(workstat.st_uid)
- || setmtime(workname, mtime) != 0
- ? -1
- : workstat.st_mode == newworkmode ? 0
- :
- # if has_fchmod
- fchmod(Ifileno(workptr), newworkmode)
- # else
- # if bad_chmod_close
- -1
- # else
- chmod(workname, newworkmode)
- # endif
- # endif
- ;
- }
-
- static int
- xpandfile(unexfile, delta, exname, dolog)
- RILE *unexfile;
- struct hshentry const *delta;
- char const **exname;
- int dolog;
- /*
- * Read unexfile and copy it to a
- * file, performing keyword substitution with data from delta.
- * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
- * If successful, stores the stream descriptor into *EXFILEP
- * and its name into *EXNAME.
- */
- {
- char const *targetname;
- int e, r;
-
- targetname = makedirtemp(1);
- if (!(exfile = fopen(targetname, FOPEN_W_WORK))) {
- eerror(targetname);
- error("can't expand working file");
- return -1;
- }
- r = 0;
- if (Expand == OLD_EXPAND)
- fastcopy(unexfile,exfile);
- else {
- for (;;) {
- e = expandline(
- unexfile, exfile, delta, false, (FILE*)0, dolog
- );
- if (e < 0)
- break;
- r |= e;
- if (e <= 1)
- break;
- }
- }
- *exname = targetname;
- aflush(exfile);
- return r & 1;
- }
-
-
-
-
- /* --------------------- G E T L O G M S G --------------------------------*/
-
-
- static struct cbuf
- getlogmsg()
- /* Obtain and yield a log message.
- * If a log message is given with -m, yield that message.
- * If this is the initial revision, yield a standard log message.
- * Otherwise, reads a character string from the terminal.
- * Stops after reading EOF or a single '.' on a
- * line. getlogmsg prompts the first time it is called for the
- * log message; during all later calls it asks whether the previous
- * log message can be reused.
- */
- {
- static char const
- emptych[] = EMPTYLOG,
- initialch[] = "Initial revision";
- static struct cbuf const
- emptylog = { emptych, sizeof(emptych)-sizeof(char) },
- initiallog = { initialch, sizeof(initialch)-sizeof(char) };
- static struct buf logbuf;
- static struct cbuf logmsg;
-
- register char *tp;
- register size_t i;
- char const *caller;
-
- if (msg.size) return msg;
-
- if (keepflag) {
- /* generate std. log message */
- caller = getcaller();
- i = sizeof(ciklog)+strlen(caller)+3;
- bufalloc(&logbuf, i+datesize);
- tp = logbuf.string;
- VOID sprintf(tp, "%s%s at ", ciklog, caller);
- VOID date2str(getcurdate(), tp+i);
- logmsg.string = tp;
- logmsg.size = strlen(tp);
- return logmsg;
- }
-
- if (!targetdelta && (
- cmpnum(newdelnum.string,"1.1")==0 ||
- cmpnum(newdelnum.string,"1.0")==0
- ))
- return initiallog;
-
- if (logmsg.size) {
- /*previous log available*/
- if (yesorno(true, "reuse log message of previous file? [yn](y): "))
- return logmsg;
- }
-
- /* now read string from stdin */
- logmsg = getsstdin("m", "log message", "", &logbuf);
-
- /* now check whether the log message is not empty */
- if (logmsg.size)
- return logmsg;
- return emptylog;
- }
-
- /* Make a linked list of Symbolic names */
-
- static void
- addassoclst(flag, sp)
- int flag;
- char * sp;
- {
- struct Symrev *pt;
-
- pt = talloc(struct Symrev);
- pt->ssymbol = sp;
- pt->override = flag;
- pt->nextsym = nil;
- if (lastassoc)
- lastassoc->nextsym = pt;
- else
- assoclst = pt;
- lastassoc = pt;
- return;
- }
-