xref: /dragonfly/gnu/usr.bin/rcs/lib/rcsrev.c (revision ac5927cd1bf74782d0a8f65614d789d15e7d8bc1)
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
countnumflds(s)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
getbranchno(revno,branchno)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 
cmpnum(num1,num2)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 
cmpnumfld(num1,num2,fld)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')
232                     ++s1;
233           for (d1=0; isdigit(*(s1+d1)); d1++)
234                     continue;
235           while (*s2=='0')
236                     ++s2;
237           for (d2=0; isdigit(*(s2+d2)); d2++)
238                     continue;
239 
240           return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
241 }
242 
243 
244           int
cmpdate(d1,d2)245 cmpdate(d1, d2)
246           char const *d1, *d2;
247 /*
248 * Compare the two dates.  This is just like cmpnum,
249 * except that for compatibility with old versions of RCS,
250 * 1900 is added to dates with two-digit years.
251 */
252 {
253           char year1[5], year2[5];
254           int r = cmpnumfld(normalizeyear(d1,year1), normalizeyear(d2,year2), 1);
255 
256           if (r)
257                     return r;
258           else {
259                     while (isdigit(*d1))
260                               d1++;
261                     d1 += *d1=='.';
262                     while (isdigit(*d2))
263                               d2++;
264                     d2 += *d2=='.';
265                     return cmpnum(d1, d2);
266           }
267 }
268 
269           static char const *
normalizeyear(date,year)270 normalizeyear(date, year)
271           char const *date;
272           char year[5];
273 {
274           if (isdigit(date[0]) && isdigit(date[1]) && !isdigit(date[2])) {
275                     year[0] = '1';
276                     year[1] = '9';
277                     year[2] = date[0];
278                     year[3] = date[1];
279                     year[4] = 0;
280                     return year;
281           } else
282                     return date;
283 }
284 
285 
286           static void
cantfindbranch(revno,date,author,state)287 cantfindbranch(revno, date, author, state)
288           char const *revno, date[datesize], *author, *state;
289 {
290           char datebuf[datesize + zonelenmax];
291 
292           rcserror("No revision on branch %s has%s%s%s%s%s%s.",
293                     revno,
294                     date ? " a date before " : "",
295                     date ? date2str(date,datebuf) : "",
296                     author ? " and author "+(date?0:4) : "",
297                     author ? author : "",
298                     state ? " and state "+(date||author?0:4) : "",
299                     state ? state : ""
300           );
301 }
302 
303           static void
absent(revno,field)304 absent(revno, field)
305           char const *revno;
306           int field;
307 {
308           struct buf t;
309           bufautobegin(&t);
310           rcserror("%s %s absent", field&1?"revision":"branch",
311                     partialno(&t,revno,field)
312           );
313           bufautoend(&t);
314 }
315 
316 
317           int
compartial(num1,num2,length)318 compartial(num1, num2, length)
319           char const *num1, *num2;
320           int length;
321 
322 /*   compare the first "length" fields of two dot numbers;
323      the omitted field is considered to be larger than any number  */
324 /*   restriction:  at least one number has length or more fields   */
325 
326 {
327           register char const *s1, *s2;
328           register size_t d1, d2;
329           register int r;
330 
331         s1 = num1;      s2 = num2;
332           if (!s1) return 1;
333           if (!s2) return -1;
334 
335           for (;;) {
336               if (!*s1) return 1;
337               if (!*s2) return -1;
338 
339               while (*s1=='0')
340                         ++s1;
341               for (d1=0; isdigit(*(s1+d1)); d1++)
342                         continue;
343               while (*s2=='0')
344                         ++s2;
345               for (d2=0; isdigit(*(s2+d2)); d2++)
346                         continue;
347 
348               if (d1 != d2)
349                         return d1<d2 ? -1 : 1;
350               if ((r = memcmp(s1, s2, d1)))
351                         return r;
352               if (!--length)
353                         return 0;
354 
355               s1 += d1;
356               s2 += d1;
357 
358               if (*s1 == '.') s1++;
359             if (*s2 == '.') s2++;
360           }
361 }
362 
363 
partialno(rev1,rev2,length)364 char * partialno(rev1,rev2,length)
365           struct buf *rev1;
366           char const *rev2;
367           register int length;
368 /* Function: Copies length fields of revision number rev2 into rev1.
369  * Return rev1's string.
370  */
371 {
372           register char *r1;
373 
374           bufscpy(rev1, rev2);
375           r1 = rev1->string;
376         while (length) {
377                     while (*r1!='.' && *r1)
378                               ++r1;
379                     ++r1;
380                 length--;
381         }
382         /* eliminate last '.'*/
383         *(r1-1)='\0';
384           return rev1->string;
385 }
386 
387 
388 
389 
390           static void
store1(store,next)391 store1(store, next)
392           struct hshentries ***store;
393           struct hshentry *next;
394 /*
395  * Allocate a new list node that addresses NEXT.
396  * Append it to the list that **STORE is the end pointer of.
397  */
398 {
399           register struct hshentries *p;
400 
401           p = ftalloc(struct hshentries);
402           p->first = next;
403           **store = p;
404           *store = &p->rest;
405 }
406 
genrevs(revno,date,author,state,store)407 struct hshentry * genrevs(revno,date,author,state,store)
408           char const *revno, *date, *author, *state;
409           struct hshentries **store;
410 /* Function: finds the deltas needed for reconstructing the
411  * revision given by revno, date, author, and state, and stores pointers
412  * to these deltas into a list whose starting address is given by store.
413  * The last delta (target delta) is returned.
414  * If the proper delta could not be found, 0 is returned.
415  */
416 {
417           int length;
418         register struct hshentry * next;
419         int result;
420           char const *branchnum;
421           struct buf t;
422           char datebuf[datesize + zonelenmax];
423 
424           bufautobegin(&t);
425 
426           if (!(next = Head)) {
427                     rcserror("RCS file empty");
428                     goto norev;
429         }
430 
431         length = countnumflds(revno);
432 
433         if (length >= 1) {
434                 /* at least one field; find branch exactly */
435                     while ((result=cmpnumfld(revno,next->num,1)) < 0) {
436                               store1(&store, next);
437                         next = next->next;
438                               if (!next) {
439                                   rcserror("branch number %s too low", partialno(&t,revno,1));
440                                   goto norev;
441                               }
442                 }
443 
444                     if (result>0) {
445                               absent(revno, 1);
446                               goto norev;
447                     }
448         }
449         if (length<=1){
450                 /* pick latest one on given branch */
451                 branchnum = next->num; /* works even for empty revno*/
452                     while (next &&
453                            cmpnumfld(branchnum,next->num,1) == 0 &&
454                            (
455                               (date && cmpdate(date,next->date) < 0) ||
456                               (author && strcmp(author,next->author) != 0) ||
457                               (state && strcmp(state,next->state) != 0)
458                            )
459                           )
460                     {
461                               store1(&store, next);
462                         next=next->next;
463                 }
464                     if (!next ||
465                     (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
466                               cantfindbranch(
467                                         length ? revno : partialno(&t,branchnum,1),
468                                         date, author, state
469                               );
470                               goto norev;
471                 } else {
472                               store1(&store, next);
473                 }
474                     *store = 0;
475                 return next;
476         }
477 
478         /* length >=2 */
479         /* find revision; may go low if length==2*/
480           while ((result=cmpnumfld(revno,next->num,2)) < 0  &&
481                (cmpnumfld(revno,next->num,1)==0) ) {
482                     store1(&store, next);
483                 next = next->next;
484                     if (!next)
485                               break;
486         }
487 
488           if (!next || cmpnumfld(revno,next->num,1) != 0) {
489                     rcserror("revision number %s too low", partialno(&t,revno,2));
490                     goto norev;
491         }
492         if ((length>2) && (result!=0)) {
493                     absent(revno, 2);
494                     goto norev;
495         }
496 
497         /* print last one */
498           store1(&store, next);
499 
500         if (length>2)
501                 return genbranch(next,revno,length,date,author,state,store);
502         else { /* length == 2*/
503                     if (date && cmpdate(date,next->date)<0) {
504                               rcserror("Revision %s has date %s.",
505                                         next->num,
506                                         date2str(next->date, datebuf)
507                               );
508                               return 0;
509                     }
510                     if (author && strcmp(author,next->author)!=0) {
511                               rcserror("Revision %s has author %s.",
512                                         next->num, next->author
513                               );
514                               return 0;
515                 }
516                     if (state && strcmp(state,next->state)!=0) {
517                               rcserror("Revision %s has state %s.",
518                                         next->num,
519                                         next->state ? next->state : "<empty>"
520                               );
521                               return 0;
522                 }
523                     *store = 0;
524                 return next;
525         }
526 
527     norev:
528           bufautoend(&t);
529           return 0;
530 }
531 
532 
533 
534 
535           static struct hshentry *
genbranch(bpoint,revno,length,date,author,state,store)536 genbranch(bpoint, revno, length, date, author, state, store)
537           struct hshentry const *bpoint;
538           char const *revno;
539           int length;
540           char const *date, *author, *state;
541           struct hshentries **store;
542 /* Function: given a branchpoint, a revision number, date, author, and state,
543  * genbranch finds the deltas necessary to reconstruct the given revision
544  * from the branch point on.
545  * Pointers to the found deltas are stored in a list beginning with store.
546  * revno must be on a side branch.
547  * Return 0 on error.
548  */
549 {
550           int field;
551         register struct hshentry * next, * trail;
552           register struct branchhead const *bhead;
553         int result;
554           struct buf t;
555           char datebuf[datesize + zonelenmax];
556 
557           field = 3;
558         bhead = bpoint->branches;
559 
560           do {
561                     if (!bhead) {
562                               bufautobegin(&t);
563                               rcserror("no side branches present for %s",
564                                         partialno(&t,revno,field-1)
565                               );
566                               bufautoend(&t);
567                               return 0;
568                     }
569 
570                 /*find branch head*/
571                 /*branches are arranged in increasing order*/
572                     while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
573                         bhead = bhead->nextbranch;
574                               if (!bhead) {
575                                   bufautobegin(&t);
576                                   rcserror("branch number %s too high",
577                                         partialno(&t,revno,field)
578                                   );
579                                   bufautoend(&t);
580                                   return 0;
581                               }
582                 }
583 
584                     if (result<0) {
585                         absent(revno, field);
586                         return 0;
587                     }
588 
589                 next = bhead->hsh;
590                 if (length==field) {
591                         /* pick latest one on that branch */
592                               trail = 0;
593                               do { if ((!date || cmpdate(date,next->date)>=0) &&
594                                          (!author || strcmp(author,next->author)==0) &&
595                                          (!state || strcmp(state,next->state)==0)
596                              ) trail = next;
597                              next=next->next;
598                               } while (next);
599 
600                               if (!trail) {
601                                    cantfindbranch(revno, date, author, state);
602                                    return 0;
603                         } else { /* print up to last one suitable */
604                              next = bhead->hsh;
605                              while (next!=trail) {
606                                           store1(&store, next);
607                                   next=next->next;
608                              }
609                                    store1(&store, next);
610                         }
611                               *store = 0;
612                         return next;
613                 }
614 
615                 /* length > field */
616                 /* find revision */
617                 /* check low */
618                 if (cmpnumfld(revno,next->num,field+1)<0) {
619                               bufautobegin(&t);
620                               rcserror("revision number %s too low",
621                                         partialno(&t,revno,field+1)
622                               );
623                               bufautoend(&t);
624                               return 0;
625                 }
626                     do {
627                               store1(&store, next);
628                         trail = next;
629                         next = next->next;
630                     } while (next && cmpnumfld(revno,next->num,field+1)>=0);
631 
632                 if ((length>field+1) &&  /*need exact hit */
633                     (cmpnumfld(revno,trail->num,field+1) !=0)){
634                               absent(revno, field+1);
635                               return 0;
636                 }
637                 if (length == field+1) {
638                               if (date && cmpdate(date,trail->date)<0) {
639                                         rcserror("Revision %s has date %s.",
640                                                   trail->num,
641                                                   date2str(trail->date, datebuf)
642                                         );
643                                         return 0;
644                         }
645                               if (author && strcmp(author,trail->author)!=0) {
646                                         rcserror("Revision %s has author %s.",
647                                                   trail->num, trail->author
648                                         );
649                                         return 0;
650                         }
651                               if (state && strcmp(state,trail->state)!=0) {
652                                         rcserror("Revision %s has state %s.",
653                                                   trail->num,
654                                                   trail->state ? trail->state : "<empty>"
655                                         );
656                                         return 0;
657                         }
658                 }
659                 bhead = trail->branches;
660 
661           } while ((field+=2) <= length);
662           *store = 0;
663         return trail;
664 }
665 
666 
667           static char const *
lookupsym(id)668 lookupsym(id)
669           char const *id;
670 /* Function: looks up id in the list of symbolic names starting
671  * with pointer SYMBOLS, and returns a pointer to the corresponding
672  * revision number.  Return 0 if not present.
673  */
674 {
675           register struct assoc const *next;
676           for (next = Symbols;  next;  next = next->nextassoc)
677                 if (strcmp(id, next->symbol)==0)
678                               return next->num;
679           return 0;
680 }
681 
expandsym(source,target)682 int expandsym(source, target)
683           char const *source;
684           struct buf *target;
685 /* Function: Source points to a revision number. Expandsym copies
686  * the number to target, but replaces all symbolic fields in the
687  * source number with their numeric values.
688  * Expand a branch followed by `.' to the latest revision on that branch.
689  * Ignore `.' after a revision.  Remove leading zeros.
690  * returns false on error;
691  */
692 {
693           return fexpandsym(source, target, (RILE*)0);
694 }
695 
696           int
fexpandsym(source,target,fp)697 fexpandsym(source, target, fp)
698           char const *source;
699           struct buf *target;
700           RILE *fp;
701 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM.  */
702 {
703           register char const *sp, *bp;
704           register char *tp;
705           char const *tlim;
706           int dots;
707 
708           sp = source;
709           bufalloc(target, 1);
710           tp = target->string;
711           if (!sp || !*sp) { /* Accept 0 pointer as a legal value.  */
712                 *tp='\0';
713                 return true;
714         }
715           if (sp[0] == KDELIM  &&  !sp[1]) {
716                     if (!getoldkeys(fp))
717                               return false;
718                     if (!*prevrev.string) {
719                               workerror("working file lacks revision number");
720                               return false;
721                     }
722                     bufscpy(target, prevrev.string);
723                     return true;
724           }
725           tlim = tp + target->size;
726           dots = 0;
727 
728           for (;;) {
729                     register char *p = tp;
730                     size_t s = tp - target->string;
731                     int id = false;
732                     for (;;) {
733                         switch (ctab[(unsigned char)*sp]) {
734                               case IDCHAR:
735                               case LETTER:
736                               case Letter:
737                                   id = true;
738                                   /* fall into */
739                               case DIGIT:
740                                   if (tlim <= p)
741                                             p = bufenlarge(target, &tlim);
742                                   *p++ = *sp++;
743                                   continue;
744 
745                               default:
746                                   break;
747                         }
748                         break;
749                     }
750                     if (tlim <= p)
751                               p = bufenlarge(target, &tlim);
752                     *p = 0;
753                     tp = target->string + s;
754 
755                     if (id) {
756                               bp = lookupsym(tp);
757                               if (!bp) {
758                                         rcserror("Symbolic name `%s' is undefined.",tp);
759                                 return false;
760                         }
761                     } else {
762                               /* skip leading zeros */
763                               for (bp = tp;  *bp=='0' && isdigit(bp[1]);  bp++)
764                                         continue;
765 
766                               if (!*bp) {
767                                   if (s || *sp!='.')
768                                         break;
769                                   else {
770                                         /* Insert default branch before initial `.'.  */
771                                         char const *b;
772                                         if (Dbranch)
773                                             b = Dbranch;
774                                         else if (Head)
775                                             b = Head->num;
776                                         else
777                                             break;
778                                         getbranchno(b, target);
779                                         bp = tp = target->string;
780                                         tlim = tp + target->size;
781                                   }
782                               }
783                     }
784 
785                     while ((*tp++ = *bp++))
786                               if (tlim <= tp)
787                                         tp = bufenlarge(target, &tlim);
788 
789                     switch (*sp++) {
790                         case '\0':
791                               return true;
792 
793                         case '.':
794                               if (!*sp) {
795                                         if (dots & 1)
796                                                   break;
797                                         if (!(bp = branchtip(target->string)))
798                                                   return false;
799                                         bufscpy(target, bp);
800                                         return true;
801                               }
802                               ++dots;
803                               tp[-1] = '.';
804                               continue;
805                     }
806                     break;
807         }
808 
809           rcserror("improper revision number: %s", source);
810           return false;
811 }
812 
813           char const *
namedrev(name,delta)814 namedrev(name, delta)
815           char const *name;
816           struct hshentry *delta;
817 /* Yield NAME if it names DELTA, 0 otherwise.  */
818 {
819           if (name) {
820                     char const *id = 0, *p, *val;
821                     for (p = name;  ;  p++)
822                               switch (ctab[(unsigned char)*p]) {
823                                         case IDCHAR:
824                                         case LETTER:
825                                         case Letter:
826                                                   id = name;
827                                                   break;
828 
829                                         case DIGIT:
830                                                   break;
831 
832                                         case UNKN:
833                                                   if (!*p && id &&
834                                                             (val = lookupsym(id)) &&
835                                                             strcmp(val, delta->num) == 0
836                                                   )
837                                                             return id;
838                                                   /* fall into */
839                                         default:
840                                                   return 0;
841                               }
842           }
843           return 0;
844 }
845 
846           static char const *
branchtip(branch)847 branchtip(branch)
848           char const *branch;
849 {
850           struct hshentry *h;
851           struct hshentries *hs;
852 
853           h  =  genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
854           return h ? h->num : (char const*)0;
855 }
856 
857           char const *
tiprev()858 tiprev()
859 {
860           return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
861 }
862 
863 
864 
865 #ifdef REVTEST
866 
867 /*
868 * Test the routines that generate a sequence of delta numbers
869 * needed to regenerate a given delta.
870 */
871 
872 char const cmdid[] = "revtest";
873 
874           int
main(argc,argv)875 main(argc,argv)
876 int argc; char * argv[];
877 {
878           static struct buf numricrevno;
879           char symrevno[100];       /* used for input of revision numbers */
880         char author[20];
881         char state[20];
882         char date[20];
883           struct hshentries *gendeltas;
884         struct hshentry * target;
885         int i;
886 
887         if (argc<2) {
888                     aputs("No input file\n",stderr);
889                     exitmain(EXIT_FAILURE);
890         }
891           if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
892                     faterror("can't open input file %s", argv[1]);
893         }
894         Lexinit();
895         getadmin();
896 
897         gettree();
898 
899         getdesc(false);
900 
901         do {
902                 /* all output goes to stderr, to have diagnostics and       */
903                 /* errors in sequence.                                      */
904                     aputs("\nEnter revision number or <return> or '.': ",stderr);
905                     if (!fgets(symrevno, 100, stdin)) break;
906                 if (*symrevno == '.') break;
907                     aprintf(stderr,"%s;\n",symrevno);
908                     expandsym(symrevno,&numricrevno);
909                     aprintf(stderr,"expanded number: %s; ",numricrevno.string);
910                     aprintf(stderr,"Date: ");
911                     fgets(date, 20, stdin); aprintf(stderr,"%s; ",date);
912                     aprintf(stderr,"Author: ");
913                     fgets(author, 20, stdin); aprintf(stderr,"%s; ",author);
914                     aprintf(stderr,"State: ");
915                     fgets(state, 20, stdin); aprintf(stderr, "%s;\n", state);
916                     target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0,
917                                          *state?state:(char*)0, &gendeltas);
918                     if (target) {
919                               while (gendeltas) {
920                                         aprintf(stderr,"%s\n",gendeltas->first->num);
921                                         gendeltas = gendeltas->next;
922                         }
923                 }
924         } while (true);
925           aprintf(stderr,"done\n");
926           exitmain(EXIT_SUCCESS);
927 }
928 
exiterr()929 void exiterr() { _exit(EXIT_FAILURE); }
930 
931 #endif
932