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