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
7This file is part of RCS.
8
9RCS is free software; you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 2, or (at your option)
12any later version.
13
14RCS is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with RCS; see the file COPYING.
21If not, write to the Free Software Foundation,
2259 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24Report 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
108libId(revId, "$FreeBSD$")
109
110static char const *branchtip P((char const*));
111static char const *lookupsym P((char const*));
112static char const *normalizeyear P((char const*,char[5]));
113static struct hshentry *genbranch P((struct hshentry const*,char const*,int,char const*,char const*,char const*,struct hshentries**));
114static void absent P((char const*,int));
115static void cantfindbranch P((char const*,char const[datesize],char const*,char const*));
116static void store1 P((struct hshentries***,struct hshentry*));
117
118
119
120	int
121countnumflds(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
139getbranchno(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
163int 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
207int 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
236cmpdate(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 *
257normalizeyear(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
274cantfindbranch(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
291absent(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
305compartial(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
345char * 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
372store1(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
388struct 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 *
517genbranch(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 *
649lookupsym(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
663int 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
678fexpandsym(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 *
794namedrev(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 *
827branchtip(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 *
838tiprev()
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
852char const cmdid[] = "revtest";
853
854	int
855main(argc,argv)
856int 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
909void exiterr() { _exit(EXIT_FAILURE); }
910
911#endif
912