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