home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 22 gnu / 22-gnu.zip / rcs57pc3.zip / rcs / src / ci.c < prev    next >
Text File  |  1995-06-16  |  38KB  |  1,320 lines

  1. /* Check in revisions of RCS files from working files.  */
  2.  
  3. /* Copyright 1982, 1988, 1989 Walter Tichy
  4.    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
  5.    Distributed under license by the Free Software Foundation, Inc.
  6.  
  7. This file is part of RCS.
  8.  
  9. RCS is free software; you can redistribute it and/or modify
  10. it under the terms of the GNU General Public License as published by
  11. the Free Software Foundation; either version 2, or (at your option)
  12. any later version.
  13.  
  14. RCS is distributed in the hope that it will be useful,
  15. but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. GNU General Public License for more details.
  18.  
  19. You should have received a copy of the GNU General Public License
  20. along with RCS; see the file COPYING.
  21. If not, write to the Free Software Foundation,
  22. 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  23.  
  24. Report problems and direct all questions to:
  25.  
  26.     rcs-bugs@cs.purdue.edu
  27.  
  28. */
  29.  
  30. /*
  31.  * $Log: ci.c,v $
  32.  * Revision 5.30  1995/06/16 06:19:24  eggert
  33.  * Update FSF address.
  34.  *
  35.  * Revision 5.29  1995/06/01 16:23:43  eggert
  36.  * (main): Add -kb.
  37.  * Use `cmpdate', not `cmpnum', to compare dates.
  38.  * This is for MKS RCS's incompatible 20th-century date format.
  39.  * Don't worry about errno after ftruncate fails.
  40.  * Fix input file rewinding bug when large_memory && !maps_memory
  41.  * and checking in a branch tip.
  42.  *
  43.  * (fixwork): Fall back on chmod if fchmod fails, since it might be ENOSYS.
  44.  *
  45.  * Revision 5.28  1994/03/20 04:52:58  eggert
  46.  * Do not generate a corrupted RCS file if the user modifies the working file
  47.  * while `ci' is running.
  48.  * Do not remove the lock when `ci -l' reverts.
  49.  * Move buffer-flushes out of critical sections, since they aren't critical.
  50.  * Use ORCSerror to clean up after a fatal error.
  51.  * Specify subprocess input via file descriptor, not file name.
  52.  *
  53.  * Revision 5.27  1993/11/09 17:40:15  eggert
  54.  * -V now prints version on stdout and exits.  Don't print usage twice.
  55.  *
  56.  * Revision 5.26  1993/11/03 17:42:27  eggert
  57.  * Add -z.  Don't subtract from RCS file timestamp even if -T.
  58.  * Scan for and use Name keyword if -k.
  59.  * Don't discard ignored phrases.  Improve quality of diagnostics.
  60.  *
  61.  * Revision 5.25  1992/07/28  16:12:44  eggert
  62.  * Add -i, -j, -V.  Check that working and RCS files are distinct.
  63.  *
  64.  * Revision 5.24  1992/02/17  23:02:06  eggert
  65.  * `-rREV' now just specifies a revision REV; only bare `-r' reverts to default.
  66.  * Add -T.
  67.  *
  68.  * Revision 5.23  1992/01/27  16:42:51  eggert
  69.  * Always unlock branchpoint if caller has a lock.
  70.  * Add support for bad_chmod_close, bad_creat0.  lint -> RCS_lint
  71.  *
  72.  * Revision 5.22  1992/01/06  02:42:34  eggert
  73.  * Invoke utime() before chmod() to keep some buggy systems happy.
  74.  *
  75.  * Revision 5.21  1991/11/20  17:58:07  eggert
  76.  * Don't read the delta tree from a nonexistent RCS file.
  77.  *
  78.  * Revision 5.20  1991/10/07  17:32:46  eggert
  79.  * Fix log bugs.  Remove lint.
  80.  *
  81.  * Revision 5.19  1991/09/26  23:10:30  eggert
  82.  * Plug file descriptor leak.
  83.  *
  84.  * Revision 5.18  1991/09/18  07:29:10  eggert
  85.  * Work around a common ftruncate() bug.
  86.  *
  87.  * Revision 5.17  1991/09/10  22:15:46  eggert
  88.  * Fix test for redirected stdin.
  89.  *
  90.  * Revision 5.16  1991/08/19  23:17:54  eggert
  91.  * When there are no changes, revert to previous revision instead of aborting.
  92.  * Add piece tables, -M, -r$.  Tune.
  93.  *
  94.  * Revision 5.15  1991/04/21  11:58:14  eggert
  95.  * Ensure that working file is newer than RCS file after ci -[lu].
  96.  * Add -x, RCSINIT, MS-DOS support.
  97.  *
  98.  * Revision 5.14  1991/02/28  19:18:47  eggert
  99.  * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first.
  100.  * Fix ci -ko -l mode bug.  Open work file at most once.
  101.  *
  102.  * Revision 5.13  1991/02/25  07:12:33  eggert
  103.  * getdate -> getcurdate (SVR4 name clash)
  104.  *
  105.  * Revision 5.12  1990/12/31  01:00:12  eggert
  106.  * Don't use uninitialized storage when handling -{N,n}.
  107.  *
  108.  * Revision 5.11  1990/12/04  05:18:36  eggert
  109.  * Use -I for prompts and -q for diagnostics.
  110.  *
  111.  * Revision 5.10  1990/11/05  20:30:10  eggert
  112.  * Don't remove working file when aborting due to no changes.
  113.  *
  114.  * Revision 5.9  1990/11/01  05:03:23  eggert
  115.  * Add -I and new -t behavior.  Permit arbitrary data in logs.
  116.  *
  117.  * Revision 5.8  1990/10/04  06:30:09  eggert
  118.  * Accumulate exit status across files.
  119.  *
  120.  * Revision 5.7  1990/09/25  20:11:46  hammer
  121.  * fixed another small typo
  122.  *
  123.  * Revision 5.6  1990/09/24  21:48:50  hammer
  124.  * added cleanups from Paul Eggert.
  125.  *
  126.  * Revision 5.5  1990/09/21  06:16:38  hammer
  127.  * made it handle multiple -{N,n}'s.  Also, made it treat re-directed stdin
  128.  * the same as the terminal
  129.  *
  130.  * Revision 5.4  1990/09/20  02:38:51  eggert
  131.  * ci -k now checks dates more thoroughly.
  132.  *
  133.  * Revision 5.3  1990/09/11  02:41:07  eggert
  134.  * Fix revision bug with `ci -k file1 file2'.
  135.  *
  136.  * Revision 5.2  1990/09/04  08:02:10  eggert
  137.  * Permit adjacent revisions with identical time stamps (possible on fast hosts).
  138.  * Improve incomplete line handling.  Standardize yes-or-no procedure.
  139.  *
  140.  * Revision 5.1  1990/08/29  07:13:44  eggert
  141.  * Expand locker value like co.  Clean old log messages too.
  142.  *
  143.  * Revision 5.0  1990/08/22  08:10:00  eggert
  144.  * Don't require a final newline.
  145.  * Make lock and temp files faster and safer.
  146.  * Remove compile-time limits; use malloc instead.
  147.  * Permit dates past 1999/12/31.  Switch to GMT.
  148.  * Add setuid support.  Don't pass +args to diff.  Check diff's output.
  149.  * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
  150.  * Check diff's output.
  151.  *
  152.  * Revision 4.9  89/05/01  15:10:54  narten
  153.  * changed copyright header to reflect current distribution rules
  154.  * 
  155.  * Revision 4.8  88/11/08  13:38:23  narten
  156.  * changes from root@seismo.CSS.GOV (Super User)
  157.  * -d with no arguments uses the mod time of the file it is checking in
  158.  * 
  159.  * Revision 4.7  88/08/09  19:12:07  eggert
  160.  * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one.
  161.  * Use execv(), not system(); allow cc -R; remove lint.
  162.  * isatty(fileno(stdin)) -> ttystdin()
  163.  * 
  164.  * Revision 4.6  87/12/18  11:34:41  narten
  165.  * lint cleanups (from Guy Harris)
  166.  * 
  167.  * Revision 4.5  87/10/18  10:18:48  narten
  168.  * Updating version numbers. Changes relative to revision 1.1 are actually
  169.  * relative to 4.3
  170.  * 
  171.  * Revision 1.3  87/09/24  13:57:19  narten
  172.  * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
  173.  * warnings)
  174.  * 
  175.  * Revision 1.2  87/03/27  14:21:33  jenkins
  176.  * Port to suns
  177.  * 
  178.  * Revision 4.3  83/12/15  12:28:54  wft
  179.  * ci -u and ci -l now set mode of working file properly.
  180.  * 
  181.  * Revision 4.2  83/12/05  13:40:54  wft
  182.  * Merged with 3.9.1.1: added calls to clearerr(stdin).
  183.  * made rewriteflag external.
  184.  * 
  185.  * Revision 4.1  83/05/10  17:03:06  wft
  186.  * Added option -d and -w, and updated assingment of date, etc. to new delta.
  187.  * Added handling of default branches.
  188.  * Option -k generates std. log message; fixed undef. pointer in reading of log.
  189.  * Replaced getlock() with findlock(), link--unlink with rename(),
  190.  * getpwuid() with getcaller().
  191.  * Moved all revision number generation to new routine addelta().
  192.  * Removed calls to stat(); now done by pairfilenames().
  193.  * Changed most calls to catchints() with restoreints().
  194.  * Directed all interactive messages to stderr.
  195.  * 
  196.  * Revision 3.9.1.1  83/10/19  04:21:03  lepreau
  197.  * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
  198.  * 
  199.  * Revision 3.9  83/02/15  15:25:44  wft
  200.  * 4.2 prerelease
  201.  * 
  202.  * Revision 3.9  83/02/15  15:25:44  wft
  203.  * Added call to fastcopy() to copy remainder of RCS file.
  204.  *
  205.  * Revision 3.8  83/01/14  15:34:05  wft
  206.  * Added ignoring of interrupts while new RCS file is renamed;
  207.  * Avoids deletion of RCS files by interrupts.
  208.  *
  209.  * Revision 3.7  82/12/10  16:09:20  wft
  210.  * Corrected checking of return code from diff.
  211.  *
  212.  * Revision 3.6  82/12/08  21:34:49  wft
  213.  * Using DATEFORM to prepare date of checked-in revision;
  214.  * Fixed return from addbranch().
  215.  *
  216.  * Revision 3.5  82/12/04  18:32:42  wft
  217.  * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated
  218.  * field lockedby in removelock(), moved getlogmsg() before calling diff.
  219.  *
  220.  * Revision 3.4  82/12/02  13:27:13  wft
  221.  * added option -k.
  222.  *
  223.  * Revision 3.3  82/11/28  20:53:31  wft
  224.  * Added mustcheckin() to check for redundant checkins.
  225.  * Added xpandfile() to do keyword expansion for -u and -l;
  226.  * -m appends linefeed to log message if necessary.
  227.  * getlogmsg() suppresses prompt if stdin is not a terminal.
  228.  * Replaced keeplock with lockflag, fclose() with ffclose(),
  229.  * %02d with %.2d, getlogin() with getpwuid().
  230.  *
  231.  * Revision 3.2  82/10/18  20:57:23  wft
  232.  * An RCS file inherits its mode during the first ci from the working file,
  233.  * otherwise it stays the same, except that write permission is removed.
  234.  * Fixed ci -l, added ci -u (both do an implicit co after the ci).
  235.  * Fixed call to getlogin(), added call to getfullRCSname(), added check
  236.  * for write error.
  237.  * Changed conflicting identifiers.
  238.  *
  239.  * Revision 3.1  82/10/13  16:04:59  wft
  240.  * fixed type of variables receiving from getc() (char -> int).
  241.  * added include file dbm.h for getting BYTESIZ. This is used
  242.  * to check the return code from diff portably.
  243.  */
  244.  
  245. #include "rcsbase.h"
  246.  
  247. struct Symrev {
  248.        char const *ssymbol;
  249.        int override;
  250.        struct Symrev * nextsym;
  251. };
  252.  
  253. static char const *getcurdate P((void));
  254. static int addbranch P((struct hshentry*,struct buf*,int));
  255. static int addelta P((void));
  256. static int addsyms P((char const*));
  257. static int fixwork P((mode_t,time_t));
  258. static int removelock P((struct hshentry*));
  259. static int xpandfile P((RILE*,struct hshentry const*,char const**,int));
  260. static struct cbuf getlogmsg P((void));
  261. static void cleanup P((void));
  262. static void incnum P((char const*,struct buf*));
  263. static void addassoclst P((int,char const*));
  264.  
  265. static FILE *exfile;
  266. static RILE *workptr;            /* working file pointer        */
  267. static struct buf newdelnum;        /* new revision number        */
  268. static struct cbuf msg;
  269. static int exitstatus;
  270. static int forceciflag;            /* forces check in        */
  271. static int keepflag, keepworkingfile, rcsinitflag;
  272. static struct hshentries *gendeltas;    /* deltas to be generated    */
  273. static struct hshentry *targetdelta;    /* old delta to be generated    */
  274. static struct hshentry newdelta;    /* new delta to be inserted    */
  275. static struct stat workstat;
  276. static struct Symrev *assoclst, **nextassoc;
  277.  
  278. mainProg(ciId, "ci", "$Id: ci.c,v 5.30 1995/06/16 06:19:24 eggert Exp $")
  279. {
  280.     static char const cmdusage[] =
  281.         "\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -T -Vn -wwho -xsuff -zzone file ...";
  282.     static char const default_state[] = DEFAULTSTATE;
  283.  
  284.     char altdate[datesize];
  285.     char olddate[datesize];
  286.     char newdatebuf[datesize + zonelenmax];
  287.     char targetdatebuf[datesize + zonelenmax];
  288.     char *a, **newargv, *textfile;
  289.     char const *author, *krev, *rev, *state;
  290.     char const *diffname, *expname;
  291.     char const *newworkname;
  292.     int initflag, mustread;
  293.     int lockflag, lockthis, mtimeflag, removedlock, Ttimeflag;
  294.     int r;
  295.     int changedRCS, changework, dolog, newhead;
  296.     int usestatdate; /* Use mod time of file for -d.  */
  297.     mode_t newworkmode; /* mode for working file */
  298.     time_t mtime, wtime;
  299.     struct hshentry *workdelta;
  300.  
  301.     setrid();
  302.  
  303.     author = rev = state = textfile = 0;
  304.     initflag = lockflag = mustread = false;
  305.     mtimeflag = false;
  306.     Ttimeflag = false;
  307.     altdate[0]= '\0'; /* empty alternate date for -d */
  308.     usestatdate=false;
  309.     suffixes = X_DEFAULT;
  310.     nextassoc = &assoclst;
  311.  
  312.     argc = getRCSINIT(argc, argv, &newargv);
  313.     argv = newargv;
  314.     while (a = *++argv,  0<--argc && *a++=='-') {
  315.         switch (*a++) {
  316.  
  317.                 case 'r':
  318.             if (*a)
  319.                 goto revno;
  320.             keepworkingfile = lockflag = false;
  321.             break;
  322.  
  323.         case 'l':
  324.             keepworkingfile = lockflag = true;
  325.         revno:
  326.             if (*a) {
  327.                 if (rev) warn("redefinition of revision number");
  328.                 rev = a;
  329.                         }
  330.                         break;
  331.  
  332.                 case 'u':
  333.                         keepworkingfile=true; lockflag=false;
  334.                         goto revno;
  335.  
  336.         case 'i':
  337.             initflag = true;
  338.             goto revno;
  339.  
  340.         case 'j':
  341.             mustread = true;
  342.             goto revno;
  343.  
  344.         case 'I':
  345.             interactiveflag = true;
  346.             goto revno;
  347.  
  348.                 case 'q':
  349.                         quietflag=true;
  350.                         goto revno;
  351.  
  352.                 case 'f':
  353.                         forceciflag=true;
  354.                         goto revno;
  355.  
  356.                 case 'k':
  357.                         keepflag=true;
  358.                         goto revno;
  359.  
  360.                 case 'm':
  361.             if (msg.size) redefined('m');
  362.             msg = cleanlogmsg(a, strlen(a));
  363.             if (!msg.size)
  364.                 error("missing message for -m option");
  365.                         break;
  366.  
  367.                 case 'n':
  368.             if (!*a) {
  369.                                 error("missing symbolic name after -n");
  370.                 break;
  371.                     }
  372.             checkssym(a);
  373.             addassoclst(false, a);
  374.                 break;
  375.         
  376.         case 'N':
  377.             if (!*a) {
  378.                                 error("missing symbolic name after -N");
  379.                 break;
  380.                     }
  381.             checkssym(a);
  382.             addassoclst(true, a);
  383.                 break;
  384.  
  385.                 case 's':
  386.             if (*a) {
  387.                 if (state) redefined('s');
  388.                 checksid(a);
  389.                 state = a;
  390.             } else
  391.                 error("missing state for -s option");
  392.                         break;
  393.  
  394.                 case 't':
  395.             if (*a) {
  396.                 if (textfile) redefined('t');
  397.                 textfile = a;
  398.                         }
  399.                         break;
  400.  
  401.         case 'd':
  402.             if (altdate[0] || usestatdate)
  403.                 redefined('d');
  404.             altdate[0] = '\0';
  405.             if (!(usestatdate = !*a))
  406.                 str2date(a, altdate);
  407.                         break;
  408.  
  409.         case 'M':
  410.             mtimeflag = true;
  411.             goto revno;
  412.  
  413.         case 'w':
  414.             if (*a) {
  415.                 if (author) redefined('w');
  416.                 checksid(a);
  417.                 author = a;
  418.             } else
  419.                 error("missing author for -w option");
  420.                         break;
  421.  
  422.         case 'x':
  423.             suffixes = a;
  424.             break;
  425.  
  426.         case 'V':
  427.             setRCSversion(*argv);
  428.             break;
  429.  
  430.         case 'z':
  431.             zone_set(a);
  432.             break;
  433.  
  434.         case 'T':
  435.             if (!*a) {
  436.                 Ttimeflag = true;
  437.                 break;
  438.             }
  439.             /* fall into */
  440.                 default:
  441.             error("unknown option: %s%s", *argv, cmdusage);
  442.                 };
  443.         }  /* end processing of options */
  444.  
  445.     /* Handle all pathnames.  */
  446.     if (nerror) cleanup();
  447.     else if (argc < 1) faterror("no input file%s", cmdusage);
  448.     else for (;  0 < argc;  cleanup(), ++argv, --argc) {
  449.     targetdelta = 0;
  450.     ffree();
  451.  
  452.     switch (pairnames(argc, argv, rcswriteopen, mustread, false)) {
  453.  
  454.         case -1:                /* New RCS file */
  455. #        if has_setuid && has_getuid
  456.             if (euid() != ruid()) {
  457.             workerror("setuid initial checkin prohibited; use `rcs -i -a' first");
  458.             continue;
  459.             }
  460. #        endif
  461.         rcsinitflag = true;
  462.                 break;
  463.  
  464.         case 0:                 /* Error */
  465.                 continue;
  466.  
  467.         case 1:                 /* Normal checkin with prev . RCS file */
  468.         if (initflag) {
  469.             rcserror("already exists");
  470.             continue;
  471.         }
  472.         rcsinitflag = !Head;
  473.         }
  474.  
  475.     /*
  476.      * RCSname contains the name of the RCS file, and
  477.      * workname contains the name of the working file.
  478.      * If the RCS file exists, finptr contains the file descriptor for the
  479.      * RCS file, and RCSstat is set. The admin node is initialized.
  480.          */
  481.  
  482.     diagnose("%s  <--  %s\n", RCSname, workname);
  483.  
  484.     if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) {
  485.         eerror(workname);
  486.         continue;
  487.     }
  488.  
  489.     if (finptr) {
  490.         if (same_file(RCSstat, workstat, 0)) {
  491.             rcserror("RCS file is the same as working file %s.",
  492.                 workname
  493.             );
  494.             continue;
  495.         }
  496.         if (!checkaccesslist())
  497.             continue;
  498.     }
  499.  
  500.     krev = rev;
  501.         if (keepflag) {
  502.                 /* get keyword values from working file */
  503.         if (!getoldkeys(workptr)) continue;
  504.         if (!rev  &&  !*(krev = prevrev.string)) {
  505.             workerror("can't find a revision number");
  506.                         continue;
  507.                 }
  508.         if (!*prevdate.string && *altdate=='\0' && usestatdate==false)
  509.             workwarn("can't find a date");
  510.         if (!*prevauthor.string && !author)
  511.             workwarn("can't find an author");
  512.         if (!*prevstate.string && !state)
  513.             workwarn("can't find a state");
  514.         } /* end processing keepflag */
  515.  
  516.     /* Read the delta tree.  */
  517.     if (finptr)
  518.         gettree();
  519.  
  520.         /* expand symbolic revision number */
  521.     if (!fexpandsym(krev, &newdelnum, workptr))
  522.         continue;
  523.  
  524.         /* splice new delta into tree */
  525.     if ((removedlock = addelta()) < 0)
  526.         continue;
  527.  
  528.     newdelta.num = newdelnum.string;
  529.     newdelta.branches = 0;
  530.     newdelta.lockedby = 0; /* This might be changed by addlock().  */
  531.     newdelta.selector = true;
  532.     newdelta.name = 0;
  533.     clear_buf(&newdelta.ig);
  534.     clear_buf(&newdelta.igtext);
  535.     /* set author */
  536.     if (author)
  537.         newdelta.author=author;     /* set author given by -w         */
  538.     else if (keepflag && *prevauthor.string)
  539.         newdelta.author=prevauthor.string; /* preserve old author if possible*/
  540.     else    newdelta.author=getcaller();/* otherwise use caller's id      */
  541.     newdelta.state = default_state;
  542.     if (state)
  543.         newdelta.state=state;       /* set state given by -s          */
  544.     else if (keepflag && *prevstate.string)
  545.         newdelta.state=prevstate.string;   /* preserve old state if possible */
  546.     if (usestatdate) {
  547.         time2date(workstat.st_mtime, altdate);
  548.     }
  549.     if (*altdate!='\0')
  550.         newdelta.date=altdate;      /* set date given by -d           */
  551.     else if (keepflag && *prevdate.string) {
  552.         /* Preserve old date if possible.  */
  553.         str2date(prevdate.string, olddate);
  554.         newdelta.date = olddate;
  555.     } else
  556.         newdelta.date = getcurdate();  /* use current date */
  557.     /* now check validity of date -- needed because of -d and -k          */
  558.     if (targetdelta &&
  559.         cmpdate(newdelta.date,targetdelta->date) < 0) {
  560.         rcserror("Date %s precedes %s in revision %s.",
  561.             date2str(newdelta.date, newdatebuf),
  562.             date2str(targetdelta->date, targetdatebuf),
  563.             targetdelta->num
  564.         );
  565.         continue;
  566.     }
  567.  
  568.  
  569.     if (lockflag  &&  addlock(&newdelta, true) < 0) continue;
  570.  
  571.     if (keepflag && *prevname.string)
  572.         if (addsymbol(newdelta.num, prevname.string, false)  <  0)
  573.         continue;
  574.     if (!addsyms(newdelta.num))
  575.         continue;
  576.  
  577.     
  578.     putadmin();
  579.         puttree(Head,frewrite);
  580.     putdesc(false,textfile);
  581.  
  582.     changework = Expand < MIN_UNCHANGED_EXPAND;
  583.     dolog = true;
  584.     lockthis = lockflag;
  585.     workdelta = &newdelta;
  586.  
  587.         /* build rest of file */
  588.     if (rcsinitflag) {
  589.         diagnose("initial revision: %s\n", newdelta.num);
  590.                 /* get logmessage */
  591.                 newdelta.log=getlogmsg();
  592.         putdftext(&newdelta, workptr, frewrite, false);
  593.         RCSstat.st_mode = workstat.st_mode;
  594.         RCSstat.st_nlink = 0;
  595.         changedRCS = true;
  596.         } else {
  597.         diffname = maketemp(0);
  598.         newhead  =  Head == &newdelta;
  599.         if (!newhead)
  600.             foutptr = frewrite;
  601.         expname = buildrevision(
  602.             gendeltas, targetdelta, (FILE*)0, false
  603.         );
  604.         if (
  605.             !forceciflag  &&
  606.             strcmp(newdelta.state, targetdelta->state) == 0  &&
  607.             (changework = rcsfcmp(
  608.             workptr, &workstat, expname, targetdelta
  609.             )) <= 0
  610.         ) {
  611.             diagnose("file is unchanged; reverting to previous revision %s\n",
  612.             targetdelta->num
  613.             );
  614.             if (removedlock < lockflag) {
  615.             diagnose("previous revision was not locked; ignoring -l option\n");
  616.             lockthis = 0;
  617.             }
  618.             dolog = false;
  619.             if (! (changedRCS = lockflag<removedlock || assoclst))
  620.             workdelta = targetdelta;
  621.             else {
  622.             /*
  623.              * We have started to build the wrong new RCS file.
  624.              * Start over from the beginning.
  625.              */
  626.             long hwm = ftell(frewrite);
  627.             int bad_truncate;
  628.             Orewind(frewrite);
  629.  
  630.             /*
  631.             * Work around a common ftruncate() bug:
  632.             * NFS won't let you truncate a file that you
  633.             * currently lack permissions for, even if you
  634.             * had permissions when you opened it.
  635.             * Also, Posix 1003.1b-1993 sec 5.6.7.2 p 128 l 1022
  636.             * says ftruncate might fail because it's not supported.
  637.             */
  638. #            if !has_ftruncate
  639. #                undef ftruncate
  640. #                define ftruncate(fd,length) (-1)
  641. #            endif
  642.             bad_truncate = ftruncate(fileno(frewrite), (off_t)0);
  643.  
  644.             Irewind(finptr);
  645.             Lexinit();
  646.             getadmin();
  647.             gettree();
  648.             if (!(workdelta = genrevs(
  649.                 targetdelta->num, (char*)0, (char*)0, (char*)0,
  650.                 &gendeltas
  651.             )))
  652.                 continue;
  653.             workdelta->log = targetdelta->log;
  654.             if (newdelta.state != default_state)
  655.                 workdelta->state = newdelta.state;
  656.             if (lockthis<removedlock && removelock(workdelta)<0)
  657.                 continue;
  658.             if (!addsyms(workdelta->num))
  659.                 continue;
  660.             if (dorewrite(true, true) != 0)
  661.                 continue;
  662.             fastcopy(finptr, frewrite);
  663.             if (bad_truncate)
  664.                 while (ftell(frewrite) < hwm)
  665.                 /* White out any earlier mistake with '\n's.  */
  666.                 /* This is unlikely.  */
  667.                 afputc('\n', frewrite);
  668.             }
  669.         } else {
  670.             int wfd = Ifileno(workptr);
  671.             struct stat checkworkstat;
  672.             char const *diffv[6 + !!OPEN_O_BINARY], **diffp;
  673. #            if large_memory && !maps_memory
  674.             FILE *wfile = workptr->stream;
  675.             long wfile_off;
  676. #            endif
  677. #            if !has_fflush_input && !(large_memory && maps_memory)
  678.                 off_t wfd_off;
  679. #            endif
  680.  
  681.             diagnose("new revision: %s; previous revision: %s\n",
  682.             newdelta.num, targetdelta->num
  683.             );
  684.             newdelta.log = getlogmsg();
  685. #            if !large_memory
  686.             Irewind(workptr);
  687. #            if has_fflush_input
  688.                 if (fflush(workptr) != 0)
  689.                 Ierror();
  690. #            endif
  691. #            else
  692. #            if !maps_memory
  693.                 if (
  694.                     (wfile_off = ftell(wfile)) == -1
  695.                  ||    fseek(wfile, 0L, SEEK_SET) != 0
  696. #                 if has_fflush_input
  697.                  ||    fflush(wfile) != 0
  698. #                 endif
  699.                 )
  700.                 Ierror();
  701. #            endif
  702. #            endif
  703. #            if !has_fflush_input && !(large_memory && maps_memory)
  704.             wfd_off = lseek(wfd, (off_t)0, SEEK_CUR);
  705.             if (wfd_off == -1
  706.                 || (wfd_off != 0
  707.                 && lseek(wfd, (off_t)0, SEEK_SET) != 0))
  708.                 Ierror();
  709. #            endif
  710.             diffp = diffv;
  711.             *++diffp = DIFF;
  712.             *++diffp = DIFFFLAGS;
  713. #            if OPEN_O_BINARY
  714.             if (Expand == BINARY_EXPAND)
  715.                 *++diffp = "--binary";
  716. #            endif
  717.             *++diffp = newhead ? "-" : expname;
  718.             *++diffp = newhead ? expname : "-";
  719.             *++diffp = 0;
  720.             switch (runv(wfd, diffname, diffv)) {
  721.             case DIFF_FAILURE: case DIFF_SUCCESS: break;
  722.             default: rcsfaterror("diff failed");
  723.             }
  724. #            if !has_fflush_input && !(large_memory && maps_memory)
  725.             if (lseek(wfd, wfd_off, SEEK_CUR) == -1)
  726.                 Ierror();
  727. #            endif
  728. #            if large_memory && !maps_memory
  729.             if (fseek(wfile, wfile_off, SEEK_SET) != 0)
  730.                 Ierror();
  731. #            endif
  732.             if (newhead) {
  733.             Irewind(workptr);
  734.             putdftext(&newdelta, workptr, frewrite, false);
  735.             if (!putdtext(targetdelta,diffname,frewrite,true)) continue;
  736.             } else
  737.             if (!putdtext(&newdelta,diffname,frewrite,true)) continue;
  738.  
  739.             /*
  740.             * Check whether the working file changed during checkin,
  741.             * to avoid producing an inconsistent RCS file.
  742.             */
  743.             if (
  744.             fstat(wfd, &checkworkstat) != 0
  745.              ||    workstat.st_mtime != checkworkstat.st_mtime
  746.              ||    workstat.st_size != checkworkstat.st_size
  747.             ) {
  748.             workerror("file changed during checkin");
  749.             continue;
  750.             }
  751.  
  752.             changedRCS = true;
  753.                 }
  754.         }
  755.  
  756.     /* Deduce time_t of new revision if it is needed later.  */
  757.     wtime = (time_t)-1;
  758.     if (mtimeflag | Ttimeflag)
  759.         wtime = date2time(workdelta->date);
  760.  
  761.     if (donerewrite(changedRCS,
  762.         !Ttimeflag ? (time_t)-1
  763.         : finptr && wtime < RCSstat.st_mtime ? RCSstat.st_mtime
  764.         : wtime
  765.     ) != 0)
  766.         continue;
  767.  
  768.         if (!keepworkingfile) {
  769.         Izclose(&workptr);
  770.         r = un_link(workname); /* Get rid of old file */
  771.         } else {
  772.         newworkmode = WORKMODE(RCSstat.st_mode,
  773.             !   (Expand==VAL_EXPAND  ||  lockthis < StrictLocks)
  774.         );
  775.         mtime = mtimeflag ? wtime : (time_t)-1;
  776.  
  777.         /* Expand if it might change or if we can't fix mode, time.  */
  778.         if (changework  ||  (r=fixwork(newworkmode,mtime)) != 0) {
  779.             Irewind(workptr);
  780.             /* Expand keywords in file.  */
  781.             locker_expansion = lockthis;
  782.             workdelta->name = 
  783.             namedrev(
  784.                 assoclst ? assoclst->ssymbol
  785.                 : keepflag && *prevname.string ? prevname.string
  786.                 : rev,
  787.                 workdelta
  788.             );
  789.             switch (xpandfile(
  790.             workptr, workdelta, &newworkname, dolog
  791.             )) {
  792.             default:
  793.                 continue;
  794.  
  795.             case 0:
  796.                 /*
  797.                  * No expansion occurred; try to reuse working file
  798.                  * unless we already tried and failed.
  799.                  */
  800.                 if (changework)
  801.                 if ((r=fixwork(newworkmode,mtime)) == 0)
  802.                     break;
  803.                 /* fall into */
  804.             case 1:
  805.                 Izclose(&workptr);
  806.                 aflush(exfile);
  807.                 ignoreints();
  808.                 r = chnamemod(&exfile, newworkname,
  809.                     workname, 1, newworkmode, mtime
  810.                 );
  811.                 keepdirtemp(newworkname);
  812.                 restoreints();
  813.             }
  814.         }
  815.         }
  816.     if (r != 0) {
  817.         eerror(workname);
  818.         continue;
  819.     }
  820.     diagnose("done\n");
  821.  
  822.     }
  823.  
  824.     tempunlink();
  825.     exitmain(exitstatus);
  826. }       /* end of main (ci) */
  827.  
  828.     static void
  829. cleanup()
  830. {
  831.     if (nerror) exitstatus = EXIT_FAILURE;
  832.     Izclose(&finptr);
  833.     Izclose(&workptr);
  834.     Ozclose(&exfile);
  835.     Ozclose(&fcopy);
  836.     ORCSclose();
  837.     dirtempunlink();
  838. }
  839.  
  840. #if RCS_lint
  841. #    define exiterr ciExit
  842. #endif
  843.     void
  844. exiterr()
  845. {
  846.     ORCSerror();
  847.     dirtempunlink();
  848.     tempunlink();
  849.     _exit(EXIT_FAILURE);
  850. }
  851.  
  852. /*****************************************************************/
  853. /* the rest are auxiliary routines                               */
  854.  
  855.  
  856.     static int
  857. addelta()
  858. /* Function: Appends a delta to the delta tree, whose number is
  859.  * given by newdelnum.  Updates Head, newdelnum, newdelnumlength,
  860.  * and the links in newdelta.
  861.  * Return -1 on error, 1 if a lock is removed, 0 otherwise.
  862.  */
  863. {
  864.     register char *tp;
  865.     register int i;
  866.     int removedlock;
  867.     int newdnumlength;  /* actual length of new rev. num. */
  868.  
  869.     newdnumlength = countnumflds(newdelnum.string);
  870.  
  871.     if (rcsinitflag) {
  872.                 /* this covers non-existing RCS file and a file initialized with rcs -i */
  873.         if (newdnumlength==0 && Dbranch) {
  874.             bufscpy(&newdelnum, Dbranch);
  875.             newdnumlength = countnumflds(Dbranch);
  876.         }
  877.         if (newdnumlength==0) bufscpy(&newdelnum, "1.1");
  878.         else if (newdnumlength==1) bufscat(&newdelnum, ".1");
  879.         else if (newdnumlength>2) {
  880.             rcserror("Branch point doesn't exist for revision %s.",
  881.             newdelnum.string
  882.             );
  883.             return -1;
  884.                 } /* newdnumlength == 2 is OK;  */
  885.                 Head = &newdelta;
  886.         newdelta.next = 0;
  887.         return 0;
  888.         }
  889.         if (newdnumlength==0) {
  890.                 /* derive new revision number from locks */
  891.         switch (findlock(true, &targetdelta)) {
  892.  
  893.           default:
  894.             /* found two or more old locks */
  895.             return -1;
  896.  
  897.           case 1:
  898.                     /* found an old lock */
  899.                     /* check whether locked revision exists */
  900.             if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas))
  901.             return -1;
  902.                     if (targetdelta==Head) {
  903.                         /* make new head */
  904.                         newdelta.next=Head;
  905.                         Head= &newdelta;
  906.             } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) {
  907.                         /* new tip revision on side branch */
  908.                         targetdelta->next= &newdelta;
  909.             newdelta.next = 0;
  910.                     } else {
  911.                         /* middle revision; start a new branch */
  912.             bufscpy(&newdelnum, "");
  913.             return addbranch(targetdelta, &newdelnum, 1);
  914.                     }
  915.             incnum(targetdelta->num, &newdelnum);
  916.             return 1; /* successful use of existing lock */
  917.  
  918.           case 0:
  919.                     /* no existing lock; try Dbranch */
  920.                     /* update newdelnum */
  921.             if (StrictLocks || !myself(RCSstat.st_uid)) {
  922.             rcserror("no lock set by %s", getcaller());
  923.             return -1;
  924.                     }
  925.                     if (Dbranch) {
  926.             bufscpy(&newdelnum, Dbranch);
  927.                     } else {
  928.             incnum(Head->num, &newdelnum);
  929.                     }
  930.             newdnumlength = countnumflds(newdelnum.string);
  931.                     /* now fall into next statement */
  932.                 }
  933.         }
  934.         if (newdnumlength<=2) {
  935.                 /* add new head per given number */
  936.                 if(newdnumlength==1) {
  937.                     /* make a two-field number out of it*/
  938.             if (cmpnumfld(newdelnum.string,Head->num,1)==0)
  939.             incnum(Head->num, &newdelnum);
  940.             else
  941.             bufscat(&newdelnum, ".1");
  942.                 }
  943.         if (cmpnum(newdelnum.string,Head->num) <= 0) {
  944.             rcserror("revision %s too low; must be higher than %s",
  945.               newdelnum.string, Head->num
  946.             );
  947.             return -1;
  948.                 }
  949.         targetdelta = Head;
  950.         if (0 <= (removedlock = removelock(Head))) {
  951.             if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas))
  952.             return -1;
  953.             newdelta.next = Head;
  954.             Head = &newdelta;
  955.         }
  956.         return removedlock;
  957.         } else {
  958.                 /* put new revision on side branch */
  959.                 /*first, get branch point */
  960.         tp = newdelnum.string;
  961.         for (i = newdnumlength - ((newdnumlength&1) ^ 1);  --i;  )
  962.             while (*tp++ != '.')
  963.                 continue;
  964.         *--tp = 0; /* Kill final dot to get old delta temporarily. */
  965.         if (!(targetdelta=genrevs(newdelnum.string,(char*)0,(char*)0,(char*)0,&gendeltas)))
  966.             return -1;
  967.         if (cmpnum(targetdelta->num, newdelnum.string) != 0) {
  968.             rcserror("can't find branch point %s", newdelnum.string);
  969.             return -1;
  970.                 }
  971.         *tp = '.'; /* Restore final dot. */
  972.         return addbranch(targetdelta, &newdelnum, 0);
  973.         }
  974. }
  975.  
  976.  
  977.  
  978.     static int
  979. addbranch(branchpoint, num, removedlock)
  980.     struct hshentry *branchpoint;
  981.     struct buf *num;
  982.     int removedlock;
  983. /* adds a new branch and branch delta at branchpoint.
  984.  * If num is the null string, appends the new branch, incrementing
  985.  * the highest branch number (initially 1), and setting the level number to 1.
  986.  * the new delta and branchhead are in globals newdelta and newbranch, resp.
  987.  * the new number is placed into num.
  988.  * Return -1 on error, 1 if a lock is removed, 0 otherwise.
  989.  * If REMOVEDLOCK is 1, a lock was already removed.
  990.  */
  991. {
  992.     struct branchhead *bhead, **btrail;
  993.     struct buf branchnum;
  994.     int result;
  995.     int field, numlength;
  996.     static struct branchhead newbranch;  /* new branch to be inserted */
  997.  
  998.     numlength = countnumflds(num->string);
  999.  
  1000.     if (!branchpoint->branches) {
  1001.                 /* start first branch */
  1002.                 branchpoint->branches = &newbranch;
  1003.                 if (numlength==0) {
  1004.             bufscpy(num, branchpoint->num);
  1005.             bufscat(num, ".1.1");
  1006.         } else if (numlength&1)
  1007.             bufscat(num, ".1");
  1008.         newbranch.nextbranch = 0;
  1009.  
  1010.     } else if (numlength==0) {
  1011.                 /* append new branch to the end */
  1012.                 bhead=branchpoint->branches;
  1013.                 while (bhead->nextbranch) bhead=bhead->nextbranch;
  1014.                 bhead->nextbranch = &newbranch;
  1015.         bufautobegin(&branchnum);
  1016.         getbranchno(bhead->hsh->num, &branchnum);
  1017.         incnum(branchnum.string, num);
  1018.         bufautoend(&branchnum);
  1019.         bufscat(num, ".1");
  1020.         newbranch.nextbranch = 0;
  1021.         } else {
  1022.                 /* place the branch properly */
  1023.         field = numlength - ((numlength&1) ^ 1);
  1024.                 /* field of branch number */
  1025.         btrail = &branchpoint->branches;
  1026.         while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) {
  1027.             btrail = &(*btrail)->nextbranch;
  1028.             if (!*btrail) {
  1029.                 result = -1;
  1030.                 break;
  1031.             }
  1032.                 }
  1033.         if (result < 0) {
  1034.                         /* insert/append new branchhead */
  1035.             newbranch.nextbranch = *btrail;
  1036.             *btrail = &newbranch;
  1037.             if (numlength&1) bufscat(num, ".1");
  1038.                 } else {
  1039.                         /* branch exists; append to end */
  1040.             bufautobegin(&branchnum);
  1041.             getbranchno(num->string, &branchnum);
  1042.             targetdelta = genrevs(
  1043.                 branchnum.string, (char*)0, (char*)0, (char*)0,
  1044.                 &gendeltas
  1045.             );
  1046.             bufautoend(&branchnum);
  1047.             if (!targetdelta)
  1048.                 return -1;
  1049.             if (cmpnum(num->string,targetdelta->num) <= 0) {
  1050.                 rcserror("revision %s too low; must be higher than %s",
  1051.                       num->string, targetdelta->num
  1052.                 );
  1053.                 return -1;
  1054.                         }
  1055.             if (!removedlock
  1056.                 && 0 <= (removedlock = removelock(targetdelta))
  1057.             ) {
  1058.                 if (numlength&1)
  1059.                 incnum(targetdelta->num,num);
  1060.                 targetdelta->next = &newdelta;
  1061.                 newdelta.next = 0;
  1062.             }
  1063.             return removedlock;
  1064.             /* Don't do anything to newbranch.  */
  1065.                 }
  1066.         }
  1067.         newbranch.hsh = &newdelta;
  1068.     newdelta.next = 0;
  1069.     if (branchpoint->lockedby)
  1070.         if (strcmp(branchpoint->lockedby, getcaller()) == 0)
  1071.         return removelock(branchpoint); /* This returns 1.  */
  1072.     return removedlock;
  1073. }
  1074.  
  1075.     static int
  1076. addsyms(num)
  1077.     char const *num;
  1078. {
  1079.     register struct Symrev *p;
  1080.  
  1081.     for (p = assoclst;  p;  p = p->nextsym)
  1082.         if (addsymbol(num, p->ssymbol, p->override)  <  0)
  1083.             return false;
  1084.     return true;
  1085. }
  1086.  
  1087.  
  1088.     static void
  1089. incnum(onum,nnum)
  1090.     char const *onum;
  1091.     struct buf *nnum;
  1092. /* Increment the last field of revision number onum by one and
  1093.  * place the result into nnum.
  1094.  */
  1095. {
  1096.     register char *tp, *np;
  1097.     register size_t l;
  1098.  
  1099.     l = strlen(onum);
  1100.     bufalloc(nnum, l+2);
  1101.     np = tp = nnum->string;
  1102.     VOID strcpy(np, onum);
  1103.     for (tp = np + l;  np != tp;  )
  1104.         if (isdigit(*--tp)) {
  1105.             if (*tp != '9') {
  1106.                 ++*tp;
  1107.                 return;
  1108.             }
  1109.             *tp = '0';
  1110.         } else {
  1111.             tp++;
  1112.             break;
  1113.         }
  1114.     /* We changed 999 to 000; now change it to 1000.  */
  1115.     *tp = '1';
  1116.     tp = np + l;
  1117.     *tp++ = '0';
  1118.     *tp = 0;
  1119. }
  1120.  
  1121.  
  1122.  
  1123.     static int
  1124. removelock(delta)
  1125. struct hshentry * delta;
  1126. /* function: Finds the lock held by caller on delta,
  1127.  * removes it, and returns nonzero if successful.
  1128.  * Print an error message and return -1 if there is no such lock.
  1129.  * An exception is if !StrictLocks, and caller is the owner of
  1130.  * the RCS file. If caller does not have a lock in this case,
  1131.  * return 0; return 1 if a lock is actually removed.
  1132.  */
  1133. {
  1134.     register struct rcslock *next, **trail;
  1135.     char const *num;
  1136.  
  1137.         num=delta->num;
  1138.     for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
  1139.         if (next->delta == delta)
  1140.         if (strcmp(getcaller(), next->login) == 0) {
  1141.             /* We found a lock on delta by caller; delete it.  */
  1142.             *trail = next->nextlock;
  1143.             delta->lockedby = 0;
  1144.             return 1;
  1145.         } else {
  1146.             rcserror("revision %s locked by %s", num, next->login);
  1147.             return -1;
  1148.                 }
  1149.     if (!StrictLocks && myself(RCSstat.st_uid))
  1150.         return 0;
  1151.     rcserror("no lock set by %s for revision %s", getcaller(), num);
  1152.     return -1;
  1153. }
  1154.  
  1155.  
  1156.  
  1157.     static char const *
  1158. getcurdate()
  1159. /* Return a pointer to the current date.  */
  1160. {
  1161.     static char buffer[datesize]; /* date buffer */
  1162.  
  1163.     if (!buffer[0])
  1164.         time2date(now(), buffer);
  1165.         return buffer;
  1166. }
  1167.  
  1168.     static int
  1169. #if has_prototypes
  1170. fixwork(mode_t newworkmode, time_t mtime)
  1171.   /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
  1172. #else
  1173.   fixwork(newworkmode, mtime)
  1174.     mode_t newworkmode;
  1175.     time_t mtime;
  1176. #endif
  1177. {
  1178.     return
  1179.             1 < workstat.st_nlink
  1180.             ||    (newworkmode&S_IWUSR && !myself(workstat.st_uid))
  1181.             ||    setmtime(workname, mtime) != 0
  1182.         ?   -1
  1183.         :    workstat.st_mode == newworkmode  ?  0
  1184. #if has_fchmod
  1185.         :    fchmod(Ifileno(workptr), newworkmode) == 0  ?  0
  1186. #endif
  1187. #if bad_chmod_close
  1188.         :    -1
  1189. #else
  1190.         :    chmod(workname, newworkmode)
  1191. #endif
  1192.     ;
  1193. }
  1194.  
  1195.     static int
  1196. xpandfile(unexfile, delta, exname, dolog)
  1197.     RILE *unexfile;
  1198.     struct hshentry const *delta;
  1199.     char const **exname;
  1200.     int dolog;
  1201. /*
  1202.  * Read unexfile and copy it to a
  1203.  * file, performing keyword substitution with data from delta.
  1204.  * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
  1205.  * If successful, stores the stream descriptor into *EXFILEP
  1206.  * and its name into *EXNAME.
  1207.  */
  1208. {
  1209.     char const *targetname;
  1210.     int e, r;
  1211.  
  1212.     targetname = makedirtemp(1);
  1213.     if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) {
  1214.         eerror(targetname);
  1215.         workerror("can't build working file");
  1216.         return -1;
  1217.         }
  1218.     r = 0;
  1219.     if (MIN_UNEXPAND <= Expand)
  1220.         fastcopy(unexfile,exfile);
  1221.     else {
  1222.         for (;;) {
  1223.             e = expandline(
  1224.                 unexfile, exfile, delta, false, (FILE*)0, dolog
  1225.             );
  1226.             if (e < 0)
  1227.                 break;
  1228.             r |= e;
  1229.             if (e <= 1)
  1230.                 break;
  1231.         }
  1232.     }
  1233.     *exname = targetname;
  1234.     return r & 1;
  1235. }
  1236.  
  1237.  
  1238.  
  1239.  
  1240. /* --------------------- G E T L O G M S G --------------------------------*/
  1241.  
  1242.  
  1243.     static struct cbuf
  1244. getlogmsg()
  1245. /* Obtain and yield a log message.
  1246.  * If a log message is given with -m, yield that message.
  1247.  * If this is the initial revision, yield a standard log message.
  1248.  * Otherwise, reads a character string from the terminal.
  1249.  * Stops after reading EOF or a single '.' on a
  1250.  * line. getlogmsg prompts the first time it is called for the
  1251.  * log message; during all later calls it asks whether the previous
  1252.  * log message can be reused.
  1253.  */
  1254. {
  1255.     static char const
  1256.         emptych[] = EMPTYLOG,
  1257.         initialch[] = "Initial revision";
  1258.     static struct cbuf const
  1259.         emptylog = { emptych, sizeof(emptych)-sizeof(char) },
  1260.         initiallog = { initialch, sizeof(initialch)-sizeof(char) };
  1261.     static struct buf logbuf;
  1262.     static struct cbuf logmsg;
  1263.  
  1264.     register char *tp;
  1265.     register size_t i;
  1266.     char const *caller;
  1267.  
  1268.     if (msg.size) return msg;
  1269.  
  1270.     if (keepflag) {
  1271.         /* generate std. log message */
  1272.         caller = getcaller();
  1273.         i = sizeof(ciklog)+strlen(caller)+3;
  1274.         bufalloc(&logbuf, i + datesize + zonelenmax);
  1275.         tp = logbuf.string;
  1276.         VOID sprintf(tp, "%s%s at ", ciklog, caller);
  1277.         VOID date2str(getcurdate(), tp+i);
  1278.         logmsg.string = tp;
  1279.         logmsg.size = strlen(tp);
  1280.         return logmsg;
  1281.     }
  1282.  
  1283.     if (!targetdelta && (
  1284.         cmpnum(newdelnum.string,"1.1")==0 ||
  1285.         cmpnum(newdelnum.string,"1.0")==0
  1286.     ))
  1287.         return initiallog;
  1288.  
  1289.     if (logmsg.size) {
  1290.                 /*previous log available*/
  1291.         if (yesorno(true, "reuse log message of previous file? [yn](y): "))
  1292.         return logmsg;
  1293.         }
  1294.  
  1295.         /* now read string from stdin */
  1296.     logmsg = getsstdin("m", "log message", "", &logbuf);
  1297.  
  1298.         /* now check whether the log message is not empty */
  1299.     if (logmsg.size)
  1300.         return logmsg;
  1301.     return emptylog;
  1302. }
  1303.  
  1304. /*  Make a linked list of Symbolic names  */
  1305.  
  1306.         static void
  1307. addassoclst(flag, sp)
  1308.     int flag;
  1309.     char const *sp;
  1310. {
  1311.         struct Symrev *pt;
  1312.     
  1313.     pt = talloc(struct Symrev);
  1314.     pt->ssymbol = sp;
  1315.     pt->override = flag;
  1316.     pt->nextsym = 0;
  1317.     *nextassoc = pt;
  1318.     nextassoc = &pt->nextsym;
  1319. }
  1320.