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