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