1284345Ssjg/* Handle RCS revision numbers. */ 2284345Ssjg 3284345Ssjg/* Copyright 1982, 1988, 1989 Walter Tichy 4284345Ssjg Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert 5284345Ssjg Distributed under license by the Free Software Foundation, Inc. 6284345Ssjg 7284345SsjgThis file is part of RCS. 8284345Ssjg 9284345SsjgRCS is free software; you can redistribute it and/or modify 10284345Ssjgit under the terms of the GNU General Public License as published by 11296127Sbdrewerythe Free Software Foundation; either version 2, or (at your option) 12284345Ssjgany later version. 13291563Sbdrewery 14284345SsjgRCS is distributed in the hope that it will be useful, 15284345Ssjgbut WITHOUT ANY WARRANTY; without even the implied warranty of 16284345SsjgMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17284345SsjgGNU General Public License for more details. 18284345Ssjg 19284345SsjgYou should have received a copy of the GNU General Public License 20284345Ssjgalong 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$") 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