From: CSRG Date: Thu, 18 Feb 1988 04:47:53 +0000 (-0800) Subject: BSD 4_3_Tahoe development X-Git-Tag: BSD-4_3_Net_1^2~213 X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/commitdiff_plain/a7abfc7349ceb59f0183c475ef6d42117444a2f2 BSD 4_3_Tahoe development Work on file usr/src/new/rcs/src/ci.c Synthesized-from: CSRG/cd2/4.3tahoe --- diff --git a/usr/src/new/rcs/src/ci.c b/usr/src/new/rcs/src/ci.c new file mode 100644 index 0000000000..78823f21b9 --- /dev/null +++ b/usr/src/new/rcs/src/ci.c @@ -0,0 +1,953 @@ +/* + * RCS checkin operation + */ +#ifndef lint + static char rcsid[]= + "$Header: /usr/src/local/bin/rcs/src/RCS/ci.c,v 4.6 87/12/18 11:34:41 narten Exp $ Purdue CS"; +#endif +/******************************************************************* + * check revisions into RCS files + ******************************************************************* + * + * Copyright (C) 1982 by Walter F. Tichy + * Purdue University + * Computer Science Department + * West Lafayette, IN 47907 + * + * All rights reserved. No part of this software may be sold or distributed + * in any form or by any means without the prior written permission of the + * author. + * Report problems and direct all inquiries to Tichy@purdue (ARPA net). + */ + + + +/* $Log: ci.c,v $ + * Revision 4.6 87/12/18 11:34:41 narten + * lint cleanups (from Guy Harris) + * + * Revision 4.5 87/10/18 10:18:48 narten + * Updating version numbers. Changes relative to revision 1.1 are actually + * relative to 4.3 + * + * Revision 1.3 87/09/24 13:57:19 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:21:33 jenkins + * Port to suns + * + * Revision 1.1 84/01/23 14:49:54 kcs + * Initial revision + * + * Revision 4.3 83/12/15 12:28:54 wft + * ci -u and ci -l now set mode of working file properly. + * + * Revision 4.2 83/12/05 13:40:54 wft + * Merged with 3.9.1.1: added calls to clearerr(stdin). + * made rewriteflag external. + * + * Revision 4.1 83/05/10 17:03:06 wft + * Added option -d and -w, and updated assingment of date, etc. to new delta. + * Added handling of default branches. + * Option -k generates std. log message; fixed undef. pointer in reading of log. + * Replaced getlock() with findlock(), link--unlink with rename(), + * getpwuid() with getcaller(). + * Moved all revision number generation to new routine addelta(). + * Removed calls to stat(); now done by pairfilenames(). + * Changed most calls to catchints() with restoreints(). + * Directed all interactive messages to stderr. + * + * Revision 3.9.1.1 83/10/19 04:21:03 lepreau + * Added clearerr(stdin) to getlogmsg() for re-reading stdin. + * + * Revision 3.9 83/02/15 15:25:44 wft + * 4.2 prerelease + * + * Revision 3.9 83/02/15 15:25:44 wft + * Added call to fastcopy() to copy remainder of RCS file. + * + * Revision 3.8 83/01/14 15:34:05 wft + * Added ignoring of interrupts while new RCS file is renamed; + * Avoids deletion of RCS files by interrupts. + * + * Revision 3.7 82/12/10 16:09:20 wft + * Corrected checking of return code from diff. + * + * Revision 3.6 82/12/08 21:34:49 wft + * Using DATEFORM to prepare date of checked-in revision; + * Fixed return from addbranch(). + * + * Revision 3.5 82/12/04 18:32:42 wft + * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated + * field lockedby in removelock(), moved getlogmsg() before calling diff. + * + * Revision 3.4 82/12/02 13:27:13 wft + * added option -k. + * + * Revision 3.3 82/11/28 20:53:31 wft + * Added mustcheckin() to check for redundant checkins. + * Added xpandfile() to do keyword expansion for -u and -l; + * -m appends linefeed to log message if necessary. + * getlogmsg() suppresses prompt if stdin is not a terminal. + * Replaced keeplock with lockflag, fclose() with ffclose(), + * %02d with %.2d, getlogin() with getpwuid(). + * + * Revision 3.2 82/10/18 20:57:23 wft + * An RCS file inherits its mode during the first ci from the working file, + * otherwise it stays the same, except that write permission is removed. + * Fixed ci -l, added ci -u (both do an implicit co after the ci). + * Fixed call to getlogin(), added call to getfullRCSname(), added check + * for write error. + * Changed conflicting identifiers. + * + * Revision 3.1 82/10/13 16:04:59 wft + * fixed type of variables receiving from getc() (char -> int). + * added include file dbm.h for getting BYTESIZ. This is used + * to check the return code from diff portably. + */ + +#include "rcsbase.h" +#ifndef lint +static char rcsbaseid[] = RCSBASE; +#endif +#include +#include +#include "time.h" + +extern int rename(); /*rename files */ +extern char * getcaller(); /*login of caller */ +extern struct hshentry * genrevs(); /*generate delta numbers */ +extern int nextc; /*next input character */ +extern quietflag; /*suppresses diagnostics if true */ +extern int nerror; /*counter for errors */ +extern char * buildrevision(); /*constructs desired revision */ +extern char * checkid(); /*check identifiers */ +extern int partime(); /*parse free-format date/time */ +extern long maketime(); /*convert parsed time to unix time. */ +extern long time(); /*get date and time */ +extern struct tm * localtime(); /*convert unixtime into tm-structure */ +extern char * getdate(); /*formates current date (forward) */ +extern char * mktempfile(); /*temporary file name generator */ +extern struct lock * addlock(); /*adds a new lock */ +extern char * getlogmsg(); /*obtains log message; forward */ +extern struct hshentry * removelock(); /*finds a caller's lock (forward) */ +extern struct hshentry * findlock(); /*finds a lock */ +extern char * xpandfile(); /*perform keyword expansion; forward */ + +extern char prevauthor[]; +extern char prevdate[]; +extern char prevrev[]; +extern char prevstate []; +extern FILE * finptr; /* RCS input file */ +extern FILE * frewrite; /* new RCS file */ +extern int rewriteflag; /* indicates whether input should be */ + /* echoed to frewrite */ + +char * newRCSfilename, * diffilename; +char * RCSfilename,*workfilename,*expfilename,*newworkfilename; +extern struct stat RCSstat, workstat; /* file status of RCS and work file */ +extern int haveRCSstat, haveworkstat;/* status indicators */ + + +int copyflag; /* indicates whether a string should be copied into memory*/ + +char * rev, * state, *msg; + +int initflag, rcsinitflag; +int lockflag, keepworkingfile,keepflag; +int forceciflag; /* forces check in */ +int symrebindflag; char * symbol; +int textflag; char * textfile; +char * caller; /* caller's login; */ +char * author; /* alternate author for -w option */ +char altdate[datelength]; /* alternate date for -d */ +struct hshentry * targetdelta; /* old delta to be generated */ +char * olddeltanum; /* number of old delta */ +struct hshentry * gendeltas[hshsize]; /* stores deltas to be generated */ +char newdelnum[revlength]; /* holds new revision number */ +int newdnumlength; /* actual length of new rev. num. */ +char branchpointnum[revlength]; /* number of branchpoint */ +struct hshentry newdelta; /* new delta to be inserted */ +struct branchhead newbranch; /* new branch to be inserted */ +char logmsg[logsize]; /* buffer for log message */ + +main (argc, argv) +int argc; +char * argv[]; +{ + char * nametest; + char * cmdusage; /* holds command format */ + char command[NCPPN+50]; /* holds diff commands */ + int msglen; /* length of message given by -m */ + int exit_stats; /* return code for system() calls */ + int newRCSmode; /* mode for RCS file */ + long unixtime; + struct tm parseddate, *ftm; + + catchints(); + cmdid = "ci"; + cmdusage = "command format:\nci -r[rev] -l[rev] -u[rev] -f[rev] -k[rev] -q[rev] -mmsg -nname -Nname -sstate -t[txtfile] file ..."; + rev = state = msg = symbol = textfile = nil; + initflag= rcsinitflag= symrebindflag= textflag= quietflag= false; + forceciflag= lockflag= keepworkingfile= keepflag= false; + caller = getcaller(); author = nil; /* author may be reset by -w */ + altdate[0]= '\0'; /* empty alternate date for -d */ + + while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) { + switch ((*argv)[1]) { + + case 'r': + lockflag=false; + revno: if ((*argv)[2]!='\0') { + if (rev!=nil) warn("Redefinition of revision number"); + rev = (*argv)+2; + } + break; + + case 'l': + keepworkingfile=lockflag=true; + goto revno; + + case 'u': + keepworkingfile=true; lockflag=false; + goto revno; + + case 'q': + quietflag=true; + goto revno; + + case 'f': + forceciflag=true; + goto revno; + + case 'k': + keepflag=true; + goto revno; + + case 'm': + if ((*argv)[2]!='\0'){ + if (msg!=nil)warn("Redefinition of -m option"); + msg = (*argv)+2; + msglen=strlen(msg); + if (msglen >= logsize) { + warn("log message truncated to %d characters", + logsize); + msg[logsize-2]='\n'; + msg[logsize-1]='\0'; + } + if (msg[msglen-1]!='\n') { + /*append linefeed*/ + VOID strcpy(logmsg,msg);msg=logmsg; + msg[msglen] = '\n'; + msg[++msglen]= '\0'; + } + } else warn("Missing message for -m option"); + break; + + case 'n': + symrebindflag=false; + if ((*argv)[2]!='\0'){ + if (symbol!=nil)warn("Redefinition of symbolic name"); + symbol = (*argv)+2; + if (!(nametest=checkid(symbol,' '))||*nametest) + faterror("Name %s must be one word",symbol); + } else warn("Missing name for -n option"); + break; + + case 'N': + symrebindflag=true; + if ((*argv)[2]!='\0'){ + if (symbol!=nil)warn("Redefinition of symbolic name"); + symbol = (*argv)+2; + if (!(nametest=checkid(symbol,' '))||*nametest) + faterror("Name %s must be one word",symbol); + } else warn("Missing name for -N option"); + break; + + case 's': + if ((*argv)[2]!='\0'){ + if (state!=nil)warn("Redefinition of -s option"); + state = (*argv)+2; + VOID checkid(state,' '); + } else warn("Missing state for -s option"); + break; + + case 't': + textflag=true; + if ((*argv)[2]!='\0'){ + if (textfile!=nil)warn("Redefinition of -t option"); + textfile = (*argv)+2; + } + break; + + case 'd': + if ((*argv)[2]!='\0'){ + if (altdate[0]!='\0')warn("Redefinition of -d option"); + /* process the date */ + if ( partime((*argv)+2, &parseddate) == 0) { + faterror("Can't parse date/time: %s", (*argv)+2); + break; + } + if ( (unixtime = maketime(&parseddate)) == 0L) { + faterror("Inconsistent date/time: %s",(*argv)+2); + break; + } + ftm = localtime(&unixtime); + VOID sprintf(altdate,DATEFORM, + ftm->tm_year,ftm->tm_mon+1,ftm->tm_mday,ftm->tm_hour,ftm->tm_min,ftm->tm_sec); + } else warn("Missing date for -d option"); + break; + + case 'w': + if ((*argv)[2]!='\0'){ + if (author!=nil)warn("Redefinition of -w option"); + author = (*argv)+2; + VOID checkid(author,' '); + } else warn("Missing author for -w option"); + break; + + + + + default: + faterror("unknown option: %s\n%s", *argv,cmdusage); + }; + } /* end processing of options */ + + if (argc<1) faterror("No input file\n%s",cmdusage); + + if (!isatty(fileno(stdin)) && msg==nil && textflag && textfile==nil) { + /* would need both log message and descriptive text from a file */ + faterror("Can't take both log and description from redirected stdin; use -ttextfile"); + } + /* now handle all filenames */ + do { + gendeltas[0] = nil; + copyflag=rewriteflag=false; + finptr=frewrite=NULL; + targetdelta=nil; + olddeltanum=nil; + + switch (pairfilenames(argc,argv,false,false)) { + + case -1: /* New RCS file */ + initflag=true; rcsinitflag=false; + break; + + case 0: /* Error */ + continue; + + case 1: /* Normal checkin with prev . RCS file */ + initflag=false; rcsinitflag=(Head==nil); + } + + /* now RCSfilename contains the name of the RCS file, and + * workfilename contains the name of the working file. + * if !initflag, finptr contains the file descriptor for the + * RCS file. The admin node is initialized. + * workstat and RCSstat are set. + */ + + diagnose("%s <-- %s", RCSfilename,workfilename); + + if (access(workfilename,4)!=0) { + error("working file %s not readable or nonexistent", + workfilename); + continue; + } + + if (!trydiraccess(RCSfilename)) continue; /* give up */ + if (!initflag && !checkaccesslist(caller)) continue; /* give up */ + if (!trysema(RCSfilename,true)) continue; /* give up */ + + if (keepflag) { + /* get keyword values from working file */ + if (!getoldkeys(workfilename)) continue; + if (rev==nil && *(rev=prevrev)=='\0') { + error("Can't find a revision number in %s",workfilename); + continue; + } + if (*prevdate=='\0' && *altdate=='\0') + warn("Can't find a date in %s",workfilename); + if (*prevauthor=='\0' && author==nil) + warn("Can't find an author in %s", workfilename); + if (*prevstate=='\0' && state==nil) + warn("Can't find a state in %s", workfilename); + } /* end processing keepflag */ + + gettree(); /* reads in the delta tree.*/ + + /* expand symbolic revision number */ + if (!expandsym(rev,newdelnum)) continue; + + /* splice new delta into tree */ + if (!addelta()) continue; + + if (initflag||rcsinitflag) { + diagnose("initial revision: %s",newdelnum); + } else diagnose("new revision: %s; previous revision: %s", + newdelnum,olddeltanum); + + newdelta.num=newdelnum; + newdelta.branches=nil; + newdelta.log=nil; + newdelta.lockedby=nil; /*might be changed by addlock() */ + /* set author */ + if (author!=nil) + newdelta.author=author; /* set author given by -w */ + elsif (keepflag && *prevauthor!='\0') + newdelta.author=prevauthor; /* preserve old author of possible*/ + else newdelta.author=caller; /* otherwise use caller's id */ + if (state!=nil) + newdelta.state=state; /* set state given by -s */ + elsif (keepflag && *prevstate!='\0') + newdelta.state=prevstate; /* preserve old state if possilbe */ + else newdelta.state=DEFAULTSTATE;/* otherwise use default state */ + if (*altdate!='\0') + newdelta.date=altdate; /* set date given by -d */ + elsif (keepflag && *prevdate!='\0') /* preserve old date if possible */ + newdelta.date =prevdate; + else + newdelta.date = getdate(); /* use current date */ + /* now check validity of date -- needed because of -d and -k */ + if (targetdelta!=nil && + cmpnum(newdelta.date,targetdelta->date)<=0) { + error("Date %s is not later than %s in existing revision %s", + newdelta.date,targetdelta->date, targetdelta->num); + continue; + } + + + if (lockflag && !addlock(&newdelta,caller)) continue; + if (symbol && !addsymbol(&newdelta,symbol,symrebindflag)) continue; + + /* prepare for rewriting the RCS file */ + newRCSfilename=mktempfile(RCSfilename,NEWRCSFILE); + if ((frewrite=fopen(newRCSfilename, "w"))==NULL) { + error("Can't open file %s",newRCSfilename); + continue; + } + putadmin(frewrite); + puttree(Head,frewrite); + VOID putdesc(initflag,textflag,textfile,quietflag); + + + /* build rest of file */ + if (initflag||rcsinitflag) { + /* get logmessage */ + newdelta.log=getlogmsg(); + if(!putdtext(newdelnum,newdelta.log,workfilename,frewrite)) continue; + ffclose(frewrite); frewrite=NULL; + } else { + diffilename=mktempfile("/tmp/",DIFFILE); + if (&newdelta==Head) { + /* prepend new one */ + rewriteflag=false; + if (!(expfilename= + buildrevision(gendeltas,targetdelta,"/tmp/",false))) continue; + if (!mustcheckin(expfilename,targetdelta)) continue; + /* don't check in files that aren't different, unless forced*/ + newdelta.log=getlogmsg(); + VOID sprintf(command,"%s -n %s %s > %s\n", DIFF, + workfilename,expfilename,diffilename); + exit_stats = system (command); + if (exit_stats != 0 && exit_stats != (1 << BYTESIZ)) + faterror ("diff failed"); + /* diff returns 2 in the upper byte on failure */ + if(!putdtext(newdelnum,newdelta.log,workfilename,frewrite)) continue; + if(!putdtext(olddeltanum,targetdelta->log,diffilename,frewrite)) continue; + } else { + /* insert new delta text */ + rewriteflag=true; + if (!(expfilename= + buildrevision(gendeltas,targetdelta,"/tmp/",false))) continue; + if (!mustcheckin(expfilename,targetdelta)) continue; + /* don't check in files that aren't different, unless forced*/ + newdelta.log=getlogmsg(); + VOID sprintf(command,"%s -n %s %s > %s\n", DIFF, + expfilename,workfilename,diffilename); + exit_stats = system (command); + if (exit_stats != 0 && exit_stats != (1 << BYTESIZ)) + faterror ("diff failed"); + if(!putdtext(newdelnum,newdelta.log,diffilename,frewrite)) continue; + } + + /* rewrite rest of RCS file */ + fastcopy(finptr,frewrite); + ffclose(frewrite); frewrite=NULL; + } + ignoreints(); + if (rename(newRCSfilename,RCSfilename)<0) { + error("Can't write new RCS file %s; saved in %s", + RCSfilename,newRCSfilename); + newRCSfilename[0]='\0'; /* avoid deletion by cleanup*/ + restoreints(); + VOID cleanup(); + break; + } + newRCSfilename[0]='\0'; /* avoid re-unlinking by cleanup()*/ + + newRCSmode= (initflag|rcsinitflag?workstat.st_mode:RCSstat.st_mode)& ~0222; + /* newRCSmode is also used to adjust mode of working file for -u and -l */ + if (chmod(RCSfilename,newRCSmode)<0) + warn("Can't set mode of %s",RCSfilename); + + restoreints(); +# ifdef SNOOPFILE + logcommand("ci",&newdelta,gendeltas,caller); +# endif + + if (!keepworkingfile) { + VOID unlink(workfilename); /* get rid of old file */ + } else { + /* expand keywords in file */ + newworkfilename= + xpandfile(workfilename,workfilename /*for directory*/,&newdelta); + if (!newworkfilename) continue; /* expand failed */ + ignoreints(); + if (rename(newworkfilename,workfilename) <0) { + error("Can't expand keywords in %s",workfilename); + restoreints(); + continue; + } + newworkfilename[0]='\0'; /* avoid re-unlink by cleanup */ + if (chmod(workfilename, WORKMODE(newRCSmode))<0) + warn("Can't adjust mode of %s",workfilename); + restoreints(); + } + diagnose("done"); + + } while (cleanup(), + ++argv, --argc >=1); + + exit(nerror!=0); + /*NOTREACHED*/ +} /* end of main (ci) */ +/*****************************************************************/ +/* the rest are auxiliary routines */ + + +int addelta() +/* Function: Appends a delta to the delta tree, whose number is + * given by newdelnum[]. Updates Head, newdelnum, newdenumlength, + * olddeltanum and the links in newdelta. + * Retruns false on error, true on success. + */ +{ + register char * sp, * tp; + register int i; + + newdnumlength=countnumflds(newdelnum); + + if (initflag || rcsinitflag ) { + /* this covers non-existing RCS file and a file initialized with rcs -i */ + if ((newdnumlength==0)&&(Dbranch!=nil)) { + VOID strcpy(newdelnum,Dbranch->num); + newdnumlength=countnumflds(newdelnum); + } + if (newdnumlength==0) VOID strcpy(newdelnum,"1.1"); + elsif (newdnumlength==1) VOID strcat(newdelnum,".1"); + elsif (newdnumlength>2) { + error("Branch point does not exist for %s",newdelnum); + return false; + } /* newdnumlength == 2 is OK; */ + olddeltanum=nil; + Head = &newdelta; + newdelta.next=nil; + return true; + } + if (newdnumlength==0) { + /* derive new revision number from locks */ + targetdelta=findlock(caller,true); /*find and delete it*/ + if (targetdelta) { + /* found an old lock */ + olddeltanum=targetdelta->num; + /* check whether locked revision exists */ + if (!genrevs(olddeltanum,(char *)nil,(char *)nil,(char *)nil,gendeltas)) return false; + if (targetdelta==Head) { + /* make new head */ + newdelta.next=Head; + Head= &newdelta; + incnum(olddeltanum, newdelnum); + } elsif ((targetdelta->next==nil)&&(countnumflds(olddeltanum)>2)) { + /* new tip revision on side branch */ + targetdelta->next= &newdelta; + newdelta.next = nil; + incnum(olddeltanum, newdelnum); + } else { + /* middle revision; start a new branch */ + newdelnum[0]='\0'; + if (!addbranch(targetdelta,newdelnum)) return false; + } + return true; /* successfull use of existing lock */ + } else { + /* no existing lock; try Dbranch */ + /* update newdelnum */ + if (!((StrictLocks==false) && (getuid() == RCSstat.st_uid))) { + error("no lock set by %s",caller); + return false; + } + if (Dbranch) { + VOID strcpy(newdelnum,Dbranch->num); + } else { + incnum(Head->num,newdelnum); + } + newdnumlength=countnumflds(newdelnum); + /* now fall into next statement */ + } + } + if (newdnumlength<=2) { + /* add new head per given number */ + olddeltanum=Head->num; + if(newdnumlength==1) { + /* make a two-field number out of it*/ + if (cmpnumfld(newdelnum,olddeltanum,1)==0) + incnum(olddeltanum,newdelnum); + else VOID strcat(newdelnum, ".1"); + } + if (cmpnum(newdelnum,olddeltanum) <= 0) { + error("deltanumber %s too low; must be higher than %s", + newdelnum,Head->num); + return false; + } + if (!(targetdelta=removelock(caller,Head))) return false; + if (!(genrevs(olddeltanum,(char *)nil,(char *)nil,(char *)nil,gendeltas))) return false; + newdelta.next=Head; + Head= &newdelta; + } else { + /* put new revision on side branch */ + /*first, get branch point */ + tp=branchpointnum; sp=newdelnum; + for(i=newdnumlength-(newdnumlength%2==1?1:2);i>0;i--) { + while (*sp != '.') *tp++ = *sp++; /*copy field*/ + *tp++ = *sp++; /*copy dot */ + } + *(tp-1) = '\0'; /* kill final dot */ + olddeltanum=branchpointnum; /*temporary old delta*/ + if (!(targetdelta=genrevs(branchpointnum,(char *)nil,(char *)nil,(char *)nil,gendeltas))) + return false; + if (cmpnum(targetdelta->num,branchpointnum)!=0) { + error("Cannot find branchpoint %s",branchpointnum); + return false; + } + if (!addbranch(targetdelta,newdelnum)) return false; + } + return true; +} + + + +int addbranch(branchpoint,num) +struct hshentry * branchpoint; +char * num; +/* adds a new branch and branch delta at branchpoint. + * If num is the null string, appends the new branch, incrementing + * the highest branch number (initially 1), and setting the level number to 1. + * the new delta and branchhead are in globals newdelta and newbranch, resp. + * the new number is placed into num. + * returns false on error. + */ +{ + struct branchhead * bhead, * btrail; + char branchnum[revlength]; + int numlength, result, field; + + numlength = countnumflds(num); + + if (branchpoint->branches==nil) { + /* start first branch */ + branchpoint->branches = &newbranch; + if (numlength==0) { + VOID strcpy(num, branchpoint->num); + VOID strcat(num,".1.1"); + } elsif(countnumflds(num)%2 == 1) + VOID strcat(num, ".1"); + newbranch.nextbranch=nil; + + } elsif (numlength==0) { + /* append new branch to the end */ + bhead=branchpoint->branches; + while (bhead->nextbranch) bhead=bhead->nextbranch; + bhead->nextbranch = &newbranch; + getbranchno(bhead->hsh->num,branchnum); + incnum(branchnum,num); + VOID strcat(num,".1"); + newbranch.nextbranch=nil; + } else { + /* place the branch properly */ + field = numlength - (numlength%2 ==1?0:1); + /* field of branch number */ + bhead=branchpoint->branches; + while ((bhead!=nil) && + ((result=cmpnumfld(num,bhead->hsh->num,field))>0)) { + btrail=bhead; + bhead=bhead->nextbranch; + } + if (bhead==nil || result<0) { + /* insert/append new branchhead */ + if (bhead==branchpoint->branches) + branchpoint->branches= &newbranch; + else btrail->nextbranch= &newbranch; + newbranch.nextbranch=bhead; + if (numlength%2 ==1) VOID strcat(num,".1"); + } else { + /* branch exists; append to end */ + getbranchno(num,branchnum); + if (!(targetdelta=genrevs(branchnum,(char *)nil,(char *)nil,(char *)nil, + gendeltas))) return false; + olddeltanum=targetdelta->num; + if (cmpnum(num,olddeltanum) <= 0) { + error("deltanumber %s too low; must be higher than %s", + num,olddeltanum); + return false; + } + if (!removelock(caller,targetdelta)) return false; + if (numlength%2==1) incnum(olddeltanum,num); + targetdelta->next= &newdelta; + newdelta.next=nil; + return true; /* Don't do anything to newbranch */ + } + } + newbranch.hsh = &newdelta; + newdelta.next=nil; + return true; +} + + + +struct hshentry * removelock(who,delta) +char * who; struct hshentry * delta; +/* function: Finds the lock held by who on delta, + * removes it, and returns a pointer to the delta. + * Prints an error message and returns nil if there is no such lock. + * An exception is if StrictLocks==false, and who is the owner of + * the RCS file. If who does not have a lock in this case, + * delta is returned. + */ +{ + register struct lock * next, * trail; + char * num; + struct lock dummy; + int whomatch, nummatch; + + num=delta->num; + dummy.nextlock=next=Locks; + trail = &dummy; + while (next!=nil) { + whomatch=strcmp(who,next->login); + nummatch=strcmp(num,next->delta->num); + if ((whomatch==0) && (nummatch==0)) break; + /*found a lock on delta by who*/ + if ((whomatch!=0)&&(nummatch==0)) { + error("revision %s locked by %s",num,next->login); + return nil; + } + trail=next; + next=next->nextlock; + } + if (next!=nil) { + /*found one; delete it */ + trail->nextlock=next->nextlock; + Locks=dummy.nextlock; + next->delta->lockedby=nil; /* reset locked-by */ + return next->delta; + } else { + if (!((StrictLocks==false) && (getuid() == RCSstat.st_uid))) { + error("no lock set by %s for revision %s",who,num); + return nil; + } else { + return delta; + } + } +} + + + +char * getdate() +/* Function: returns a pointer to the current date in the form + * YY.MM.DD.hh.mm.ss\0 + */ +{ + long clock; + struct tm * tm; + static char buffer[datelength]; /* date buffer */ + clock=time((long *)0); + tm=localtime(&clock); + VOID sprintf(buffer, DATEFORM, + tm->tm_year, tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + return buffer; +} + + +char * xpandfile (unexfname,dir,delta) +char * unexfname, * dir; +struct hshentry * delta; +/* Function: Reads file unexpfname and copies it to a + * file in dir, performing keyword substitution with data from delta. + * returns the name of the expanded file if successful, nil otherwise. + */ +{ char * targetfname; + FILE * unexfile, *exfile; + + targetfname=mktempfile(dir,TMPFILE3); + if ((unexfile=fopen(unexfname, "r" ))==NULL || + (exfile =fopen(targetfname,"w"))==NULL) { + error("Can't expand file %s",unexfname); + return nil; + } + while (expandline(unexfile,exfile,delta,false,false)); /*expand*/ + ffclose(unexfile);ffclose(exfile); + return targetfname; +} + + +mustcheckin (unexfname,delta) +char * unexfname; struct hshentry * delta; +/* Function: determines whether checkin should proceed. + * Compares the wrkfilename with unexfname, disregarding keywords. + * If the 2 files differ, returns true. If they do not differ, asks the user + * whether to return true or false (i.e., whether to checkin the file anyway. + * If the files do not differ, and quietflag==true, returns false. + * Shortcut: If forceciflag==true, mustcheckin() always returns true. + */ +{ register int c; + int response, result; + + if (forceciflag) return true; + + if (!rcsfcmp(workfilename,unexfname,delta)) return true; + /* If files are different, must check them in. */ + + /* files are the same */ + diagnose("File %s is unchanged with respect to revision %s", + workfilename,delta->num); + if (quietflag || !isatty(fileno(stdin))) { + /* Files are the same, but can't ask, so don't checkin*/ + result=false; + } else { + /* ask user whether to check in */ + VOID fputs("checkin anyway? [ny](n): ",stderr); + response=c=getchar(); + while (!(c==EOF || c=='\n')) c=getchar();/*skip to end of line*/ + result=(response=='y'||response=='Y'); + } + if (result==false) { + if (quietflag) { + warn("checkin aborted since %s was not changed; %s %sdeleted.", + workfilename,workfilename,keepworkingfile?"not ":""); + } else { + diagnose("checkin aborted; %s %sdeleted.", + workfilename,keepworkingfile?"not ":""); + } + if (!keepworkingfile) VOID unlink(workfilename); + } + return result; +} + + + + +/* --------------------- G E T L O G M S G --------------------------------*/ +extern int stdinread; /* is >0 if redirected stdin has been read once. */ + + +char * getlogmsg() +/* Function: obtains a log message and returns a pointer to it. + * If a log message is given via the -m option, a pointer to that + * string is returned. + * If this is the initial revision, a standard log message is returned. + * Otherwise, reads a character string from the terminal. + * The string must be terminated with a control-d or a single '.' on a + * line. getlogmsg prompts the first time it is called for the + * log message; during all later calls it asks whether the previous + * log message can be reused. + * returns a pointer to the character string; the pointer is always non-nil. + */ +{ + static logyet = false; /*indicates whether previous log present*/ + static char emptylog[] = "*** empty log message ***\n"; + static char initiallog[]= "Initial revision\n"; + char response; + int cin; + register char c, old1, old2, * tp; + + if (msg) return msg; + + if ((olddeltanum==nil)&& + ((cmpnum(newdelnum,"1.1")==0)||(cmpnum(newdelnum,"1.0")==0))) { + return initiallog; + } + if (keepflag) { + /* generate std. log message */ + VOID sprintf(logmsg, "checked in with -k by %s at %s.\n",caller,getdate()); + return(logmsg); + } + if (logyet) { + /*previous log available*/ + if (!isatty(fileno(stdin))) return logmsg; /* reuse if stdin is not a terminal*/ + /* otherwise ask */ + clearerr(stdin); /* reset EOF ptr */ + VOID fputs("reuse log message of previous file? [yn](y): ",stderr); + cin=getchar(); + response=cin; + while (!(cin==EOF || cin=='\n')) cin=getchar();/*skip to end of line*/ + if (response=='\n'||response=='y'||response=='Y') + return logmsg; + else + logmsg[0]='\0'; /*kill existing log message */ + } + + /* now read string from stdin */ + if (isatty(fileno(stdin))) { + VOID fputs("enter log message:\n(terminate with ^D or single '.')\n>> ",stderr); + } else { /* redirected stdin */ + if (stdinread>0) + faterror("Can't reread redirected stdin for log message; use -m"); + stdinread++; + } + + tp=logmsg; old1='\n'; old2=' '; + if (feof(stdin)) + clearerr(stdin); + for (;;) { + cin=getchar(); + if (cin==EOF) { + if(isatty(fileno(stdin))) VOID putc('\n',stderr); + if ((tp==logmsg)||(*(tp-1)!='\n')) *tp++ = '\n'; /* append newline */ + *tp = '\0'; /*terminate*/ + break; + } + if (cin=='\n' && old1=='.' && old2=='\n') { + *(tp-1) = '\0'; /*kill last period */ + break; + } + if (tp>=logmsg+logsize-2) { /* overflow */ + if (!isatty(fileno(stdin))) { + warn("log message truncated to %d characters",logsize); + logmsg[logsize-2]='\n';logmsg[logsize-1]='\0'; + return logmsg; + } + VOID fprintf(stderr,"log message too long. Maximum: %d\n",logsize); + VOID fputs("reenter log message:\n>> ",stderr); + tp=logmsg; old1='\n'; old2=' '; + while (cin!='\n') cin=getchar(); /*skip line */ + continue; + } + if (cin=='\n' && isatty(fileno(stdin))) VOID fputs(">> ",stderr); + *tp++ = cin; old2=old1; old1=cin; /* this is the actual work!*/ + /*SDELIM will be changed to double SDELIM by putdtext*/ + } /* end for */ + + /* now check whether the log message is not empty */ + tp=logmsg; + while ((c= *tp++)==' '||c=='\t'||c=='\n'||c=='\f'); + if (*tp=='\0') { + logyet=false; + return emptylog; + } else { + logyet=true; + return logmsg; + } +}