Home | History | Annotate | Line # | Download | only in src
      1 /*	$NetBSD: rcsrev.c,v 1.2 2016/01/14 04:22:39 christos Exp $	*/
      2 
      3 /* Handle RCS revision numbers.  */
      4 
      5 /* Copyright 1982, 1988, 1989 Walter Tichy
      6    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
      7    Distributed under license by the Free Software Foundation, Inc.
      8 
      9 This file is part of RCS.
     10 
     11 RCS is free software; you can redistribute it and/or modify
     12 it under the terms of the GNU General Public License as published by
     13 the Free Software Foundation; either version 2, or (at your option)
     14 any later version.
     15 
     16 RCS is distributed in the hope that it will be useful,
     17 but WITHOUT ANY WARRANTY; without even the implied warranty of
     18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     19 GNU General Public License for more details.
     20 
     21 You should have received a copy of the GNU General Public License
     22 along with RCS; see the file COPYING.
     23 If not, write to the Free Software Foundation,
     24 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
     25 
     26 Report problems and direct all questions to:
     27 
     28     rcs-bugs (at) cs.purdue.edu
     29 
     30 */
     31 
     32 /*
     33  * Log: rcsrev.c,v
     34  * Revision 5.10  1995/06/16 06:19:24  eggert
     35  * Update FSF address.
     36  *
     37  * Revision 5.9  1995/06/01 16:23:43  eggert
     38  * (cmpdate, normalizeyear): New functions work around MKS RCS incompatibility.
     39  * (cmpnum, compartial): s[d] -> *(s+d) to work around Cray compiler bug.
     40  * (genrevs, genbranch): cmpnum -> cmpdate
     41  *
     42  * Revision 5.8  1994/03/17 14:05:48  eggert
     43  * Remove lint.
     44  *
     45  * Revision 5.7  1993/11/09 17:40:15  eggert
     46  * Fix format string typos.
     47  *
     48  * Revision 5.6  1993/11/03 17:42:27  eggert
     49  * Revision number `.N' now stands for `D.N', where D is the default branch.
     50  * Add -z.  Improve quality of diagnostics.  Add `namedrev' for Name support.
     51  *
     52  * Revision 5.5  1992/07/28  16:12:44  eggert
     53  * Identifiers may now start with a digit.  Avoid `unsigned'.
     54  *
     55  * Revision 5.4  1992/01/06  02:42:34  eggert
     56  * while (E) ; -> while (E) continue;
     57  *
     58  * Revision 5.3  1991/08/19  03:13:55  eggert
     59  * Add `-r$', `-rB.'.  Remove botches like `<now>' from messages.  Tune.
     60  *
     61  * Revision 5.2  1991/04/21  11:58:28  eggert
     62  * Add tiprev().
     63  *
     64  * Revision 5.1  1991/02/25  07:12:43  eggert
     65  * Avoid overflow when comparing revision numbers.
     66  *
     67  * Revision 5.0  1990/08/22  08:13:43  eggert
     68  * Remove compile-time limits; use malloc instead.
     69  * Ansify and Posixate.  Tune.
     70  * Remove possibility of an internal error.  Remove lint.
     71  *
     72  * Revision 4.5  89/05/01  15:13:22  narten
     73  * changed copyright header to reflect current distribution rules
     74  *
     75  * Revision 4.4  87/12/18  11:45:22  narten
     76  * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
     77  * since there's now a return value there with a value. (Guy Harris)
     78  *
     79  * Revision 4.3  87/10/18  10:38:42  narten
     80  * Updating version numbers. Changes relative to version 1.1 actually
     81  * relative to 4.1
     82  *
     83  * Revision 1.3  87/09/24  14:00:37  narten
     84  * Sources now pass through lint (if you ignore printf/sprintf/fprintf
     85  * warnings)
     86  *
     87  * Revision 1.2  87/03/27  14:22:37  jenkins
     88  * Port to suns
     89  *
     90  * Revision 4.1  83/03/25  21:10:45  wft
     91  * Only changed Header to Id.
     92  *
     93  * Revision 3.4  82/12/04  13:24:08  wft
     94  * Replaced getdelta() with gettree().
     95  *
     96  * Revision 3.3  82/11/28  21:33:15  wft
     97  * fixed compartial() and compnum() for nil-parameters; fixed nils
     98  * in error messages. Testprogram output shortenend.
     99  *
    100  * Revision 3.2  82/10/18  21:19:47  wft
    101  * renamed compnum->cmpnum, compnumfld->cmpnumfld,
    102  * numericrevno->numricrevno.
    103  *
    104  * Revision 3.1  82/10/11  19:46:09  wft
    105  * changed expandsym() to check for source==nil; returns zero length string
    106  * in that case.
    107  */
    108 
    109 #include "rcsbase.h"
    110 
    111 libId(revId, "Id: rcsrev.c,v 5.10 1995/06/16 06:19:24 eggert Exp ")
    112 
    113 static char const *branchtip P((char const*));
    114 static char const *lookupsym P((char const*));
    115 static char const *normalizeyear P((char const*,char[5]));
    116 static struct hshentry *genbranch P((struct hshentry const*,char const*,int,char const*,char const*,char const*,struct hshentries**));
    117 static void absent P((char const*,int));
    118 static void cantfindbranch P((char const*,char const[datesize],char const*,char const*));
    119 static void store1 P((struct hshentries***,struct hshentry*));
    120 
    121 
    122 
    123 	int
    124 countnumflds(s)
    125 	char const *s;
    126 /* Given a pointer s to a dotted number (date or revision number),
    127  * countnumflds returns the number of digitfields in s.
    128  */
    129 {
    130 	register char const *sp;
    131 	register int count;
    132 	if (!(sp=s) || !*sp)
    133 		return 0;
    134         count = 1;
    135 	do {
    136                 if (*sp++ == '.') count++;
    137 	} while (*sp);
    138         return(count);
    139 }
    140 
    141 	void
    142 getbranchno(revno,branchno)
    143 	char const *revno;
    144 	struct buf *branchno;
    145 /* Given a revision number revno, getbranchno copies the number of the branch
    146  * on which revno is into branchno. If revno itself is a branch number,
    147  * it is copied unchanged.
    148  */
    149 {
    150 	register int numflds;
    151 	register char *tp;
    152 
    153 	bufscpy(branchno, revno);
    154         numflds=countnumflds(revno);
    155 	if (!(numflds & 1)) {
    156 		tp = branchno->string;
    157 		while (--numflds)
    158 			while (*tp++ != '.')
    159 				continue;
    160                 *(tp-1)='\0';
    161         }
    162 }
    163 
    164 
    165 
    166 int cmpnum(num1, num2)
    167 	char const *num1, *num2;
    168 /* compares the two dotted numbers num1 and num2 lexicographically
    169  * by field. Individual fields are compared numerically.
    170  * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
    171  * omitted fields are assumed to be higher than the existing ones.
    172 */
    173 {
    174 	register char const *s1, *s2;
    175 	register size_t d1, d2;
    176 	register int r;
    177 
    178 	s1 = num1 ? num1 : "";
    179 	s2 = num2 ? num2 : "";
    180 
    181 	for (;;) {
    182 		/* Give precedence to shorter one.  */
    183 		if (!*s1)
    184 			return (unsigned char)*s2;
    185 		if (!*s2)
    186 			return -1;
    187 
    188 		/* Strip leading zeros, then find number of digits.  */
    189 		while (*s1=='0') ++s1;
    190 		while (*s2=='0') ++s2;
    191 		for (d1=0; isdigit(*(s1+d1)); d1++) continue;
    192 		for (d2=0; isdigit(*(s2+d2)); d2++) continue;
    193 
    194 		/* Do not convert to integer; it might overflow!  */
    195 		if (d1 != d2)
    196 			return d1<d2 ? -1 : 1;
    197 		if ((r = memcmp(s1, s2, d1)))
    198 			return r;
    199 		s1 += d1;
    200 		s2 += d1;
    201 
    202                 /* skip '.' */
    203 		if (*s1) s1++;
    204 		if (*s2) s2++;
    205 	}
    206 }
    207 
    208 
    209 
    210 int cmpnumfld(num1, num2, fld)
    211 	char const *num1, *num2;
    212 	int fld;
    213 /* Compare the two dotted numbers at field fld.
    214  * num1 and num2 must have at least fld fields.
    215  * fld must be positive.
    216 */
    217 {
    218 	register char const *s1, *s2;
    219 	register size_t d1, d2;
    220 
    221 	s1 = num1;
    222 	s2 = num2;
    223         /* skip fld-1 fields */
    224 	while (--fld) {
    225 		while (*s1++ != '.')
    226 			continue;
    227 		while (*s2++ != '.')
    228 			continue;
    229 	}
    230         /* Now s1 and s2 point to the beginning of the respective fields */
    231 	while (*s1=='0') ++s1;  for (d1=0; isdigit(*(s1+d1)); d1++) continue;
    232 	while (*s2=='0') ++s2;  for (d2=0; isdigit(*(s2+d2)); d2++) continue;
    233 
    234 	return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
    235 }
    236 
    237 
    238 	int
    239 cmpdate(d1, d2)
    240 	char const *d1, *d2;
    241 /*
    242 * Compare the two dates.  This is just like cmpnum,
    243 * except that for compatibility with old versions of RCS,
    244 * 1900 is added to dates with two-digit years.
    245 */
    246 {
    247 	char year1[5], year2[5];
    248 	int r = cmpnumfld(normalizeyear(d1,year1), normalizeyear(d2,year2), 1);
    249 
    250 	if (r)
    251 		return r;
    252 	else {
    253 		while (isdigit(*d1)) d1++;  d1 += *d1=='.';
    254 		while (isdigit(*d2)) d2++;  d2 += *d2=='.';
    255 		return cmpnum(d1, d2);
    256 	}
    257 }
    258 
    259 	static char const *
    260 normalizeyear(date, year)
    261 	char const *date;
    262 	char year[5];
    263 {
    264 	if (isdigit(date[0]) && isdigit(date[1]) && !isdigit(date[2])) {
    265 		year[0] = '1';
    266 		year[1] = '9';
    267 		year[2] = date[0];
    268 		year[3] = date[1];
    269 		year[4] = 0;
    270 		return year;
    271 	} else
    272 		return date;
    273 }
    274 
    275 
    276 	static void
    277 cantfindbranch(revno, date, author, state)
    278 	char const *revno, date[datesize], *author, *state;
    279 {
    280 	char datebuf[datesize + zonelenmax];
    281 
    282 	rcserror("No revision on branch %s has%s%s%s%s%s%s.",
    283 		revno,
    284 		date ? " a date before " : "",
    285 		date ? date2str(date,datebuf) : "",
    286 		author ? " and author "+(date?0:4) : "",
    287 		author ? author : "",
    288 		state ? " and state "+(date||author?0:4) : "",
    289 		state ? state : ""
    290 	);
    291 }
    292 
    293 	static void
    294 absent(revno, field)
    295 	char const *revno;
    296 	int field;
    297 {
    298 	struct buf t;
    299 	bufautobegin(&t);
    300 	rcserror("%s %s absent", field&1?"revision":"branch",
    301 		partialno(&t,revno,field)
    302 	);
    303 	bufautoend(&t);
    304 }
    305 
    306 
    307 	int
    308 compartial(num1, num2, length)
    309 	char const *num1, *num2;
    310 	int length;
    311 
    312 /*   compare the first "length" fields of two dot numbers;
    313      the omitted field is considered to be larger than any number  */
    314 /*   restriction:  at least one number has length or more fields   */
    315 
    316 {
    317 	register char const *s1, *s2;
    318 	register size_t d1, d2;
    319 	register int r;
    320 
    321         s1 = num1;      s2 = num2;
    322 	if (!s1) return 1;
    323 	if (!s2) return -1;
    324 
    325 	for (;;) {
    326 	    if (!*s1) return 1;
    327 	    if (!*s2) return -1;
    328 
    329 	    while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue;
    330 	    while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue;
    331 
    332 	    if (d1 != d2)
    333 		    return d1<d2 ? -1 : 1;
    334 	    if ((r = memcmp(s1, s2, d1)))
    335 		    return r;
    336 	    if (!--length)
    337 		    return 0;
    338 
    339 	    s1 += d1;
    340 	    s2 += d1;
    341 
    342 	    if (*s1 == '.') s1++;
    343             if (*s2 == '.') s2++;
    344 	}
    345 }
    346 
    347 
    348 char * partialno(rev1,rev2,length)
    349 	struct buf *rev1;
    350 	char const *rev2;
    351 	register int length;
    352 /* Function: Copies length fields of revision number rev2 into rev1.
    353  * Return rev1's string.
    354  */
    355 {
    356 	register char *r1;
    357 
    358 	bufscpy(rev1, rev2);
    359 	r1 = rev1->string;
    360         while (length) {
    361 		while (*r1!='.' && *r1)
    362 			++r1;
    363 		++r1;
    364                 length--;
    365         }
    366         /* eliminate last '.'*/
    367         *(r1-1)='\0';
    368 	return rev1->string;
    369 }
    370 
    371 
    372 
    373 
    374 	static void
    375 store1(store, next)
    376 	struct hshentries ***store;
    377 	struct hshentry *next;
    378 /*
    379  * Allocate a new list node that addresses NEXT.
    380  * Append it to the list that **STORE is the end pointer of.
    381  */
    382 {
    383 	register struct hshentries *p;
    384 
    385 	p = ftalloc(struct hshentries);
    386 	p->first = next;
    387 	**store = p;
    388 	*store = &p->rest;
    389 }
    390 
    391 struct hshentry * genrevs(revno,date,author,state,store)
    392 	char const *revno, *date, *author, *state;
    393 	struct hshentries **store;
    394 /* Function: finds the deltas needed for reconstructing the
    395  * revision given by revno, date, author, and state, and stores pointers
    396  * to these deltas into a list whose starting address is given by store.
    397  * The last delta (target delta) is returned.
    398  * If the proper delta could not be found, 0 is returned.
    399  */
    400 {
    401 	int length;
    402         register struct hshentry * next;
    403         int result;
    404 	char const *branchnum;
    405 	struct buf t;
    406 	char datebuf[datesize + zonelenmax];
    407 
    408 	bufautobegin(&t);
    409 
    410 	if (!(next = Head)) {
    411 		rcserror("RCS file empty");
    412 		goto norev;
    413         }
    414 
    415         length = countnumflds(revno);
    416 
    417         if (length >= 1) {
    418                 /* at least one field; find branch exactly */
    419 		while ((result=cmpnumfld(revno,next->num,1)) < 0) {
    420 			store1(&store, next);
    421                         next = next->next;
    422 			if (!next) {
    423 			    rcserror("branch number %s too low", partialno(&t,revno,1));
    424 			    goto norev;
    425 			}
    426                 }
    427 
    428 		if (result>0) {
    429 			absent(revno, 1);
    430 			goto norev;
    431 		}
    432         }
    433         if (length<=1){
    434                 /* pick latest one on given branch */
    435                 branchnum = next->num; /* works even for empty revno*/
    436 		while (next &&
    437 		       cmpnumfld(branchnum,next->num,1) == 0 &&
    438 		       (
    439 			(date && cmpdate(date,next->date) < 0) ||
    440 			(author && strcmp(author,next->author) != 0) ||
    441 			(state && strcmp(state,next->state) != 0)
    442 		       )
    443 		      )
    444 		{
    445 			store1(&store, next);
    446                         next=next->next;
    447                 }
    448 		if (!next ||
    449                     (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
    450 			cantfindbranch(
    451 				length ? revno : partialno(&t,branchnum,1),
    452 				date, author, state
    453 			);
    454 			goto norev;
    455                 } else {
    456 			store1(&store, next);
    457                 }
    458 		*store = 0;
    459                 return next;
    460         }
    461 
    462         /* length >=2 */
    463         /* find revision; may go low if length==2*/
    464 	while ((result=cmpnumfld(revno,next->num,2)) < 0  &&
    465                (cmpnumfld(revno,next->num,1)==0) ) {
    466 		store1(&store, next);
    467                 next = next->next;
    468 		if (!next)
    469 			break;
    470         }
    471 
    472 	if (!next || cmpnumfld(revno,next->num,1) != 0) {
    473 		rcserror("revision number %s too low", partialno(&t,revno,2));
    474 		goto norev;
    475         }
    476         if ((length>2) && (result!=0)) {
    477 		absent(revno, 2);
    478 		goto norev;
    479         }
    480 
    481         /* print last one */
    482 	store1(&store, next);
    483 
    484         if (length>2)
    485                 return genbranch(next,revno,length,date,author,state,store);
    486         else { /* length == 2*/
    487 		if (date && cmpdate(date,next->date)<0) {
    488 			rcserror("Revision %s has date %s.",
    489 				next->num,
    490 				date2str(next->date, datebuf)
    491 			);
    492 			return 0;
    493 		}
    494 		if (author && strcmp(author,next->author)!=0) {
    495 			rcserror("Revision %s has author %s.",
    496 				next->num, next->author
    497 			);
    498 			return 0;
    499                 }
    500 		if (state && strcmp(state,next->state)!=0) {
    501 			rcserror("Revision %s has state %s.",
    502 				next->num,
    503 				next->state ? next->state : "<empty>"
    504 			);
    505 			return 0;
    506                 }
    507 		*store = 0;
    508                 return next;
    509         }
    510 
    511     norev:
    512 	bufautoend(&t);
    513 	return 0;
    514 }
    515 
    516 
    517 
    518 
    519 	static struct hshentry *
    520 genbranch(bpoint, revno, length, date, author, state, store)
    521 	struct hshentry const *bpoint;
    522 	char const *revno;
    523 	int length;
    524 	char const *date, *author, *state;
    525 	struct hshentries **store;
    526 /* Function: given a branchpoint, a revision number, date, author, and state,
    527  * genbranch finds the deltas necessary to reconstruct the given revision
    528  * from the branch point on.
    529  * Pointers to the found deltas are stored in a list beginning with store.
    530  * revno must be on a side branch.
    531  * Return 0 on error.
    532  */
    533 {
    534 	int field;
    535         register struct hshentry * next, * trail;
    536 	register struct branchhead const *bhead;
    537         int result;
    538 	struct buf t;
    539 	char datebuf[datesize + zonelenmax];
    540 
    541 	field = 3;
    542         bhead = bpoint->branches;
    543 
    544 	do {
    545 		if (!bhead) {
    546 			bufautobegin(&t);
    547 			rcserror("no side branches present for %s",
    548 				partialno(&t,revno,field-1)
    549 			);
    550 			bufautoend(&t);
    551 			return 0;
    552 		}
    553 
    554                 /*find branch head*/
    555                 /*branches are arranged in increasing order*/
    556 		while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
    557                         bhead = bhead->nextbranch;
    558 			if (!bhead) {
    559 			    bufautobegin(&t);
    560 			    rcserror("branch number %s too high",
    561 				partialno(&t,revno,field)
    562 			    );
    563 			    bufautoend(&t);
    564 			    return 0;
    565 			}
    566                 }
    567 
    568 		if (result<0) {
    569 		    absent(revno, field);
    570 		    return 0;
    571 		}
    572 
    573                 next = bhead->hsh;
    574                 if (length==field) {
    575                         /* pick latest one on that branch */
    576 			trail = 0;
    577 			do { if ((!date || cmpdate(date,next->date)>=0) &&
    578 				 (!author || strcmp(author,next->author)==0) &&
    579 				 (!state || strcmp(state,next->state)==0)
    580                              ) trail = next;
    581                              next=next->next;
    582 			} while (next);
    583 
    584 			if (!trail) {
    585 			     cantfindbranch(revno, date, author, state);
    586 			     return 0;
    587                         } else { /* print up to last one suitable */
    588                              next = bhead->hsh;
    589                              while (next!=trail) {
    590 				  store1(&store, next);
    591                                   next=next->next;
    592                              }
    593 			     store1(&store, next);
    594                         }
    595 			*store = 0;
    596                         return next;
    597                 }
    598 
    599                 /* length > field */
    600                 /* find revision */
    601                 /* check low */
    602                 if (cmpnumfld(revno,next->num,field+1)<0) {
    603 			bufautobegin(&t);
    604 			rcserror("revision number %s too low",
    605 				partialno(&t,revno,field+1)
    606 			);
    607 			bufautoend(&t);
    608 			return 0;
    609                 }
    610 		do {
    611 			store1(&store, next);
    612                         trail = next;
    613                         next = next->next;
    614 		} while (next && cmpnumfld(revno,next->num,field+1)>=0);
    615 
    616                 if ((length>field+1) &&  /*need exact hit */
    617                     (cmpnumfld(revno,trail->num,field+1) !=0)){
    618 			absent(revno, field+1);
    619 			return 0;
    620                 }
    621                 if (length == field+1) {
    622 			if (date && cmpdate(date,trail->date)<0) {
    623 				rcserror("Revision %s has date %s.",
    624 					trail->num,
    625 					date2str(trail->date, datebuf)
    626 				);
    627 				return 0;
    628                         }
    629 			if (author && strcmp(author,trail->author)!=0) {
    630 				rcserror("Revision %s has author %s.",
    631 					trail->num, trail->author
    632 				);
    633 				return 0;
    634                         }
    635 			if (state) {
    636 				const char *st;
    637 
    638 				if (trail->state == NULL)
    639 					st = "<empty>";
    640 				else if (strcmp(trail->state, state) != 0)
    641 					st = trail->state;
    642 				else
    643 					st = NULL;
    644 
    645 				if (st)
    646 					rcserror("Revision %s has state %s.",
    647 					    trail->num, st);
    648 				return 0;
    649                         }
    650                 }
    651                 bhead = trail->branches;
    652 
    653 	} while ((field+=2) <= length);
    654 	*store = 0;
    655         return trail;
    656 }
    657 
    658 
    659 	static char const *
    660 lookupsym(id)
    661 	char const *id;
    662 /* Function: looks up id in the list of symbolic names starting
    663  * with pointer SYMBOLS, and returns a pointer to the corresponding
    664  * revision number.  Return 0 if not present.
    665  */
    666 {
    667 	register struct assoc const *next;
    668 	for (next = Symbols;  next;  next = next->nextassoc)
    669                 if (strcmp(id, next->symbol)==0)
    670 			return next->num;
    671 	return 0;
    672 }
    673 
    674 int expandsym(source, target)
    675 	char const *source;
    676 	struct buf *target;
    677 /* Function: Source points to a revision number. Expandsym copies
    678  * the number to target, but replaces all symbolic fields in the
    679  * source number with their numeric values.
    680  * Expand a branch followed by `.' to the latest revision on that branch.
    681  * Ignore `.' after a revision.  Remove leading zeros.
    682  * returns false on error;
    683  */
    684 {
    685 	return fexpandsym(source, target, (RILE*)0);
    686 }
    687 
    688 	int
    689 fexpandsym(source, target, fp)
    690 	char const *source;
    691 	struct buf *target;
    692 	RILE *fp;
    693 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM.  */
    694 {
    695 	register char const *sp, *bp;
    696 	register char *tp;
    697 	char const *tlim;
    698 	int dots;
    699 
    700 	sp = source;
    701 	bufalloc(target, 1);
    702 	tp = target->string;
    703 	if (!sp || !*sp) { /* Accept 0 pointer as a legal value.  */
    704                 *tp='\0';
    705                 return true;
    706         }
    707 	if (sp[0] == KDELIM  &&  !sp[1]) {
    708 		if (!getoldkeys(fp))
    709 			return false;
    710 		if (!*prevrev.string) {
    711 			workerror("working file lacks revision number");
    712 			return false;
    713 		}
    714 		bufscpy(target, prevrev.string);
    715 		return true;
    716 	}
    717 	tlim = tp + target->size;
    718 	dots = 0;
    719 
    720 	for (;;) {
    721 		register char *p = tp;
    722 		size_t s = tp - target->string;
    723 		int id = false;
    724 		for (;;) {
    725 		    switch (ctab[(unsigned char)*sp]) {
    726 			case IDCHAR:
    727 			case LETTER:
    728 			case Letter:
    729 			    id = true;
    730 			    /* fall into */
    731 			case DIGIT:
    732 			    if (tlim <= p)
    733 				    p = bufenlarge(target, &tlim);
    734 			    *p++ = *sp++;
    735 			    continue;
    736 
    737 			default:
    738 			    break;
    739 		    }
    740 		    break;
    741 		}
    742 		if (tlim <= p)
    743 			p = bufenlarge(target, &tlim);
    744 		*p = 0;
    745 		tp = target->string + s;
    746 
    747 		if (id) {
    748 			bp = lookupsym(tp);
    749 			if (!bp) {
    750 				rcserror("Symbolic name `%s' is undefined.",tp);
    751                                 return false;
    752                         }
    753 		} else {
    754 			/* skip leading zeros */
    755 			for (bp = tp;  *bp=='0' && isdigit(bp[1]);  bp++)
    756 				continue;
    757 
    758 			if (!*bp) {
    759 			    if (s || *sp!='.')
    760 				break;
    761 			    else {
    762 				/* Insert default branch before initial `.'.  */
    763 				char const *b;
    764 				if (Dbranch)
    765 				    b = Dbranch;
    766 				else if (Head)
    767 				    b = Head->num;
    768 				else
    769 				    break;
    770 				getbranchno(b, target);
    771 				bp = tp = target->string;
    772 				tlim = tp + target->size;
    773 			    }
    774 			}
    775 		}
    776 
    777 		while ((*tp++ = *bp++))
    778 			if (tlim <= tp)
    779 				tp = bufenlarge(target, &tlim);
    780 
    781 		switch (*sp++) {
    782 		    case '\0':
    783 			return true;
    784 
    785 		    case '.':
    786 			if (!*sp) {
    787 				if (dots & 1)
    788 					break;
    789 				if (!(bp = branchtip(target->string)))
    790 					return false;
    791 				bufscpy(target, bp);
    792 				return true;
    793 			}
    794 			++dots;
    795 			tp[-1] = '.';
    796 			continue;
    797 		}
    798 		break;
    799         }
    800 
    801 	rcserror("improper revision number: %s", source);
    802 	return false;
    803 }
    804 
    805 	char const *
    806 namedrev(name, delta)
    807 	char const *name;
    808 	struct hshentry *delta;
    809 /* Yield NAME if it names DELTA, 0 otherwise.  */
    810 {
    811 	if (name) {
    812 		char const *id = 0, *p, *val;
    813 		for (p = name;  ;  p++)
    814 			switch (ctab[(unsigned char)*p]) {
    815 				case IDCHAR:
    816 				case LETTER:
    817 				case Letter:
    818 					id = name;
    819 					break;
    820 
    821 				case DIGIT:
    822 					break;
    823 
    824 				case UNKN:
    825 					if (!*p && id &&
    826 						(val = lookupsym(id)) &&
    827 						strcmp(val, delta->num) == 0
    828 					)
    829 						return id;
    830 					/* fall into */
    831 				default:
    832 					return 0;
    833 			}
    834 	}
    835 	return 0;
    836 }
    837 
    838 	static char const *
    839 branchtip(branch)
    840 	char const *branch;
    841 {
    842 	struct hshentry *h;
    843 	struct hshentries *hs;
    844 
    845 	h  =  genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
    846 	return h ? h->num : (char const*)0;
    847 }
    848 
    849 	char const *
    850 tiprev()
    851 {
    852 	return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
    853 }
    854 
    855 
    856 
    857 #ifdef REVTEST
    858 
    859 /*
    860 * Test the routines that generate a sequence of delta numbers
    861 * needed to regenerate a given delta.
    862 */
    863 
    864 char const cmdid[] = "revtest";
    865 
    866 	int
    867 main(argc,argv)
    868 int argc; char * argv[];
    869 {
    870 	static struct buf numricrevno;
    871 	char symrevno[100];       /* used for input of revision numbers */
    872         char author[20];
    873         char state[20];
    874         char date[20];
    875 	struct hshentries *gendeltas;
    876         struct hshentry * target;
    877         int i;
    878 
    879         if (argc<2) {
    880 		aputs("No input file\n",stderr);
    881 		exitmain(EXIT_FAILURE);
    882         }
    883 	if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
    884 		faterror("can't open input file %s", argv[1]);
    885         }
    886         Lexinit();
    887         getadmin();
    888 
    889         gettree();
    890 
    891         getdesc(false);
    892 
    893         do {
    894                 /* all output goes to stderr, to have diagnostics and       */
    895                 /* errors in sequence.                                      */
    896 		aputs("\nEnter revision number or <return> or '.': ",stderr);
    897 		if (!gets(symrevno)) break;
    898                 if (*symrevno == '.') break;
    899 		aprintf(stderr,"%s;\n",symrevno);
    900 		expandsym(symrevno,&numricrevno);
    901 		aprintf(stderr,"expanded number: %s; ",numricrevno.string);
    902 		aprintf(stderr,"Date: ");
    903 		gets(date); aprintf(stderr,"%s; ",date);
    904 		aprintf(stderr,"Author: ");
    905 		gets(author); aprintf(stderr,"%s; ",author);
    906 		aprintf(stderr,"State: ");
    907 		gets(state); aprintf(stderr, "%s;\n", state);
    908 		target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0,
    909 				 *state?state:(char*)0, &gendeltas);
    910 		if (target) {
    911 			while (gendeltas) {
    912 				aprintf(stderr,"%s\n",gendeltas->first->num);
    913 				gendeltas = gendeltas->next;
    914                         }
    915                 }
    916         } while (true);
    917 	aprintf(stderr,"done\n");
    918 	exitmain(EXIT_SUCCESS);
    919 }
    920 
    921 void exiterr() { _exit(EXIT_FAILURE); }
    922 
    923 #endif
    924