/* * 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; } }