Subject: v24i007: RCS source control system, Part07/12 Newsgroups: comp.sources.unix Approved: rsalz@uunet.UU.NET X-Checksum-Snefru: 5267d696 2667712a 5d7f1b27 d40cfeda Submitted-by: Adam Hammer 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=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 ((22 ? (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 (inum); 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'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 num1num2, 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 n1string; 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 : "", X author==nil?"":author, state==nil?"":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?"":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?"":date, X author==nil?"":author, state==nil?"":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?"":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 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...