home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / RCS_SRC.ZIP / CI.BAK < prev    next >
Text File  |  1991-01-15  |  42KB  |  1,013 lines

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