[comp.sources.unix] v24i007: RCS source control system, Part07/12

rsalz@bbn.com (Rich Salz) (02/23/91)

Submitted-by: Adam Hammer <hammer@cs.purdue.edu>
Posting-number: Volume 24, Issue 7
Archive-name: rcs/part07

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  src/co.c src/rcsfcmp.c src/rcsrev.c
# Wrapped by rsalz@litchi.bbn.com on Thu Feb 21 14:37:02 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 7 (of 12)."'
if test -f 'src/co.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/co.c'\"
else
  echo shar: Extracting \"'src/co.c'\" \(21836 characters\)
  sed "s/^X//" >'src/co.c' <<'END_OF_FILE'
X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
X   Copyright 1990 by Paul Eggert
X   Distributed under license by the Free Software Foundation, Inc.
X
XThis file is part of RCS.
X
XRCS is free software; you can redistribute it and/or modify
Xit under the terms of the GNU General Public License as published by
Xthe Free Software Foundation; either version 1, or (at your option)
Xany later version.
X
XRCS is distributed in the hope that it will be useful,
Xbut WITHOUT ANY WARRANTY; without even the implied warranty of
XMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
XGNU General Public License for more details.
X
XYou should have received a copy of the GNU General Public License
Xalong with RCS; see the file COPYING.  If not, write to
Xthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
X
XReport problems and direct all questions to:
X
X    rcs-bugs@cs.purdue.edu
X
X*/
X
X/*
X *                     RCS checkout operation
X */
X/*****************************************************************************
X *                       check out revisions from RCS files
X *****************************************************************************
X */
X
X
X/* $Log: co.c,v $
X * Revision 5.6  1990/12/04  05:18:38  eggert
X * Don't checkaccesslist() unless necessary.
X * Use -I for prompts and -q for diagnostics.
X *
X * Revision 5.5  1990/11/01  05:03:26  eggert
X * Fix -j.  Add -I.
X *
X * Revision 5.4  1990/10/04  06:30:11  eggert
X * Accumulate exit status across files.
X *
X * Revision 5.3  1990/09/11  02:41:09  eggert
X * co -kv yields a readonly working file.
X *
X * Revision 5.2  1990/09/04  08:02:13  eggert
X * Standardize yes-or-no procedure.
X *
X * Revision 5.0  1990/08/22  08:10:02  eggert
X * Permit multiple locks by same user.  Add setuid support.
X * Remove compile-time limits; use malloc instead.
X * Permit dates past 1999/12/31.  Switch to GMT.
X * Make lock and temp files faster and safer.
X * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
X *
X * Revision 4.7  89/05/01  15:11:41  narten
X * changed copyright header to reflect current distribution rules
X * 
X * Revision 4.6  88/08/09  19:12:15  eggert
X * Fix "co -d" core dump; rawdate wasn't always initialized.
X * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint
X * 
X * Revision 4.5  87/12/18  11:35:40  narten
X * lint cleanups (from Guy Harris)
X * 
X * Revision 4.4  87/10/18  10:20:53  narten
X * Updating version numbers changes relative to 1.1, are actually
X * relative to 4.2
X * 
X * Revision 1.3  87/09/24  13:58:30  narten
X * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
X * warnings)
X * 
X * Revision 1.2  87/03/27  14:21:38  jenkins
X * Port to suns
X * 
X * Revision 4.2  83/12/05  13:39:48  wft
X * made rewriteflag external.
X * 
X * Revision 4.1  83/05/10  16:52:55  wft
X * Added option -u and -f.
X * Added handling of default branch.
X * Replaced getpwuid() with getcaller().
X * Removed calls to stat(); now done by pairfilenames().
X * Changed and renamed rmoldfile() to rmworkfile().
X * Replaced catchints() calls with restoreints(), unlink()--link() with rename();
X * 
X * Revision 3.7  83/02/15  15:27:07  wft
X * Added call to fastcopy() to copy remainder of RCS file.
X *
X * Revision 3.6  83/01/15  14:37:50  wft
X * Added ignoring of interrupts while RCS file is renamed; this avoids
X * deletion of RCS files during the unlink/link window.
X *
X * Revision 3.5  82/12/08  21:40:11  wft
X * changed processing of -d to use DATEFORM; removed actual from
X * call to preparejoin; re-fixed printing of done at the end.
X *
X * Revision 3.4  82/12/04  18:40:00  wft
X * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE.
X * Fixed printing of "done".
X *
X * Revision 3.3  82/11/28  22:23:11  wft
X * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
X * %02d with %.2d, mode generation for working file with WORKMODE.
X * Fixed nil printing. Fixed -j combined with -l and -p, and exit
X * for non-existing revisions in preparejoin().
X *
X * Revision 3.2  82/10/18  20:47:21  wft
X * Mode of working file is now maintained even for co -l, but write permission
X * is removed.
X * The working file inherits its mode from the RCS file, plus write permission
X * for the owner. The write permission is not given if locking is strict and
X * co does not lock.
X * An existing working file without write permission is deleted automatically.
X * Otherwise, co asks (empty answer: abort co).
X * Call to getfullRCSname() added, check for write error added, call
X * for getlogin() fixed.
X *
X * Revision 3.1  82/10/13  16:01:30  wft
X * fixed type of variables receiving from getc() (char -> int).
X * removed unused variables.
X */
X
X
X
X
X#include "rcsbase.h"
X
Xstatic const char *getancestor P((const char*,const char*));
Xstatic int buildjoin P((const char*));
Xstatic int creatempty P((void));
Xstatic int fixworkmode P((const char*));
Xstatic int preparejoin P((void));
Xstatic int rmlock P((const struct hshentry*));
Xstatic int rmworkfile P((void));
Xstatic void cleanup P((void));
X
Xstatic const char quietarg[] = "-q";
X
Xstatic const char *join, *versionarg;
Xstatic const char *joinlist[joinlength];/* revisions to be joined	*/
Xstatic int exitstatus;
Xstatic int forceflag, tostdout;
Xstatic int lastjoin;			/* index of last element in joinlist  */
Xstatic int lockflag; /* -1 -> unlock, 0 -> do nothing, 1 -> lock */
Xstatic struct hshentries *gendeltas;	/* deltas to be generated	*/
Xstatic struct hshentry *targetdelta;	/* final delta to be generated	*/
X
XmainProg(coId, "co", "$Id: co.c,v 5.6 1990/12/04 05:18:38 eggert Exp $")
X{
X	static const char cmdusage[] =
X		"\nco usage: co -{flpqru}[rev] -ddate -jjoinlist -sstate -w[login] -Vn file ...";
X
X	const char *author, *date, *rev, *state;
X	const char *neworkfilename;
X	int changelock;  /* 1 if a lock has been changed, -1 if error */
X	int expmode, r;
X	struct buf numericrev;	/* expanded revision number	*/
X	char finaldate[datesize];
X
X	initid();
X	catchints();
X	author = date = rev = state = nil;
X	bufautobegin(&numericrev);
X	expmode = -1;
X	versionarg = nil;
X
X        while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
X                switch ((*argv)[1]) {
X
X                case 'r':
X                revno:  if ((*argv)[2]!='\0') {
X				if (rev) warn("redefinition of revision number");
X                                rev = (*argv)+2;
X                        }
X                        break;
X
X		case 'f':
X			forceflag=true;
X			goto revno;
X
X                case 'l':
X			if (lockflag < 0) {
X                                warn("-l overrides -u.");
X                        }
X			lockflag = 1;
X                        goto revno;
X
X                case 'u':
X			if (0 < lockflag) {
X                                warn("-l overrides -u.");
X                        }
X			lockflag = -1;
X                        goto revno;
X
X                case 'p':
X                        tostdout=true;
X                        goto revno;
X
X		case 'I':
X			interactiveflag = true;
X			goto revno;
X
X                case 'q':
X                        quietflag=true;
X                        goto revno;
X
X                case 'd':
X			if (date)
X				redefined('d');
X			str2date(*argv+2, finaldate);
X                        date=finaldate;
X                        break;
X
X                case 'j':
X                        if ((*argv)[2]!='\0'){
X				if (join) redefined('j');
X                                join = (*argv)+2;
X                        }
X                        break;
X
X                case 's':
X                        if ((*argv)[2]!='\0'){
X				if (state) redefined('s');
X                                state = (*argv)+2;
X                        }
X                        break;
X
X                case 'w':
X			if (author) redefined('w');
X                        if ((*argv)[2]!='\0')
X                                author = (*argv)+2;
X			else
X				author = getcaller();
X                        break;
X
X		case 'V':
X			if (versionarg) redefined('V');
X			versionarg = *argv;
X			setRCSversion(versionarg);
X			break;
X
X		case 'k':    /*  set keyword expand mode  */
X			if (0 <= expmode) redefined('k');
X			if (0 <= (expmode = str2expmode(*argv+2)))
X			    break;
X			/* fall into */
X                default:
X			faterror("unknown option: %s%s", *argv, cmdusage);
X
X                };
X        } /* end of option processing */
X
X	if (argc<1) faterror("no input file%s", cmdusage);
X
X        /* now handle all filenames */
X        do {
X        finptr=frewrite=NULL;
X	fcopy = foutptr = NULL;
X	ffree();
X
X	if (!pairfilenames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, tostdout))
X		continue;
X
X        /* now RCSfilename contains the name of the RCS file, and finptr
X         * the file descriptor. If tostdout is false, workfilename contains
X         * the name of the working file, otherwise undefined (not nil!).
X	 * Also, RCSstat has been set.
X         */
X	diagnose("%s  -->  %s\n", RCSfilename,tostdout?"stdout":workfilename);
X
X	if (!tostdout) {
X		if (!getworkstat()) continue; /* give up */
X		if (!initeditfiles(workfilename)) {
X			if (errno == EACCES)
X				error("%s: parent directory isn't writable",
X					workfilename
X				);
X			else
X				eerror(resultfile);
X			continue;
X		}
X	}
X	if (0 <= expmode)
X		Expand = expmode;
X	if (0 < lockflag  &&  Expand == VAL_EXPAND) {
X		error("cannot combine -kv and -l");
X		continue;
X	}
X
X        gettree();  /* reads in the delta tree */
X
X        if (Head==nil) {
X                /* no revisions; create empty file */
X		diagnose("no revisions present; generating empty revision 0.0\n");
X                if (!tostdout)
X                        if (!creatempty()) continue;
X                /* Can't reserve a delta, so don't call addlock */
X        } else {
X                if (rev!=nil) {
X                        /* expand symbolic revision number */
X			if (!expandsym(rev, &numericrev))
X                                continue;
X		} else
X			switch (lockflag<0 ? findlock(false,&targetdelta) : 0) {
X			    default:
X				continue;
X			    case 0:
X				bufscpy(&numericrev, Dbranch?Dbranch:"");
X				break;
X			    case 1:
X				bufscpy(&numericrev, targetdelta->num);
X				break;
X			}
X                /* get numbers of deltas to be generated */
X		if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas)))
X                        continue;
X                /* check reservations */
X		changelock = 0;
X		if (lockflag) {
X		    changelock =
X		       lockflag<0 ? rmlock(targetdelta) : addlock(targetdelta);
X		    if (changelock) {
X			if (changelock<0 || !checkaccesslist())
X			    continue;
X		    } else {
X			ffclose(frewrite);  frewrite=NULL;
X			seteid();
X			ignoreints();
X			r = unlink(newRCSfilename);
X			keepdirtemp(newRCSfilename);
X			restoreints();
X			setrid();
X			if (r != 0) {
X			    eerror(RCSfilename);
X			    continue;
X			}
X		    }
X		}
X
X                if (join && !preparejoin()) continue;
X
X		diagnose("revision %s%s\n",targetdelta->num,
X			 0<lockflag ? " (locked)" :
X			 lockflag<0 ? " (unlocked)" : "");
X
X                /* remove old working file if necessary */
X                if (!tostdout)
X                        if (!rmworkfile()) continue;
X
X                /* prepare for rewriting the RCS file */
X		if (changelock) {
X                        putadmin(frewrite);
X                        puttree(Head,frewrite);
X			aprintf(frewrite, "\n\n%s%c",Kdesc,nextc);
X			foutptr = frewrite;
X		}
X
X                /* skip description */
X                getdesc(false); /* don't echo*/
X
X		locker_expansion = 0 < lockflag;
X                if (!(neworkfilename=buildrevision(gendeltas,targetdelta,
X						   tostdout,Expand!=OLD_EXPAND)))
X                                continue;
X
X		if (changelock && !nerror) {
X                        /* rewrite the rest of the RCSfile */
X                        fastcopy(finptr,frewrite);
X			ffclose(finptr); finptr=NULL; /*Help the file system.*/
X                        ffclose(frewrite); frewrite=NULL;
X			seteid();
X			if ((r = chmod(newRCSfilename, RCSstat.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH))) == 0) {
X			    ignoreints();
X			    r = re_name(newRCSfilename,RCSfilename);
X			    keepdirtemp(newRCSfilename);
X			    restoreints();
X			}
X			setrid();
X			if (r != 0) {
X				eerror(RCSfilename);
X				error("saved in %s", newRCSfilename);
X				dirtempunlink();
X                                break;
X                        }
X                }
X
X                if (join) {
X			if (!buildjoin(neworkfilename)) continue;
X                }
X                if (!tostdout) {
X			if (!fixworkmode(neworkfilename))
X				continue;
X			ignoreints();
X			r = re_name(neworkfilename,workfilename);
X			keepdirtemp(neworkfilename);
X			restoreints();
X			if (r != 0) {
X				eerror(workfilename);
X				error("see %s", neworkfilename);
X                                continue;
X                        }
X		}
X        }
X	if (!tostdout) diagnose("done\n");
X        } while (cleanup(),
X                 ++argv, --argc >=1);
X
X	tempunlink();
X	exitmain(exitstatus);
X
X}       /* end of main (co) */
X
X	static void
Xcleanup()
X{
X	if (nerror) exitstatus = EXIT_FAILURE;
X	if (finptr) ffclose(finptr);
X	if (frewrite) ffclose(frewrite);
X	dirtempunlink();
X}
X
X#if lint
X#	define exiterr coExit
X#endif
X	exiting void
Xexiterr()
X{
X	dirtempunlink();
X	tempunlink();
X	_exit(EXIT_FAILURE);
X}
X
X
X/*****************************************************************
X * The following routines are auxiliary routines
X *****************************************************************/
X
X	static int
Xrmworkfile()
X/* Function: prepares to remove workfilename, if it exists, and if
X * it is read-only.
X * Otherwise (file writable):
X *   if !quietmode asks the user whether to really delete it (default: fail);
X *   otherwise failure.
X * Returns 0 on failure to get permission, -1 if there's nothing to remove,
X * 1 if there is a file to remove.
X */
X{
X	if (haveworkstat)	  /* File doesn't exist; set by pairfilenames*/
X	    return -1;
X
X	if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) {
X	    /* File is writable */
X	    if (!yesorno(false, "writable %s exists; remove it? [ny](n): ",
X			workfilename
X	    )) {
X		error(!quietflag && ttystdin()
X			? "checkout aborted"
X			: "writable %s exists; checkout aborted", workfilename);
X		return 0;
X            }
X        }
X	/* Actual unlink is done later by caller. */
X	return 1;
X}
X
X	static int
Xfixworkmode(f)
X	const char *f;
X{
X	if (
X		chmod(f, WORKMODE(RCSstat.st_mode,
X		    !(Expand==VAL_EXPAND  ||  lockflag<=0 && StrictLocks)
X		)) < 0
X	) {
X		eerror(workfilename);
X		return false;
X	}
X	return true;
X}
X
X
X	static int
Xcreatempty()
X/* Function: creates an empty working file.
X * First, removes an existing working file with rmworkfile().
X */
X{
X        int  fdesc;              /* file descriptor */
X	int s;
X
X	if (!(s = rmworkfile()))
X		return false;
X	if (0 < s  &&  unlink(workfilename) != 0) {
X		eerror(workfilename);
X		return false;
X	}
X        fdesc=creat(workfilename,0);
X	if (fdesc < 0)
X		efaterror(workfilename);
X	VOID close(fdesc); /* empty file */
X	return fixworkmode(workfilename);
X}
X
X
X	static int
Xrmlock(delta)
X	const struct hshentry *delta;
X/* Function: removes the lock held by caller on delta.
X * Returns -1 if someone else holds the lock,
X * 0 if there is no lock on delta,
X * and 1 if a lock was found and removed.
X */
X{       register struct lock * next, * trail;
X	const char *num;
X        struct lock dummy;
X        int whomatch, nummatch;
X
X        num=delta->num;
X        dummy.nextlock=next=Locks;
X        trail = &dummy;
X        while (next!=nil) {
X		whomatch = strcmp(getcaller(), next->login);
X                nummatch=strcmp(num,next->delta->num);
X                if ((whomatch==0) && (nummatch==0)) break;
X			/*found a lock on delta by caller*/
X                if ((whomatch!=0)&&(nummatch==0)) {
X                    error("revision %s locked by %s; use co -r or rcs -u",num,next->login);
X                    return -1;
X                }
X                trail=next;
X                next=next->nextlock;
X        }
X        if (next!=nil) {
X                /*found one; delete it */
X                trail->nextlock=next->nextlock;
X                Locks=dummy.nextlock;
X                next->delta->lockedby=nil; /* reset locked-by */
X                return 1; /*success*/
X        } else  return 0; /*no lock on delta*/
X}
X
X
X
X
X/*****************************************************************
X * The rest of the routines are for handling joins
X *****************************************************************/
X
X
X	static const char *
Xaddjoin(joinrev)
X	char *joinrev;
X/* Add joinrev's number to joinlist, yielding address of char past joinrev,
X * or nil if no such revision exists.
X */
X{
X	register char *j;
X	register const struct hshentry *d;
X	char terminator;
X	struct buf numrev;
X	struct hshentries *joindeltas;
X
X	j = joinrev;
X	for (;;) {
X	    switch (*j++) {
X		default:
X		    continue;
X		case 0:
X		case ' ': case '\t': case '\n':
X		case ':': case ',': case ';':
X		    break;
X	    }
X	    break;
X	}
X	terminator = *--j;
X	*j = 0;
X	bufautobegin(&numrev);
X	d = 0;
X	if (expandsym(joinrev, &numrev))
X	    d = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&joindeltas);
X	bufautoend(&numrev);
X	*j = terminator;
X	if (d) {
X		joinlist[++lastjoin] = d->num;
X		return j;
X	}
X	return nil;
X}
X
X	static int
Xpreparejoin()
X/* Function: Parses a join list pointed to by join and places pointers to the
X * revision numbers into joinlist.
X */
X{
X	register const char *j;
X
X        j=join;
X        lastjoin= -1;
X        for (;;) {
X                while ((*j==' ')||(*j=='\t')||(*j==',')) j++;
X                if (*j=='\0') break;
X                if (lastjoin>=joinlength-2) {
X                        error("too many joins");
X                        return(false);
X                }
X		if (!(j = addjoin(j))) return false;
X                while ((*j==' ') || (*j=='\t')) j++;
X                if (*j == ':') {
X                        j++;
X                        while((*j==' ') || (*j=='\t')) j++;
X                        if (*j!='\0') {
X				if (!(j = addjoin(j))) return false;
X                        } else {
X                                error("join pair incomplete");
X                                return false;
X                        }
X                } else {
X                        if (lastjoin==0) { /* first pair */
X                                /* common ancestor missing */
X                                joinlist[1]=joinlist[0];
X                                lastjoin=1;
X                                /*derive common ancestor*/
X				if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1])))
X                                       return false;
X                        } else {
X                                error("join pair incomplete");
X                                return false;
X                        }
X                }
X        }
X        if (lastjoin<1) {
X                error("empty join");
X                return false;
X        } else  return true;
X}
X
X
X
X	static const char *
Xgetancestor(r1, r2)
X	const char *r1, *r2;
X/* Yield the common ancestor of r1 and r2 if successful, nil otherwise.
X * Work reliably only if r1 and r2 are not branch numbers.
X */
X{
X	static struct buf t1, t2;
X
X	unsigned l1, l2, l3;
X	const char *r;
X
X	l1 = countnumflds(r1);
X	l2 = countnumflds(r2);
X	if ((2<l1 || 2<l2)  &&  cmpnum(r1,r2)!=0) {
X	    /* not on main trunk or identical */
X	    l3 = 0;
X	    while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0)
X		l3 += 2;
X	    /* This will terminate since r1 and r2 are not the same; see above. */
X	    if (l3==0) {
X		/* no common prefix; common ancestor on main trunk */
X		VOID partialno(&t1, r1, l1>2 ? (unsigned)2 : l1);
X		VOID partialno(&t2, r2, l2>2 ? (unsigned)2 : l2);
X		r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string;
X		if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0)
X			return r;
X	    } else if (cmpnumfld(r1, r2, l3+1)!=0)
X			return partialno(&t1,r1,l3);
X	}
X	error("common ancestor of %s and %s undefined", r1, r2);
X	return nil;
X}
X
X
X
X	static int
Xbuildjoin(initialfile)
X	const char *initialfile;
X/* Function: merge pairs of elements in joinlist into initialfile
X * If tostdout is set, copy result to stdout.
X * All unlinking of initialfile, rev2, and rev3 should be done by *tempunlink().
X */
X{
X	struct buf commarg;
X	struct buf subs;
X	const char *rev2, *rev3;
X        int i;
X	int status;
X	const char *cov[8], *mergev[12];
X	const char **p;
X
X	bufautobegin(&commarg);
X	bufautobegin(&subs);
X	rev2 = maketemp(0);
X	rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */
X
X	cov[0] = nil;
X	/* cov[1] setup below */
X	cov[2] = CO;
X	/* cov[3] setup below */
X	p = &cov[4];
X	if (versionarg) *p++ = versionarg;
X	*p++ = quietarg;
X	*p++ = RCSfilename;
X	*p = nil;
X
X	mergev[0] = nil;
X	mergev[1] = nil;
X	mergev[2] = MERGE;
X	mergev[3] = mergev[5] = "-L";
X	/* rest of mergev setup below */
X
X        i=0;
X        while (i<lastjoin) {
X                /*prepare marker for merge*/
X                if (i==0)
X			bufscpy(&subs, targetdelta->num);
X		else {
X			bufscat(&subs, ",");
X			bufscat(&subs, joinlist[i-2]);
X			bufscat(&subs, ":");
X			bufscat(&subs, joinlist[i-1]);
X		}
X		diagnose("revision %s\n",joinlist[i]);
X		bufscpy(&commarg, "-p");
X		bufscat(&commarg, joinlist[i]);
X		cov[1] = rev2;
X		cov[3] = commarg.string;
X		if (runv(cov))
X			goto badmerge;
X		diagnose("revision %s\n",joinlist[i+1]);
X		bufscpy(&commarg, "-p");
X		bufscat(&commarg, joinlist[i+1]);
X		cov[1] = rev3;
X		cov[3] = commarg.string;
X		if (runv(cov))
X			goto badmerge;
X		diagnose("merging...\n");
X		mergev[4] = subs.string;
X		mergev[6] = joinlist[i+1];
X		p = &mergev[7];
X		if (quietflag) *p++ = quietarg;
X		if (lastjoin<=i+2 && tostdout) *p++ = "-p";
X		*p++ = initialfile;
X		*p++ = rev2;
X		*p++ = rev3;
X		*p = nil;
X		status = runv(mergev);
X		if (!WIFEXITED(status) || 1<WEXITSTATUS(status))
X			goto badmerge;
X                i=i+2;
X        }
X	bufautoend(&commarg);
X	bufautoend(&subs);
X        return true;
X
X    badmerge:
X	nerror++;
X	bufautoend(&commarg);
X	bufautoend(&subs);
X	return false;
X}
END_OF_FILE
  if test 21836 -ne `wc -c <'src/co.c'`; then
    echo shar: \"'src/co.c'\" unpacked with wrong size!
  fi
  # end of 'src/co.c'
fi
if test -f 'src/rcsfcmp.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/rcsfcmp.c'\"
else
  echo shar: Extracting \"'src/rcsfcmp.c'\" \(7674 characters\)
  sed "s/^X//" >'src/rcsfcmp.c' <<'END_OF_FILE'
X/*
X *                     RCS file comparison
X */
X/*****************************************************************************
X *                       rcsfcmp()
X *                       Testprogram: define FCMPTEST
X *****************************************************************************
X */
X
X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
X   Copyright 1990 by Paul Eggert
X   Distributed under license by the Free Software Foundation, Inc.
X
XThis file is part of RCS.
X
XRCS is free software; you can redistribute it and/or modify
Xit under the terms of the GNU General Public License as published by
Xthe Free Software Foundation; either version 1, or (at your option)
Xany later version.
X
XRCS is distributed in the hope that it will be useful,
Xbut WITHOUT ANY WARRANTY; without even the implied warranty of
XMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
XGNU General Public License for more details.
X
XYou should have received a copy of the GNU General Public License
Xalong with RCS; see the file COPYING.  If not, write to
Xthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
X
XReport problems and direct all questions to:
X
X    rcs-bugs@cs.purdue.edu
X
X*/
X
X
X
X
X
X/* $Log: rcsfcmp.c,v $
X * Revision 5.5  1990/11/27  09:26:05  eggert
X * Fix comment leader bug.
X *
X * Revision 5.4  1990/11/01  05:03:42  eggert
X * Permit arbitrary data in logs and comment leaders.
X *
X * Revision 5.3  1990/09/11  02:41:15  eggert
X * Don't ignore differences inside keyword strings if -ko is set.
X *
X * Revision 5.1  1990/08/29  07:13:58  eggert
X * Clean old log messages too.
X *
X * Revision 5.0  1990/08/22  08:12:49  eggert
X * Don't append "checked in with -k by " log to logs,
X * so that checking in a program with -k doesn't change it.
X * Ansify and Posixate.  Remove lint.
X *
X * Revision 4.5  89/05/01  15:12:42  narten
X * changed copyright header to reflect current distribution rules
X * 
X * Revision 4.4  88/08/09  19:12:50  eggert
X * Shrink stdio code size.
X * 
X * Revision 4.3  87/12/18  11:40:02  narten
X * lint cleanups (Guy Harris)
X * 
X * Revision 4.2  87/10/18  10:33:06  narten
X * updting version number. Changes relative to 1.1 actually relative to 
X * 4.1
X * 
X * Revision 1.2  87/03/27  14:22:19  jenkins
X * Port to suns
X * 
X * Revision 4.1  83/05/10  16:24:04  wft
X * Marker matching now uses trymatch(). Marker pattern is now
X * checked precisely.
X * 
X * Revision 3.1  82/12/04  13:21:40  wft
X * Initial revision.
X *
X */
X
X/*
X#define FCMPTEST
X*/
X/* Testprogram; prints out whether two files are identical,
X * except for keywords
X */
X
X#include  "rcsbase.h"
X
XlibId(fcmpId, "$Id: rcsfcmp.c,v 5.5 1990/11/27 09:26:05 eggert Exp $")
X
X
X	int
Xrcsfcmp(xfname,uxfname,delta)
X	const char *xfname, *uxfname;
X	const struct hshentry *delta;
X/* Function: compares the files xfname and uxfname. Returns true
X * if xfname has the same contents as uxfname, while disregarding
X * keyword values. For the LOG-keyword, rcsfcmp skips the log message
X * given by the parameter delta in xfname. Thus, rcsfcmp returns true
X * if xfname contains the same as uxfname, with the keywords expanded.
X * Implementation: character-by-character comparison until $ is found.
X * If a $ is found, read in the marker keywords; if they are real keywords
X * and identical, read in keyword value. If value is terminated properly,
X * disregard it and optionally skip log message; otherwise, compare value.
X */
X{
X    register int xc,uxc;
X    char xkeyword[keylength+2],   uxkeyword[keylength+2];
X    int eqkeyvals;
X    register FILE * xfp, * uxfp;
X    register int delimiter;
X    register char * tp;
X    register const char *sp;
X    int result;
X    enum markers match1,match2;
X
X    errno = 0;
X    if (!(xfp=fopen(sp=xfname,"r")) || !(errno=0, uxfp=fopen(sp=uxfname,"r"))) {
X       efaterror(sp);
X    }
X    result=false;
X    delimiter = Expand==OLD_EXPAND ? EOF : KDELIM;
X    xc=getc(xfp); uxc=getc(uxfp);
X    while( xc == uxc) { /* comparison loop */
X        if (xc==EOF) { /* finished; everything is the same*/
X            result=true;
X            break;
X        }
X	if (xc != delimiter) {
X            /* get the next characters */
X            xc=getc(xfp); uxc=getc(uxfp);
X        } else {
X            /* try to get both keywords */
X            tp = xkeyword;
X            while( (xc=getc(xfp))!=EOF && (tp< xkeyword+keylength) && (xc!='\n')
X                   && (xc!=KDELIM) && (xc!=VDELIM))
X                *tp++ = xc;
X	    *tp++ = xc;  /* add closing K/VDELIM */
X            *tp='\0';
X            tp = uxkeyword;
X            while( (uxc=getc(uxfp))!=EOF && (tp< uxkeyword+keylength) && (uxc!='\n')
X                   && (uxc!=KDELIM) && (uxc!=VDELIM))
X                *tp++ = uxc;
X	    *tp++ = xc;  /* add closing K/VDELIM */
X            *tp='\0';
X	    /* Now we have 2 keywords, or something that looks like it. */
X	    match1 = trymatch(xkeyword);
X	    match2 = trymatch(uxkeyword);
X	    if (match1 != match2) break; /* not identical */
X#ifdef FCMPTEST
X	    VOID printf("found potential keywords %s and %s\n",xkeyword,uxkeyword);
X#endif
X
X	    if (match1 == Nomatch) {
X		/* not a keyword pattern, but could still be identical */
X		if (strcmp(xkeyword,uxkeyword)==0)
X		     continue;
X		else break;
X	    }
X#ifdef FCMPTEST
X	    VOID printf("found common keyword %s\n",xkeyword);
X#endif
X	    eqkeyvals = 1;
X	    for (;;) {
X		if (xc==uxc) {
X		    if (xc==KDELIM)
X			break;
X		} else {
X		    eqkeyvals = 0;
X		    if (xc==KDELIM) {
X			while (uxc!=KDELIM && uxc!='\n' && uxc!=EOF)
X			    uxc = getc(uxfp);
X			break;
X		    }
X		    if (uxc==KDELIM) {
X			while (xc!=KDELIM && xc!='\n' && xc!=EOF)
X			    xc = getc(xfp);
X			break;
X		    }
X		}
X		if (xc=='\n' || uxc=='\n' || xc==EOF || uxc==EOF)
X		    break;
X		xc = getc(xfp);
X		uxc = getc(uxfp);
X	    }
X	    if (xc!=uxc) break; /* not the same */
X	    if (xc==KDELIM) {
X		xc=getc(xfp); uxc=getc(uxfp); /* skip closing KDELIM */
X		/* if the keyword is LOG, also skip the log message in xfp*/
X		if (match1==Log) {
X		    /* first, compute the number of line feeds in log msg */
X		    unsigned lncnt;
X		    size_t ls, ccnt;
X		    lncnt = 3;
X		    sp = delta->log.string;
X		    ls = delta->log.size;
X		    if (sizeof(ciklog)-1<=ls && !strncmp(sp,ciklog,sizeof(ciklog)-1))
X			continue; /* this log message wasn't inserted */
X		    while (ls--) if (*sp++=='\n') lncnt++;
X		    while(xc!=EOF) {
X			if (xc=='\n')
X			    if(--lncnt==0) break;
X			xc=getc(xfp);
X		    }
X		    /* skip last comment leader */
X		    /* Can't just skip another line here, because there may be */
X		    /* additional characters on the line (after the Log....$)  */
X		    for (ccnt=Comment.size; ccnt--; ) {
X			xc=getc(xfp);
X			if(xc=='\n') break;
X			/* reads to the end of the comment leader or '\n',     */
X			/* whatever comes first. This is because some editors  */
X			/* strip off trailing blanks from a leader like " * ". */
X		    }
X		}
X	    } else {
X		/* both end in the same character, but not a KDELIM */
X		/* must compare string values.*/
X#ifdef FCMPTEST
X		VOID printf("non-terminated keywords %s, potentially different values\n",xkeyword);
X#endif
X		if (!eqkeyvals) break;
X            }
X        }
X    }
X    ffclose(xfp); ffclose(uxfp);
X    return result;
X}
X
X
X
X#ifdef FCMPTEST
X
Xconst char cmdid[] = "rcsfcmp";
X
Xmain(argc, argv)
Xint  argc; char  *argv[];
X/* first argument: comment leader; 2nd: log message, 3rd: expanded file,
X * 4th: unexpanded file
X */
X{       struct hshentry delta;
X
X	Comment.string = argv[1];
X	Comment.size = strlen(argv[1]);
X	delta.log.string = argv[2];
X	delta.log.size = strlen(argv[2]);
X        if (rcsfcmp(argv[3],argv[4],&delta))
X                VOID printf("files are the same\n");
X        else    VOID printf("files are different\n");
X}
X#endif
END_OF_FILE
  if test 7674 -ne `wc -c <'src/rcsfcmp.c'`; then
    echo shar: \"'src/rcsfcmp.c'\" unpacked with wrong size!
  fi
  # end of 'src/rcsfcmp.c'
fi
if test -f 'src/rcsrev.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/rcsrev.c'\"
else
  echo shar: Extracting \"'src/rcsrev.c'\" \(20500 characters\)
  sed "s/^X//" >'src/rcsrev.c' <<'END_OF_FILE'
X/*
X *                     RCS revision number handling
X */
X
X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
X   Copyright 1990 by Paul Eggert
X   Distributed under license by the Free Software Foundation, Inc.
X
XThis file is part of RCS.
X
XRCS is free software; you can redistribute it and/or modify
Xit under the terms of the GNU General Public License as published by
Xthe Free Software Foundation; either version 1, or (at your option)
Xany later version.
X
XRCS is distributed in the hope that it will be useful,
Xbut WITHOUT ANY WARRANTY; without even the implied warranty of
XMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
XGNU General Public License for more details.
X
XYou should have received a copy of the GNU General Public License
Xalong with RCS; see the file COPYING.  If not, write to
Xthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
X
XReport problems and direct all questions to:
X
X    rcs-bugs@cs.purdue.edu
X
X*/
X
X
X
X
X/* $Log: rcsrev.c,v $
X * Revision 5.0  1990/08/22  08:13:43  eggert
X * Remove compile-time limits; use malloc instead.
X * Ansify and Posixate.  Tune.
X * Remove possibility of an internal error.  Remove lint.
X *
X * Revision 4.5  89/05/01  15:13:22  narten
X * changed copyright header to reflect current distribution rules
X * 
X * Revision 4.4  87/12/18  11:45:22  narten
X * more lint cleanups. Also, the NOTREACHED comment is no longer necessary, 
X * since there's now a return value there with a value. (Guy Harris)
X * 
X * Revision 4.3  87/10/18  10:38:42  narten
X * Updating version numbers. Changes relative to version 1.1 actually 
X * relative to 4.1
X * 
X * Revision 1.3  87/09/24  14:00:37  narten
X * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
X * warnings)
X * 
X * Revision 1.2  87/03/27  14:22:37  jenkins
X * Port to suns
X * 
X * Revision 4.1  83/03/25  21:10:45  wft
X * Only changed $Header to $Id.
X * 
X * Revision 3.4  82/12/04  13:24:08  wft
X * Replaced getdelta() with gettree().
X *
X * Revision 3.3  82/11/28  21:33:15  wft
X * fixed compartial() and compnum() for nil-parameters; fixed nils
X * in error messages. Testprogram output shortenend.
X *
X * Revision 3.2  82/10/18  21:19:47  wft
X * renamed compnum->cmpnum, compnumfld->cmpnumfld,
X * numericrevno->numricrevno.
X *
X * Revision 3.1  82/10/11  19:46:09  wft
X * changed expandsym() to check for source==nil; returns zero length string
X * in that case.
X */
X
X
X
X/*
X#define REVTEST
X*/
X/* version REVTEST is for testing the routines that generate a sequence
X * of delta numbers needed to regenerate a given delta.
X */
X
X#include "rcsbase.h"
X
XlibId(revId, "$Id: rcsrev.c,v 5.0 1990/08/22 08:13:43 eggert Exp $")
X
Xstatic struct hshentry *genbranch P((const struct hshentry*,const char*,unsigned,const char*,const char*,const char*,struct hshentries**));
X
X
X
X	unsigned
Xcountnumflds(s)
X	const char *s;
X/* Given a pointer s to a dotted number (date or revision number),
X * countnumflds returns the number of digitfields in s.
X */
X{
X	register const char *sp;
X	register unsigned count;
X        if ((sp=s)==nil) return(0);
X        if (*sp == '\0') return(0);
X        count = 1;
X	do {
X                if (*sp++ == '.') count++;
X	} while (*sp);
X        if (*(--sp) == '.') count--; /*trailing periods don't count*/
X        return(count);
X}
X
X	void
Xgetbranchno(revno,branchno)
X	const char *revno;
X	struct buf *branchno;
X/* Given a non-nil revision number revno, getbranchno copies the number of the branch
X * on which revno is into branchno. If revno itself is a branch number,
X * it is copied unchanged.
X */
X{
X	register unsigned numflds;
X	register char *tp;
X
X	bufscpy(branchno, revno);
X        numflds=countnumflds(revno);
X	if (!(numflds & 1)) {
X		tp = branchno->string;
X		while (--numflds)
X			while (*tp++ != '.')
X				;
X                *(tp-1)='\0';
X        }
X}
X
X
X
Xint cmpnum(num1, num2)
X	const char *num1, *num2;
X/* compares the two dotted numbers num1 and num2 lexicographically
X * by field. Individual fields are compared numerically.
X * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
X * omitted fields are assumed to be higher than the existing ones.
X*/
X{
X	register const char *s1, *s2;
X        register int n1, n2;
X
X        s1=num1==nil?"":num1;
X        s2=num2==nil?"":num2;
X
X        do {
X                n1 = 0;
X		while (isdigit(*s1))
X			n1 = n1*10 + (*s1++ - '0');
X                /* skip '.' */
X                if (*s1=='.') s1++;
X
X                n2 = 0;
X		while (isdigit(*s2))
X			n2 = n2*10 + (*s2++ - '0');
X                /* skip '.' */
X                if (*s2=='.') s2++;
X
X        } while ((n1==n2) && (*s1!='\0') && (*s2!='\0'));
X
X        if (((*s1=='\0') && (*s2=='\0')) || (n1!=n2))
X                return (n1 - n2);
X        /*now n1==n2 and one of s1 or s2 is shorter*/
X        /*give precedence to shorter one*/
X        if (*s1=='\0') return 1;
X        else           return -1;
X
X}
X
X
X
Xint cmpnumfld(num1, num2, fld)
X	const char *num1, *num2;
X	unsigned fld;
X/* compares the two dotted numbers at field fld and returns
X * num1[fld]-num2[fld]. Assumes that num1 and num2 have at least fld fields.
X * fld must be positive.
X*/
X{
X	register const char *s1, *s2;
X	register unsigned n1, n2;
X
X	s1 = num1;
X	s2 = num2;
X        /* skip fld-1 fields */
X	for (n1 = fld;  (--n1);  ) {
X		while (*s1++ != '.')
X			;
X		while (*s2++ != '.')
X			;
X	}
X        /* Now s1 and s2 point to the beginning of the respective fields */
X        /* compute numerical value and compare */
X        n1 = 0;
X	while (isdigit(*s1))
X		n1 = n1*10 + (*s1++ - '0');
X        n2 = 0;
X	while (isdigit(*s2))
X		n2 = n2*10 + (*s2++ - '0');
X	return n1<n2 ? -1 : n1==n2 ? 0 : 1;
X}
X
X
X	int
Xcompartial(num1, num2, length)
X	const char *num1, *num2;
X	unsigned length;
X
X/*   compare the first "length" fields of two dot numbers;
X     the omitted field is considered to be larger than any number  */
X/*   restriction:  at least one number has length or more fields   */
X
X{
X	register const char *s1, *s2;
X        register        int     n1, n2;
X
X
X        s1 = num1;      s2 = num2;
X        if ( s1==nil || *s1 == '\0' ) return 1;
X        if ( s2==nil || *s2 == '\0' ) return -1;
X
X	for (;;) {
X            n1 = 0;
X	    while (isdigit(*s1))
X		n1 = n1*10 + (*s1++ - '0');
X            if ( *s1 == '.' ) s1++;    /*  skip .   */
X
X            n2 = 0;
X	    while (isdigit(*s2))
X		n2 = n2*10 + (*s2++ - '0');
X            if (*s2 == '.') s2++;
X
X	    if ( n1 != n2 ) return n1-n2;
X	    if ( --length == 0 ) return 0;
X	    if ( *s1 == '\0' ) return 1;
X	    if ( *s2 == '\0' ) return -1;
X	}
X}
X
X
Xchar * partialno(rev1,rev2,length)
X	struct buf *rev1;
X	const char *rev2;
X	register unsigned length;
X/* Function: Copies length fields of revision number rev2 into rev1.
X * Return rev1's string.
X */
X{
X	register char *r1;
X
X	bufscpy(rev1, rev2);
X	r1 = rev1->string;
X        while (length) {
X		while (*r1!='.' && *r1)
X			++r1;
X		++r1;
X                length--;
X        }
X        /* eliminate last '.'*/
X        *(r1-1)='\0';
X	return rev1->string;
X}
X
X
X
X
X	static void
Xstore1(store, next)
X	struct hshentries ***store;
X	struct hshentry *next;
X/*
X * Allocate a new list node that addresses NEXT.
X * Append it to the list that **STORE is the end pointer of.
X */
X{
X	register struct hshentries *p;
X
X	p = ftalloc(struct hshentries);
X	p->first = next;
X	**store = p;
X	*store = &p->rest;
X}
X
Xstruct hshentry * genrevs(revno,date,author,state,store)
X	const char *revno, *date, *author, *state;
X	struct hshentries **store;
X/* Function: finds the deltas needed for reconstructing the
X * revision given by revno, date, author, and state, and stores pointers
X * to these deltas into a list whose starting address is given by store.
X * The last delta (target delta) is returned.
X * If the proper delta could not be found, nil is returned.
X */
X{
X	unsigned length;
X        register struct hshentry * next;
X        int result;
X	const char *branchnum;
X	struct buf t;
X
X	bufautobegin(&t);
X
X	if (!(next = Head)) {
X		error("RCS file empty");
X		goto norev;
X        }
X
X        length = countnumflds(revno);
X
X        if (length >= 1) {
X                /* at least one field; find branch exactly */
X		while ((result=cmpnumfld(revno,next->num,1)) < 0) {
X			store1(&store, next);
X                        next = next->next;
X			if (!next) {
X			    error("branch number %s too low", partialno(&t,revno,1));
X			    goto norev;
X			}
X                }
X
X		if (result>0) {
X			error("branch number %s absent", partialno(&t,revno,1));
X			goto norev;
X		}
X        }
X        if (length<=1){
X                /* pick latest one on given branch */
X                branchnum = next->num; /* works even for empty revno*/
X                while ((next!=nil) &&
X                       (cmpnumfld(branchnum,next->num,1)==0) &&
X                       !(
X                        (date==nil?1:(cmpnum(date,next->date)>=0)) &&
X                        (author==nil?1:(strcmp(author,next->author)==0)) &&
X                        (state ==nil?1:(strcmp(state, next->state) ==0))
X                        )
X                       )
X		{
X			store1(&store, next);
X                        next=next->next;
X                }
X                if ((next==nil) ||
X                    (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
X			error("can't find revision on branch %s with a date before %s, author %s, and state %s",
X				length ? revno : partialno(&t,branchnum,1),
X				date ? date : "<now>",
X                                author==nil?"<any>":author, state==nil?"<any>":state);
X			goto norev;
X                } else {
X			store1(&store, next);
X                }
X                *store = nil;
X                return next;
X        }
X
X        /* length >=2 */
X        /* find revision; may go low if length==2*/
X	while ((result=cmpnumfld(revno,next->num,2)) < 0  &&
X               (cmpnumfld(revno,next->num,1)==0) ) {
X		store1(&store, next);
X                next = next->next;
X		if (!next)
X			break;
X        }
X
X        if ((next==nil) || (cmpnumfld(revno,next->num,1)!=0)) {
X		error("revision number %s too low", partialno(&t,revno,2));
X		goto norev;
X        }
X        if ((length>2) && (result!=0)) {
X		error("revision %s absent", partialno(&t,revno,2));
X		goto norev;
X        }
X
X        /* print last one */
X	store1(&store, next);
X
X        if (length>2)
X                return genbranch(next,revno,length,date,author,state,store);
X        else { /* length == 2*/
X                if ((date!=nil) && (cmpnum(date,next->date)<0)){
X                        error("Revision %s has date %s.",next->num, next->date);
X                        return nil;
X                }
X                if ((author!=nil)&&(strcmp(author,next->author)!=0)) {
X                        error("Revision %s has author %s.",next->num,next->author);
X                        return nil;
X                }
X                if ((state!=nil)&&(strcmp(state,next->state)!=0)) {
X                        error("Revision %s has state %s.",next->num,
X                               next->state==nil?"<empty>":next->state);
X                        return nil;
X                }
X                *store=nil;
X                return next;
X        }
X    norev:
X	bufautoend(&t);
X	return nil;
X}
X
X
X
X
X	static struct hshentry *
Xgenbranch(bpoint, revno, length, date, author, state, store)
X	const struct hshentry *bpoint;
X	const char *revno;
X	unsigned length;
X	const char *date, *author, *state;
X	struct hshentries **store;
X/* Function: given a branchpoint, a revision number, date, author, and state,
X * genbranch finds the deltas necessary to reconstruct the given revision
X * from the branch point on.
X * Pointers to the found deltas are stored in a list beginning with store.
X * revno must be on a side branch.
X * return nil on error
X */
X{
X	unsigned field;
X        register struct hshentry * next, * trail;
X	register const struct branchhead *bhead;
X        int result;
X	struct buf t;
X
X	field = 3;
X        bhead = bpoint->branches;
X
X	do {
X		if (!bhead) {
X			bufautobegin(&t);
X			error("no side branches present for %s", partialno(&t,revno,field-1));
X			bufautoend(&t);
X			return nil;
X		}
X
X                /*find branch head*/
X                /*branches are arranged in increasing order*/
X		while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
X                        bhead = bhead->nextbranch;
X			if (!bhead) {
X			    bufautobegin(&t);
X			    error("branch number %s too high",partialno(&t,revno,field));
X			    bufautoend(&t);
X			    return nil;
X			}
X                }
X
X		if (result<0) {
X		    bufautobegin(&t);
X		    error("branch number %s absent", partialno(&t,revno,field));
X		    bufautoend(&t);
X		    return nil;
X		}
X
X                next = bhead->hsh;
X                if (length==field) {
X                        /* pick latest one on that branch */
X                        trail=nil;
X                        do { if ((date==nil?1:(cmpnum(date,next->date)>=0)) &&
X                                 (author==nil?1:(strcmp(author,next->author)==0)) &&
X                                 (state ==nil?1:(strcmp(state, next->state) ==0))
X                             ) trail = next;
X                             next=next->next;
X                        } while (next!=nil);
X
X                        if (trail==nil) {
X			     error("can't find revision on branch %s with a date before %s, author %s, and state %s",
X                                        revno, date==nil?"<now>":date,
X                                        author==nil?"<any>":author, state==nil?"<any>":state);
X                             return nil;
X                        } else { /* print up to last one suitable */
X                             next = bhead->hsh;
X                             while (next!=trail) {
X				  store1(&store, next);
X                                  next=next->next;
X                             }
X			     store1(&store, next);
X                        }
X			*store = nil;
X                        return next;
X                }
X
X                /* length > field */
X                /* find revision */
X                /* check low */
X                if (cmpnumfld(revno,next->num,field+1)<0) {
X			bufautobegin(&t);
X			error("revision number %s too low", partialno(&t,revno,field+1));
X			bufautoend(&t);
X                        return(nil);
X                }
X		do {
X			store1(&store, next);
X                        trail = next;
X                        next = next->next;
X                } while ((next!=nil) &&
X                       (cmpnumfld(revno,next->num,field+1) >=0));
X
X                if ((length>field+1) &&  /*need exact hit */
X                    (cmpnumfld(revno,trail->num,field+1) !=0)){
X			bufautobegin(&t);
X			error("revision %s absent", partialno(&t,revno,field+1));
X			bufautoend(&t);
X                        return(nil);
X                }
X                if (length == field+1) {
X                        if ((date!=nil) && (cmpnum(date,trail->date)<0)){
X                                error("Revision %s has date %s.",trail->num, trail->date);
X                                return nil;
X                        }
X                        if ((author!=nil)&&(strcmp(author,trail->author)!=0)) {
X                                error("Revision %s has author %s.",trail->num,trail->author);
X                                return nil;
X                        }
X                        if ((state!=nil)&&(strcmp(state,trail->state)!=0)) {
X                                error("Revision %s has state %s.",trail->num,
X                                       trail->state==nil?"<empty>":trail->state);
X                                return nil;
X                        }
X                }
X                bhead = trail->branches;
X
X	} while ((field+=2) <= length);
X        * store = nil;
X        return trail;
X}
X
X
X	static const char *
Xlookupsym(id)
X	const char *id;
X/* Function: looks up id in the list of symbolic names starting
X * with pointer SYMBOLS, and returns a pointer to the corresponding
X * revision number. Returns nil if not present.
X */
X{
X	register const struct assoc *next;
X        next = Symbols;
X        while (next!=nil) {
X                if (strcmp(id, next->symbol)==0)
X			return next->num;
X                else    next=next->nextassoc;
X        }
X        return nil;
X}
X
Xint expandsym(source, target)
X	const char *source;
X	struct buf *target;
X/* Function: Source points to a revision number. Expandsym copies
X * the number to target, but replaces all symbolic fields in the
X * source number with their numeric values.
X * A trailing '.' is omitted; leading zeroes are compressed.
X * returns false on error;
X */
X{
X	register const char *sp;
X	register char *tp;
X	const char *tlim;
X        register enum tokens d;
X
X	bufalloc(target, 1);
X	tp = target->string;
X	sp = source;
X        if (sp == nil) { /*accept nil pointer as a legal value*/
X                *tp='\0';
X                return true;
X        }
X	tlim = tp + target->size;
X
X        while (*sp != '\0') {
X		switch (ctab[(unsigned char)*sp]) {
X		    case DIGIT:
X                        if (*sp=='0') {
X                                /* skip leading zeroes */
X                                sp++;
X                                while(*sp == '0') sp++;
X				if (!*sp || *sp=='.') --sp; /* single zero */
X                        }
X			while (isdigit(*sp)) {
X				if (tlim <= tp)
X					tp = bufenlarge(target, &tlim);
X				*tp++ = *sp++;
X			}
X			if (tlim <= tp)
X				tp = bufenlarge(target, &tlim);
X                        if ((*sp == '\0') || ((*sp=='.')&&(*(sp+1)=='\0'))) {
X                                *tp='\0'; return true;
X                        }
X			if (*sp != '.')
X				goto improper;
X			*tp++ = *sp++;
X			break;
X
X		    case LETTER:
X		    case Letter:
X			{
X			register char *bp = tp;
X			register size_t s = tp - target->string;
X			do {
X				if (tlim <= bp)
X					bp = bufenlarge(target, &tlim);
X				*bp++ = *sp++;
X			} while ((d=ctab[(unsigned char)*sp])==LETTER ||
X			      d==Letter || d==DIGIT ||
X                              (d==IDCHAR));
X			if (tlim <= bp)
X				bp = bufenlarge(target, &tlim);
X                        *bp= '\0';
X			tp = target->string + s;
X			}
X			{
X			register const char *bp = lookupsym(tp);
X                        if (bp==nil) {
X				error("Symbolic number %s is undefined.", tp);
X                                return false;
X                        } else { /* copy number */
X				do {
X					if (tlim <= tp)
X						tp = bufenlarge(target, &tlim);
X				} while ((*tp++ = *bp++));
X                        }
X			}
X                        if ((*sp == '\0') || ((*sp=='.')&&(*(sp+1)=='\0')))
X                                return true;
X			if (*sp++ != '.')
X				goto improper;
X			tp[-1] = '.';
X			break;
X
X		    default:
X		    improper:
X			error("improper revision number: %s", source);
X                        return false;
X                }
X        }
X	if (tlim<=tp)
X		tp = bufenlarge(target,&tlim);
X        *tp = '\0';
X        return true;
X}
X
X
X
X#ifdef REVTEST
X
Xconst char cmdid[] = "revtest";
X
X	int
Xmain(argc,argv)
Xint argc; char * argv[];
X{
X	static struct buf numricrevno;
X	char symrevno[100];       /* used for input of revision numbers */
X        char author[20];
X        char state[20];
X        char date[20];
X	struct hshentries *gendeltas;
X        struct hshentry * target;
X        int i;
X
X        if (argc<2) {
X		aputs("No input file\n",stderr);
X		exitmain(EXIT_FAILURE);
X        }
X        if ((finptr=fopen(argv[1], "r")) == NULL) {
X		faterror("can't open input file %s", argv[1]);
X        }
X        Lexinit();
X        getadmin();
X
X        gettree();
X
X        getdesc(false);
X
X        do {
X                /* all output goes to stderr, to have diagnostics and       */
X                /* errors in sequence.                                      */
X		aputs("\nEnter revision number or <return> or '.': ",stderr);
X                if(gets(symrevno)==NULL) break;
X                if (*symrevno == '.') break;
X		aprintf(stderr,"%s;\n",symrevno);
X		expandsym(symrevno,&numricrevno);
X		aprintf(stderr,"expanded number: %s; ",numricrevno.string);
X		aprintf(stderr,"Date: ");
X		gets(date); aprintf(stderr,"%s; ",date);
X		aprintf(stderr,"Author: ");
X		gets(author); aprintf(stderr,"%s; ",author);
X		aprintf(stderr,"State: ");
X		gets(state); aprintf(stderr, "%s;\n", state);
X		target = genrevs(numricrevno.string, *date?date:(char *)nil, *author?author:(char *)nil,
X				 *state?state:(char*)nil, &gendeltas);
X                if (target!=nil) {
X			while (gendeltas) {
X				aprintf(stderr,"%s\n",gendeltas->first->num);
X				gendeltas = gendeltas->next;
X                        }
X                }
X        } while (true);
X	aprintf(stderr,"done\n");
X	exitmain(EXIT_SUCCESS);
X}
X
Xexiting void exiterr() { _exit(EXIT_FAILURE); }
X
X#endif
END_OF_FILE
  if test 20500 -ne `wc -c <'src/rcsrev.c'`; then
    echo shar: \"'src/rcsrev.c'\" unpacked with wrong size!
  fi
  # end of 'src/rcsrev.c'
fi
echo shar: End of archive 7 \(of 12\).
cp /dev/null ark7isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 12 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0
exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.