xref: /dragonfly/gnu/usr.bin/rcs/lib/rcsrev.c (revision cfd1aba3)
1 /* Handle RCS revision numbers.  */
2 
3 /* Copyright 1982, 1988, 1989 Walter Tichy
4    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5    Distributed under license by the Free Software Foundation, Inc.
6 
7 This file is part of RCS.
8 
9 RCS is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
12 any later version.
13 
14 RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RCS; see the file COPYING.
21 If not, write to the Free Software Foundation,
22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 
24 Report problems and direct all questions to:
25 
26     rcs-bugs@cs.purdue.edu
27 
28 */
29 
30 /*
31  * $FreeBSD: src/gnu/usr.bin/rcs/lib/rcsrev.c,v 1.8 1999/08/27 23:36:48 peter Exp $
32  * $DragonFly: src/gnu/usr.bin/rcs/lib/rcsrev.c,v 1.2 2003/06/17 04:25:47 dillon Exp $
33  *
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, "$DragonFly: src/gnu/usr.bin/rcs/lib/rcsrev.c,v 1.2 2003/06/17 04:25:47 dillon 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 && strcmp(state,trail->state)!=0) {
636 				rcserror("Revision %s has state %s.",
637 					trail->num,
638 					trail->state ? trail->state : "<empty>"
639 				);
640 				return 0;
641                         }
642                 }
643                 bhead = trail->branches;
644 
645 	} while ((field+=2) <= length);
646 	*store = 0;
647         return trail;
648 }
649 
650 
651 	static char const *
652 lookupsym(id)
653 	char const *id;
654 /* Function: looks up id in the list of symbolic names starting
655  * with pointer SYMBOLS, and returns a pointer to the corresponding
656  * revision number.  Return 0 if not present.
657  */
658 {
659 	register struct assoc const *next;
660 	for (next = Symbols;  next;  next = next->nextassoc)
661                 if (strcmp(id, next->symbol)==0)
662 			return next->num;
663 	return 0;
664 }
665 
666 int expandsym(source, target)
667 	char const *source;
668 	struct buf *target;
669 /* Function: Source points to a revision number. Expandsym copies
670  * the number to target, but replaces all symbolic fields in the
671  * source number with their numeric values.
672  * Expand a branch followed by `.' to the latest revision on that branch.
673  * Ignore `.' after a revision.  Remove leading zeros.
674  * returns false on error;
675  */
676 {
677 	return fexpandsym(source, target, (RILE*)0);
678 }
679 
680 	int
681 fexpandsym(source, target, fp)
682 	char const *source;
683 	struct buf *target;
684 	RILE *fp;
685 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM.  */
686 {
687 	register char const *sp, *bp;
688 	register char *tp;
689 	char const *tlim;
690 	int dots;
691 
692 	sp = source;
693 	bufalloc(target, 1);
694 	tp = target->string;
695 	if (!sp || !*sp) { /* Accept 0 pointer as a legal value.  */
696                 *tp='\0';
697                 return true;
698         }
699 	if (sp[0] == KDELIM  &&  !sp[1]) {
700 		if (!getoldkeys(fp))
701 			return false;
702 		if (!*prevrev.string) {
703 			workerror("working file lacks revision number");
704 			return false;
705 		}
706 		bufscpy(target, prevrev.string);
707 		return true;
708 	}
709 	tlim = tp + target->size;
710 	dots = 0;
711 
712 	for (;;) {
713 		register char *p = tp;
714 		size_t s = tp - target->string;
715 		int id = false;
716 		for (;;) {
717 		    switch (ctab[(unsigned char)*sp]) {
718 			case IDCHAR:
719 			case LETTER:
720 			case Letter:
721 			    id = true;
722 			    /* fall into */
723 			case DIGIT:
724 			    if (tlim <= p)
725 				    p = bufenlarge(target, &tlim);
726 			    *p++ = *sp++;
727 			    continue;
728 
729 			default:
730 			    break;
731 		    }
732 		    break;
733 		}
734 		if (tlim <= p)
735 			p = bufenlarge(target, &tlim);
736 		*p = 0;
737 		tp = target->string + s;
738 
739 		if (id) {
740 			bp = lookupsym(tp);
741 			if (!bp) {
742 				rcserror("Symbolic name `%s' is undefined.",tp);
743                                 return false;
744                         }
745 		} else {
746 			/* skip leading zeros */
747 			for (bp = tp;  *bp=='0' && isdigit(bp[1]);  bp++)
748 				continue;
749 
750 			if (!*bp) {
751 			    if (s || *sp!='.')
752 				break;
753 			    else {
754 				/* Insert default branch before initial `.'.  */
755 				char const *b;
756 				if (Dbranch)
757 				    b = Dbranch;
758 				else if (Head)
759 				    b = Head->num;
760 				else
761 				    break;
762 				getbranchno(b, target);
763 				bp = tp = target->string;
764 				tlim = tp + target->size;
765 			    }
766 			}
767 		}
768 
769 		while ((*tp++ = *bp++))
770 			if (tlim <= tp)
771 				tp = bufenlarge(target, &tlim);
772 
773 		switch (*sp++) {
774 		    case '\0':
775 			return true;
776 
777 		    case '.':
778 			if (!*sp) {
779 				if (dots & 1)
780 					break;
781 				if (!(bp = branchtip(target->string)))
782 					return false;
783 				bufscpy(target, bp);
784 				return true;
785 			}
786 			++dots;
787 			tp[-1] = '.';
788 			continue;
789 		}
790 		break;
791         }
792 
793 	rcserror("improper revision number: %s", source);
794 	return false;
795 }
796 
797 	char const *
798 namedrev(name, delta)
799 	char const *name;
800 	struct hshentry *delta;
801 /* Yield NAME if it names DELTA, 0 otherwise.  */
802 {
803 	if (name) {
804 		char const *id = 0, *p, *val;
805 		for (p = name;  ;  p++)
806 			switch (ctab[(unsigned char)*p]) {
807 				case IDCHAR:
808 				case LETTER:
809 				case Letter:
810 					id = name;
811 					break;
812 
813 				case DIGIT:
814 					break;
815 
816 				case UNKN:
817 					if (!*p && id &&
818 						(val = lookupsym(id)) &&
819 						strcmp(val, delta->num) == 0
820 					)
821 						return id;
822 					/* fall into */
823 				default:
824 					return 0;
825 			}
826 	}
827 	return 0;
828 }
829 
830 	static char const *
831 branchtip(branch)
832 	char const *branch;
833 {
834 	struct hshentry *h;
835 	struct hshentries *hs;
836 
837 	h  =  genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
838 	return h ? h->num : (char const*)0;
839 }
840 
841 	char const *
842 tiprev()
843 {
844 	return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
845 }
846 
847 
848 
849 #ifdef REVTEST
850 
851 /*
852 * Test the routines that generate a sequence of delta numbers
853 * needed to regenerate a given delta.
854 */
855 
856 char const cmdid[] = "revtest";
857 
858 	int
859 main(argc,argv)
860 int argc; char * argv[];
861 {
862 	static struct buf numricrevno;
863 	char symrevno[100];       /* used for input of revision numbers */
864         char author[20];
865         char state[20];
866         char date[20];
867 	struct hshentries *gendeltas;
868         struct hshentry * target;
869         int i;
870 
871         if (argc<2) {
872 		aputs("No input file\n",stderr);
873 		exitmain(EXIT_FAILURE);
874         }
875 	if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
876 		faterror("can't open input file %s", argv[1]);
877         }
878         Lexinit();
879         getadmin();
880 
881         gettree();
882 
883         getdesc(false);
884 
885         do {
886                 /* all output goes to stderr, to have diagnostics and       */
887                 /* errors in sequence.                                      */
888 		aputs("\nEnter revision number or <return> or '.': ",stderr);
889 		if (!fgets(symrevno, 100, stdin)) break;
890                 if (*symrevno == '.') break;
891 		aprintf(stderr,"%s;\n",symrevno);
892 		expandsym(symrevno,&numricrevno);
893 		aprintf(stderr,"expanded number: %s; ",numricrevno.string);
894 		aprintf(stderr,"Date: ");
895 		fgets(date, 20, stdin); aprintf(stderr,"%s; ",date);
896 		aprintf(stderr,"Author: ");
897 		fgets(author, 20, stdin); aprintf(stderr,"%s; ",author);
898 		aprintf(stderr,"State: ");
899 		fgets(state, 20, stdin); aprintf(stderr, "%s;\n", state);
900 		target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0,
901 				 *state?state:(char*)0, &gendeltas);
902 		if (target) {
903 			while (gendeltas) {
904 				aprintf(stderr,"%s\n",gendeltas->first->num);
905 				gendeltas = gendeltas->next;
906                         }
907                 }
908         } while (true);
909 	aprintf(stderr,"done\n");
910 	exitmain(EXIT_SUCCESS);
911 }
912 
913 void exiterr() { _exit(EXIT_FAILURE); }
914 
915 #endif
916