]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - gnu/usr.bin/rcs/lib/rcsrev.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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  * 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
108 libId(revId, "$FreeBSD$")
109
110 static char const *branchtip P((char const*));
111 static char const *lookupsym P((char const*));
112 static char const *normalizeyear P((char const*,char[5]));
113 static struct hshentry *genbranch P((struct hshentry const*,char const*,int,char const*,char const*,char const*,struct hshentries**));
114 static void absent P((char const*,int));
115 static void cantfindbranch P((char const*,char const[datesize],char const*,char const*));
116 static void store1 P((struct hshentries***,struct hshentry*));
117
118
119
120         int
121 countnumflds(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
139 getbranchno(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
163 int 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
207 int 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
236 cmpdate(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 *
257 normalizeyear(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
274 cantfindbranch(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
291 absent(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
305 compartial(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
345 char * 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
372 store1(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
388 struct 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 *
517 genbranch(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 *
649 lookupsym(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
663 int 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
678 fexpandsym(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 *
794 namedrev(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 *
827 branchtip(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 *
838 tiprev()
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
852 char const cmdid[] = "revtest";
853
854         int
855 main(argc,argv)
856 int 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
909 void exiterr() { _exit(EXIT_FAILURE); }
910
911 #endif