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