1284345Ssjg/* Handle RCS revision numbers.  */
2284345Ssjg
3284345Ssjg/* Copyright 1982, 1988, 1989 Walter Tichy
4284345Ssjg   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5284345Ssjg   Distributed under license by the Free Software Foundation, Inc.
6284345Ssjg
7284345SsjgThis file is part of RCS.
8284345Ssjg
9284345SsjgRCS is free software; you can redistribute it and/or modify
10284345Ssjgit under the terms of the GNU General Public License as published by
11296127Sbdrewerythe Free Software Foundation; either version 2, or (at your option)
12284345Ssjgany later version.
13291563Sbdrewery
14284345SsjgRCS is distributed in the hope that it will be useful,
15284345Ssjgbut WITHOUT ANY WARRANTY; without even the implied warranty of
16284345SsjgMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17284345SsjgGNU General Public License for more details.
18284345Ssjg
19284345SsjgYou should have received a copy of the GNU General Public License
20284345Ssjgalong 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