1 /* $NetBSD: rcsrev.c,v 1.2 2016/01/14 04:22:39 christos Exp $ */
2
3 /* Handle RCS revision numbers. */
4
5 /* Copyright 1982, 1988, 1989 Walter Tichy
6 Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
7 Distributed under license by the Free Software Foundation, Inc.
8
9 This file is part of RCS.
10
11 RCS is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2, or (at your option)
14 any later version.
15
16 RCS is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with RCS; see the file COPYING.
23 If not, write to the Free Software Foundation,
24 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 Report problems and direct all questions to:
27
28 rcs-bugs@cs.purdue.edu
29
30 */
31
32 /*
33 * Log: rcsrev.c,v
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, "Id: rcsrev.c,v 5.10 1995/06/16 06:19:24 eggert 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') ++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
cmpdate(d1,d2)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 *
normalizeyear(date,year)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
cantfindbranch(revno,date,author,state)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
absent(revno,field)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
compartial(num1,num2,length)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
partialno(rev1,rev2,length)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
store1(store,next)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
genrevs(revno,date,author,state,store)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 *
genbranch(bpoint,revno,length,date,author,state,store)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) {
636 const char *st;
637
638 if (trail->state == NULL)
639 st = "<empty>";
640 else if (strcmp(trail->state, state) != 0)
641 st = trail->state;
642 else
643 st = NULL;
644
645 if (st)
646 rcserror("Revision %s has state %s.",
647 trail->num, st);
648 return 0;
649 }
650 }
651 bhead = trail->branches;
652
653 } while ((field+=2) <= length);
654 *store = 0;
655 return trail;
656 }
657
658
659 static char const *
lookupsym(id)660 lookupsym(id)
661 char const *id;
662 /* Function: looks up id in the list of symbolic names starting
663 * with pointer SYMBOLS, and returns a pointer to the corresponding
664 * revision number. Return 0 if not present.
665 */
666 {
667 register struct assoc const *next;
668 for (next = Symbols; next; next = next->nextassoc)
669 if (strcmp(id, next->symbol)==0)
670 return next->num;
671 return 0;
672 }
673
expandsym(source,target)674 int expandsym(source, target)
675 char const *source;
676 struct buf *target;
677 /* Function: Source points to a revision number. Expandsym copies
678 * the number to target, but replaces all symbolic fields in the
679 * source number with their numeric values.
680 * Expand a branch followed by `.' to the latest revision on that branch.
681 * Ignore `.' after a revision. Remove leading zeros.
682 * returns false on error;
683 */
684 {
685 return fexpandsym(source, target, (RILE*)0);
686 }
687
688 int
fexpandsym(source,target,fp)689 fexpandsym(source, target, fp)
690 char const *source;
691 struct buf *target;
692 RILE *fp;
693 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */
694 {
695 register char const *sp, *bp;
696 register char *tp;
697 char const *tlim;
698 int dots;
699
700 sp = source;
701 bufalloc(target, 1);
702 tp = target->string;
703 if (!sp || !*sp) { /* Accept 0 pointer as a legal value. */
704 *tp='\0';
705 return true;
706 }
707 if (sp[0] == KDELIM && !sp[1]) {
708 if (!getoldkeys(fp))
709 return false;
710 if (!*prevrev.string) {
711 workerror("working file lacks revision number");
712 return false;
713 }
714 bufscpy(target, prevrev.string);
715 return true;
716 }
717 tlim = tp + target->size;
718 dots = 0;
719
720 for (;;) {
721 register char *p = tp;
722 size_t s = tp - target->string;
723 int id = false;
724 for (;;) {
725 switch (ctab[(unsigned char)*sp]) {
726 case IDCHAR:
727 case LETTER:
728 case Letter:
729 id = true;
730 /* fall into */
731 case DIGIT:
732 if (tlim <= p)
733 p = bufenlarge(target, &tlim);
734 *p++ = *sp++;
735 continue;
736
737 default:
738 break;
739 }
740 break;
741 }
742 if (tlim <= p)
743 p = bufenlarge(target, &tlim);
744 *p = 0;
745 tp = target->string + s;
746
747 if (id) {
748 bp = lookupsym(tp);
749 if (!bp) {
750 rcserror("Symbolic name `%s' is undefined.",tp);
751 return false;
752 }
753 } else {
754 /* skip leading zeros */
755 for (bp = tp; *bp=='0' && isdigit(bp[1]); bp++)
756 continue;
757
758 if (!*bp) {
759 if (s || *sp!='.')
760 break;
761 else {
762 /* Insert default branch before initial `.'. */
763 char const *b;
764 if (Dbranch)
765 b = Dbranch;
766 else if (Head)
767 b = Head->num;
768 else
769 break;
770 getbranchno(b, target);
771 bp = tp = target->string;
772 tlim = tp + target->size;
773 }
774 }
775 }
776
777 while ((*tp++ = *bp++))
778 if (tlim <= tp)
779 tp = bufenlarge(target, &tlim);
780
781 switch (*sp++) {
782 case '\0':
783 return true;
784
785 case '.':
786 if (!*sp) {
787 if (dots & 1)
788 break;
789 if (!(bp = branchtip(target->string)))
790 return false;
791 bufscpy(target, bp);
792 return true;
793 }
794 ++dots;
795 tp[-1] = '.';
796 continue;
797 }
798 break;
799 }
800
801 rcserror("improper revision number: %s", source);
802 return false;
803 }
804
805 char const *
namedrev(name,delta)806 namedrev(name, delta)
807 char const *name;
808 struct hshentry *delta;
809 /* Yield NAME if it names DELTA, 0 otherwise. */
810 {
811 if (name) {
812 char const *id = 0, *p, *val;
813 for (p = name; ; p++)
814 switch (ctab[(unsigned char)*p]) {
815 case IDCHAR:
816 case LETTER:
817 case Letter:
818 id = name;
819 break;
820
821 case DIGIT:
822 break;
823
824 case UNKN:
825 if (!*p && id &&
826 (val = lookupsym(id)) &&
827 strcmp(val, delta->num) == 0
828 )
829 return id;
830 /* fall into */
831 default:
832 return 0;
833 }
834 }
835 return 0;
836 }
837
838 static char const *
branchtip(branch)839 branchtip(branch)
840 char const *branch;
841 {
842 struct hshentry *h;
843 struct hshentries *hs;
844
845 h = genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
846 return h ? h->num : (char const*)0;
847 }
848
849 char const *
tiprev()850 tiprev()
851 {
852 return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
853 }
854
855
856
857 #ifdef REVTEST
858
859 /*
860 * Test the routines that generate a sequence of delta numbers
861 * needed to regenerate a given delta.
862 */
863
864 char const cmdid[] = "revtest";
865
866 int
main(argc,argv)867 main(argc,argv)
868 int argc; char * argv[];
869 {
870 static struct buf numricrevno;
871 char symrevno[100]; /* used for input of revision numbers */
872 char author[20];
873 char state[20];
874 char date[20];
875 struct hshentries *gendeltas;
876 struct hshentry * target;
877 int i;
878
879 if (argc<2) {
880 aputs("No input file\n",stderr);
881 exitmain(EXIT_FAILURE);
882 }
883 if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
884 faterror("can't open input file %s", argv[1]);
885 }
886 Lexinit();
887 getadmin();
888
889 gettree();
890
891 getdesc(false);
892
893 do {
894 /* all output goes to stderr, to have diagnostics and */
895 /* errors in sequence. */
896 aputs("\nEnter revision number or <return> or '.': ",stderr);
897 if (!gets(symrevno)) break;
898 if (*symrevno == '.') break;
899 aprintf(stderr,"%s;\n",symrevno);
900 expandsym(symrevno,&numricrevno);
901 aprintf(stderr,"expanded number: %s; ",numricrevno.string);
902 aprintf(stderr,"Date: ");
903 gets(date); aprintf(stderr,"%s; ",date);
904 aprintf(stderr,"Author: ");
905 gets(author); aprintf(stderr,"%s; ",author);
906 aprintf(stderr,"State: ");
907 gets(state); aprintf(stderr, "%s;\n", state);
908 target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0,
909 *state?state:(char*)0, &gendeltas);
910 if (target) {
911 while (gendeltas) {
912 aprintf(stderr,"%s\n",gendeltas->first->num);
913 gendeltas = gendeltas->next;
914 }
915 }
916 } while (true);
917 aprintf(stderr,"done\n");
918 exitmain(EXIT_SUCCESS);
919 }
920
exiterr()921 void exiterr() { _exit(EXIT_FAILURE); }
922
923 #endif
924