xref: /dragonfly/gnu/usr.bin/rcs/lib/rcsrev.c (revision c37c9ab3)
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')
232 		++s1;
233 	for (d1=0; isdigit(*(s1+d1)); d1++)
234 		continue;
235 	while (*s2=='0')
236 		++s2;
237 	for (d2=0; isdigit(*(s2+d2)); d2++)
238 		continue;
239 
240 	return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
241 }
242 
243 
244 	int
245 cmpdate(d1, d2)
246 	char const *d1, *d2;
247 /*
248 * Compare the two dates.  This is just like cmpnum,
249 * except that for compatibility with old versions of RCS,
250 * 1900 is added to dates with two-digit years.
251 */
252 {
253 	char year1[5], year2[5];
254 	int r = cmpnumfld(normalizeyear(d1,year1), normalizeyear(d2,year2), 1);
255 
256 	if (r)
257 		return r;
258 	else {
259 		while (isdigit(*d1))
260 			d1++;
261 		d1 += *d1=='.';
262 		while (isdigit(*d2))
263 			d2++;
264 		d2 += *d2=='.';
265 		return cmpnum(d1, d2);
266 	}
267 }
268 
269 	static char const *
270 normalizeyear(date, year)
271 	char const *date;
272 	char year[5];
273 {
274 	if (isdigit(date[0]) && isdigit(date[1]) && !isdigit(date[2])) {
275 		year[0] = '1';
276 		year[1] = '9';
277 		year[2] = date[0];
278 		year[3] = date[1];
279 		year[4] = 0;
280 		return year;
281 	} else
282 		return date;
283 }
284 
285 
286 	static void
287 cantfindbranch(revno, date, author, state)
288 	char const *revno, date[datesize], *author, *state;
289 {
290 	char datebuf[datesize + zonelenmax];
291 
292 	rcserror("No revision on branch %s has%s%s%s%s%s%s.",
293 		revno,
294 		date ? " a date before " : "",
295 		date ? date2str(date,datebuf) : "",
296 		author ? " and author "+(date?0:4) : "",
297 		author ? author : "",
298 		state ? " and state "+(date||author?0:4) : "",
299 		state ? state : ""
300 	);
301 }
302 
303 	static void
304 absent(revno, field)
305 	char const *revno;
306 	int field;
307 {
308 	struct buf t;
309 	bufautobegin(&t);
310 	rcserror("%s %s absent", field&1?"revision":"branch",
311 		partialno(&t,revno,field)
312 	);
313 	bufautoend(&t);
314 }
315 
316 
317 	int
318 compartial(num1, num2, length)
319 	char const *num1, *num2;
320 	int length;
321 
322 /*   compare the first "length" fields of two dot numbers;
323      the omitted field is considered to be larger than any number  */
324 /*   restriction:  at least one number has length or more fields   */
325 
326 {
327 	register char const *s1, *s2;
328 	register size_t d1, d2;
329 	register int r;
330 
331         s1 = num1;      s2 = num2;
332 	if (!s1) return 1;
333 	if (!s2) return -1;
334 
335 	for (;;) {
336 	    if (!*s1) return 1;
337 	    if (!*s2) return -1;
338 
339 	    while (*s1=='0')
340 		    ++s1;
341 	    for (d1=0; isdigit(*(s1+d1)); d1++)
342 		    continue;
343 	    while (*s2=='0')
344 		    ++s2;
345 	    for (d2=0; isdigit(*(s2+d2)); d2++)
346 		    continue;
347 
348 	    if (d1 != d2)
349 		    return d1<d2 ? -1 : 1;
350 	    if ((r = memcmp(s1, s2, d1)))
351 		    return r;
352 	    if (!--length)
353 		    return 0;
354 
355 	    s1 += d1;
356 	    s2 += d1;
357 
358 	    if (*s1 == '.') s1++;
359             if (*s2 == '.') s2++;
360 	}
361 }
362 
363 
364 char * partialno(rev1,rev2,length)
365 	struct buf *rev1;
366 	char const *rev2;
367 	register int length;
368 /* Function: Copies length fields of revision number rev2 into rev1.
369  * Return rev1's string.
370  */
371 {
372 	register char *r1;
373 
374 	bufscpy(rev1, rev2);
375 	r1 = rev1->string;
376         while (length) {
377 		while (*r1!='.' && *r1)
378 			++r1;
379 		++r1;
380                 length--;
381         }
382         /* eliminate last '.'*/
383         *(r1-1)='\0';
384 	return rev1->string;
385 }
386 
387 
388 
389 
390 	static void
391 store1(store, next)
392 	struct hshentries ***store;
393 	struct hshentry *next;
394 /*
395  * Allocate a new list node that addresses NEXT.
396  * Append it to the list that **STORE is the end pointer of.
397  */
398 {
399 	register struct hshentries *p;
400 
401 	p = ftalloc(struct hshentries);
402 	p->first = next;
403 	**store = p;
404 	*store = &p->rest;
405 }
406 
407 struct hshentry * genrevs(revno,date,author,state,store)
408 	char const *revno, *date, *author, *state;
409 	struct hshentries **store;
410 /* Function: finds the deltas needed for reconstructing the
411  * revision given by revno, date, author, and state, and stores pointers
412  * to these deltas into a list whose starting address is given by store.
413  * The last delta (target delta) is returned.
414  * If the proper delta could not be found, 0 is returned.
415  */
416 {
417 	int length;
418         register struct hshentry * next;
419         int result;
420 	char const *branchnum;
421 	struct buf t;
422 	char datebuf[datesize + zonelenmax];
423 
424 	bufautobegin(&t);
425 
426 	if (!(next = Head)) {
427 		rcserror("RCS file empty");
428 		goto norev;
429         }
430 
431         length = countnumflds(revno);
432 
433         if (length >= 1) {
434                 /* at least one field; find branch exactly */
435 		while ((result=cmpnumfld(revno,next->num,1)) < 0) {
436 			store1(&store, next);
437                         next = next->next;
438 			if (!next) {
439 			    rcserror("branch number %s too low", partialno(&t,revno,1));
440 			    goto norev;
441 			}
442                 }
443 
444 		if (result>0) {
445 			absent(revno, 1);
446 			goto norev;
447 		}
448         }
449         if (length<=1){
450                 /* pick latest one on given branch */
451                 branchnum = next->num; /* works even for empty revno*/
452 		while (next &&
453 		       cmpnumfld(branchnum,next->num,1) == 0 &&
454 		       (
455 			(date && cmpdate(date,next->date) < 0) ||
456 			(author && strcmp(author,next->author) != 0) ||
457 			(state && strcmp(state,next->state) != 0)
458 		       )
459 		      )
460 		{
461 			store1(&store, next);
462                         next=next->next;
463                 }
464 		if (!next ||
465                     (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
466 			cantfindbranch(
467 				length ? revno : partialno(&t,branchnum,1),
468 				date, author, state
469 			);
470 			goto norev;
471                 } else {
472 			store1(&store, next);
473                 }
474 		*store = 0;
475                 return next;
476         }
477 
478         /* length >=2 */
479         /* find revision; may go low if length==2*/
480 	while ((result=cmpnumfld(revno,next->num,2)) < 0  &&
481                (cmpnumfld(revno,next->num,1)==0) ) {
482 		store1(&store, next);
483                 next = next->next;
484 		if (!next)
485 			break;
486         }
487 
488 	if (!next || cmpnumfld(revno,next->num,1) != 0) {
489 		rcserror("revision number %s too low", partialno(&t,revno,2));
490 		goto norev;
491         }
492         if ((length>2) && (result!=0)) {
493 		absent(revno, 2);
494 		goto norev;
495         }
496 
497         /* print last one */
498 	store1(&store, next);
499 
500         if (length>2)
501                 return genbranch(next,revno,length,date,author,state,store);
502         else { /* length == 2*/
503 		if (date && cmpdate(date,next->date)<0) {
504 			rcserror("Revision %s has date %s.",
505 				next->num,
506 				date2str(next->date, datebuf)
507 			);
508 			return 0;
509 		}
510 		if (author && strcmp(author,next->author)!=0) {
511 			rcserror("Revision %s has author %s.",
512 				next->num, next->author
513 			);
514 			return 0;
515                 }
516 		if (state && strcmp(state,next->state)!=0) {
517 			rcserror("Revision %s has state %s.",
518 				next->num,
519 				next->state ? next->state : "<empty>"
520 			);
521 			return 0;
522                 }
523 		*store = 0;
524                 return next;
525         }
526 
527     norev:
528 	bufautoend(&t);
529 	return 0;
530 }
531 
532 
533 
534 
535 	static struct hshentry *
536 genbranch(bpoint, revno, length, date, author, state, store)
537 	struct hshentry const *bpoint;
538 	char const *revno;
539 	int length;
540 	char const *date, *author, *state;
541 	struct hshentries **store;
542 /* Function: given a branchpoint, a revision number, date, author, and state,
543  * genbranch finds the deltas necessary to reconstruct the given revision
544  * from the branch point on.
545  * Pointers to the found deltas are stored in a list beginning with store.
546  * revno must be on a side branch.
547  * Return 0 on error.
548  */
549 {
550 	int field;
551         register struct hshentry * next, * trail;
552 	register struct branchhead const *bhead;
553         int result;
554 	struct buf t;
555 	char datebuf[datesize + zonelenmax];
556 
557 	field = 3;
558         bhead = bpoint->branches;
559 
560 	do {
561 		if (!bhead) {
562 			bufautobegin(&t);
563 			rcserror("no side branches present for %s",
564 				partialno(&t,revno,field-1)
565 			);
566 			bufautoend(&t);
567 			return 0;
568 		}
569 
570                 /*find branch head*/
571                 /*branches are arranged in increasing order*/
572 		while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
573                         bhead = bhead->nextbranch;
574 			if (!bhead) {
575 			    bufautobegin(&t);
576 			    rcserror("branch number %s too high",
577 				partialno(&t,revno,field)
578 			    );
579 			    bufautoend(&t);
580 			    return 0;
581 			}
582                 }
583 
584 		if (result<0) {
585 		    absent(revno, field);
586 		    return 0;
587 		}
588 
589                 next = bhead->hsh;
590                 if (length==field) {
591                         /* pick latest one on that branch */
592 			trail = 0;
593 			do { if ((!date || cmpdate(date,next->date)>=0) &&
594 				 (!author || strcmp(author,next->author)==0) &&
595 				 (!state || strcmp(state,next->state)==0)
596                              ) trail = next;
597                              next=next->next;
598 			} while (next);
599 
600 			if (!trail) {
601 			     cantfindbranch(revno, date, author, state);
602 			     return 0;
603                         } else { /* print up to last one suitable */
604                              next = bhead->hsh;
605                              while (next!=trail) {
606 				  store1(&store, next);
607                                   next=next->next;
608                              }
609 			     store1(&store, next);
610                         }
611 			*store = 0;
612                         return next;
613                 }
614 
615                 /* length > field */
616                 /* find revision */
617                 /* check low */
618                 if (cmpnumfld(revno,next->num,field+1)<0) {
619 			bufautobegin(&t);
620 			rcserror("revision number %s too low",
621 				partialno(&t,revno,field+1)
622 			);
623 			bufautoend(&t);
624 			return 0;
625                 }
626 		do {
627 			store1(&store, next);
628                         trail = next;
629                         next = next->next;
630 		} while (next && cmpnumfld(revno,next->num,field+1)>=0);
631 
632                 if ((length>field+1) &&  /*need exact hit */
633                     (cmpnumfld(revno,trail->num,field+1) !=0)){
634 			absent(revno, field+1);
635 			return 0;
636                 }
637                 if (length == field+1) {
638 			if (date && cmpdate(date,trail->date)<0) {
639 				rcserror("Revision %s has date %s.",
640 					trail->num,
641 					date2str(trail->date, datebuf)
642 				);
643 				return 0;
644                         }
645 			if (author && strcmp(author,trail->author)!=0) {
646 				rcserror("Revision %s has author %s.",
647 					trail->num, trail->author
648 				);
649 				return 0;
650                         }
651 			if (state && strcmp(state,trail->state)!=0) {
652 				rcserror("Revision %s has state %s.",
653 					trail->num,
654 					trail->state ? trail->state : "<empty>"
655 				);
656 				return 0;
657                         }
658                 }
659                 bhead = trail->branches;
660 
661 	} while ((field+=2) <= length);
662 	*store = 0;
663         return trail;
664 }
665 
666 
667 	static char const *
668 lookupsym(id)
669 	char const *id;
670 /* Function: looks up id in the list of symbolic names starting
671  * with pointer SYMBOLS, and returns a pointer to the corresponding
672  * revision number.  Return 0 if not present.
673  */
674 {
675 	register struct assoc const *next;
676 	for (next = Symbols;  next;  next = next->nextassoc)
677                 if (strcmp(id, next->symbol)==0)
678 			return next->num;
679 	return 0;
680 }
681 
682 int expandsym(source, target)
683 	char const *source;
684 	struct buf *target;
685 /* Function: Source points to a revision number. Expandsym copies
686  * the number to target, but replaces all symbolic fields in the
687  * source number with their numeric values.
688  * Expand a branch followed by `.' to the latest revision on that branch.
689  * Ignore `.' after a revision.  Remove leading zeros.
690  * returns false on error;
691  */
692 {
693 	return fexpandsym(source, target, (RILE*)0);
694 }
695 
696 	int
697 fexpandsym(source, target, fp)
698 	char const *source;
699 	struct buf *target;
700 	RILE *fp;
701 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM.  */
702 {
703 	register char const *sp, *bp;
704 	register char *tp;
705 	char const *tlim;
706 	int dots;
707 
708 	sp = source;
709 	bufalloc(target, 1);
710 	tp = target->string;
711 	if (!sp || !*sp) { /* Accept 0 pointer as a legal value.  */
712                 *tp='\0';
713                 return true;
714         }
715 	if (sp[0] == KDELIM  &&  !sp[1]) {
716 		if (!getoldkeys(fp))
717 			return false;
718 		if (!*prevrev.string) {
719 			workerror("working file lacks revision number");
720 			return false;
721 		}
722 		bufscpy(target, prevrev.string);
723 		return true;
724 	}
725 	tlim = tp + target->size;
726 	dots = 0;
727 
728 	for (;;) {
729 		register char *p = tp;
730 		size_t s = tp - target->string;
731 		int id = false;
732 		for (;;) {
733 		    switch (ctab[(unsigned char)*sp]) {
734 			case IDCHAR:
735 			case LETTER:
736 			case Letter:
737 			    id = true;
738 			    /* fall into */
739 			case DIGIT:
740 			    if (tlim <= p)
741 				    p = bufenlarge(target, &tlim);
742 			    *p++ = *sp++;
743 			    continue;
744 
745 			default:
746 			    break;
747 		    }
748 		    break;
749 		}
750 		if (tlim <= p)
751 			p = bufenlarge(target, &tlim);
752 		*p = 0;
753 		tp = target->string + s;
754 
755 		if (id) {
756 			bp = lookupsym(tp);
757 			if (!bp) {
758 				rcserror("Symbolic name `%s' is undefined.",tp);
759                                 return false;
760                         }
761 		} else {
762 			/* skip leading zeros */
763 			for (bp = tp;  *bp=='0' && isdigit(bp[1]);  bp++)
764 				continue;
765 
766 			if (!*bp) {
767 			    if (s || *sp!='.')
768 				break;
769 			    else {
770 				/* Insert default branch before initial `.'.  */
771 				char const *b;
772 				if (Dbranch)
773 				    b = Dbranch;
774 				else if (Head)
775 				    b = Head->num;
776 				else
777 				    break;
778 				getbranchno(b, target);
779 				bp = tp = target->string;
780 				tlim = tp + target->size;
781 			    }
782 			}
783 		}
784 
785 		while ((*tp++ = *bp++))
786 			if (tlim <= tp)
787 				tp = bufenlarge(target, &tlim);
788 
789 		switch (*sp++) {
790 		    case '\0':
791 			return true;
792 
793 		    case '.':
794 			if (!*sp) {
795 				if (dots & 1)
796 					break;
797 				if (!(bp = branchtip(target->string)))
798 					return false;
799 				bufscpy(target, bp);
800 				return true;
801 			}
802 			++dots;
803 			tp[-1] = '.';
804 			continue;
805 		}
806 		break;
807         }
808 
809 	rcserror("improper revision number: %s", source);
810 	return false;
811 }
812 
813 	char const *
814 namedrev(name, delta)
815 	char const *name;
816 	struct hshentry *delta;
817 /* Yield NAME if it names DELTA, 0 otherwise.  */
818 {
819 	if (name) {
820 		char const *id = 0, *p, *val;
821 		for (p = name;  ;  p++)
822 			switch (ctab[(unsigned char)*p]) {
823 				case IDCHAR:
824 				case LETTER:
825 				case Letter:
826 					id = name;
827 					break;
828 
829 				case DIGIT:
830 					break;
831 
832 				case UNKN:
833 					if (!*p && id &&
834 						(val = lookupsym(id)) &&
835 						strcmp(val, delta->num) == 0
836 					)
837 						return id;
838 					/* fall into */
839 				default:
840 					return 0;
841 			}
842 	}
843 	return 0;
844 }
845 
846 	static char const *
847 branchtip(branch)
848 	char const *branch;
849 {
850 	struct hshentry *h;
851 	struct hshentries *hs;
852 
853 	h  =  genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
854 	return h ? h->num : (char const*)0;
855 }
856 
857 	char const *
858 tiprev()
859 {
860 	return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
861 }
862 
863 
864 
865 #ifdef REVTEST
866 
867 /*
868 * Test the routines that generate a sequence of delta numbers
869 * needed to regenerate a given delta.
870 */
871 
872 char const cmdid[] = "revtest";
873 
874 	int
875 main(argc,argv)
876 int argc; char * argv[];
877 {
878 	static struct buf numricrevno;
879 	char symrevno[100];       /* used for input of revision numbers */
880         char author[20];
881         char state[20];
882         char date[20];
883 	struct hshentries *gendeltas;
884         struct hshentry * target;
885         int i;
886 
887         if (argc<2) {
888 		aputs("No input file\n",stderr);
889 		exitmain(EXIT_FAILURE);
890         }
891 	if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
892 		faterror("can't open input file %s", argv[1]);
893         }
894         Lexinit();
895         getadmin();
896 
897         gettree();
898 
899         getdesc(false);
900 
901         do {
902                 /* all output goes to stderr, to have diagnostics and       */
903                 /* errors in sequence.                                      */
904 		aputs("\nEnter revision number or <return> or '.': ",stderr);
905 		if (!fgets(symrevno, 100, stdin)) break;
906                 if (*symrevno == '.') break;
907 		aprintf(stderr,"%s;\n",symrevno);
908 		expandsym(symrevno,&numricrevno);
909 		aprintf(stderr,"expanded number: %s; ",numricrevno.string);
910 		aprintf(stderr,"Date: ");
911 		fgets(date, 20, stdin); aprintf(stderr,"%s; ",date);
912 		aprintf(stderr,"Author: ");
913 		fgets(author, 20, stdin); aprintf(stderr,"%s; ",author);
914 		aprintf(stderr,"State: ");
915 		fgets(state, 20, stdin); aprintf(stderr, "%s;\n", state);
916 		target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0,
917 				 *state?state:(char*)0, &gendeltas);
918 		if (target) {
919 			while (gendeltas) {
920 				aprintf(stderr,"%s\n",gendeltas->first->num);
921 				gendeltas = gendeltas->next;
922                         }
923                 }
924         } while (true);
925 	aprintf(stderr,"done\n");
926 	exitmain(EXIT_SUCCESS);
927 }
928 
929 void exiterr() { _exit(EXIT_FAILURE); }
930 
931 #endif
932