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