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 7 This file is part of RCS. 8 9 RCS is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 2, or (at your option) 12 any later version. 13 14 RCS is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with RCS; see the file COPYING. 21 If not, write to the Free Software Foundation, 22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 24 Report problems and direct all questions to: 25 26 rcs-bugs@cs.purdue.edu 27 28 */ 29 30 /* 31 * $FreeBSD: src/gnu/usr.bin/rcs/lib/rcsrev.c,v 1.8 1999/08/27 23:36:48 peter Exp $ 32 * $DragonFly: src/gnu/usr.bin/rcs/lib/rcsrev.c,v 1.2 2003/06/17 04:25:47 dillon Exp $ 33 * 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, "$DragonFly: src/gnu/usr.bin/rcs/lib/rcsrev.c,v 1.2 2003/06/17 04:25:47 dillon 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 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 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 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 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') 232 ++s1; 233 for (d1=0; isdigit(*(s1+d1)); d1++) 234 continue; 235 while (*s2=='0') 236 ++s2; 237 for (d2=0; isdigit(*(s2+d2)); d2++) 238 continue; 239 240 return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1; 241 } 242 243 244 int 245 cmpdate(d1, d2) 246 char const *d1, *d2; 247 /* 248 * Compare the two dates. This is just like cmpnum, 249 * except that for compatibility with old versions of RCS, 250 * 1900 is added to dates with two-digit years. 251 */ 252 { 253 char year1[5], year2[5]; 254 int r = cmpnumfld(normalizeyear(d1,year1), normalizeyear(d2,year2), 1); 255 256 if (r) 257 return r; 258 else { 259 while (isdigit(*d1)) 260 d1++; 261 d1 += *d1=='.'; 262 while (isdigit(*d2)) 263 d2++; 264 d2 += *d2=='.'; 265 return cmpnum(d1, d2); 266 } 267 } 268 269 static char const * 270 normalizeyear(date, year) 271 char const *date; 272 char year[5]; 273 { 274 if (isdigit(date[0]) && isdigit(date[1]) && !isdigit(date[2])) { 275 year[0] = '1'; 276 year[1] = '9'; 277 year[2] = date[0]; 278 year[3] = date[1]; 279 year[4] = 0; 280 return year; 281 } else 282 return date; 283 } 284 285 286 static void 287 cantfindbranch(revno, date, author, state) 288 char const *revno, date[datesize], *author, *state; 289 { 290 char datebuf[datesize + zonelenmax]; 291 292 rcserror("No revision on branch %s has%s%s%s%s%s%s.", 293 revno, 294 date ? " a date before " : "", 295 date ? date2str(date,datebuf) : "", 296 author ? " and author "+(date?0:4) : "", 297 author ? author : "", 298 state ? " and state "+(date||author?0:4) : "", 299 state ? state : "" 300 ); 301 } 302 303 static void 304 absent(revno, field) 305 char const *revno; 306 int field; 307 { 308 struct buf t; 309 bufautobegin(&t); 310 rcserror("%s %s absent", field&1?"revision":"branch", 311 partialno(&t,revno,field) 312 ); 313 bufautoend(&t); 314 } 315 316 317 int 318 compartial(num1, num2, length) 319 char const *num1, *num2; 320 int length; 321 322 /* compare the first "length" fields of two dot numbers; 323 the omitted field is considered to be larger than any number */ 324 /* restriction: at least one number has length or more fields */ 325 326 { 327 register char const *s1, *s2; 328 register size_t d1, d2; 329 register int r; 330 331 s1 = num1; s2 = num2; 332 if (!s1) return 1; 333 if (!s2) return -1; 334 335 for (;;) { 336 if (!*s1) return 1; 337 if (!*s2) return -1; 338 339 while (*s1=='0') 340 ++s1; 341 for (d1=0; isdigit(*(s1+d1)); d1++) 342 continue; 343 while (*s2=='0') 344 ++s2; 345 for (d2=0; isdigit(*(s2+d2)); d2++) 346 continue; 347 348 if (d1 != d2) 349 return d1<d2 ? -1 : 1; 350 if ((r = memcmp(s1, s2, d1))) 351 return r; 352 if (!--length) 353 return 0; 354 355 s1 += d1; 356 s2 += d1; 357 358 if (*s1 == '.') s1++; 359 if (*s2 == '.') s2++; 360 } 361 } 362 363 364 char * partialno(rev1,rev2,length) 365 struct buf *rev1; 366 char const *rev2; 367 register int length; 368 /* Function: Copies length fields of revision number rev2 into rev1. 369 * Return rev1's string. 370 */ 371 { 372 register char *r1; 373 374 bufscpy(rev1, rev2); 375 r1 = rev1->string; 376 while (length) { 377 while (*r1!='.' && *r1) 378 ++r1; 379 ++r1; 380 length--; 381 } 382 /* eliminate last '.'*/ 383 *(r1-1)='\0'; 384 return rev1->string; 385 } 386 387 388 389 390 static void 391 store1(store, next) 392 struct hshentries ***store; 393 struct hshentry *next; 394 /* 395 * Allocate a new list node that addresses NEXT. 396 * Append it to the list that **STORE is the end pointer of. 397 */ 398 { 399 register struct hshentries *p; 400 401 p = ftalloc(struct hshentries); 402 p->first = next; 403 **store = p; 404 *store = &p->rest; 405 } 406 407 struct hshentry * genrevs(revno,date,author,state,store) 408 char const *revno, *date, *author, *state; 409 struct hshentries **store; 410 /* Function: finds the deltas needed for reconstructing the 411 * revision given by revno, date, author, and state, and stores pointers 412 * to these deltas into a list whose starting address is given by store. 413 * The last delta (target delta) is returned. 414 * If the proper delta could not be found, 0 is returned. 415 */ 416 { 417 int length; 418 register struct hshentry * next; 419 int result; 420 char const *branchnum; 421 struct buf t; 422 char datebuf[datesize + zonelenmax]; 423 424 bufautobegin(&t); 425 426 if (!(next = Head)) { 427 rcserror("RCS file empty"); 428 goto norev; 429 } 430 431 length = countnumflds(revno); 432 433 if (length >= 1) { 434 /* at least one field; find branch exactly */ 435 while ((result=cmpnumfld(revno,next->num,1)) < 0) { 436 store1(&store, next); 437 next = next->next; 438 if (!next) { 439 rcserror("branch number %s too low", partialno(&t,revno,1)); 440 goto norev; 441 } 442 } 443 444 if (result>0) { 445 absent(revno, 1); 446 goto norev; 447 } 448 } 449 if (length<=1){ 450 /* pick latest one on given branch */ 451 branchnum = next->num; /* works even for empty revno*/ 452 while (next && 453 cmpnumfld(branchnum,next->num,1) == 0 && 454 ( 455 (date && cmpdate(date,next->date) < 0) || 456 (author && strcmp(author,next->author) != 0) || 457 (state && strcmp(state,next->state) != 0) 458 ) 459 ) 460 { 461 store1(&store, next); 462 next=next->next; 463 } 464 if (!next || 465 (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ { 466 cantfindbranch( 467 length ? revno : partialno(&t,branchnum,1), 468 date, author, state 469 ); 470 goto norev; 471 } else { 472 store1(&store, next); 473 } 474 *store = 0; 475 return next; 476 } 477 478 /* length >=2 */ 479 /* find revision; may go low if length==2*/ 480 while ((result=cmpnumfld(revno,next->num,2)) < 0 && 481 (cmpnumfld(revno,next->num,1)==0) ) { 482 store1(&store, next); 483 next = next->next; 484 if (!next) 485 break; 486 } 487 488 if (!next || cmpnumfld(revno,next->num,1) != 0) { 489 rcserror("revision number %s too low", partialno(&t,revno,2)); 490 goto norev; 491 } 492 if ((length>2) && (result!=0)) { 493 absent(revno, 2); 494 goto norev; 495 } 496 497 /* print last one */ 498 store1(&store, next); 499 500 if (length>2) 501 return genbranch(next,revno,length,date,author,state,store); 502 else { /* length == 2*/ 503 if (date && cmpdate(date,next->date)<0) { 504 rcserror("Revision %s has date %s.", 505 next->num, 506 date2str(next->date, datebuf) 507 ); 508 return 0; 509 } 510 if (author && strcmp(author,next->author)!=0) { 511 rcserror("Revision %s has author %s.", 512 next->num, next->author 513 ); 514 return 0; 515 } 516 if (state && strcmp(state,next->state)!=0) { 517 rcserror("Revision %s has state %s.", 518 next->num, 519 next->state ? next->state : "<empty>" 520 ); 521 return 0; 522 } 523 *store = 0; 524 return next; 525 } 526 527 norev: 528 bufautoend(&t); 529 return 0; 530 } 531 532 533 534 535 static struct hshentry * 536 genbranch(bpoint, revno, length, date, author, state, store) 537 struct hshentry const *bpoint; 538 char const *revno; 539 int length; 540 char const *date, *author, *state; 541 struct hshentries **store; 542 /* Function: given a branchpoint, a revision number, date, author, and state, 543 * genbranch finds the deltas necessary to reconstruct the given revision 544 * from the branch point on. 545 * Pointers to the found deltas are stored in a list beginning with store. 546 * revno must be on a side branch. 547 * Return 0 on error. 548 */ 549 { 550 int field; 551 register struct hshentry * next, * trail; 552 register struct branchhead const *bhead; 553 int result; 554 struct buf t; 555 char datebuf[datesize + zonelenmax]; 556 557 field = 3; 558 bhead = bpoint->branches; 559 560 do { 561 if (!bhead) { 562 bufautobegin(&t); 563 rcserror("no side branches present for %s", 564 partialno(&t,revno,field-1) 565 ); 566 bufautoend(&t); 567 return 0; 568 } 569 570 /*find branch head*/ 571 /*branches are arranged in increasing order*/ 572 while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) { 573 bhead = bhead->nextbranch; 574 if (!bhead) { 575 bufautobegin(&t); 576 rcserror("branch number %s too high", 577 partialno(&t,revno,field) 578 ); 579 bufautoend(&t); 580 return 0; 581 } 582 } 583 584 if (result<0) { 585 absent(revno, field); 586 return 0; 587 } 588 589 next = bhead->hsh; 590 if (length==field) { 591 /* pick latest one on that branch */ 592 trail = 0; 593 do { if ((!date || cmpdate(date,next->date)>=0) && 594 (!author || strcmp(author,next->author)==0) && 595 (!state || strcmp(state,next->state)==0) 596 ) trail = next; 597 next=next->next; 598 } while (next); 599 600 if (!trail) { 601 cantfindbranch(revno, date, author, state); 602 return 0; 603 } else { /* print up to last one suitable */ 604 next = bhead->hsh; 605 while (next!=trail) { 606 store1(&store, next); 607 next=next->next; 608 } 609 store1(&store, next); 610 } 611 *store = 0; 612 return next; 613 } 614 615 /* length > field */ 616 /* find revision */ 617 /* check low */ 618 if (cmpnumfld(revno,next->num,field+1)<0) { 619 bufautobegin(&t); 620 rcserror("revision number %s too low", 621 partialno(&t,revno,field+1) 622 ); 623 bufautoend(&t); 624 return 0; 625 } 626 do { 627 store1(&store, next); 628 trail = next; 629 next = next->next; 630 } while (next && cmpnumfld(revno,next->num,field+1)>=0); 631 632 if ((length>field+1) && /*need exact hit */ 633 (cmpnumfld(revno,trail->num,field+1) !=0)){ 634 absent(revno, field+1); 635 return 0; 636 } 637 if (length == field+1) { 638 if (date && cmpdate(date,trail->date)<0) { 639 rcserror("Revision %s has date %s.", 640 trail->num, 641 date2str(trail->date, datebuf) 642 ); 643 return 0; 644 } 645 if (author && strcmp(author,trail->author)!=0) { 646 rcserror("Revision %s has author %s.", 647 trail->num, trail->author 648 ); 649 return 0; 650 } 651 if (state && strcmp(state,trail->state)!=0) { 652 rcserror("Revision %s has state %s.", 653 trail->num, 654 trail->state ? trail->state : "<empty>" 655 ); 656 return 0; 657 } 658 } 659 bhead = trail->branches; 660 661 } while ((field+=2) <= length); 662 *store = 0; 663 return trail; 664 } 665 666 667 static char const * 668 lookupsym(id) 669 char const *id; 670 /* Function: looks up id in the list of symbolic names starting 671 * with pointer SYMBOLS, and returns a pointer to the corresponding 672 * revision number. Return 0 if not present. 673 */ 674 { 675 register struct assoc const *next; 676 for (next = Symbols; next; next = next->nextassoc) 677 if (strcmp(id, next->symbol)==0) 678 return next->num; 679 return 0; 680 } 681 682 int expandsym(source, target) 683 char const *source; 684 struct buf *target; 685 /* Function: Source points to a revision number. Expandsym copies 686 * the number to target, but replaces all symbolic fields in the 687 * source number with their numeric values. 688 * Expand a branch followed by `.' to the latest revision on that branch. 689 * Ignore `.' after a revision. Remove leading zeros. 690 * returns false on error; 691 */ 692 { 693 return fexpandsym(source, target, (RILE*)0); 694 } 695 696 int 697 fexpandsym(source, target, fp) 698 char const *source; 699 struct buf *target; 700 RILE *fp; 701 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */ 702 { 703 register char const *sp, *bp; 704 register char *tp; 705 char const *tlim; 706 int dots; 707 708 sp = source; 709 bufalloc(target, 1); 710 tp = target->string; 711 if (!sp || !*sp) { /* Accept 0 pointer as a legal value. */ 712 *tp='\0'; 713 return true; 714 } 715 if (sp[0] == KDELIM && !sp[1]) { 716 if (!getoldkeys(fp)) 717 return false; 718 if (!*prevrev.string) { 719 workerror("working file lacks revision number"); 720 return false; 721 } 722 bufscpy(target, prevrev.string); 723 return true; 724 } 725 tlim = tp + target->size; 726 dots = 0; 727 728 for (;;) { 729 register char *p = tp; 730 size_t s = tp - target->string; 731 int id = false; 732 for (;;) { 733 switch (ctab[(unsigned char)*sp]) { 734 case IDCHAR: 735 case LETTER: 736 case Letter: 737 id = true; 738 /* fall into */ 739 case DIGIT: 740 if (tlim <= p) 741 p = bufenlarge(target, &tlim); 742 *p++ = *sp++; 743 continue; 744 745 default: 746 break; 747 } 748 break; 749 } 750 if (tlim <= p) 751 p = bufenlarge(target, &tlim); 752 *p = 0; 753 tp = target->string + s; 754 755 if (id) { 756 bp = lookupsym(tp); 757 if (!bp) { 758 rcserror("Symbolic name `%s' is undefined.",tp); 759 return false; 760 } 761 } else { 762 /* skip leading zeros */ 763 for (bp = tp; *bp=='0' && isdigit(bp[1]); bp++) 764 continue; 765 766 if (!*bp) { 767 if (s || *sp!='.') 768 break; 769 else { 770 /* Insert default branch before initial `.'. */ 771 char const *b; 772 if (Dbranch) 773 b = Dbranch; 774 else if (Head) 775 b = Head->num; 776 else 777 break; 778 getbranchno(b, target); 779 bp = tp = target->string; 780 tlim = tp + target->size; 781 } 782 } 783 } 784 785 while ((*tp++ = *bp++)) 786 if (tlim <= tp) 787 tp = bufenlarge(target, &tlim); 788 789 switch (*sp++) { 790 case '\0': 791 return true; 792 793 case '.': 794 if (!*sp) { 795 if (dots & 1) 796 break; 797 if (!(bp = branchtip(target->string))) 798 return false; 799 bufscpy(target, bp); 800 return true; 801 } 802 ++dots; 803 tp[-1] = '.'; 804 continue; 805 } 806 break; 807 } 808 809 rcserror("improper revision number: %s", source); 810 return false; 811 } 812 813 char const * 814 namedrev(name, delta) 815 char const *name; 816 struct hshentry *delta; 817 /* Yield NAME if it names DELTA, 0 otherwise. */ 818 { 819 if (name) { 820 char const *id = 0, *p, *val; 821 for (p = name; ; p++) 822 switch (ctab[(unsigned char)*p]) { 823 case IDCHAR: 824 case LETTER: 825 case Letter: 826 id = name; 827 break; 828 829 case DIGIT: 830 break; 831 832 case UNKN: 833 if (!*p && id && 834 (val = lookupsym(id)) && 835 strcmp(val, delta->num) == 0 836 ) 837 return id; 838 /* fall into */ 839 default: 840 return 0; 841 } 842 } 843 return 0; 844 } 845 846 static char const * 847 branchtip(branch) 848 char const *branch; 849 { 850 struct hshentry *h; 851 struct hshentries *hs; 852 853 h = genrevs(branch, (char*)0, (char*)0, (char*)0, &hs); 854 return h ? h->num : (char const*)0; 855 } 856 857 char const * 858 tiprev() 859 { 860 return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0; 861 } 862 863 864 865 #ifdef REVTEST 866 867 /* 868 * Test the routines that generate a sequence of delta numbers 869 * needed to regenerate a given delta. 870 */ 871 872 char const cmdid[] = "revtest"; 873 874 int 875 main(argc,argv) 876 int argc; char * argv[]; 877 { 878 static struct buf numricrevno; 879 char symrevno[100]; /* used for input of revision numbers */ 880 char author[20]; 881 char state[20]; 882 char date[20]; 883 struct hshentries *gendeltas; 884 struct hshentry * target; 885 int i; 886 887 if (argc<2) { 888 aputs("No input file\n",stderr); 889 exitmain(EXIT_FAILURE); 890 } 891 if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) { 892 faterror("can't open input file %s", argv[1]); 893 } 894 Lexinit(); 895 getadmin(); 896 897 gettree(); 898 899 getdesc(false); 900 901 do { 902 /* all output goes to stderr, to have diagnostics and */ 903 /* errors in sequence. */ 904 aputs("\nEnter revision number or <return> or '.': ",stderr); 905 if (!fgets(symrevno, 100, stdin)) break; 906 if (*symrevno == '.') break; 907 aprintf(stderr,"%s;\n",symrevno); 908 expandsym(symrevno,&numricrevno); 909 aprintf(stderr,"expanded number: %s; ",numricrevno.string); 910 aprintf(stderr,"Date: "); 911 fgets(date, 20, stdin); aprintf(stderr,"%s; ",date); 912 aprintf(stderr,"Author: "); 913 fgets(author, 20, stdin); aprintf(stderr,"%s; ",author); 914 aprintf(stderr,"State: "); 915 fgets(state, 20, stdin); aprintf(stderr, "%s;\n", state); 916 target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0, 917 *state?state:(char*)0, &gendeltas); 918 if (target) { 919 while (gendeltas) { 920 aprintf(stderr,"%s\n",gendeltas->first->num); 921 gendeltas = gendeltas->next; 922 } 923 } 924 } while (true); 925 aprintf(stderr,"done\n"); 926 exitmain(EXIT_SUCCESS); 927 } 928 929 void exiterr() { _exit(EXIT_FAILURE); } 930 931 #endif 932