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