rcsrev.c revision 256198
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 7This file is part of RCS. 8 9RCS is free software; you can redistribute it and/or modify 10it under the terms of the GNU General Public License as published by 11the Free Software Foundation; either version 2, or (at your option) 12any later version. 13 14RCS is distributed in the hope that it will be useful, 15but WITHOUT ANY WARRANTY; without even the implied warranty of 16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17GNU General Public License for more details. 18 19You should have received a copy of the GNU General Public License 20along with RCS; see the file COPYING. 21If not, write to the Free Software Foundation, 2259 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 24Report 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 108libId(revId, "$FreeBSD: head/gnu/usr.bin/rcs/lib/rcsrev.c 50472 1999-08-27 23:37:10Z peter $") 109 110static char const *branchtip P((char const*)); 111static char const *lookupsym P((char const*)); 112static char const *normalizeyear P((char const*,char[5])); 113static struct hshentry *genbranch P((struct hshentry const*,char const*,int,char const*,char const*,char const*,struct hshentries**)); 114static void absent P((char const*,int)); 115static void cantfindbranch P((char const*,char const[datesize],char const*,char const*)); 116static void store1 P((struct hshentries***,struct hshentry*)); 117 118 119 120 int 121countnumflds(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 139getbranchno(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 163int 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 207int 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 236cmpdate(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 * 257normalizeyear(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 274cantfindbranch(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 291absent(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 305compartial(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 345char * 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 372store1(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 388struct 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 * 517genbranch(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 * 649lookupsym(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 663int 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 678fexpandsym(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 * 794namedrev(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 * 827branchtip(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 * 838tiprev() 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 852char const cmdid[] = "revtest"; 853 854 int 855main(argc,argv) 856int 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 909void exiterr() { _exit(EXIT_FAILURE); } 910 911#endif 912