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