111894Speter/* Handle RCS revision numbers. */ 29Sjkh 311894Speter/* Copyright 1982, 1988, 1989 Walter Tichy 411894Speter Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert 59Sjkh Distributed under license by the Free Software Foundation, Inc. 69Sjkh 79SjkhThis file is part of RCS. 89Sjkh 99SjkhRCS is free software; you can redistribute it and/or modify 109Sjkhit under the terms of the GNU General Public License as published by 119Sjkhthe Free Software Foundation; either version 2, or (at your option) 129Sjkhany later version. 139Sjkh 149SjkhRCS is distributed in the hope that it will be useful, 159Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of 169SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 179SjkhGNU General Public License for more details. 189Sjkh 199SjkhYou should have received a copy of the GNU General Public License 2011894Speteralong with RCS; see the file COPYING. 2111894SpeterIf not, write to the Free Software Foundation, 2211894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 239Sjkh 249SjkhReport problems and direct all questions to: 259Sjkh 269Sjkh rcs-bugs@cs.purdue.edu 279Sjkh 289Sjkh*/ 299Sjkh 3011894Speter/* 3111894Speter * Revision 5.10 1995/06/16 06:19:24 eggert 3211894Speter * Update FSF address. 338858Srgrimes * 3411894Speter * Revision 5.9 1995/06/01 16:23:43 eggert 3511894Speter * (cmpdate, normalizeyear): New functions work around MKS RCS incompatibility. 3611894Speter * (cmpnum, compartial): s[d] -> *(s+d) to work around Cray compiler bug. 3711894Speter * (genrevs, genbranch): cmpnum -> cmpdate 3811894Speter * 3911894Speter * Revision 5.8 1994/03/17 14:05:48 eggert 4011894Speter * Remove lint. 4111894Speter * 4211894Speter * Revision 5.7 1993/11/09 17:40:15 eggert 4311894Speter * Fix format string typos. 4411894Speter * 4511894Speter * Revision 5.6 1993/11/03 17:42:27 eggert 4611894Speter * Revision number `.N' now stands for `D.N', where D is the default branch. 4711894Speter * Add -z. Improve quality of diagnostics. Add `namedrev' for Name support. 4811894Speter * 4911894Speter * Revision 5.5 1992/07/28 16:12:44 eggert 5011894Speter * Identifiers may now start with a digit. Avoid `unsigned'. 5111894Speter * 5211894Speter * Revision 5.4 1992/01/06 02:42:34 eggert 5311894Speter * while (E) ; -> while (E) continue; 5411894Speter * 559Sjkh * Revision 5.3 1991/08/19 03:13:55 eggert 569Sjkh * Add `-r$', `-rB.'. Remove botches like `<now>' from messages. Tune. 579Sjkh * 589Sjkh * Revision 5.2 1991/04/21 11:58:28 eggert 599Sjkh * Add tiprev(). 609Sjkh * 619Sjkh * Revision 5.1 1991/02/25 07:12:43 eggert 629Sjkh * Avoid overflow when comparing revision numbers. 639Sjkh * 649Sjkh * Revision 5.0 1990/08/22 08:13:43 eggert 659Sjkh * Remove compile-time limits; use malloc instead. 669Sjkh * Ansify and Posixate. Tune. 679Sjkh * Remove possibility of an internal error. Remove lint. 689Sjkh * 699Sjkh * Revision 4.5 89/05/01 15:13:22 narten 709Sjkh * changed copyright header to reflect current distribution rules 718858Srgrimes * 729Sjkh * Revision 4.4 87/12/18 11:45:22 narten 738858Srgrimes * more lint cleanups. Also, the NOTREACHED comment is no longer necessary, 749Sjkh * since there's now a return value there with a value. (Guy Harris) 758858Srgrimes * 769Sjkh * Revision 4.3 87/10/18 10:38:42 narten 778858Srgrimes * Updating version numbers. Changes relative to version 1.1 actually 789Sjkh * relative to 4.1 798858Srgrimes * 809Sjkh * Revision 1.3 87/09/24 14:00:37 narten 818858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf 829Sjkh * warnings) 838858Srgrimes * 849Sjkh * Revision 1.2 87/03/27 14:22:37 jenkins 859Sjkh * Port to suns 868858Srgrimes * 879Sjkh * Revision 4.1 83/03/25 21:10:45 wft 889Sjkh * Only changed $Header to $Id. 898858Srgrimes * 909Sjkh * Revision 3.4 82/12/04 13:24:08 wft 919Sjkh * Replaced getdelta() with gettree(). 929Sjkh * 939Sjkh * Revision 3.3 82/11/28 21:33:15 wft 949Sjkh * fixed compartial() and compnum() for nil-parameters; fixed nils 959Sjkh * in error messages. Testprogram output shortenend. 969Sjkh * 979Sjkh * Revision 3.2 82/10/18 21:19:47 wft 989Sjkh * renamed compnum->cmpnum, compnumfld->cmpnumfld, 999Sjkh * numericrevno->numricrevno. 1009Sjkh * 1019Sjkh * Revision 3.1 82/10/11 19:46:09 wft 1029Sjkh * changed expandsym() to check for source==nil; returns zero length string 1039Sjkh * in that case. 1049Sjkh */ 1059Sjkh 1069Sjkh#include "rcsbase.h" 1079Sjkh 10850472SpeterlibId(revId, "$FreeBSD$") 1099Sjkh 1109Sjkhstatic char const *branchtip P((char const*)); 11111894Speterstatic char const *lookupsym P((char const*)); 11211894Speterstatic char const *normalizeyear P((char const*,char[5])); 11311894Speterstatic struct hshentry *genbranch P((struct hshentry const*,char const*,int,char const*,char const*,char const*,struct hshentries**)); 11411894Speterstatic void absent P((char const*,int)); 11511894Speterstatic void cantfindbranch P((char const*,char const[datesize],char const*,char const*)); 11611894Speterstatic void store1 P((struct hshentries***,struct hshentry*)); 1179Sjkh 1189Sjkh 1199Sjkh 12011894Speter int 1219Sjkhcountnumflds(s) 1229Sjkh char const *s; 1239Sjkh/* Given a pointer s to a dotted number (date or revision number), 1249Sjkh * countnumflds returns the number of digitfields in s. 1259Sjkh */ 1269Sjkh{ 1279Sjkh register char const *sp; 12811894Speter register int count; 12911894Speter if (!(sp=s) || !*sp) 13011894Speter return 0; 1319Sjkh count = 1; 1329Sjkh do { 1339Sjkh if (*sp++ == '.') count++; 1349Sjkh } while (*sp); 1359Sjkh return(count); 1369Sjkh} 1379Sjkh 1389Sjkh void 1399Sjkhgetbranchno(revno,branchno) 1409Sjkh char const *revno; 1419Sjkh struct buf *branchno; 14211894Speter/* Given a revision number revno, getbranchno copies the number of the branch 1439Sjkh * on which revno is into branchno. If revno itself is a branch number, 1449Sjkh * it is copied unchanged. 1459Sjkh */ 1469Sjkh{ 14711894Speter register int numflds; 1489Sjkh register char *tp; 1499Sjkh 1509Sjkh bufscpy(branchno, revno); 1519Sjkh numflds=countnumflds(revno); 1529Sjkh if (!(numflds & 1)) { 1539Sjkh tp = branchno->string; 1549Sjkh while (--numflds) 1559Sjkh while (*tp++ != '.') 15611894Speter continue; 1579Sjkh *(tp-1)='\0'; 1589Sjkh } 1599Sjkh} 1609Sjkh 1619Sjkh 1629Sjkh 1639Sjkhint cmpnum(num1, num2) 1649Sjkh char const *num1, *num2; 1659Sjkh/* compares the two dotted numbers num1 and num2 lexicographically 1669Sjkh * by field. Individual fields are compared numerically. 1679Sjkh * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp. 1689Sjkh * omitted fields are assumed to be higher than the existing ones. 1699Sjkh*/ 1709Sjkh{ 1719Sjkh register char const *s1, *s2; 1729Sjkh register size_t d1, d2; 1739Sjkh register int r; 1749Sjkh 17511894Speter s1 = num1 ? num1 : ""; 17611894Speter s2 = num2 ? num2 : ""; 1779Sjkh 1789Sjkh for (;;) { 1799Sjkh /* Give precedence to shorter one. */ 1809Sjkh if (!*s1) 1819Sjkh return (unsigned char)*s2; 1829Sjkh if (!*s2) 1839Sjkh return -1; 1849Sjkh 1859Sjkh /* Strip leading zeros, then find number of digits. */ 18611894Speter while (*s1=='0') ++s1; 18711894Speter while (*s2=='0') ++s2; 18811894Speter for (d1=0; isdigit(*(s1+d1)); d1++) continue; 18911894Speter for (d2=0; isdigit(*(s2+d2)); d2++) continue; 1909Sjkh 1919Sjkh /* Do not convert to integer; it might overflow! */ 1929Sjkh if (d1 != d2) 1939Sjkh return d1<d2 ? -1 : 1; 1949Sjkh if ((r = memcmp(s1, s2, d1))) 1959Sjkh return r; 1969Sjkh s1 += d1; 1979Sjkh s2 += d1; 1989Sjkh 1999Sjkh /* skip '.' */ 2009Sjkh if (*s1) s1++; 2019Sjkh if (*s2) s2++; 2029Sjkh } 2039Sjkh} 2049Sjkh 2059Sjkh 2069Sjkh 2079Sjkhint cmpnumfld(num1, num2, fld) 2089Sjkh char const *num1, *num2; 20911894Speter int fld; 2109Sjkh/* Compare the two dotted numbers at field fld. 2119Sjkh * num1 and num2 must have at least fld fields. 2129Sjkh * fld must be positive. 2139Sjkh*/ 2149Sjkh{ 2159Sjkh register char const *s1, *s2; 2169Sjkh register size_t d1, d2; 2179Sjkh 2189Sjkh s1 = num1; 2199Sjkh s2 = num2; 2209Sjkh /* skip fld-1 fields */ 2219Sjkh while (--fld) { 2229Sjkh while (*s1++ != '.') 22311894Speter continue; 2249Sjkh while (*s2++ != '.') 22511894Speter continue; 2269Sjkh } 2279Sjkh /* Now s1 and s2 point to the beginning of the respective fields */ 22811894Speter while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue; 22911894Speter while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue; 2309Sjkh 2319Sjkh return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1; 2329Sjkh} 2339Sjkh 2349Sjkh 23511894Speter int 23611894Spetercmpdate(d1, d2) 23711894Speter char const *d1, *d2; 23811894Speter/* 23911894Speter* Compare the two dates. This is just like cmpnum, 24011894Speter* except that for compatibility with old versions of RCS, 24111894Speter* 1900 is added to dates with two-digit years. 24211894Speter*/ 24311894Speter{ 24411894Speter char year1[5], year2[5]; 24511894Speter int r = cmpnumfld(normalizeyear(d1,year1), normalizeyear(d2,year2), 1); 24611894Speter 24711894Speter if (r) 24811894Speter return r; 24911894Speter else { 25011894Speter while (isdigit(*d1)) d1++; d1 += *d1=='.'; 25111894Speter while (isdigit(*d2)) d2++; d2 += *d2=='.'; 25211894Speter return cmpnum(d1, d2); 25311894Speter } 25411894Speter} 25511894Speter 25611894Speter static char const * 25711894Speternormalizeyear(date, year) 25811894Speter char const *date; 25911894Speter char year[5]; 26011894Speter{ 26111894Speter if (isdigit(date[0]) && isdigit(date[1]) && !isdigit(date[2])) { 26211894Speter year[0] = '1'; 26311894Speter year[1] = '9'; 26411894Speter year[2] = date[0]; 26511894Speter year[3] = date[1]; 26611894Speter year[4] = 0; 26711894Speter return year; 26811894Speter } else 26911894Speter return date; 27011894Speter} 27111894Speter 27211894Speter 2739Sjkh static void 2749Sjkhcantfindbranch(revno, date, author, state) 2759Sjkh char const *revno, date[datesize], *author, *state; 2769Sjkh{ 27711894Speter char datebuf[datesize + zonelenmax]; 2789Sjkh 27911894Speter rcserror("No revision on branch %s has%s%s%s%s%s%s.", 2809Sjkh revno, 2819Sjkh date ? " a date before " : "", 2829Sjkh date ? date2str(date,datebuf) : "", 2839Sjkh author ? " and author "+(date?0:4) : "", 2849Sjkh author ? author : "", 2859Sjkh state ? " and state "+(date||author?0:4) : "", 2869Sjkh state ? state : "" 2879Sjkh ); 2889Sjkh} 2899Sjkh 2909Sjkh static void 2919Sjkhabsent(revno, field) 2929Sjkh char const *revno; 29311894Speter int field; 2949Sjkh{ 2959Sjkh struct buf t; 2969Sjkh bufautobegin(&t); 29711894Speter rcserror("%s %s absent", field&1?"revision":"branch", 2989Sjkh partialno(&t,revno,field) 2999Sjkh ); 3009Sjkh bufautoend(&t); 3019Sjkh} 3029Sjkh 3039Sjkh 3049Sjkh int 3059Sjkhcompartial(num1, num2, length) 3069Sjkh char const *num1, *num2; 30711894Speter int length; 3089Sjkh 3099Sjkh/* compare the first "length" fields of two dot numbers; 3109Sjkh the omitted field is considered to be larger than any number */ 3119Sjkh/* restriction: at least one number has length or more fields */ 3129Sjkh 3139Sjkh{ 3149Sjkh register char const *s1, *s2; 3159Sjkh register size_t d1, d2; 3169Sjkh register int r; 3179Sjkh 3189Sjkh s1 = num1; s2 = num2; 3199Sjkh if (!s1) return 1; 3209Sjkh if (!s2) return -1; 3219Sjkh 3229Sjkh for (;;) { 3239Sjkh if (!*s1) return 1; 3249Sjkh if (!*s2) return -1; 3259Sjkh 32611894Speter while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue; 32711894Speter while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue; 3289Sjkh 3299Sjkh if (d1 != d2) 3309Sjkh return d1<d2 ? -1 : 1; 3319Sjkh if ((r = memcmp(s1, s2, d1))) 3329Sjkh return r; 33311894Speter if (!--length) 33411894Speter return 0; 33511894Speter 3369Sjkh s1 += d1; 3379Sjkh s2 += d1; 3389Sjkh 3399Sjkh if (*s1 == '.') s1++; 3409Sjkh if (*s2 == '.') s2++; 3419Sjkh } 3429Sjkh} 3439Sjkh 3449Sjkh 3459Sjkhchar * partialno(rev1,rev2,length) 3469Sjkh struct buf *rev1; 3479Sjkh char const *rev2; 34811894Speter register int length; 3499Sjkh/* Function: Copies length fields of revision number rev2 into rev1. 3509Sjkh * Return rev1's string. 3519Sjkh */ 3529Sjkh{ 3539Sjkh register char *r1; 3549Sjkh 3559Sjkh bufscpy(rev1, rev2); 3569Sjkh r1 = rev1->string; 3579Sjkh while (length) { 3589Sjkh while (*r1!='.' && *r1) 3599Sjkh ++r1; 3609Sjkh ++r1; 3619Sjkh length--; 3629Sjkh } 3639Sjkh /* eliminate last '.'*/ 3649Sjkh *(r1-1)='\0'; 3659Sjkh return rev1->string; 3669Sjkh} 3679Sjkh 3689Sjkh 3699Sjkh 3709Sjkh 3719Sjkh static void 3729Sjkhstore1(store, next) 3739Sjkh struct hshentries ***store; 3749Sjkh struct hshentry *next; 3759Sjkh/* 3769Sjkh * Allocate a new list node that addresses NEXT. 3779Sjkh * Append it to the list that **STORE is the end pointer of. 3789Sjkh */ 3799Sjkh{ 3809Sjkh register struct hshentries *p; 3819Sjkh 3829Sjkh p = ftalloc(struct hshentries); 3839Sjkh p->first = next; 3849Sjkh **store = p; 3859Sjkh *store = &p->rest; 3869Sjkh} 3879Sjkh 3889Sjkhstruct hshentry * genrevs(revno,date,author,state,store) 3899Sjkh char const *revno, *date, *author, *state; 3909Sjkh struct hshentries **store; 3919Sjkh/* Function: finds the deltas needed for reconstructing the 3929Sjkh * revision given by revno, date, author, and state, and stores pointers 3939Sjkh * to these deltas into a list whose starting address is given by store. 3949Sjkh * The last delta (target delta) is returned. 39511894Speter * If the proper delta could not be found, 0 is returned. 3969Sjkh */ 3979Sjkh{ 39811894Speter int length; 3999Sjkh register struct hshentry * next; 4009Sjkh int result; 4019Sjkh char const *branchnum; 4029Sjkh struct buf t; 40311894Speter char datebuf[datesize + zonelenmax]; 4049Sjkh 4059Sjkh bufautobegin(&t); 4069Sjkh 4079Sjkh if (!(next = Head)) { 40811894Speter rcserror("RCS file empty"); 4099Sjkh goto norev; 4109Sjkh } 4119Sjkh 4129Sjkh length = countnumflds(revno); 4139Sjkh 4149Sjkh if (length >= 1) { 4159Sjkh /* at least one field; find branch exactly */ 4169Sjkh while ((result=cmpnumfld(revno,next->num,1)) < 0) { 4179Sjkh store1(&store, next); 4189Sjkh next = next->next; 4199Sjkh if (!next) { 42011894Speter rcserror("branch number %s too low", partialno(&t,revno,1)); 4219Sjkh goto norev; 4229Sjkh } 4239Sjkh } 4249Sjkh 4259Sjkh if (result>0) { 4269Sjkh absent(revno, 1); 4279Sjkh goto norev; 4289Sjkh } 4299Sjkh } 4309Sjkh if (length<=1){ 4319Sjkh /* pick latest one on given branch */ 4329Sjkh branchnum = next->num; /* works even for empty revno*/ 43311894Speter while (next && 43411894Speter cmpnumfld(branchnum,next->num,1) == 0 && 43511894Speter ( 43611894Speter (date && cmpdate(date,next->date) < 0) || 43711894Speter (author && strcmp(author,next->author) != 0) || 43811894Speter (state && strcmp(state,next->state) != 0) 43911894Speter ) 44011894Speter ) 4419Sjkh { 4429Sjkh store1(&store, next); 4439Sjkh next=next->next; 4449Sjkh } 44511894Speter if (!next || 4469Sjkh (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ { 4479Sjkh cantfindbranch( 4489Sjkh length ? revno : partialno(&t,branchnum,1), 4499Sjkh date, author, state 4509Sjkh ); 4519Sjkh goto norev; 4529Sjkh } else { 4539Sjkh store1(&store, next); 4549Sjkh } 45511894Speter *store = 0; 4569Sjkh return next; 4579Sjkh } 4589Sjkh 4599Sjkh /* length >=2 */ 4609Sjkh /* find revision; may go low if length==2*/ 4619Sjkh while ((result=cmpnumfld(revno,next->num,2)) < 0 && 4629Sjkh (cmpnumfld(revno,next->num,1)==0) ) { 4639Sjkh store1(&store, next); 4649Sjkh next = next->next; 4659Sjkh if (!next) 4669Sjkh break; 4679Sjkh } 4689Sjkh 46911894Speter if (!next || cmpnumfld(revno,next->num,1) != 0) { 47011894Speter rcserror("revision number %s too low", partialno(&t,revno,2)); 4719Sjkh goto norev; 4729Sjkh } 4739Sjkh if ((length>2) && (result!=0)) { 4749Sjkh absent(revno, 2); 4759Sjkh goto norev; 4769Sjkh } 4779Sjkh 4789Sjkh /* print last one */ 4799Sjkh store1(&store, next); 4809Sjkh 4819Sjkh if (length>2) 4829Sjkh return genbranch(next,revno,length,date,author,state,store); 4839Sjkh else { /* length == 2*/ 48411894Speter if (date && cmpdate(date,next->date)<0) { 48511894Speter rcserror("Revision %s has date %s.", 4869Sjkh next->num, 4879Sjkh date2str(next->date, datebuf) 4889Sjkh ); 48911894Speter return 0; 49011894Speter } 49111894Speter if (author && strcmp(author,next->author)!=0) { 49211894Speter rcserror("Revision %s has author %s.", 49311894Speter next->num, next->author 49411894Speter ); 49511894Speter return 0; 4969Sjkh } 49711894Speter if (state && strcmp(state,next->state)!=0) { 49811894Speter rcserror("Revision %s has state %s.", 49911894Speter next->num, 50011894Speter next->state ? next->state : "<empty>" 50111894Speter ); 50211894Speter return 0; 5039Sjkh } 50411894Speter *store = 0; 5059Sjkh return next; 5069Sjkh } 5079Sjkh 5089Sjkh norev: 5099Sjkh bufautoend(&t); 51011894Speter return 0; 5119Sjkh} 5129Sjkh 5139Sjkh 5149Sjkh 5159Sjkh 5169Sjkh static struct hshentry * 5179Sjkhgenbranch(bpoint, revno, length, date, author, state, store) 5189Sjkh struct hshentry const *bpoint; 5199Sjkh char const *revno; 52011894Speter int length; 5219Sjkh char const *date, *author, *state; 5229Sjkh struct hshentries **store; 5239Sjkh/* Function: given a branchpoint, a revision number, date, author, and state, 5249Sjkh * genbranch finds the deltas necessary to reconstruct the given revision 5259Sjkh * from the branch point on. 5269Sjkh * Pointers to the found deltas are stored in a list beginning with store. 5279Sjkh * revno must be on a side branch. 52811894Speter * Return 0 on error. 5299Sjkh */ 5309Sjkh{ 53111894Speter int field; 5329Sjkh register struct hshentry * next, * trail; 5339Sjkh register struct branchhead const *bhead; 5349Sjkh int result; 5359Sjkh struct buf t; 53611894Speter char datebuf[datesize + zonelenmax]; 5379Sjkh 5389Sjkh field = 3; 5399Sjkh bhead = bpoint->branches; 5409Sjkh 5419Sjkh do { 5429Sjkh if (!bhead) { 5439Sjkh bufautobegin(&t); 54411894Speter rcserror("no side branches present for %s", 54511894Speter partialno(&t,revno,field-1) 54611894Speter ); 5479Sjkh bufautoend(&t); 54811894Speter return 0; 5499Sjkh } 5509Sjkh 5519Sjkh /*find branch head*/ 5529Sjkh /*branches are arranged in increasing order*/ 5539Sjkh while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) { 5549Sjkh bhead = bhead->nextbranch; 5559Sjkh if (!bhead) { 5569Sjkh bufautobegin(&t); 55711894Speter rcserror("branch number %s too high", 55811894Speter partialno(&t,revno,field) 55911894Speter ); 5609Sjkh bufautoend(&t); 56111894Speter return 0; 5629Sjkh } 5639Sjkh } 5649Sjkh 5659Sjkh if (result<0) { 5669Sjkh absent(revno, field); 56711894Speter return 0; 5689Sjkh } 5699Sjkh 5709Sjkh next = bhead->hsh; 5719Sjkh if (length==field) { 5729Sjkh /* pick latest one on that branch */ 57311894Speter trail = 0; 57411894Speter do { if ((!date || cmpdate(date,next->date)>=0) && 57511894Speter (!author || strcmp(author,next->author)==0) && 57611894Speter (!state || strcmp(state,next->state)==0) 5779Sjkh ) trail = next; 5789Sjkh next=next->next; 57911894Speter } while (next); 5809Sjkh 58111894Speter if (!trail) { 5829Sjkh cantfindbranch(revno, date, author, state); 58311894Speter return 0; 5849Sjkh } else { /* print up to last one suitable */ 5859Sjkh next = bhead->hsh; 5869Sjkh while (next!=trail) { 5879Sjkh store1(&store, next); 5889Sjkh next=next->next; 5899Sjkh } 5909Sjkh store1(&store, next); 5919Sjkh } 59211894Speter *store = 0; 5939Sjkh return next; 5949Sjkh } 5959Sjkh 5969Sjkh /* length > field */ 5979Sjkh /* find revision */ 5989Sjkh /* check low */ 5999Sjkh if (cmpnumfld(revno,next->num,field+1)<0) { 6009Sjkh bufautobegin(&t); 60111894Speter rcserror("revision number %s too low", 60211894Speter partialno(&t,revno,field+1) 60311894Speter ); 6049Sjkh bufautoend(&t); 60511894Speter return 0; 6069Sjkh } 6079Sjkh do { 6089Sjkh store1(&store, next); 6099Sjkh trail = next; 6109Sjkh next = next->next; 61111894Speter } while (next && cmpnumfld(revno,next->num,field+1)>=0); 6129Sjkh 6139Sjkh if ((length>field+1) && /*need exact hit */ 6149Sjkh (cmpnumfld(revno,trail->num,field+1) !=0)){ 6159Sjkh absent(revno, field+1); 61611894Speter return 0; 6179Sjkh } 6189Sjkh if (length == field+1) { 61911894Speter if (date && cmpdate(date,trail->date)<0) { 62011894Speter rcserror("Revision %s has date %s.", 6219Sjkh trail->num, 6229Sjkh date2str(trail->date, datebuf) 6239Sjkh ); 62411894Speter return 0; 6259Sjkh } 62611894Speter if (author && strcmp(author,trail->author)!=0) { 62711894Speter rcserror("Revision %s has author %s.", 62811894Speter trail->num, trail->author 62911894Speter ); 63011894Speter return 0; 6319Sjkh } 63211894Speter if (state && strcmp(state,trail->state)!=0) { 63311894Speter rcserror("Revision %s has state %s.", 63411894Speter trail->num, 63511894Speter trail->state ? trail->state : "<empty>" 63611894Speter ); 63711894Speter return 0; 6389Sjkh } 6399Sjkh } 6409Sjkh bhead = trail->branches; 6419Sjkh 6429Sjkh } while ((field+=2) <= length); 64311894Speter *store = 0; 6449Sjkh return trail; 6459Sjkh} 6469Sjkh 6479Sjkh 6489Sjkh static char const * 6499Sjkhlookupsym(id) 6509Sjkh char const *id; 6519Sjkh/* Function: looks up id in the list of symbolic names starting 6529Sjkh * with pointer SYMBOLS, and returns a pointer to the corresponding 65311894Speter * revision number. Return 0 if not present. 6549Sjkh */ 6559Sjkh{ 6569Sjkh register struct assoc const *next; 65711894Speter for (next = Symbols; next; next = next->nextassoc) 6589Sjkh if (strcmp(id, next->symbol)==0) 6599Sjkh return next->num; 66011894Speter return 0; 6619Sjkh} 6629Sjkh 6639Sjkhint expandsym(source, target) 6649Sjkh char const *source; 6659Sjkh struct buf *target; 6669Sjkh/* Function: Source points to a revision number. Expandsym copies 6679Sjkh * the number to target, but replaces all symbolic fields in the 6689Sjkh * source number with their numeric values. 6699Sjkh * Expand a branch followed by `.' to the latest revision on that branch. 6709Sjkh * Ignore `.' after a revision. Remove leading zeros. 6719Sjkh * returns false on error; 6729Sjkh */ 6739Sjkh{ 6749Sjkh return fexpandsym(source, target, (RILE*)0); 6759Sjkh} 6769Sjkh 6779Sjkh int 6789Sjkhfexpandsym(source, target, fp) 6799Sjkh char const *source; 6809Sjkh struct buf *target; 6819Sjkh RILE *fp; 6829Sjkh/* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */ 6839Sjkh{ 6849Sjkh register char const *sp, *bp; 6859Sjkh register char *tp; 6869Sjkh char const *tlim; 68711894Speter int dots; 6889Sjkh 6899Sjkh sp = source; 6909Sjkh bufalloc(target, 1); 6919Sjkh tp = target->string; 69211894Speter if (!sp || !*sp) { /* Accept 0 pointer as a legal value. */ 6939Sjkh *tp='\0'; 6949Sjkh return true; 6959Sjkh } 6969Sjkh if (sp[0] == KDELIM && !sp[1]) { 6979Sjkh if (!getoldkeys(fp)) 6989Sjkh return false; 6999Sjkh if (!*prevrev.string) { 70011894Speter workerror("working file lacks revision number"); 7019Sjkh return false; 7029Sjkh } 7039Sjkh bufscpy(target, prevrev.string); 7049Sjkh return true; 7059Sjkh } 7069Sjkh tlim = tp + target->size; 7079Sjkh dots = 0; 7089Sjkh 7099Sjkh for (;;) { 71011894Speter register char *p = tp; 71111894Speter size_t s = tp - target->string; 71211894Speter int id = false; 71311894Speter for (;;) { 71411894Speter switch (ctab[(unsigned char)*sp]) { 71511894Speter case IDCHAR: 71611894Speter case LETTER: 71711894Speter case Letter: 71811894Speter id = true; 71911894Speter /* fall into */ 72011894Speter case DIGIT: 72111894Speter if (tlim <= p) 72211894Speter p = bufenlarge(target, &tlim); 72311894Speter *p++ = *sp++; 72411894Speter continue; 7259Sjkh 72611894Speter default: 72711894Speter break; 72811894Speter } 72911894Speter break; 73011894Speter } 73111894Speter if (tlim <= p) 73211894Speter p = bufenlarge(target, &tlim); 73311894Speter *p = 0; 73411894Speter tp = target->string + s; 73511894Speter 73611894Speter if (id) { 7379Sjkh bp = lookupsym(tp); 73811894Speter if (!bp) { 73911894Speter rcserror("Symbolic name `%s' is undefined.",tp); 7409Sjkh return false; 7419Sjkh } 74211894Speter } else { 74311894Speter /* skip leading zeros */ 74411894Speter for (bp = tp; *bp=='0' && isdigit(bp[1]); bp++) 74511894Speter continue; 7469Sjkh 74711894Speter if (!*bp) 74811894Speter if (s || *sp!='.') 74911894Speter break; 75011894Speter else { 75111894Speter /* Insert default branch before initial `.'. */ 75211894Speter char const *b; 75311894Speter if (Dbranch) 75411894Speter b = Dbranch; 75511894Speter else if (Head) 75611894Speter b = Head->num; 75711894Speter else 75811894Speter break; 75911894Speter getbranchno(b, target); 76011894Speter bp = tp = target->string; 76111894Speter tlim = tp + target->size; 76211894Speter } 76311894Speter } 76411894Speter 76511894Speter while ((*tp++ = *bp++)) 76611894Speter if (tlim <= tp) 76711894Speter tp = bufenlarge(target, &tlim); 76811894Speter 7699Sjkh switch (*sp++) { 77011894Speter case '\0': 7719Sjkh return true; 77211894Speter 77311894Speter case '.': 77411894Speter if (!*sp) { 77511894Speter if (dots & 1) 77611894Speter break; 77711894Speter if (!(bp = branchtip(target->string))) 77811894Speter return false; 77911894Speter bufscpy(target, bp); 78011894Speter return true; 78111894Speter } 78211894Speter ++dots; 78311894Speter tp[-1] = '.'; 78411894Speter continue; 7859Sjkh } 78611894Speter break; 7879Sjkh } 7889Sjkh 78911894Speter rcserror("improper revision number: %s", source); 7909Sjkh return false; 7919Sjkh} 7929Sjkh 79311894Speter char const * 79411894Speternamedrev(name, delta) 79511894Speter char const *name; 79611894Speter struct hshentry *delta; 79711894Speter/* Yield NAME if it names DELTA, 0 otherwise. */ 79811894Speter{ 79911894Speter if (name) { 80011894Speter char const *id = 0, *p, *val; 80111894Speter for (p = name; ; p++) 80211894Speter switch (ctab[(unsigned char)*p]) { 80311894Speter case IDCHAR: 80411894Speter case LETTER: 80511894Speter case Letter: 80611894Speter id = name; 80711894Speter break; 80811894Speter 80911894Speter case DIGIT: 81011894Speter break; 81111894Speter 81211894Speter case UNKN: 81311894Speter if (!*p && id && 81411894Speter (val = lookupsym(id)) && 81511894Speter strcmp(val, delta->num) == 0 81611894Speter ) 81711894Speter return id; 81811894Speter /* fall into */ 81911894Speter default: 82011894Speter return 0; 82111894Speter } 82211894Speter } 82311894Speter return 0; 82411894Speter} 82511894Speter 8269Sjkh static char const * 8279Sjkhbranchtip(branch) 8289Sjkh char const *branch; 8299Sjkh{ 8309Sjkh struct hshentry *h; 8319Sjkh struct hshentries *hs; 8329Sjkh 8339Sjkh h = genrevs(branch, (char*)0, (char*)0, (char*)0, &hs); 8349Sjkh return h ? h->num : (char const*)0; 8359Sjkh} 8369Sjkh 8379Sjkh char const * 8389Sjkhtiprev() 8399Sjkh{ 8409Sjkh return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0; 8419Sjkh} 8429Sjkh 8439Sjkh 8449Sjkh 8459Sjkh#ifdef REVTEST 8469Sjkh 84711894Speter/* 84811894Speter* Test the routines that generate a sequence of delta numbers 84911894Speter* needed to regenerate a given delta. 85011894Speter*/ 85111894Speter 8529Sjkhchar const cmdid[] = "revtest"; 8539Sjkh 8549Sjkh int 8559Sjkhmain(argc,argv) 8569Sjkhint argc; char * argv[]; 8579Sjkh{ 8589Sjkh static struct buf numricrevno; 8599Sjkh char symrevno[100]; /* used for input of revision numbers */ 8609Sjkh char author[20]; 8619Sjkh char state[20]; 8629Sjkh char date[20]; 8639Sjkh struct hshentries *gendeltas; 8649Sjkh struct hshentry * target; 8659Sjkh int i; 8669Sjkh 8679Sjkh if (argc<2) { 8689Sjkh aputs("No input file\n",stderr); 8699Sjkh exitmain(EXIT_FAILURE); 8709Sjkh } 8719Sjkh if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) { 8729Sjkh faterror("can't open input file %s", argv[1]); 8739Sjkh } 8749Sjkh Lexinit(); 8759Sjkh getadmin(); 8769Sjkh 8779Sjkh gettree(); 8789Sjkh 8799Sjkh getdesc(false); 8809Sjkh 8819Sjkh do { 8829Sjkh /* all output goes to stderr, to have diagnostics and */ 8839Sjkh /* errors in sequence. */ 8849Sjkh aputs("\nEnter revision number or <return> or '.': ",stderr); 88512553Sjkh if (!fgets(symrevno, 100, stdin)) break; 8869Sjkh if (*symrevno == '.') break; 8879Sjkh aprintf(stderr,"%s;\n",symrevno); 8889Sjkh expandsym(symrevno,&numricrevno); 8899Sjkh aprintf(stderr,"expanded number: %s; ",numricrevno.string); 8909Sjkh aprintf(stderr,"Date: "); 89112553Sjkh fgets(date, 20, stdin); aprintf(stderr,"%s; ",date); 8929Sjkh aprintf(stderr,"Author: "); 89312553Sjkh fgets(author, 20, stdin); aprintf(stderr,"%s; ",author); 8949Sjkh aprintf(stderr,"State: "); 89512553Sjkh fgets(state, 20, stdin); aprintf(stderr, "%s;\n", state); 89611894Speter target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0, 89711894Speter *state?state:(char*)0, &gendeltas); 89811894Speter if (target) { 8999Sjkh while (gendeltas) { 9009Sjkh aprintf(stderr,"%s\n",gendeltas->first->num); 9019Sjkh gendeltas = gendeltas->next; 9029Sjkh } 9039Sjkh } 9049Sjkh } while (true); 9059Sjkh aprintf(stderr,"done\n"); 9069Sjkh exitmain(EXIT_SUCCESS); 9079Sjkh} 9089Sjkh 90911894Spetervoid exiterr() { _exit(EXIT_FAILURE); } 9109Sjkh 9119Sjkh#endif 912