1: /* 2: * RCS checkin operation 3: */ 4: #ifndef lint 5: static char rcsid[]= 6: "$Header: /usr/src/local/bin/rcs/src/RCS/ci.c,v 4.6 87/12/18 11:34:41 narten Exp $ Purdue CS"; 7: #endif 8: /******************************************************************* 9: * check revisions into RCS files 10: ******************************************************************* 11: * 12: * Copyright (C) 1982 by Walter F. Tichy 13: * Purdue University 14: * Computer Science Department 15: * West Lafayette, IN 47907 16: * 17: * All rights reserved. No part of this software may be sold or distributed 18: * in any form or by any means without the prior written permission of the 19: * author. 20: * Report problems and direct all inquiries to Tichy@purdue (ARPA net). 21: */ 22: 23: 24: 25: /* $Log: ci.c,v $ 26: * Revision 4.6 87/12/18 11:34:41 narten 27: * lint cleanups (from Guy Harris) 28: * 29: * Revision 4.5 87/10/18 10:18:48 narten 30: * Updating version numbers. Changes relative to revision 1.1 are actually 31: * relative to 4.3 32: * 33: * Revision 1.3 87/09/24 13:57:19 narten 34: * Sources now pass through lint (if you ignore printf/sprintf/fprintf 35: * warnings) 36: * 37: * Revision 1.2 87/03/27 14:21:33 jenkins 38: * Port to suns 39: * 40: * Revision 1.1 84/01/23 14:49:54 kcs 41: * Initial revision 42: * 43: * Revision 4.3 83/12/15 12:28:54 wft 44: * ci -u and ci -l now set mode of working file properly. 45: * 46: * Revision 4.2 83/12/05 13:40:54 wft 47: * Merged with 3.9.1.1: added calls to clearerr(stdin). 48: * made rewriteflag external. 49: * 50: * Revision 4.1 83/05/10 17:03:06 wft 51: * Added option -d and -w, and updated assingment of date, etc. to new delta. 52: * Added handling of default branches. 53: * Option -k generates std. log message; fixed undef. pointer in reading of log. 54: * Replaced getlock() with findlock(), link--unlink with rename(), 55: * getpwuid() with getcaller(). 56: * Moved all revision number generation to new routine addelta(). 57: * Removed calls to stat(); now done by pairfilenames(). 58: * Changed most calls to catchints() with restoreints(). 59: * Directed all interactive messages to stderr. 60: * 61: * Revision 3.9.1.1 83/10/19 04:21:03 lepreau 62: * Added clearerr(stdin) to getlogmsg() for re-reading stdin. 63: * 64: * Revision 3.9 83/02/15 15:25:44 wft 65: * 4.2 prerelease 66: * 67: * Revision 3.9 83/02/15 15:25:44 wft 68: * Added call to fastcopy() to copy remainder of RCS file. 69: * 70: * Revision 3.8 83/01/14 15:34:05 wft 71: * Added ignoring of interrupts while new RCS file is renamed; 72: * Avoids deletion of RCS files by interrupts. 73: * 74: * Revision 3.7 82/12/10 16:09:20 wft 75: * Corrected checking of return code from diff. 76: * 77: * Revision 3.6 82/12/08 21:34:49 wft 78: * Using DATEFORM to prepare date of checked-in revision; 79: * Fixed return from addbranch(). 80: * 81: * Revision 3.5 82/12/04 18:32:42 wft 82: * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated 83: * field lockedby in removelock(), moved getlogmsg() before calling diff. 84: * 85: * Revision 3.4 82/12/02 13:27:13 wft 86: * added option -k. 87: * 88: * Revision 3.3 82/11/28 20:53:31 wft 89: * Added mustcheckin() to check for redundant checkins. 90: * Added xpandfile() to do keyword expansion for -u and -l; 91: * -m appends linefeed to log message if necessary. 92: * getlogmsg() suppresses prompt if stdin is not a terminal. 93: * Replaced keeplock with lockflag, fclose() with ffclose(), 94: * %02d with %.2d, getlogin() with getpwuid(). 95: * 96: * Revision 3.2 82/10/18 20:57:23 wft 97: * An RCS file inherits its mode during the first ci from the working file, 98: * otherwise it stays the same, except that write permission is removed. 99: * Fixed ci -l, added ci -u (both do an implicit co after the ci). 100: * Fixed call to getlogin(), added call to getfullRCSname(), added check 101: * for write error. 102: * Changed conflicting identifiers. 103: * 104: * Revision 3.1 82/10/13 16:04:59 wft 105: * fixed type of variables receiving from getc() (char -> int). 106: * added include file dbm.h for getting BYTESIZ. This is used 107: * to check the return code from diff portably. 108: */ 109: 110: #include "rcsbase.h" 111: #ifndef lint 112: static char rcsbaseid[] = RCSBASE; 113: #endif 114: #include <sys/types.h> 115: #include <sys/stat.h> 116: #include "time.h" 117: 118: extern int rename(); /*rename files */ 119: extern char * getcaller(); /*login of caller */ 120: extern struct hshentry * genrevs(); /*generate delta numbers */ 121: extern int nextc; /*next input character */ 122: extern quietflag; /*suppresses diagnostics if true */ 123: extern int nerror; /*counter for errors */ 124: extern char * buildrevision(); /*constructs desired revision */ 125: extern char * checkid(); /*check identifiers */ 126: extern int partime(); /*parse free-format date/time */ 127: extern long maketime(); /*convert parsed time to unix time. */ 128: extern long time(); /*get date and time */ 129: extern struct tm * localtime(); /*convert unixtime into tm-structure */ 130: extern char * getdate(); /*formates current date (forward) */ 131: extern char * mktempfile(); /*temporary file name generator */ 132: extern struct lock * addlock(); /*adds a new lock */ 133: extern char * getlogmsg(); /*obtains log message; forward */ 134: extern struct hshentry * removelock(); /*finds a caller's lock (forward) */ 135: extern struct hshentry * findlock(); /*finds a lock */ 136: extern char * xpandfile(); /*perform keyword expansion; forward */ 137: 138: extern char prevauthor[]; 139: extern char prevdate[]; 140: extern char prevrev[]; 141: extern char prevstate []; 142: extern FILE * finptr; /* RCS input file */ 143: extern FILE * frewrite; /* new RCS file */ 144: extern int rewriteflag; /* indicates whether input should be */ 145: /* echoed to frewrite */ 146: 147: char * newRCSfilename, * diffilename; 148: char * RCSfilename,*workfilename,*expfilename,*newworkfilename; 149: extern struct stat RCSstat, workstat; /* file status of RCS and work file */ 150: extern int haveRCSstat, haveworkstat;/* status indicators */ 151: 152: 153: int copyflag; /* indicates whether a string should be copied into memory*/ 154: 155: char * rev, * state, *msg; 156: 157: int initflag, rcsinitflag; 158: int lockflag, keepworkingfile,keepflag; 159: int forceciflag; /* forces check in */ 160: int symrebindflag; char * symbol; 161: int textflag; char * textfile; 162: char * caller; /* caller's login; */ 163: char * author; /* alternate author for -w option */ 164: char altdate[datelength]; /* alternate date for -d */ 165: struct hshentry * targetdelta; /* old delta to be generated */ 166: char * olddeltanum; /* number of old delta */ 167: struct hshentry * gendeltas[hshsize]; /* stores deltas to be generated */ 168: char newdelnum[revlength]; /* holds new revision number */ 169: int newdnumlength; /* actual length of new rev. num. */ 170: char branchpointnum[revlength]; /* number of branchpoint */ 171: struct hshentry newdelta; /* new delta to be inserted */ 172: struct branchhead newbranch; /* new branch to be inserted */ 173: char logmsg[logsize]; /* buffer for log message */ 174: 175: main (argc, argv) 176: int argc; 177: char * argv[]; 178: { 179: char * nametest; 180: char * cmdusage; /* holds command format */ 181: char command[NCPPN+50]; /* holds diff commands */ 182: int msglen; /* length of message given by -m */ 183: int exit_stats; /* return code for system() calls */ 184: int newRCSmode; /* mode for RCS file */ 185: long unixtime; 186: struct tm parseddate, *ftm; 187: 188: catchints(); 189: cmdid = "ci"; 190: cmdusage = "command format:\nci -r[rev] -l[rev] -u[rev] -f[rev] -k[rev] -q[rev] -mmsg -nname -Nname -sstate -t[txtfile] file ..."; 191: rev = state = msg = symbol = textfile = nil; 192: initflag= rcsinitflag= symrebindflag= textflag= quietflag= false; 193: forceciflag= lockflag= keepworkingfile= keepflag= false; 194: caller = getcaller(); author = nil; /* author may be reset by -w */ 195: altdate[0]= '\0'; /* empty alternate date for -d */ 196: 197: while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) { 198: switch ((*argv)[1]) { 199: 200: case 'r': 201: lockflag=false; 202: revno: if ((*argv)[2]!='\0') { 203: if (rev!=nil) warn("Redefinition of revision number"); 204: rev = (*argv)+2; 205: } 206: break; 207: 208: case 'l': 209: keepworkingfile=lockflag=true; 210: goto revno; 211: 212: case 'u': 213: keepworkingfile=true; lockflag=false; 214: goto revno; 215: 216: case 'q': 217: quietflag=true; 218: goto revno; 219: 220: case 'f': 221: forceciflag=true; 222: goto revno; 223: 224: case 'k': 225: keepflag=true; 226: goto revno; 227: 228: case 'm': 229: if ((*argv)[2]!='\0'){ 230: if (msg!=nil)warn("Redefinition of -m option"); 231: msg = (*argv)+2; 232: msglen=strlen(msg); 233: if (msglen >= logsize) { 234: warn("log message truncated to %d characters", 235: logsize); 236: msg[logsize-2]='\n'; 237: msg[logsize-1]='\0'; 238: } 239: if (msg[msglen-1]!='\n') { 240: /*append linefeed*/ 241: VOID strcpy(logmsg,msg);msg=logmsg; 242: msg[msglen] = '\n'; 243: msg[++msglen]= '\0'; 244: } 245: } else warn("Missing message for -m option"); 246: break; 247: 248: case 'n': 249: symrebindflag=false; 250: if ((*argv)[2]!='\0'){ 251: if (symbol!=nil)warn("Redefinition of symbolic name"); 252: symbol = (*argv)+2; 253: if (!(nametest=checkid(symbol,' '))||*nametest) 254: faterror("Name %s must be one word",symbol); 255: } else warn("Missing name for -n option"); 256: break; 257: 258: case 'N': 259: symrebindflag=true; 260: if ((*argv)[2]!='\0'){ 261: if (symbol!=nil)warn("Redefinition of symbolic name"); 262: symbol = (*argv)+2; 263: if (!(nametest=checkid(symbol,' '))||*nametest) 264: faterror("Name %s must be one word",symbol); 265: } else warn("Missing name for -N option"); 266: break; 267: 268: case 's': 269: if ((*argv)[2]!='\0'){ 270: if (state!=nil)warn("Redefinition of -s option"); 271: state = (*argv)+2; 272: VOID checkid(state,' '); 273: } else warn("Missing state for -s option"); 274: break; 275: 276: case 't': 277: textflag=true; 278: if ((*argv)[2]!='\0'){ 279: if (textfile!=nil)warn("Redefinition of -t option"); 280: textfile = (*argv)+2; 281: } 282: break; 283: 284: case 'd': 285: if ((*argv)[2]!='\0'){ 286: if (altdate[0]!='\0')warn("Redefinition of -d option"); 287: /* process the date */ 288: if ( partime((*argv)+2, &parseddate) == 0) { 289: faterror("Can't parse date/time: %s", (*argv)+2); 290: break; 291: } 292: if ( (unixtime = maketime(&parseddate)) == 0L) { 293: faterror("Inconsistent date/time: %s",(*argv)+2); 294: break; 295: } 296: ftm = localtime(&unixtime); 297: VOID sprintf(altdate,DATEFORM, 298: ftm->tm_year,ftm->tm_mon+1,ftm->tm_mday,ftm->tm_hour,ftm->tm_min,ftm->tm_sec); 299: } else warn("Missing date for -d option"); 300: break; 301: 302: case 'w': 303: if ((*argv)[2]!='\0'){ 304: if (author!=nil)warn("Redefinition of -w option"); 305: author = (*argv)+2; 306: VOID checkid(author,' '); 307: } else warn("Missing author for -w option"); 308: break; 309: 310: 311: 312: 313: default: 314: faterror("unknown option: %s\n%s", *argv,cmdusage); 315: }; 316: } /* end processing of options */ 317: 318: if (argc<1) faterror("No input file\n%s",cmdusage); 319: 320: if (!isatty(fileno(stdin)) && msg==nil && textflag && textfile==nil) { 321: /* would need both log message and descriptive text from a file */ 322: faterror("Can't take both log and description from redirected stdin; use -ttextfile"); 323: } 324: /* now handle all filenames */ 325: do { 326: gendeltas[0] = nil; 327: copyflag=rewriteflag=false; 328: finptr=frewrite=NULL; 329: targetdelta=nil; 330: olddeltanum=nil; 331: 332: switch (pairfilenames(argc,argv,false,false)) { 333: 334: case -1: /* New RCS file */ 335: initflag=true; rcsinitflag=false; 336: break; 337: 338: case 0: /* Error */ 339: continue; 340: 341: case 1: /* Normal checkin with prev . RCS file */ 342: initflag=false; rcsinitflag=(Head==nil); 343: } 344: 345: /* now RCSfilename contains the name of the RCS file, and 346: * workfilename contains the name of the working file. 347: * if !initflag, finptr contains the file descriptor for the 348: * RCS file. The admin node is initialized. 349: * workstat and RCSstat are set. 350: */ 351: 352: diagnose("%s <-- %s", RCSfilename,workfilename); 353: 354: if (access(workfilename,4)!=0) { 355: error("working file %s not readable or nonexistent", 356: workfilename); 357: continue; 358: } 359: 360: if (!trydiraccess(RCSfilename)) continue; /* give up */ 361: if (!initflag && !checkaccesslist(caller)) continue; /* give up */ 362: if (!trysema(RCSfilename,true)) continue; /* give up */ 363: 364: if (keepflag) { 365: /* get keyword values from working file */ 366: if (!getoldkeys(workfilename)) continue; 367: if (rev==nil && *(rev=prevrev)=='\0') { 368: error("Can't find a revision number in %s",workfilename); 369: continue; 370: } 371: if (*prevdate=='\0' && *altdate=='\0') 372: warn("Can't find a date in %s",workfilename); 373: if (*prevauthor=='\0' && author==nil) 374: warn("Can't find an author in %s", workfilename); 375: if (*prevstate=='\0' && state==nil) 376: warn("Can't find a state in %s", workfilename); 377: } /* end processing keepflag */ 378: 379: gettree(); /* reads in the delta tree.*/ 380: 381: /* expand symbolic revision number */ 382: if (!expandsym(rev,newdelnum)) continue; 383: 384: /* splice new delta into tree */ 385: if (!addelta()) continue; 386: 387: if (initflag||rcsinitflag) { 388: diagnose("initial revision: %s",newdelnum); 389: } else diagnose("new revision: %s; previous revision: %s", 390: newdelnum,olddeltanum); 391: 392: newdelta.num=newdelnum; 393: newdelta.branches=nil; 394: newdelta.log=nil; 395: newdelta.lockedby=nil; /*might be changed by addlock() */ 396: /* set author */ 397: if (author!=nil) 398: newdelta.author=author; /* set author given by -w */ 399: elsif (keepflag && *prevauthor!='\0') 400: newdelta.author=prevauthor; /* preserve old author of possible*/ 401: else newdelta.author=caller; /* otherwise use caller's id */ 402: if (state!=nil) 403: newdelta.state=state; /* set state given by -s */ 404: elsif (keepflag && *prevstate!='\0') 405: newdelta.state=prevstate; /* preserve old state if possilbe */ 406: else newdelta.state=DEFAULTSTATE;/* otherwise use default state */ 407: if (*altdate!='\0') 408: newdelta.date=altdate; /* set date given by -d */ 409: elsif (keepflag && *prevdate!='\0') /* preserve old date if possible */ 410: newdelta.date =prevdate; 411: else 412: newdelta.date = getdate(); /* use current date */ 413: /* now check validity of date -- needed because of -d and -k */ 414: if (targetdelta!=nil && 415: cmpnum(newdelta.date,targetdelta->date)<=0) { 416: error("Date %s is not later than %s in existing revision %s", 417: newdelta.date,targetdelta->date, targetdelta->num); 418: continue; 419: } 420: 421: 422: if (lockflag && !addlock(&newdelta,caller)) continue; 423: if (symbol && !addsymbol(&newdelta,symbol,symrebindflag)) continue; 424: 425: /* prepare for rewriting the RCS file */ 426: newRCSfilename=mktempfile(RCSfilename,NEWRCSFILE); 427: if ((frewrite=fopen(newRCSfilename, "w"))==NULL) { 428: error("Can't open file %s",newRCSfilename); 429: continue; 430: } 431: putadmin(frewrite); 432: puttree(Head,frewrite); 433: VOID putdesc(initflag,textflag,textfile,quietflag); 434: 435: 436: /* build rest of file */ 437: if (initflag||rcsinitflag) { 438: /* get logmessage */ 439: newdelta.log=getlogmsg(); 440: if(!putdtext(newdelnum,newdelta.log,workfilename,frewrite)) continue; 441: ffclose(frewrite); frewrite=NULL; 442: } else { 443: diffilename=mktempfile("/tmp/",DIFFILE); 444: if (&newdelta==Head) { 445: /* prepend new one */ 446: rewriteflag=false; 447: if (!(expfilename= 448: buildrevision(gendeltas,targetdelta,"/tmp/",false))) continue; 449: if (!mustcheckin(expfilename,targetdelta)) continue; 450: /* don't check in files that aren't different, unless forced*/ 451: newdelta.log=getlogmsg(); 452: VOID sprintf(command,"%s -n %s %s > %s\n", DIFF, 453: workfilename,expfilename,diffilename); 454: exit_stats = system (command); 455: if (exit_stats != 0 && exit_stats != (1 << BYTESIZ)) 456: faterror ("diff failed"); 457: /* diff returns 2 in the upper byte on failure */ 458: if(!putdtext(newdelnum,newdelta.log,workfilename,frewrite)) continue; 459: if(!putdtext(olddeltanum,targetdelta->log,diffilename,frewrite)) continue; 460: } else { 461: /* insert new delta text */ 462: rewriteflag=true; 463: if (!(expfilename= 464: buildrevision(gendeltas,targetdelta,"/tmp/",false))) continue; 465: if (!mustcheckin(expfilename,targetdelta)) continue; 466: /* don't check in files that aren't different, unless forced*/ 467: newdelta.log=getlogmsg(); 468: VOID sprintf(command,"%s -n %s %s > %s\n", DIFF, 469: expfilename,workfilename,diffilename); 470: exit_stats = system (command); 471: if (exit_stats != 0 && exit_stats != (1 << BYTESIZ)) 472: faterror ("diff failed"); 473: if(!putdtext(newdelnum,newdelta.log,diffilename,frewrite)) continue; 474: } 475: 476: /* rewrite rest of RCS file */ 477: fastcopy(finptr,frewrite); 478: ffclose(frewrite); frewrite=NULL; 479: } 480: ignoreints(); 481: if (rename(newRCSfilename,RCSfilename)<0) { 482: error("Can't write new RCS file %s; saved in %s", 483: RCSfilename,newRCSfilename); 484: newRCSfilename[0]='\0'; /* avoid deletion by cleanup*/ 485: restoreints(); 486: VOID cleanup(); 487: break; 488: } 489: newRCSfilename[0]='\0'; /* avoid re-unlinking by cleanup()*/ 490: 491: newRCSmode= (initflag|rcsinitflag?workstat.st_mode:RCSstat.st_mode)& ~0222; 492: /* newRCSmode is also used to adjust mode of working file for -u and -l */ 493: if (chmod(RCSfilename,newRCSmode)<0) 494: warn("Can't set mode of %s",RCSfilename); 495: 496: restoreints(); 497: # ifdef SNOOPFILE 498: logcommand("ci",&newdelta,gendeltas,caller); 499: # endif 500: 501: if (!keepworkingfile) { 502: VOID unlink(workfilename); /* get rid of old file */ 503: } else { 504: /* expand keywords in file */ 505: newworkfilename= 506: xpandfile(workfilename,workfilename /*for directory*/,&newdelta); 507: if (!newworkfilename) continue; /* expand failed */ 508: ignoreints(); 509: if (rename(newworkfilename,workfilename) <0) { 510: error("Can't expand keywords in %s",workfilename); 511: restoreints(); 512: continue; 513: } 514: newworkfilename[0]='\0'; /* avoid re-unlink by cleanup */ 515: if (chmod(workfilename, WORKMODE(newRCSmode))<0) 516: warn("Can't adjust mode of %s",workfilename); 517: restoreints(); 518: } 519: diagnose("done"); 520: 521: } while (cleanup(), 522: ++argv, --argc >=1); 523: 524: exit(nerror!=0); 525: /*NOTREACHED*/ 526: } /* end of main (ci) */ 527: /*****************************************************************/ 528: /* the rest are auxiliary routines */ 529: 530: 531: int addelta() 532: /* Function: Appends a delta to the delta tree, whose number is 533: * given by newdelnum[]. Updates Head, newdelnum, newdenumlength, 534: * olddeltanum and the links in newdelta. 535: * Retruns false on error, true on success. 536: */ 537: { 538: register char * sp, * tp; 539: register int i; 540: 541: newdnumlength=countnumflds(newdelnum); 542: 543: if (initflag || rcsinitflag ) { 544: /* this covers non-existing RCS file and a file initialized with rcs -i */ 545: if ((newdnumlength==0)&&(Dbranch!=nil)) { 546: VOID strcpy(newdelnum,Dbranch->num); 547: newdnumlength=countnumflds(newdelnum); 548: } 549: if (newdnumlength==0) VOID strcpy(newdelnum,"1.1"); 550: elsif (newdnumlength==1) VOID strcat(newdelnum,".1"); 551: elsif (newdnumlength>2) { 552: error("Branch point does not exist for %s",newdelnum); 553: return false; 554: } /* newdnumlength == 2 is OK; */ 555: olddeltanum=nil; 556: Head = &newdelta; 557: newdelta.next=nil; 558: return true; 559: } 560: if (newdnumlength==0) { 561: /* derive new revision number from locks */ 562: targetdelta=findlock(caller,true); /*find and delete it*/ 563: if (targetdelta) { 564: /* found an old lock */ 565: olddeltanum=targetdelta->num; 566: /* check whether locked revision exists */ 567: if (!genrevs(olddeltanum,(char *)nil,(char *)nil,(char *)nil,gendeltas)) return false; 568: if (targetdelta==Head) { 569: /* make new head */ 570: newdelta.next=Head; 571: Head= &newdelta; 572: incnum(olddeltanum, newdelnum); 573: } elsif ((targetdelta->next==nil)&&(countnumflds(olddeltanum)>2)) { 574: /* new tip revision on side branch */ 575: targetdelta->next= &newdelta; 576: newdelta.next = nil; 577: incnum(olddeltanum, newdelnum); 578: } else { 579: /* middle revision; start a new branch */ 580: newdelnum[0]='\0'; 581: if (!addbranch(targetdelta,newdelnum)) return false; 582: } 583: return true; /* successfull use of existing lock */ 584: } else { 585: /* no existing lock; try Dbranch */ 586: /* update newdelnum */ 587: if (!((StrictLocks==false) && (getuid() == RCSstat.st_uid))) { 588: error("no lock set by %s",caller); 589: return false; 590: } 591: if (Dbranch) { 592: VOID strcpy(newdelnum,Dbranch->num); 593: } else { 594: incnum(Head->num,newdelnum); 595: } 596: newdnumlength=countnumflds(newdelnum); 597: /* now fall into next statement */ 598: } 599: } 600: if (newdnumlength<=2) { 601: /* add new head per given number */ 602: olddeltanum=Head->num; 603: if(newdnumlength==1) { 604: /* make a two-field number out of it*/ 605: if (cmpnumfld(newdelnum,olddeltanum,1)==0) 606: incnum(olddeltanum,newdelnum); 607: else VOID strcat(newdelnum, ".1"); 608: } 609: if (cmpnum(newdelnum,olddeltanum) <= 0) { 610: error("deltanumber %s too low; must be higher than %s", 611: newdelnum,Head->num); 612: return false; 613: } 614: if (!(targetdelta=removelock(caller,Head))) return false; 615: if (!(genrevs(olddeltanum,(char *)nil,(char *)nil,(char *)nil,gendeltas))) return false; 616: newdelta.next=Head; 617: Head= &newdelta; 618: } else { 619: /* put new revision on side branch */ 620: /*first, get branch point */ 621: tp=branchpointnum; sp=newdelnum; 622: for(i=newdnumlength-(newdnumlength%2==1?1:2);i>0;i--) { 623: while (*sp != '.') *tp++ = *sp++; /*copy field*/ 624: *tp++ = *sp++; /*copy dot */ 625: } 626: *(tp-1) = '\0'; /* kill final dot */ 627: olddeltanum=branchpointnum; /*temporary old delta*/ 628: if (!(targetdelta=genrevs(branchpointnum,(char *)nil,(char *)nil,(char *)nil,gendeltas))) 629: return false; 630: if (cmpnum(targetdelta->num,branchpointnum)!=0) { 631: error("Cannot find branchpoint %s",branchpointnum); 632: return false; 633: } 634: if (!addbranch(targetdelta,newdelnum)) return false; 635: } 636: return true; 637: } 638: 639: 640: 641: int addbranch(branchpoint,num) 642: struct hshentry * branchpoint; 643: char * num; 644: /* adds a new branch and branch delta at branchpoint. 645: * If num is the null string, appends the new branch, incrementing 646: * the highest branch number (initially 1), and setting the level number to 1. 647: * the new delta and branchhead are in globals newdelta and newbranch, resp. 648: * the new number is placed into num. 649: * returns false on error. 650: */ 651: { 652: struct branchhead * bhead, * btrail; 653: char branchnum[revlength]; 654: int numlength, result, field; 655: 656: numlength = countnumflds(num); 657: 658: if (branchpoint->branches==nil) { 659: /* start first branch */ 660: branchpoint->branches = &newbranch; 661: if (numlength==0) { 662: VOID strcpy(num, branchpoint->num); 663: VOID strcat(num,".1.1"); 664: } elsif(countnumflds(num)%2 == 1) 665: VOID strcat(num, ".1"); 666: newbranch.nextbranch=nil; 667: 668: } elsif (numlength==0) { 669: /* append new branch to the end */ 670: bhead=branchpoint->branches; 671: while (bhead->nextbranch) bhead=bhead->nextbranch; 672: bhead->nextbranch = &newbranch; 673: getbranchno(bhead->hsh->num,branchnum); 674: incnum(branchnum,num); 675: VOID strcat(num,".1"); 676: newbranch.nextbranch=nil; 677: } else { 678: /* place the branch properly */ 679: field = numlength - (numlength%2 ==1?0:1); 680: /* field of branch number */ 681: bhead=branchpoint->branches; 682: while ((bhead!=nil) && 683: ((result=cmpnumfld(num,bhead->hsh->num,field))>0)) { 684: btrail=bhead; 685: bhead=bhead->nextbranch; 686: } 687: if (bhead==nil || result<0) { 688: /* insert/append new branchhead */ 689: if (bhead==branchpoint->branches) 690: branchpoint->branches= &newbranch; 691: else btrail->nextbranch= &newbranch; 692: newbranch.nextbranch=bhead; 693: if (numlength%2 ==1) VOID strcat(num,".1"); 694: } else { 695: /* branch exists; append to end */ 696: getbranchno(num,branchnum); 697: if (!(targetdelta=genrevs(branchnum,(char *)nil,(char *)nil,(char *)nil, 698: gendeltas))) return false; 699: olddeltanum=targetdelta->num; 700: if (cmpnum(num,olddeltanum) <= 0) { 701: error("deltanumber %s too low; must be higher than %s", 702: num,olddeltanum); 703: return false; 704: } 705: if (!removelock(caller,targetdelta)) return false; 706: if (numlength%2==1) incnum(olddeltanum,num); 707: targetdelta->next= &newdelta; 708: newdelta.next=nil; 709: return true; /* Don't do anything to newbranch */ 710: } 711: } 712: newbranch.hsh = &newdelta; 713: newdelta.next=nil; 714: return true; 715: } 716: 717: 718: 719: struct hshentry * removelock(who,delta) 720: char * who; struct hshentry * delta; 721: /* function: Finds the lock held by who on delta, 722: * removes it, and returns a pointer to the delta. 723: * Prints an error message and returns nil if there is no such lock. 724: * An exception is if StrictLocks==false, and who is the owner of 725: * the RCS file. If who does not have a lock in this case, 726: * delta is returned. 727: */ 728: { 729: register struct lock * next, * trail; 730: char * num; 731: struct lock dummy; 732: int whomatch, nummatch; 733: 734: num=delta->num; 735: dummy.nextlock=next=Locks; 736: trail = &dummy; 737: while (next!=nil) { 738: whomatch=strcmp(who,next->login); 739: nummatch=strcmp(num,next->delta->num); 740: if ((whomatch==0) && (nummatch==0)) break; 741: /*found a lock on delta by who*/ 742: if ((whomatch!=0)&&(nummatch==0)) { 743: error("revision %s locked by %s",num,next->login); 744: return nil; 745: } 746: trail=next; 747: next=next->nextlock; 748: } 749: if (next!=nil) { 750: /*found one; delete it */ 751: trail->nextlock=next->nextlock; 752: Locks=dummy.nextlock; 753: next->delta->lockedby=nil; /* reset locked-by */ 754: return next->delta; 755: } else { 756: if (!((StrictLocks==false) && (getuid() == RCSstat.st_uid))) { 757: error("no lock set by %s for revision %s",who,num); 758: return nil; 759: } else { 760: return delta; 761: } 762: } 763: } 764: 765: 766: 767: char * getdate() 768: /* Function: returns a pointer to the current date in the form 769: * YY.MM.DD.hh.mm.ss\0 770: */ 771: { 772: long clock; 773: struct tm * tm; 774: static char buffer[datelength]; /* date buffer */ 775: clock=time((long *)0); 776: tm=localtime(&clock); 777: VOID sprintf(buffer, DATEFORM, 778: tm->tm_year, tm->tm_mon+1, tm->tm_mday, 779: tm->tm_hour, tm->tm_min, tm->tm_sec); 780: return buffer; 781: } 782: 783: 784: char * xpandfile (unexfname,dir,delta) 785: char * unexfname, * dir; 786: struct hshentry * delta; 787: /* Function: Reads file unexpfname and copies it to a 788: * file in dir, performing keyword substitution with data from delta. 789: * returns the name of the expanded file if successful, nil otherwise. 790: */ 791: { char * targetfname; 792: FILE * unexfile, *exfile; 793: 794: targetfname=mktempfile(dir,TMPFILE3); 795: if ((unexfile=fopen(unexfname, "r" ))==NULL || 796: (exfile =fopen(targetfname,"w"))==NULL) { 797: error("Can't expand file %s",unexfname); 798: return nil; 799: } 800: while (expandline(unexfile,exfile,delta,false,false)); /*expand*/ 801: ffclose(unexfile);ffclose(exfile); 802: return targetfname; 803: } 804: 805: 806: mustcheckin (unexfname,delta) 807: char * unexfname; struct hshentry * delta; 808: /* Function: determines whether checkin should proceed. 809: * Compares the wrkfilename with unexfname, disregarding keywords. 810: * If the 2 files differ, returns true. If they do not differ, asks the user 811: * whether to return true or false (i.e., whether to checkin the file anyway. 812: * If the files do not differ, and quietflag==true, returns false. 813: * Shortcut: If forceciflag==true, mustcheckin() always returns true. 814: */ 815: { register int c; 816: int response, result; 817: 818: if (forceciflag) return true; 819: 820: if (!rcsfcmp(workfilename,unexfname,delta)) return true; 821: /* If files are different, must check them in. */ 822: 823: /* files are the same */ 824: diagnose("File %s is unchanged with respect to revision %s", 825: workfilename,delta->num); 826: if (quietflag || !isatty(fileno(stdin))) { 827: /* Files are the same, but can't ask, so don't checkin*/ 828: result=false; 829: } else { 830: /* ask user whether to check in */ 831: VOID fputs("checkin anyway? [ny](n): ",stderr); 832: response=c=getchar(); 833: while (!(c==EOF || c=='\n')) c=getchar();/*skip to end of line*/ 834: result=(response=='y'||response=='Y'); 835: } 836: if (result==false) { 837: if (quietflag) { 838: warn("checkin aborted since %s was not changed; %s %sdeleted.", 839: workfilename,workfilename,keepworkingfile?"not ":""); 840: } else { 841: diagnose("checkin aborted; %s %sdeleted.", 842: workfilename,keepworkingfile?"not ":""); 843: } 844: if (!keepworkingfile) VOID unlink(workfilename); 845: } 846: return result; 847: } 848: 849: 850: 851: 852: /* --------------------- G E T L O G M S G --------------------------------*/ 853: extern int stdinread; /* is >0 if redirected stdin has been read once. */ 854: 855: 856: char * getlogmsg() 857: /* Function: obtains a log message and returns a pointer to it. 858: * If a log message is given via the -m option, a pointer to that 859: * string is returned. 860: * If this is the initial revision, a standard log message is returned. 861: * Otherwise, reads a character string from the terminal. 862: * The string must be terminated with a control-d or a single '.' on a 863: * line. getlogmsg prompts the first time it is called for the 864: * log message; during all later calls it asks whether the previous 865: * log message can be reused. 866: * returns a pointer to the character string; the pointer is always non-nil. 867: */ 868: { 869: static logyet = false; /*indicates whether previous log present*/ 870: static char emptylog[] = "*** empty log message ***\n"; 871: static char initiallog[]= "Initial revision\n"; 872: char response; 873: int cin; 874: register char c, old1, old2, * tp; 875: 876: if (msg) return msg; 877: 878: if ((olddeltanum==nil)&& 879: ((cmpnum(newdelnum,"1.1")==0)||(cmpnum(newdelnum,"1.0")==0))) { 880: return initiallog; 881: } 882: if (keepflag) { 883: /* generate std. log message */ 884: VOID sprintf(logmsg, "checked in with -k by %s at %s.\n",caller,getdate()); 885: return(logmsg); 886: } 887: if (logyet) { 888: /*previous log available*/ 889: if (!isatty(fileno(stdin))) return logmsg; /* reuse if stdin is not a terminal*/ 890: /* otherwise ask */ 891: clearerr(stdin); /* reset EOF ptr */ 892: VOID fputs("reuse log message of previous file? [yn](y): ",stderr); 893: cin=getchar(); 894: response=cin; 895: while (!(cin==EOF || cin=='\n')) cin=getchar();/*skip to end of line*/ 896: if (response=='\n'||response=='y'||response=='Y') 897: return logmsg; 898: else 899: logmsg[0]='\0'; /*kill existing log message */ 900: } 901: 902: /* now read string from stdin */ 903: if (isatty(fileno(stdin))) { 904: VOID fputs("enter log message:\n(terminate with ^D or single '.')\n>> ",stderr); 905: } else { /* redirected stdin */ 906: if (stdinread>0) 907: faterror("Can't reread redirected stdin for log message; use -m"); 908: stdinread++; 909: } 910: 911: tp=logmsg; old1='\n'; old2=' '; 912: if (feof(stdin)) 913: clearerr(stdin); 914: for (;;) { 915: cin=getchar(); 916: if (cin==EOF) { 917: if(isatty(fileno(stdin))) VOID putc('\n',stderr); 918: if ((tp==logmsg)||(*(tp-1)!='\n')) *tp++ = '\n'; /* append newline */ 919: *tp = '\0'; /*terminate*/ 920: break; 921: } 922: if (cin=='\n' && old1=='.' && old2=='\n') { 923: *(tp-1) = '\0'; /*kill last period */ 924: break; 925: } 926: if (tp>=logmsg+logsize-2) { /* overflow */ 927: if (!isatty(fileno(stdin))) { 928: warn("log message truncated to %d characters",logsize); 929: logmsg[logsize-2]='\n';logmsg[logsize-1]='\0'; 930: return logmsg; 931: } 932: VOID fprintf(stderr,"log message too long. Maximum: %d\n",logsize); 933: VOID fputs("reenter log message:\n>> ",stderr); 934: tp=logmsg; old1='\n'; old2=' '; 935: while (cin!='\n') cin=getchar(); /*skip line */ 936: continue; 937: } 938: if (cin=='\n' && isatty(fileno(stdin))) VOID fputs(">> ",stderr); 939: *tp++ = cin; old2=old1; old1=cin; /* this is the actual work!*/ 940: /*SDELIM will be changed to double SDELIM by putdtext*/ 941: } /* end for */ 942: 943: /* now check whether the log message is not empty */ 944: tp=logmsg; 945: while ((c= *tp++)==' '||c=='\t'||c=='\n'||c=='\f'); 946: if (*tp=='\0') { 947: logyet=false; 948: return emptylog; 949: } else { 950: logyet=true; 951: return logmsg; 952: } 953: }