rcsrev.c revision 9
19Sjkh/* 29Sjkh * RCS revision number handling 39Sjkh */ 49Sjkh 59Sjkh/* Copyright (C) 1982, 1988, 1989 Walter Tichy 69Sjkh Copyright 1990, 1991 by Paul Eggert 79Sjkh Distributed under license by the Free Software Foundation, Inc. 89Sjkh 99SjkhThis file is part of RCS. 109Sjkh 119SjkhRCS is free software; you can redistribute it and/or modify 129Sjkhit under the terms of the GNU General Public License as published by 139Sjkhthe Free Software Foundation; either version 2, or (at your option) 149Sjkhany later version. 159Sjkh 169SjkhRCS is distributed in the hope that it will be useful, 179Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of 189SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 199SjkhGNU General Public License for more details. 209Sjkh 219SjkhYou should have received a copy of the GNU General Public License 229Sjkhalong with RCS; see the file COPYING. If not, write to 239Sjkhthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 249Sjkh 259SjkhReport problems and direct all questions to: 269Sjkh 279Sjkh rcs-bugs@cs.purdue.edu 289Sjkh 299Sjkh*/ 309Sjkh 319Sjkh 329Sjkh 339Sjkh 349Sjkh/* $Log: rcsrev.c,v $ 359Sjkh * Revision 5.3 1991/08/19 03:13:55 eggert 369Sjkh * Add `-r$', `-rB.'. Remove botches like `<now>' from messages. Tune. 379Sjkh * 389Sjkh * Revision 5.2 1991/04/21 11:58:28 eggert 399Sjkh * Add tiprev(). 409Sjkh * 419Sjkh * Revision 5.1 1991/02/25 07:12:43 eggert 429Sjkh * Avoid overflow when comparing revision numbers. 439Sjkh * 449Sjkh * Revision 5.0 1990/08/22 08:13:43 eggert 459Sjkh * Remove compile-time limits; use malloc instead. 469Sjkh * Ansify and Posixate. Tune. 479Sjkh * Remove possibility of an internal error. Remove lint. 489Sjkh * 499Sjkh * Revision 4.5 89/05/01 15:13:22 narten 509Sjkh * changed copyright header to reflect current distribution rules 519Sjkh * 529Sjkh * Revision 4.4 87/12/18 11:45:22 narten 539Sjkh * more lint cleanups. Also, the NOTREACHED comment is no longer necessary, 549Sjkh * since there's now a return value there with a value. (Guy Harris) 559Sjkh * 569Sjkh * Revision 4.3 87/10/18 10:38:42 narten 579Sjkh * Updating version numbers. Changes relative to version 1.1 actually 589Sjkh * relative to 4.1 599Sjkh * 609Sjkh * Revision 1.3 87/09/24 14:00:37 narten 619Sjkh * Sources now pass through lint (if you ignore printf/sprintf/fprintf 629Sjkh * warnings) 639Sjkh * 649Sjkh * Revision 1.2 87/03/27 14:22:37 jenkins 659Sjkh * Port to suns 669Sjkh * 679Sjkh * Revision 4.1 83/03/25 21:10:45 wft 689Sjkh * Only changed $Header to $Id. 699Sjkh * 709Sjkh * Revision 3.4 82/12/04 13:24:08 wft 719Sjkh * Replaced getdelta() with gettree(). 729Sjkh * 739Sjkh * Revision 3.3 82/11/28 21:33:15 wft 749Sjkh * fixed compartial() and compnum() for nil-parameters; fixed nils 759Sjkh * in error messages. Testprogram output shortenend. 769Sjkh * 779Sjkh * Revision 3.2 82/10/18 21:19:47 wft 789Sjkh * renamed compnum->cmpnum, compnumfld->cmpnumfld, 799Sjkh * numericrevno->numricrevno. 809Sjkh * 819Sjkh * Revision 3.1 82/10/11 19:46:09 wft 829Sjkh * changed expandsym() to check for source==nil; returns zero length string 839Sjkh * in that case. 849Sjkh */ 859Sjkh 869Sjkh 879Sjkh 889Sjkh/* 899Sjkh#define REVTEST 909Sjkh*/ 919Sjkh/* version REVTEST is for testing the routines that generate a sequence 929Sjkh * of delta numbers needed to regenerate a given delta. 939Sjkh */ 949Sjkh 959Sjkh#include "rcsbase.h" 969Sjkh 979SjkhlibId(revId, "$Id: rcsrev.c,v 5.3 1991/08/19 03:13:55 eggert Exp $") 989Sjkh 999Sjkhstatic char const *branchtip P((char const*)); 1009Sjkhstatic struct hshentry *genbranch P((struct hshentry const*,char const*,unsigned,char const*,char const*,char const*,struct hshentries**)); 1019Sjkh 1029Sjkh 1039Sjkh 1049Sjkh unsigned 1059Sjkhcountnumflds(s) 1069Sjkh char const *s; 1079Sjkh/* Given a pointer s to a dotted number (date or revision number), 1089Sjkh * countnumflds returns the number of digitfields in s. 1099Sjkh */ 1109Sjkh{ 1119Sjkh register char const *sp; 1129Sjkh register unsigned count; 1139Sjkh if ((sp=s)==nil) return(0); 1149Sjkh if (*sp == '\0') return(0); 1159Sjkh count = 1; 1169Sjkh do { 1179Sjkh if (*sp++ == '.') count++; 1189Sjkh } while (*sp); 1199Sjkh return(count); 1209Sjkh} 1219Sjkh 1229Sjkh void 1239Sjkhgetbranchno(revno,branchno) 1249Sjkh char const *revno; 1259Sjkh struct buf *branchno; 1269Sjkh/* Given a non-nil revision number revno, getbranchno copies the number of the branch 1279Sjkh * on which revno is into branchno. If revno itself is a branch number, 1289Sjkh * it is copied unchanged. 1299Sjkh */ 1309Sjkh{ 1319Sjkh register unsigned numflds; 1329Sjkh register char *tp; 1339Sjkh 1349Sjkh bufscpy(branchno, revno); 1359Sjkh numflds=countnumflds(revno); 1369Sjkh if (!(numflds & 1)) { 1379Sjkh tp = branchno->string; 1389Sjkh while (--numflds) 1399Sjkh while (*tp++ != '.') 1409Sjkh ; 1419Sjkh *(tp-1)='\0'; 1429Sjkh } 1439Sjkh} 1449Sjkh 1459Sjkh 1469Sjkh 1479Sjkhint cmpnum(num1, num2) 1489Sjkh char const *num1, *num2; 1499Sjkh/* compares the two dotted numbers num1 and num2 lexicographically 1509Sjkh * by field. Individual fields are compared numerically. 1519Sjkh * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp. 1529Sjkh * omitted fields are assumed to be higher than the existing ones. 1539Sjkh*/ 1549Sjkh{ 1559Sjkh register char const *s1, *s2; 1569Sjkh register size_t d1, d2; 1579Sjkh register int r; 1589Sjkh 1599Sjkh s1=num1==nil?"":num1; 1609Sjkh s2=num2==nil?"":num2; 1619Sjkh 1629Sjkh for (;;) { 1639Sjkh /* Give precedence to shorter one. */ 1649Sjkh if (!*s1) 1659Sjkh return (unsigned char)*s2; 1669Sjkh if (!*s2) 1679Sjkh return -1; 1689Sjkh 1699Sjkh /* Strip leading zeros, then find number of digits. */ 1709Sjkh while (*s1=='0') ++s1; for (d1=0; isdigit(s1[d1]); d1++) ; 1719Sjkh while (*s2=='0') ++s2; for (d2=0; isdigit(s2[d2]); d2++) ; 1729Sjkh 1739Sjkh /* Do not convert to integer; it might overflow! */ 1749Sjkh if (d1 != d2) 1759Sjkh return d1<d2 ? -1 : 1; 1769Sjkh if ((r = memcmp(s1, s2, d1))) 1779Sjkh return r; 1789Sjkh s1 += d1; 1799Sjkh s2 += d1; 1809Sjkh 1819Sjkh /* skip '.' */ 1829Sjkh if (*s1) s1++; 1839Sjkh if (*s2) s2++; 1849Sjkh } 1859Sjkh} 1869Sjkh 1879Sjkh 1889Sjkh 1899Sjkhint cmpnumfld(num1, num2, fld) 1909Sjkh char const *num1, *num2; 1919Sjkh unsigned fld; 1929Sjkh/* Compare the two dotted numbers at field fld. 1939Sjkh * num1 and num2 must have at least fld fields. 1949Sjkh * fld must be positive. 1959Sjkh*/ 1969Sjkh{ 1979Sjkh register char const *s1, *s2; 1989Sjkh register size_t d1, d2; 1999Sjkh 2009Sjkh s1 = num1; 2019Sjkh s2 = num2; 2029Sjkh /* skip fld-1 fields */ 2039Sjkh while (--fld) { 2049Sjkh while (*s1++ != '.') 2059Sjkh ; 2069Sjkh while (*s2++ != '.') 2079Sjkh ; 2089Sjkh } 2099Sjkh /* Now s1 and s2 point to the beginning of the respective fields */ 2109Sjkh while (*s1=='0') ++s1; for (d1=0; isdigit(s1[d1]); d1++) ; 2119Sjkh while (*s2=='0') ++s2; for (d2=0; isdigit(s2[d2]); d2++) ; 2129Sjkh 2139Sjkh return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1; 2149Sjkh} 2159Sjkh 2169Sjkh 2179Sjkh static void 2189Sjkhcantfindbranch(revno, date, author, state) 2199Sjkh char const *revno, date[datesize], *author, *state; 2209Sjkh{ 2219Sjkh char datebuf[datesize]; 2229Sjkh 2239Sjkh error("No revision on branch %s has%s%s%s%s%s%s.", 2249Sjkh revno, 2259Sjkh date ? " a date before " : "", 2269Sjkh date ? date2str(date,datebuf) : "", 2279Sjkh author ? " and author "+(date?0:4) : "", 2289Sjkh author ? author : "", 2299Sjkh state ? " and state "+(date||author?0:4) : "", 2309Sjkh state ? state : "" 2319Sjkh ); 2329Sjkh} 2339Sjkh 2349Sjkh static void 2359Sjkhabsent(revno, field) 2369Sjkh char const *revno; 2379Sjkh unsigned field; 2389Sjkh{ 2399Sjkh struct buf t; 2409Sjkh bufautobegin(&t); 2419Sjkh error("%s %s absent", field&1?"revision":"branch", 2429Sjkh partialno(&t,revno,field) 2439Sjkh ); 2449Sjkh bufautoend(&t); 2459Sjkh} 2469Sjkh 2479Sjkh 2489Sjkh int 2499Sjkhcompartial(num1, num2, length) 2509Sjkh char const *num1, *num2; 2519Sjkh unsigned length; 2529Sjkh 2539Sjkh/* compare the first "length" fields of two dot numbers; 2549Sjkh the omitted field is considered to be larger than any number */ 2559Sjkh/* restriction: at least one number has length or more fields */ 2569Sjkh 2579Sjkh{ 2589Sjkh register char const *s1, *s2; 2599Sjkh register size_t d1, d2; 2609Sjkh register int r; 2619Sjkh 2629Sjkh s1 = num1; s2 = num2; 2639Sjkh if (!s1) return 1; 2649Sjkh if (!s2) return -1; 2659Sjkh 2669Sjkh for (;;) { 2679Sjkh if (!*s1) return 1; 2689Sjkh if (!*s2) return -1; 2699Sjkh 2709Sjkh while (*s1=='0') ++s1; for (d1=0; isdigit(s1[d1]); d1++) ; 2719Sjkh while (*s2=='0') ++s2; for (d2=0; isdigit(s2[d2]); d2++) ; 2729Sjkh 2739Sjkh if (d1 != d2) 2749Sjkh return d1<d2 ? -1 : 1; 2759Sjkh if ((r = memcmp(s1, s2, d1))) 2769Sjkh return r; 2779Sjkh s1 += d1; 2789Sjkh s2 += d1; 2799Sjkh 2809Sjkh if (*s1 == '.') s1++; 2819Sjkh if (*s2 == '.') s2++; 2829Sjkh 2839Sjkh if ( --length == 0 ) return 0; 2849Sjkh } 2859Sjkh} 2869Sjkh 2879Sjkh 2889Sjkhchar * partialno(rev1,rev2,length) 2899Sjkh struct buf *rev1; 2909Sjkh char const *rev2; 2919Sjkh register unsigned length; 2929Sjkh/* Function: Copies length fields of revision number rev2 into rev1. 2939Sjkh * Return rev1's string. 2949Sjkh */ 2959Sjkh{ 2969Sjkh register char *r1; 2979Sjkh 2989Sjkh bufscpy(rev1, rev2); 2999Sjkh r1 = rev1->string; 3009Sjkh while (length) { 3019Sjkh while (*r1!='.' && *r1) 3029Sjkh ++r1; 3039Sjkh ++r1; 3049Sjkh length--; 3059Sjkh } 3069Sjkh /* eliminate last '.'*/ 3079Sjkh *(r1-1)='\0'; 3089Sjkh return rev1->string; 3099Sjkh} 3109Sjkh 3119Sjkh 3129Sjkh 3139Sjkh 3149Sjkh static void 3159Sjkhstore1(store, next) 3169Sjkh struct hshentries ***store; 3179Sjkh struct hshentry *next; 3189Sjkh/* 3199Sjkh * Allocate a new list node that addresses NEXT. 3209Sjkh * Append it to the list that **STORE is the end pointer of. 3219Sjkh */ 3229Sjkh{ 3239Sjkh register struct hshentries *p; 3249Sjkh 3259Sjkh p = ftalloc(struct hshentries); 3269Sjkh p->first = next; 3279Sjkh **store = p; 3289Sjkh *store = &p->rest; 3299Sjkh} 3309Sjkh 3319Sjkhstruct hshentry * genrevs(revno,date,author,state,store) 3329Sjkh char const *revno, *date, *author, *state; 3339Sjkh struct hshentries **store; 3349Sjkh/* Function: finds the deltas needed for reconstructing the 3359Sjkh * revision given by revno, date, author, and state, and stores pointers 3369Sjkh * to these deltas into a list whose starting address is given by store. 3379Sjkh * The last delta (target delta) is returned. 3389Sjkh * If the proper delta could not be found, nil is returned. 3399Sjkh */ 3409Sjkh{ 3419Sjkh unsigned length; 3429Sjkh register struct hshentry * next; 3439Sjkh int result; 3449Sjkh char const *branchnum; 3459Sjkh struct buf t; 3469Sjkh char datebuf[datesize]; 3479Sjkh 3489Sjkh bufautobegin(&t); 3499Sjkh 3509Sjkh if (!(next = Head)) { 3519Sjkh error("RCS file empty"); 3529Sjkh goto norev; 3539Sjkh } 3549Sjkh 3559Sjkh length = countnumflds(revno); 3569Sjkh 3579Sjkh if (length >= 1) { 3589Sjkh /* at least one field; find branch exactly */ 3599Sjkh while ((result=cmpnumfld(revno,next->num,1)) < 0) { 3609Sjkh store1(&store, next); 3619Sjkh next = next->next; 3629Sjkh if (!next) { 3639Sjkh error("branch number %s too low", partialno(&t,revno,1)); 3649Sjkh goto norev; 3659Sjkh } 3669Sjkh } 3679Sjkh 3689Sjkh if (result>0) { 3699Sjkh absent(revno, 1); 3709Sjkh goto norev; 3719Sjkh } 3729Sjkh } 3739Sjkh if (length<=1){ 3749Sjkh /* pick latest one on given branch */ 3759Sjkh branchnum = next->num; /* works even for empty revno*/ 3769Sjkh while ((next!=nil) && 3779Sjkh (cmpnumfld(branchnum,next->num,1)==0) && 3789Sjkh !( 3799Sjkh (date==nil?1:(cmpnum(date,next->date)>=0)) && 3809Sjkh (author==nil?1:(strcmp(author,next->author)==0)) && 3819Sjkh (state ==nil?1:(strcmp(state, next->state) ==0)) 3829Sjkh ) 3839Sjkh ) 3849Sjkh { 3859Sjkh store1(&store, next); 3869Sjkh next=next->next; 3879Sjkh } 3889Sjkh if ((next==nil) || 3899Sjkh (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ { 3909Sjkh cantfindbranch( 3919Sjkh length ? revno : partialno(&t,branchnum,1), 3929Sjkh date, author, state 3939Sjkh ); 3949Sjkh goto norev; 3959Sjkh } else { 3969Sjkh store1(&store, next); 3979Sjkh } 3989Sjkh *store = nil; 3999Sjkh return next; 4009Sjkh } 4019Sjkh 4029Sjkh /* length >=2 */ 4039Sjkh /* find revision; may go low if length==2*/ 4049Sjkh while ((result=cmpnumfld(revno,next->num,2)) < 0 && 4059Sjkh (cmpnumfld(revno,next->num,1)==0) ) { 4069Sjkh store1(&store, next); 4079Sjkh next = next->next; 4089Sjkh if (!next) 4099Sjkh break; 4109Sjkh } 4119Sjkh 4129Sjkh if ((next==nil) || (cmpnumfld(revno,next->num,1)!=0)) { 4139Sjkh error("revision number %s too low", partialno(&t,revno,2)); 4149Sjkh goto norev; 4159Sjkh } 4169Sjkh if ((length>2) && (result!=0)) { 4179Sjkh absent(revno, 2); 4189Sjkh goto norev; 4199Sjkh } 4209Sjkh 4219Sjkh /* print last one */ 4229Sjkh store1(&store, next); 4239Sjkh 4249Sjkh if (length>2) 4259Sjkh return genbranch(next,revno,length,date,author,state,store); 4269Sjkh else { /* length == 2*/ 4279Sjkh if ((date!=nil) && (cmpnum(date,next->date)<0)){ 4289Sjkh error("Revision %s has date %s.", 4299Sjkh next->num, 4309Sjkh date2str(next->date, datebuf) 4319Sjkh ); 4329Sjkh return nil; 4339Sjkh } 4349Sjkh if ((author!=nil)&&(strcmp(author,next->author)!=0)) { 4359Sjkh error("Revision %s has author %s.",next->num,next->author); 4369Sjkh return nil; 4379Sjkh } 4389Sjkh if ((state!=nil)&&(strcmp(state,next->state)!=0)) { 4399Sjkh error("Revision %s has state %s.",next->num, 4409Sjkh next->state==nil?"<empty>":next->state); 4419Sjkh return nil; 4429Sjkh } 4439Sjkh *store=nil; 4449Sjkh return next; 4459Sjkh } 4469Sjkh 4479Sjkh norev: 4489Sjkh bufautoend(&t); 4499Sjkh return nil; 4509Sjkh} 4519Sjkh 4529Sjkh 4539Sjkh 4549Sjkh 4559Sjkh static struct hshentry * 4569Sjkhgenbranch(bpoint, revno, length, date, author, state, store) 4579Sjkh struct hshentry const *bpoint; 4589Sjkh char const *revno; 4599Sjkh unsigned length; 4609Sjkh char const *date, *author, *state; 4619Sjkh struct hshentries **store; 4629Sjkh/* Function: given a branchpoint, a revision number, date, author, and state, 4639Sjkh * genbranch finds the deltas necessary to reconstruct the given revision 4649Sjkh * from the branch point on. 4659Sjkh * Pointers to the found deltas are stored in a list beginning with store. 4669Sjkh * revno must be on a side branch. 4679Sjkh * return nil on error 4689Sjkh */ 4699Sjkh{ 4709Sjkh unsigned field; 4719Sjkh register struct hshentry * next, * trail; 4729Sjkh register struct branchhead const *bhead; 4739Sjkh int result; 4749Sjkh struct buf t; 4759Sjkh char datebuf[datesize]; 4769Sjkh 4779Sjkh field = 3; 4789Sjkh bhead = bpoint->branches; 4799Sjkh 4809Sjkh do { 4819Sjkh if (!bhead) { 4829Sjkh bufautobegin(&t); 4839Sjkh error("no side branches present for %s", partialno(&t,revno,field-1)); 4849Sjkh bufautoend(&t); 4859Sjkh return nil; 4869Sjkh } 4879Sjkh 4889Sjkh /*find branch head*/ 4899Sjkh /*branches are arranged in increasing order*/ 4909Sjkh while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) { 4919Sjkh bhead = bhead->nextbranch; 4929Sjkh if (!bhead) { 4939Sjkh bufautobegin(&t); 4949Sjkh error("branch number %s too high",partialno(&t,revno,field)); 4959Sjkh bufautoend(&t); 4969Sjkh return nil; 4979Sjkh } 4989Sjkh } 4999Sjkh 5009Sjkh if (result<0) { 5019Sjkh absent(revno, field); 5029Sjkh return nil; 5039Sjkh } 5049Sjkh 5059Sjkh next = bhead->hsh; 5069Sjkh if (length==field) { 5079Sjkh /* pick latest one on that branch */ 5089Sjkh trail=nil; 5099Sjkh do { if ((date==nil?1:(cmpnum(date,next->date)>=0)) && 5109Sjkh (author==nil?1:(strcmp(author,next->author)==0)) && 5119Sjkh (state ==nil?1:(strcmp(state, next->state) ==0)) 5129Sjkh ) trail = next; 5139Sjkh next=next->next; 5149Sjkh } while (next!=nil); 5159Sjkh 5169Sjkh if (trail==nil) { 5179Sjkh cantfindbranch(revno, date, author, state); 5189Sjkh return nil; 5199Sjkh } else { /* print up to last one suitable */ 5209Sjkh next = bhead->hsh; 5219Sjkh while (next!=trail) { 5229Sjkh store1(&store, next); 5239Sjkh next=next->next; 5249Sjkh } 5259Sjkh store1(&store, next); 5269Sjkh } 5279Sjkh *store = nil; 5289Sjkh return next; 5299Sjkh } 5309Sjkh 5319Sjkh /* length > field */ 5329Sjkh /* find revision */ 5339Sjkh /* check low */ 5349Sjkh if (cmpnumfld(revno,next->num,field+1)<0) { 5359Sjkh bufautobegin(&t); 5369Sjkh error("revision number %s too low", partialno(&t,revno,field+1)); 5379Sjkh bufautoend(&t); 5389Sjkh return(nil); 5399Sjkh } 5409Sjkh do { 5419Sjkh store1(&store, next); 5429Sjkh trail = next; 5439Sjkh next = next->next; 5449Sjkh } while ((next!=nil) && 5459Sjkh (cmpnumfld(revno,next->num,field+1) >=0)); 5469Sjkh 5479Sjkh if ((length>field+1) && /*need exact hit */ 5489Sjkh (cmpnumfld(revno,trail->num,field+1) !=0)){ 5499Sjkh absent(revno, field+1); 5509Sjkh return(nil); 5519Sjkh } 5529Sjkh if (length == field+1) { 5539Sjkh if ((date!=nil) && (cmpnum(date,trail->date)<0)){ 5549Sjkh error("Revision %s has date %s.", 5559Sjkh trail->num, 5569Sjkh date2str(trail->date, datebuf) 5579Sjkh ); 5589Sjkh return nil; 5599Sjkh } 5609Sjkh if ((author!=nil)&&(strcmp(author,trail->author)!=0)) { 5619Sjkh error("Revision %s has author %s.",trail->num,trail->author); 5629Sjkh return nil; 5639Sjkh } 5649Sjkh if ((state!=nil)&&(strcmp(state,trail->state)!=0)) { 5659Sjkh error("Revision %s has state %s.",trail->num, 5669Sjkh trail->state==nil?"<empty>":trail->state); 5679Sjkh return nil; 5689Sjkh } 5699Sjkh } 5709Sjkh bhead = trail->branches; 5719Sjkh 5729Sjkh } while ((field+=2) <= length); 5739Sjkh * store = nil; 5749Sjkh return trail; 5759Sjkh} 5769Sjkh 5779Sjkh 5789Sjkh static char const * 5799Sjkhlookupsym(id) 5809Sjkh char const *id; 5819Sjkh/* Function: looks up id in the list of symbolic names starting 5829Sjkh * with pointer SYMBOLS, and returns a pointer to the corresponding 5839Sjkh * revision number. Returns nil if not present. 5849Sjkh */ 5859Sjkh{ 5869Sjkh register struct assoc const *next; 5879Sjkh next = Symbols; 5889Sjkh while (next!=nil) { 5899Sjkh if (strcmp(id, next->symbol)==0) 5909Sjkh return next->num; 5919Sjkh else next=next->nextassoc; 5929Sjkh } 5939Sjkh return nil; 5949Sjkh} 5959Sjkh 5969Sjkhint expandsym(source, target) 5979Sjkh char const *source; 5989Sjkh struct buf *target; 5999Sjkh/* Function: Source points to a revision number. Expandsym copies 6009Sjkh * the number to target, but replaces all symbolic fields in the 6019Sjkh * source number with their numeric values. 6029Sjkh * Expand a branch followed by `.' to the latest revision on that branch. 6039Sjkh * Ignore `.' after a revision. Remove leading zeros. 6049Sjkh * returns false on error; 6059Sjkh */ 6069Sjkh{ 6079Sjkh return fexpandsym(source, target, (RILE*)0); 6089Sjkh} 6099Sjkh 6109Sjkh int 6119Sjkhfexpandsym(source, target, fp) 6129Sjkh char const *source; 6139Sjkh struct buf *target; 6149Sjkh RILE *fp; 6159Sjkh/* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */ 6169Sjkh{ 6179Sjkh register char const *sp, *bp; 6189Sjkh register char *tp; 6199Sjkh char const *tlim; 6209Sjkh register enum tokens d; 6219Sjkh unsigned dots; 6229Sjkh 6239Sjkh sp = source; 6249Sjkh bufalloc(target, 1); 6259Sjkh tp = target->string; 6269Sjkh if (!sp || !*sp) { /*accept nil pointer as a legal value*/ 6279Sjkh *tp='\0'; 6289Sjkh return true; 6299Sjkh } 6309Sjkh if (sp[0] == KDELIM && !sp[1]) { 6319Sjkh if (!getoldkeys(fp)) 6329Sjkh return false; 6339Sjkh if (!*prevrev.string) { 6349Sjkh error("working file lacks revision number"); 6359Sjkh return false; 6369Sjkh } 6379Sjkh bufscpy(target, prevrev.string); 6389Sjkh return true; 6399Sjkh } 6409Sjkh tlim = tp + target->size; 6419Sjkh dots = 0; 6429Sjkh 6439Sjkh for (;;) { 6449Sjkh switch (ctab[(unsigned char)*sp]) { 6459Sjkh case DIGIT: 6469Sjkh while (*sp=='0' && isdigit(sp[1])) 6479Sjkh /* skip leading zeroes */ 6489Sjkh sp++; 6499Sjkh do { 6509Sjkh if (tlim <= tp) 6519Sjkh tp = bufenlarge(target, &tlim); 6529Sjkh } while (isdigit(*tp++ = *sp++)); 6539Sjkh --sp; 6549Sjkh tp[-1] = '\0'; 6559Sjkh break; 6569Sjkh 6579Sjkh case LETTER: 6589Sjkh case Letter: 6599Sjkh { 6609Sjkh register char *p = tp; 6619Sjkh register size_t s = tp - target->string; 6629Sjkh do { 6639Sjkh if (tlim <= p) 6649Sjkh p = bufenlarge(target, &tlim); 6659Sjkh *p++ = *sp++; 6669Sjkh } while ((d=ctab[(unsigned char)*sp])==LETTER || 6679Sjkh d==Letter || d==DIGIT || 6689Sjkh (d==IDCHAR)); 6699Sjkh if (tlim <= p) 6709Sjkh p = bufenlarge(target, &tlim); 6719Sjkh *p = 0; 6729Sjkh tp = target->string + s; 6739Sjkh } 6749Sjkh bp = lookupsym(tp); 6759Sjkh if (bp==nil) { 6769Sjkh error("Symbolic number %s is undefined.", tp); 6779Sjkh return false; 6789Sjkh } 6799Sjkh do { 6809Sjkh if (tlim <= tp) 6819Sjkh tp = bufenlarge(target, &tlim); 6829Sjkh } while ((*tp++ = *bp++)); 6839Sjkh break; 6849Sjkh 6859Sjkh default: 6869Sjkh goto improper; 6879Sjkh } 6889Sjkh switch (*sp++) { 6899Sjkh case '\0': return true; 6909Sjkh case '.': break; 6919Sjkh default: goto improper; 6929Sjkh } 6939Sjkh if (!*sp) { 6949Sjkh if (dots & 1) 6959Sjkh goto improper; 6969Sjkh if (!(bp = branchtip(target->string))) 6979Sjkh return false; 6989Sjkh bufscpy(target, bp); 6999Sjkh return true; 7009Sjkh } 7019Sjkh ++dots; 7029Sjkh tp[-1] = '.'; 7039Sjkh } 7049Sjkh 7059Sjkh improper: 7069Sjkh error("improper revision number: %s", source); 7079Sjkh return false; 7089Sjkh} 7099Sjkh 7109Sjkh static char const * 7119Sjkhbranchtip(branch) 7129Sjkh char const *branch; 7139Sjkh{ 7149Sjkh struct hshentry *h; 7159Sjkh struct hshentries *hs; 7169Sjkh 7179Sjkh h = genrevs(branch, (char*)0, (char*)0, (char*)0, &hs); 7189Sjkh return h ? h->num : (char const*)0; 7199Sjkh} 7209Sjkh 7219Sjkh char const * 7229Sjkhtiprev() 7239Sjkh{ 7249Sjkh return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0; 7259Sjkh} 7269Sjkh 7279Sjkh 7289Sjkh 7299Sjkh#ifdef REVTEST 7309Sjkh 7319Sjkhchar const cmdid[] = "revtest"; 7329Sjkh 7339Sjkh int 7349Sjkhmain(argc,argv) 7359Sjkhint argc; char * argv[]; 7369Sjkh{ 7379Sjkh static struct buf numricrevno; 7389Sjkh char symrevno[100]; /* used for input of revision numbers */ 7399Sjkh char author[20]; 7409Sjkh char state[20]; 7419Sjkh char date[20]; 7429Sjkh struct hshentries *gendeltas; 7439Sjkh struct hshentry * target; 7449Sjkh int i; 7459Sjkh 7469Sjkh if (argc<2) { 7479Sjkh aputs("No input file\n",stderr); 7489Sjkh exitmain(EXIT_FAILURE); 7499Sjkh } 7509Sjkh if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) { 7519Sjkh faterror("can't open input file %s", argv[1]); 7529Sjkh } 7539Sjkh Lexinit(); 7549Sjkh getadmin(); 7559Sjkh 7569Sjkh gettree(); 7579Sjkh 7589Sjkh getdesc(false); 7599Sjkh 7609Sjkh do { 7619Sjkh /* all output goes to stderr, to have diagnostics and */ 7629Sjkh /* errors in sequence. */ 7639Sjkh aputs("\nEnter revision number or <return> or '.': ",stderr); 7649Sjkh if (!gets(symrevno)) break; 7659Sjkh if (*symrevno == '.') break; 7669Sjkh aprintf(stderr,"%s;\n",symrevno); 7679Sjkh expandsym(symrevno,&numricrevno); 7689Sjkh aprintf(stderr,"expanded number: %s; ",numricrevno.string); 7699Sjkh aprintf(stderr,"Date: "); 7709Sjkh gets(date); aprintf(stderr,"%s; ",date); 7719Sjkh aprintf(stderr,"Author: "); 7729Sjkh gets(author); aprintf(stderr,"%s; ",author); 7739Sjkh aprintf(stderr,"State: "); 7749Sjkh gets(state); aprintf(stderr, "%s;\n", state); 7759Sjkh target = genrevs(numricrevno.string, *date?date:(char *)nil, *author?author:(char *)nil, 7769Sjkh *state?state:(char*)nil, &gendeltas); 7779Sjkh if (target!=nil) { 7789Sjkh while (gendeltas) { 7799Sjkh aprintf(stderr,"%s\n",gendeltas->first->num); 7809Sjkh gendeltas = gendeltas->next; 7819Sjkh } 7829Sjkh } 7839Sjkh } while (true); 7849Sjkh aprintf(stderr,"done\n"); 7859Sjkh exitmain(EXIT_SUCCESS); 7869Sjkh} 7879Sjkh 7889Sjkhexiting void exiterr() { _exit(EXIT_FAILURE); } 7899Sjkh 7909Sjkh#endif 791