home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 22 gnu / 22-gnu.zip / rcs57pc3.zip / rcs / src / rcs.c < prev    next >
C/C++ Source or Header  |  1996-02-29  |  46KB  |  1,631 lines

  1. /* Change RCS file attributes.  */
  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: rcs.c,v $
  32.  * Revision 5.21  1995/06/16 06:19:24  eggert
  33.  * Update FSF address.
  34.  *
  35.  * Revision 5.20  1995/06/01 16:23:43  eggert
  36.  * (main): Warn if no options were given.  Punctuate messages properly.
  37.  *
  38.  * (sendmail): Rewind mailmess before flushing it.
  39.  * Output another warning if mail should work but fails.
  40.  *
  41.  * (buildeltatext): Pass "--binary" if -kb and if --binary makes a difference.
  42.  *
  43.  * Revision 5.19  1994/03/17 14:05:48  eggert
  44.  * Use ORCSerror to clean up after a fatal error.  Remove lint.
  45.  * Specify subprocess input via file descriptor, not file name.  Remove lint.
  46.  * Flush stderr after prompt.
  47.  *
  48.  * Revision 5.18  1993/11/09 17:40:15  eggert
  49.  * -V now prints version on stdout and exits.  Don't print usage twice.
  50.  *
  51.  * Revision 5.17  1993/11/03 17:42:27  eggert
  52.  * Add -z.  Don't lose track of -m or -t when there are no other changes.
  53.  * Don't discard ignored phrases.  Improve quality of diagnostics.
  54.  *
  55.  * Revision 5.16  1992/07/28  16:12:44  eggert
  56.  * rcs -l now asks whether you want to break the lock.
  57.  * Add -V.  Set RCS file's mode and time at right moment.
  58.  *
  59.  * Revision 5.15  1992/02/17  23:02:20  eggert
  60.  * Add -T.
  61.  *
  62.  * Revision 5.14  1992/01/27  16:42:53  eggert
  63.  * Add -M.  Avoid invoking umask(); it's one less thing to configure.
  64.  * Add support for bad_creat0.  lint -> RCS_lint
  65.  *
  66.  * Revision 5.13  1992/01/06  02:42:34  eggert
  67.  * Avoid changing RCS file in common cases where no change can occur.
  68.  *
  69.  * Revision 5.12  1991/11/20  17:58:08  eggert
  70.  * Don't read the delta tree from a nonexistent RCS file.
  71.  *
  72.  * Revision 5.11  1991/10/07  17:32:46  eggert
  73.  * Remove lint.
  74.  *
  75.  * Revision 5.10  1991/08/19  23:17:54  eggert
  76.  * Add -m, -r$, piece tables.  Revision separator is `:', not `-'.  Tune.
  77.  *
  78.  * Revision 5.9  1991/04/21  11:58:18  eggert
  79.  * Add -x, RCSINIT, MS-DOS support.
  80.  *
  81.  * Revision 5.8  1991/02/25  07:12:38  eggert
  82.  * strsave -> str_save (DG/UX name clash)
  83.  * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability
  84.  *
  85.  * Revision 5.7  1990/12/18  17:19:21  eggert
  86.  * Fix bug with multiple -n and -N options.
  87.  *
  88.  * Revision 5.6  1990/12/04  05:18:40  eggert
  89.  * Use -I for prompts and -q for diagnostics.
  90.  *
  91.  * Revision 5.5  1990/11/11  00:06:35  eggert
  92.  * Fix `rcs -e' core dump.
  93.  *
  94.  * Revision 5.4  1990/11/01  05:03:33  eggert
  95.  * Add -I and new -t behavior.  Permit arbitrary data in logs.
  96.  *
  97.  * Revision 5.3  1990/10/04  06:30:16  eggert
  98.  * Accumulate exit status across files.
  99.  *
  100.  * Revision 5.2  1990/09/04  08:02:17  eggert
  101.  * Standardize yes-or-no procedure.
  102.  *
  103.  * Revision 5.1  1990/08/29  07:13:51  eggert
  104.  * Remove unused setuid support.  Clean old log messages too.
  105.  *
  106.  * Revision 5.0  1990/08/22  08:12:42  eggert
  107.  * Don't lose names when applying -a option to multiple files.
  108.  * Remove compile-time limits; use malloc instead.  Add setuid support.
  109.  * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
  110.  * Ansify and Posixate.  Add -V.  Fix umask bug.  Make linting easier.  Tune.
  111.  * Yield proper exit status.  Check diff's output.
  112.  *
  113.  * Revision 4.11  89/05/01  15:12:06  narten
  114.  * changed copyright header to reflect current distribution rules
  115.  * 
  116.  * Revision 4.10  88/11/08  16:01:54  narten
  117.  * didn't install previous patch correctly
  118.  * 
  119.  * Revision 4.9  88/11/08  13:56:01  narten
  120.  * removed include <sysexits.h> (not needed)
  121.  * minor fix for -A option
  122.  * 
  123.  * Revision 4.8  88/08/09  19:12:27  eggert
  124.  * Don't access freed storage.
  125.  * Use execv(), not system(); yield proper exit status; remove lint.
  126.  * 
  127.  * Revision 4.7  87/12/18  11:37:17  narten
  128.  * lint cleanups (Guy Harris)
  129.  * 
  130.  * Revision 4.6  87/10/18  10:28:48  narten
  131.  * Updating verison numbers. Changes relative to 1.1 are actually 
  132.  * relative to 4.3
  133.  * 
  134.  * Revision 1.4  87/09/24  13:58:52  narten
  135.  * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
  136.  * warnings)
  137.  * 
  138.  * Revision 1.3  87/03/27  14:21:55  jenkins
  139.  * Port to suns
  140.  * 
  141.  * Revision 1.2  85/12/17  13:59:09  albitz
  142.  * Changed setstate to rcs_setstate because of conflict with random.o.
  143.  * 
  144.  * Revision 4.3  83/12/15  12:27:33  wft
  145.  * rcs -u now breaks most recent lock if it can't find a lock by the caller.
  146.  * 
  147.  * Revision 4.2  83/12/05  10:18:20  wft
  148.  * Added conditional compilation for sending mail.
  149.  * Alternatives: V4_2BSD, V6, USG, and other.
  150.  * 
  151.  * Revision 4.1  83/05/10  16:43:02  wft
  152.  * Simplified breaklock(); added calls to findlock() and getcaller().
  153.  * Added option -b (default branch). Updated -s and -w for -b.
  154.  * Removed calls to stat(); now done by pairfilenames().
  155.  * Replaced most catchints() calls with restoreints().
  156.  * Removed check for exit status of delivermail().
  157.  * Directed all interactive output to stderr.
  158.  * 
  159.  * Revision 3.9.1.1  83/12/02  22:08:51  wft
  160.  * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
  161.  * 
  162.  * Revision 3.9  83/02/15  15:38:39  wft
  163.  * Added call to fastcopy() to copy remainder of RCS file.
  164.  *
  165.  * Revision 3.8  83/01/18  17:37:51  wft
  166.  * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
  167.  *
  168.  * Revision 3.7  83/01/15  18:04:25  wft
  169.  * Removed putree(); replaced with puttree() in rcssyn.c.
  170.  * Combined putdellog() and scanlogtext(); deleted putdellog().
  171.  * Cleaned up diagnostics and error messages. Fixed problem with
  172.  * mutilated files in case of deletions in 2 files in a single command.
  173.  * Changed marking of selector from 'D' to DELETE.
  174.  *
  175.  * Revision 3.6  83/01/14  15:37:31  wft
  176.  * Added ignoring of interrupts while new RCS file is renamed;
  177.  * Avoids deletion of RCS files by interrupts.
  178.  *
  179.  * Revision 3.5  82/12/10  21:11:39  wft
  180.  * Removed unused variables, fixed checking of return code from diff,
  181.  * introduced variant COMPAT2 for skipping Suffix on -A files.
  182.  *
  183.  * Revision 3.4  82/12/04  13:18:20  wft
  184.  * Replaced getdelta() with gettree(), changed breaklock to update
  185.  * field lockedby, added some diagnostics.
  186.  *
  187.  * Revision 3.3  82/12/03  17:08:04  wft
  188.  * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
  189.  * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
  190.  * fixed -u for missing revno. Disambiguated structure members.
  191.  *
  192.  * Revision 3.2  82/10/18  21:05:07  wft
  193.  * rcs -i now generates a file mode given by the umask minus write permission;
  194.  * otherwise, rcs keeps the mode, but removes write permission.
  195.  * I added a check for write error, fixed call to getlogin(), replaced
  196.  * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
  197.  * conflicting, long identifiers.
  198.  *
  199.  * Revision 3.1  82/10/13  16:11:07  wft
  200.  * fixed type of variables receiving from getc() (char -> int).
  201.  */
  202.  
  203.  
  204. #include "rcsbase.h"
  205.  
  206. struct  Lockrev {
  207.     char const *revno;
  208.         struct  Lockrev   * nextrev;
  209. };
  210.  
  211. struct  Symrev {
  212.     char const *revno;
  213.     char const *ssymbol;
  214.         int     override;
  215.         struct  Symrev  * nextsym;
  216. };
  217.  
  218. struct Message {
  219.     char const *revno;
  220.     struct cbuf message;
  221.     struct Message *nextmessage;
  222. };
  223.  
  224. struct  Status {
  225.     char const *revno;
  226.     char const *status;
  227.         struct  Status  * nextstatus;
  228. };
  229.  
  230. enum changeaccess {append, erase};
  231. struct chaccess {
  232.     char const *login;
  233.     enum changeaccess command;
  234.     struct chaccess *nextchaccess;
  235. };
  236.  
  237. struct delrevpair {
  238.     char const *strt;
  239.     char const *end;
  240.         int     code;
  241. };
  242.  
  243. static int branchpoint P((struct hshentry*,struct hshentry*));
  244. static int breaklock P((struct hshentry const*));
  245. static int buildeltatext P((struct hshentries const*));
  246. static int doaccess P((void));
  247. static int doassoc P((void));
  248. static int dolocks P((void));
  249. static int domessages P((void));
  250. static int rcs_setstate P((char const*,char const*));
  251. static int removerevs P((void));
  252. static int sendmail P((char const*,char const*));
  253. static int setlock P((char const*));
  254. static struct Lockrev **rmnewlocklst P((char const*));
  255. static struct hshentry *searchcutpt P((char const*,int,struct hshentries*));
  256. static void buildtree P((void));
  257. static void cleanup P((void));
  258. static void getaccessor P((char*,enum changeaccess));
  259. static void getassoclst P((int,char*));
  260. static void getchaccess P((char const*,enum changeaccess));
  261. static void getdelrev P((char*));
  262. static void getmessage P((char*));
  263. static void getstates P((char*));
  264. static void scanlogtext P((struct hshentry*,int));
  265.  
  266. static struct buf numrev;
  267. static char const *headstate;
  268. static int chgheadstate, exitstatus, lockhead, unlockcaller;
  269. static int suppress_mail;
  270. static struct Lockrev *newlocklst, *rmvlocklst;
  271. static struct Message *messagelst, **nextmessage;
  272. static struct Status *statelst, **nextstate;
  273. static struct Symrev *assoclst, **nextassoc;
  274. static struct chaccess *chaccess, **nextchaccess;
  275. static struct delrevpair delrev;
  276. static struct hshentry *cuthead, *cuttail, *delstrt;
  277. static struct hshentries *gendeltas;
  278.  
  279. mainProg(rcsId, "rcs", "$Id: rcs.c,v 5.21 1995/06/16 06:19:24 eggert Exp $")
  280. {
  281.     static char const cmdusage[] =
  282.         "\nrcs usage: rcs -{ae}logins -Afile -{blu}[rev] -cstring -{iILqTU} -ksubst -mrev:msg -{nN}name[:[rev]] -orange -sstate[:rev] -t[text] -Vn -xsuff -zzone file ...";
  283.  
  284.     char *a, **newargv, *textfile;
  285.     char const *branchsym, *commsyml;
  286.     int branchflag, changed, expmode, initflag;
  287.     int strictlock, strict_selected, textflag;
  288.     int keepRCStime, Ttimeflag;
  289.     size_t commsymlen;
  290.     struct buf branchnum;
  291.     struct Lockrev *lockpt;
  292.     struct Lockrev **curlock, **rmvlock;
  293.         struct  Status  * curstate;
  294.  
  295.     nosetid();
  296.  
  297.     nextassoc = &assoclst;
  298.     nextchaccess = &chaccess;
  299.     nextmessage = &messagelst;
  300.     nextstate = &statelst;
  301.     branchsym = commsyml = textfile = 0;
  302.     branchflag = strictlock = false;
  303.     bufautobegin(&branchnum);
  304.     commsymlen = 0;
  305.     curlock = &newlocklst;
  306.     rmvlock = &rmvlocklst;
  307.     expmode = -1;
  308.     suffixes = X_DEFAULT;
  309.         initflag= textflag = false;
  310.         strict_selected = 0;
  311.     Ttimeflag = false;
  312.  
  313.         /*  preprocessing command options    */
  314.     if (1 < argc  &&  argv[1][0] != '-')
  315.         warn("No options were given; this usage is obsolescent.");
  316.  
  317.     argc = getRCSINIT(argc, argv, &newargv);
  318.     argv = newargv;
  319.     while (a = *++argv,  0<--argc && *a++=='-') {
  320.         switch (*a++) {
  321.  
  322.         case 'i':   /*  initial version  */
  323.                         initflag = true;
  324.                         break;
  325.  
  326.                 case 'b':  /* change default branch */
  327.             if (branchflag) redefined('b');
  328.                         branchflag= true;
  329.             branchsym = a;
  330.                         break;
  331.  
  332.                 case 'c':   /*  change comment symbol   */
  333.             if (commsyml) redefined('c');
  334.             commsyml = a;
  335.             commsymlen = strlen(a);
  336.                         break;
  337.  
  338.                 case 'a':  /*  add new accessor   */
  339.             getaccessor(*argv+1, append);
  340.                         break;
  341.  
  342.                 case 'A':  /*  append access list according to accessfile  */
  343.             if (!*a) {
  344.                 error("missing pathname after -A");
  345.                             break;
  346.                         }
  347.             *argv = a;
  348.             if (0 < pairnames(1,argv,rcsreadopen,true,false)) {
  349.                 while (AccessList) {
  350.                 getchaccess(str_save(AccessList->login),append);
  351.                 AccessList = AccessList->nextaccess;
  352.                 }
  353.                 Izclose(&finptr);
  354.                         }
  355.                         break;
  356.  
  357.                 case 'e':    /*  remove accessors   */
  358.             getaccessor(*argv+1, erase);
  359.                         break;
  360.  
  361.                 case 'l':    /*   lock a revision if it is unlocked   */
  362.             if (!*a) {
  363.                 /* Lock head or default branch.  */
  364.                             lockhead = true;
  365.                             break;
  366.                         }
  367.             *curlock = lockpt = talloc(struct Lockrev);
  368.             lockpt->revno = a;
  369.             lockpt->nextrev = 0;
  370.             curlock = &lockpt->nextrev;
  371.                         break;
  372.  
  373.                 case 'u':   /*  release lock of a locked revision   */
  374.             if (!*a) {
  375.                             unlockcaller=true;
  376.                             break;
  377.                         }
  378.             *rmvlock = lockpt = talloc(struct Lockrev);
  379.             lockpt->revno = a;
  380.             lockpt->nextrev = 0;
  381.             rmvlock = &lockpt->nextrev;
  382.             curlock = rmnewlocklst(lockpt->revno);
  383.                         break;
  384.  
  385.                 case 'L':   /*  set strict locking */
  386.             if (strict_selected) {
  387.                if (!strictlock)      /* Already selected -U? */
  388.                    warn("-U overridden by -L");
  389.                         }
  390.                         strictlock = true;
  391.             strict_selected = true;
  392.                         break;
  393.  
  394.                 case 'U':   /*  release strict locking */
  395.             if (strict_selected) {
  396.                if (strictlock)      /* Already selected -L? */
  397.                    warn("-L overridden by -U");
  398.                         }
  399.             strict_selected = true;
  400.                         break;
  401.  
  402.                 case 'n':    /*  add new association: error, if name exists */
  403.             if (!*a) {
  404.                 error("missing symbolic name after -n");
  405.                             break;
  406.                         }
  407.                         getassoclst(false, (*argv)+1);
  408.                         break;
  409.  
  410.                 case 'N':   /*  add or change association   */
  411.             if (!*a) {
  412.                 error("missing symbolic name after -N");
  413.                             break;
  414.                         }
  415.                         getassoclst(true, (*argv)+1);
  416.                         break;
  417.  
  418.         case 'm':   /*  change log message  */
  419.             getmessage(a);
  420.             break;
  421.  
  422.         case 'M':   /*  do not send mail */
  423.             suppress_mail = true;
  424.             break;
  425.  
  426.         case 'o':   /*  delete revisions  */
  427.             if (delrev.strt) redefined('o');
  428.             if (!*a) {
  429.                 error("missing revision range after -o");
  430.                             break;
  431.                         }
  432.                         getdelrev( (*argv)+1 );
  433.                         break;
  434.  
  435.                 case 's':   /*  change state attribute of a revision  */
  436.             if (!*a) {
  437.                 error("state missing after -s");
  438.                             break;
  439.                         }
  440.                         getstates( (*argv)+1);
  441.                         break;
  442.  
  443.                 case 't':   /*  change descriptive text   */
  444.                         textflag=true;
  445.             if (*a) {
  446.                 if (textfile) redefined('t');
  447.                 textfile = a;
  448.                         }
  449.                         break;
  450.  
  451.         case 'T':  /*  do not update last-mod time for minor changes */
  452.             if (*a)
  453.                 goto unknown;
  454.             Ttimeflag = true;
  455.             break;
  456.  
  457.         case 'I':
  458.             interactiveflag = true;
  459.             break;
  460.  
  461.                 case 'q':
  462.                         quietflag = true;
  463.                         break;
  464.  
  465.         case 'x':
  466.             suffixes = a;
  467.             break;
  468.  
  469.         case 'V':
  470.             setRCSversion(*argv);
  471.             break;
  472.  
  473.         case 'z':
  474.             zone_set(a);
  475.             break;
  476.  
  477.         case 'k':    /*  set keyword expand mode  */
  478.             if (0 <= expmode) redefined('k');
  479.             if (0 <= (expmode = str2expmode(a)))
  480.                 break;
  481.             /* fall into */
  482.                 default:
  483.         unknown:
  484.             error("unknown option: %s%s", *argv, cmdusage);
  485.                 };
  486.         }  /* end processing of options */
  487.  
  488.     /* Now handle all pathnames.  */
  489.     if (nerror) cleanup();
  490.     else if (argc < 1) faterror("no input file%s", cmdusage);
  491.     else for (;  0 < argc;  cleanup(), ++argv, --argc) {
  492.  
  493.     ffree();
  494.  
  495.         if ( initflag ) {
  496.         switch (pairnames(argc, argv, rcswriteopen, false, false)) {
  497.                 case -1: break;        /*  not exist; ok */
  498.                 case  0: continue;     /*  error         */
  499.         case  1: rcserror("already exists");
  500.                          continue;
  501.             }
  502.     }
  503.         else  {
  504.         switch (pairnames(argc, argv, rcswriteopen, true, false)) {
  505.                 case -1: continue;    /*  not exist      */
  506.                 case  0: continue;    /*  errors         */
  507.                 case  1: break;       /*  file exists; ok*/
  508.             }
  509.     }
  510.  
  511.  
  512.     /*
  513.      * RCSname contains the name of the RCS file, and
  514.      * workname contains the name of the working file.
  515.          * if !initflag, finptr contains the file descriptor for the
  516.          * RCS file. The admin node is initialized.
  517.          */
  518.  
  519.     diagnose("RCS file: %s\n", RCSname);
  520.  
  521.     changed = initflag | textflag;
  522.     keepRCStime = Ttimeflag;
  523.     if (!initflag) {
  524.         if (!checkaccesslist()) continue;
  525.         gettree(); /* Read the delta tree.  */
  526.     }
  527.  
  528.         /*  update admin. node    */
  529.     if (strict_selected) {
  530.         changed  |=  StrictLocks ^ strictlock;
  531.         StrictLocks = strictlock;
  532.     }
  533.     if (
  534.         commsyml &&
  535.         (
  536.         commsymlen != Comment.size ||
  537.         memcmp(commsyml, Comment.string, commsymlen) != 0
  538.         )
  539.     ) {
  540.         Comment.string = commsyml;
  541.         Comment.size = strlen(commsyml);
  542.         changed = true;
  543.     }
  544.     if (0 <= expmode  &&  Expand != expmode) {
  545.         Expand = expmode;
  546.         changed = true;
  547.     }
  548.  
  549.         /* update default branch */
  550.     if (branchflag && expandsym(branchsym, &branchnum)) {
  551.         if (countnumflds(branchnum.string)) {
  552.         if (cmpnum(Dbranch, branchnum.string) != 0) {
  553.             Dbranch = branchnum.string;
  554.             changed = true;
  555.         }
  556.             } else
  557.         if (Dbranch) {
  558.             Dbranch = 0;
  559.             changed = true;
  560.         }
  561.     }
  562.  
  563.     changed |= doaccess();    /* Update access list.  */
  564.  
  565.     changed |= doassoc();    /* Update association list.  */
  566.  
  567.     changed |= dolocks();    /* Update locks.  */
  568.  
  569.     changed |= domessages();    /* Update log messages.  */
  570.  
  571.         /*  update state attribution  */
  572.         if (chgheadstate) {
  573.             /* change state of default branch or head */
  574.         if (!Dbranch) {
  575.         if (!Head)
  576.             rcswarn("can't change states in an empty tree");
  577.         else if (strcmp(Head->state, headstate) != 0) {
  578.             Head->state = headstate;
  579.             changed = true;
  580.         }
  581.         } else
  582.         changed |= rcs_setstate(Dbranch,headstate);
  583.         }
  584.     for (curstate = statelst;  curstate;  curstate = curstate->nextstatus)
  585.         changed |= rcs_setstate(curstate->revno,curstate->status);
  586.  
  587.     cuthead = cuttail = 0;
  588.     if (delrev.strt && removerevs()) {
  589.             /*  rebuild delta tree if some deltas are deleted   */
  590.             if ( cuttail )
  591.         VOID genrevs(
  592.             cuttail->num, (char *)0, (char *)0, (char *)0,
  593.             &gendeltas
  594.         );
  595.             buildtree();
  596.         changed = true;
  597.         keepRCStime = false;
  598.         }
  599.  
  600.     if (nerror)
  601.         continue;
  602.  
  603.     putadmin();
  604.         if ( Head )
  605.            puttree(Head, frewrite);
  606.     putdesc(textflag,textfile);
  607.  
  608.         if ( Head) {
  609.         if (delrev.strt || messagelst) {
  610.         if (!cuttail || buildeltatext(gendeltas)) {
  611.             advise_access(finptr, MADV_SEQUENTIAL);
  612.             scanlogtext((struct hshentry *)0, false);
  613.                     /* copy rest of delta text nodes that are not deleted      */
  614.             changed = true;
  615.         }
  616.             }
  617.         }
  618.  
  619.     if (initflag) {
  620.         /* Adjust things for donerewrite's sake.  */
  621.         if (stat(workname, &RCSstat) != 0) {
  622. #            if bad_creat0
  623.             mode_t m = umask(0);
  624.             (void) umask(m);
  625.             RCSstat.st_mode = (S_IRUSR|S_IRGRP|S_IROTH) & ~m;
  626. #            else
  627.             changed = -1;
  628. #            endif
  629.         }
  630.         RCSstat.st_nlink = 0;
  631.         keepRCStime = false;
  632.     }
  633.     if (donerewrite(changed,
  634.         keepRCStime ? RCSstat.st_mtime : (time_t)-1
  635.     ) != 0)
  636.         break;
  637.  
  638.     diagnose("done\n");
  639.     }
  640.  
  641.     tempunlink();
  642.     exitmain(exitstatus);
  643. }       /* end of main (rcs) */
  644.  
  645.     static void
  646. cleanup()
  647. {
  648.     if (nerror) exitstatus = EXIT_FAILURE;
  649.     Izclose(&finptr);
  650.     Ozclose(&fcopy);
  651.     ORCSclose();
  652.     dirtempunlink();
  653. }
  654.  
  655.     void
  656. exiterr()
  657. {
  658.     ORCSerror();
  659.     dirtempunlink();
  660.     tempunlink();
  661.     _exit(EXIT_FAILURE);
  662. }
  663.  
  664.  
  665.     static void
  666. getassoclst(flag, sp)
  667. int     flag;
  668. char    * sp;
  669. /*  Function:   associate a symbolic name to a revision or branch,      */
  670. /*              and store in assoclst                                   */
  671.  
  672. {
  673.         struct   Symrev  * pt;
  674.     char const *temp;
  675.         int                c;
  676.  
  677.     while ((c = *++sp) == ' ' || c == '\t' || c =='\n')
  678.         continue;
  679.         temp = sp;
  680.     sp = checksym(sp, ':');  /*  check for invalid symbolic name  */
  681.     c = *sp;   *sp = '\0';
  682.         while( c == ' ' || c == '\t' || c == '\n')  c = *++sp;
  683.  
  684.         if ( c != ':' && c != '\0') {
  685.         error("invalid string %s after option -n or -N",sp);
  686.             return;
  687.         }
  688.  
  689.     pt = talloc(struct Symrev);
  690.         pt->ssymbol = temp;
  691.         pt->override = flag;
  692.     if (c == '\0')  /*  delete symbol  */
  693.         pt->revno = 0;
  694.         else {
  695.         while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
  696.         continue;
  697.         pt->revno = sp;
  698.         }
  699.     pt->nextsym = 0;
  700.     *nextassoc = pt;
  701.     nextassoc = &pt->nextsym;
  702. }
  703.  
  704.  
  705.     static void
  706. getchaccess(login, command)
  707.     char const *login;
  708.     enum changeaccess command;
  709. {
  710.     register struct chaccess *pt;
  711.  
  712.     pt = talloc(struct chaccess);
  713.     pt->login = login;
  714.     pt->command = command;
  715.     pt->nextchaccess = 0;
  716.     *nextchaccess = pt;
  717.     nextchaccess = &pt->nextchaccess;
  718. }
  719.  
  720.  
  721.  
  722.     static void
  723. getaccessor(opt, command)
  724.     char *opt;
  725.     enum changeaccess command;
  726. /*   Function:  get the accessor list of options -e and -a,     */
  727. /*        and store in chaccess                */
  728.  
  729.  
  730. {
  731.         register c;
  732.     register char *sp;
  733.  
  734.     sp = opt;
  735.     while ((c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',')
  736.         continue;
  737.         if ( c == '\0') {
  738.         if (command == erase  &&  sp-opt == 1) {
  739.         getchaccess((char*)0, command);
  740.         return;
  741.         }
  742.         error("missing login name after option -a or -e");
  743.         return;
  744.         }
  745.  
  746.         while( c != '\0') {
  747.         getchaccess(sp, command);
  748.         sp = checkid(sp,',');
  749.         c = *sp;   *sp = '\0';
  750.                 while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
  751.         }
  752. }
  753.  
  754.  
  755.     static void
  756. getmessage(option)
  757.     char *option;
  758. {
  759.     struct Message *pt;
  760.     struct cbuf cb;
  761.     char *m;
  762.  
  763.     if (!(m = strchr(option, ':'))) {
  764.         error("-m option lacks revision number");
  765.         return;
  766.     }
  767.     *m++ = 0;
  768.     cb = cleanlogmsg(m, strlen(m));
  769.     if (!cb.size) {
  770.         error("-m option lacks log message");
  771.         return;
  772.     }
  773.     pt = talloc(struct Message);
  774.     pt->revno = option;
  775.     pt->message = cb;
  776.     pt->nextmessage = 0;
  777.     *nextmessage = pt;
  778.     nextmessage = &pt->nextmessage;
  779. }
  780.  
  781.  
  782.     static void
  783. getstates(sp)
  784. char    *sp;
  785. /*   Function:  get one state attribute and the corresponding   */
  786. /*              revision and store in statelst                  */
  787.  
  788. {
  789.     char const *temp;
  790.         struct  Status  *pt;
  791.         register        c;
  792.  
  793.     while ((c = *++sp) ==' ' || c == '\t' || c == '\n')
  794.         continue;
  795.         temp = sp;
  796.     sp = checkid(sp,':');  /* check for invalid state attribute */
  797.     c = *sp;   *sp = '\0';
  798.         while( c == ' ' || c == '\t' || c == '\n' )  c = *++sp;
  799.  
  800.         if ( c == '\0' ) {  /*  change state of def. branch or Head  */
  801.             chgheadstate = true;
  802.             headstate  = temp;
  803.             return;
  804.         }
  805.         else if ( c != ':' ) {
  806.         error("missing ':' after state in option -s");
  807.             return;
  808.         }
  809.  
  810.     while ((c = *++sp) == ' ' || c == '\t' || c == '\n')
  811.         continue;
  812.     pt = talloc(struct Status);
  813.         pt->status     = temp;
  814.         pt->revno      = sp;
  815.     pt->nextstatus = 0;
  816.     *nextstate = pt;
  817.     nextstate = &pt->nextstatus;
  818. }
  819.  
  820.  
  821.  
  822.     static void
  823. getdelrev(sp)
  824. char    *sp;
  825. /*   Function:  get revision range or branch to be deleted,     */
  826. /*              and place in delrev                             */
  827. {
  828.         int    c;
  829.         struct  delrevpair      *pt;
  830.     int separator;
  831.  
  832.     pt = &delrev;
  833.     while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
  834.         continue;
  835.  
  836.     /* Support old ambiguous '-' syntax; this will go away.  */
  837.     if (strchr(sp,':'))
  838.         separator = ':';
  839.     else {
  840.         if (strchr(sp,'-')  &&  VERSION(5) <= RCSversion)
  841.             warn("`-' is obsolete in `-o%s'; use `:' instead", sp);
  842.         separator = '-';
  843.     }
  844.  
  845.     if (c == separator) { /* -o:rev */
  846.         while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
  847.         continue;
  848.             pt->strt = sp;    pt->code = 1;
  849.             while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
  850.             *sp = '\0';
  851.         pt->end = 0;
  852.             return;
  853.         }
  854.         else {
  855.             pt->strt = sp;
  856.             while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
  857.            && c != separator )  c = *++sp;
  858.             *sp = '\0';
  859.             while( c == ' ' || c == '\n' || c == '\t' )  c = *++sp;
  860.             if ( c == '\0' )  {  /*   -o rev or branch   */
  861.         pt->code = 0;
  862.         pt->end = 0;
  863.                 return;
  864.             }
  865.         if (c != separator) {
  866.         error("invalid range %s %s after -o", pt->strt, sp);
  867.             }
  868.         while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
  869.         continue;
  870.         if (!c) {  /* -orev: */
  871.         pt->code = 2;
  872.         pt->end = 0;
  873.                 return;
  874.             }
  875.         }
  876.     /* -orev1:rev2 */
  877.     pt->end = sp;  pt->code = 3;
  878.         while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
  879.         *sp = '\0';
  880. }
  881.  
  882.  
  883.  
  884.  
  885.     static void
  886. scanlogtext(delta,edit)
  887.     struct hshentry *delta;
  888.     int edit;
  889. /* Function: Scans delta text nodes up to and including the one given
  890.  * by delta, or up to last one present, if !delta.
  891.  * For the one given by delta (if delta), the log message is saved into
  892.  * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied.
  893.  * Assumes the initial lexeme must be read in first.
  894.  * Does not advance nexttok after it is finished, except if !delta.
  895.  */
  896. {
  897.     struct hshentry const *nextdelta;
  898.     struct cbuf cb;
  899.  
  900.     for (;;) {
  901.         foutptr = 0;
  902.         if (eoflex()) {
  903.                     if(delta)
  904.             rcsfaterror("can't find delta for revision %s",
  905.                 delta->num
  906.             );
  907.             return; /* no more delta text nodes */
  908.                 }
  909.         nextlex();
  910.         if (!(nextdelta=getnum()))
  911.             fatserror("delta number corrupted");
  912.         if (nextdelta->selector) {
  913.             foutptr = frewrite;
  914.             aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
  915.                 }
  916.         getkeystring(Klog);
  917.         if (nextdelta == cuttail) {
  918.             cb = savestring(&curlogbuf);
  919.             if (!delta->log.string)
  920.                 delta->log = cleanlogmsg(curlogbuf.string, cb.size);
  921.             nextlex();
  922.             delta->igtext = getphrases(Ktext);
  923.         } else {
  924.             if (nextdelta->log.string && nextdelta->selector) {
  925.                 foutptr = 0;
  926.                 readstring();
  927.                 foutptr = frewrite;
  928.                 putstring(foutptr, false, nextdelta->log, true);
  929.                 afputc(nextc, foutptr);
  930.             } else
  931.                 readstring();
  932.             ignorephrases(Ktext);
  933.         }
  934.         getkeystring(Ktext);
  935.  
  936.         if (delta==nextdelta)
  937.             break;
  938.         readstring(); /* skip over it */
  939.  
  940.     }
  941.     /* got the one we're looking for */
  942.     if (edit)
  943.         editstring((struct hshentry*)0);
  944.     else
  945.         enterstring();
  946. }
  947.  
  948.  
  949.  
  950.     static struct Lockrev **
  951. rmnewlocklst(which)
  952.     char const *which;
  953. /* Remove lock to revision WHICH from newlocklst.  */
  954. {
  955.     struct Lockrev *pt, **pre;
  956.  
  957.     pre = &newlocklst;
  958.     while ((pt = *pre))
  959.         if (strcmp(pt->revno, which) != 0)
  960.         pre = &pt->nextrev;
  961.         else {
  962.         *pre = pt->nextrev;
  963.         tfree(pt);
  964.         }
  965.         return pre;
  966. }
  967.  
  968.  
  969.  
  970.     static int
  971. doaccess()
  972. {
  973.     register struct chaccess *ch;
  974.     register struct access **p, *t;
  975.     register int changed = false;
  976.  
  977.     for (ch = chaccess;  ch;  ch = ch->nextchaccess) {
  978.         switch (ch->command) {
  979.         case erase:
  980.             if (!ch->login) {
  981.                 if (AccessList) {
  982.                 AccessList = 0;
  983.                 changed = true;
  984.                 }
  985.             } else
  986.                 for (p = &AccessList; (t = *p); p = &t->nextaccess)
  987.                 if (strcmp(ch->login, t->login) == 0) {
  988.                     *p = t->nextaccess;
  989.                     changed = true;
  990.                     break;
  991.                 }
  992.             break;
  993.         case append:
  994.             for (p = &AccessList;  ;  p = &t->nextaccess)
  995.                 if (!(t = *p)) {
  996.                     *p = t = ftalloc(struct access);
  997.                     t->login = ch->login;
  998.                     t->nextaccess = 0;
  999.                     changed = true;
  1000.                     break;
  1001.                 } else if (strcmp(ch->login, t->login) == 0)
  1002.                     break;
  1003.             break;
  1004.         }
  1005.     }
  1006.     return changed;
  1007. }
  1008.  
  1009.  
  1010.     static int
  1011. sendmail(Delta, who)
  1012.     char const *Delta, *who;
  1013. /*   Function:  mail to who, informing him that his lock on delta was
  1014.  *   broken by caller. Ask first whether to go ahead. Return false on
  1015.  *   error or if user decides not to break the lock.
  1016.  */
  1017. {
  1018. #ifdef SENDMAIL
  1019.     char const *messagefile;
  1020.     int old1, old2, c, status;
  1021.         FILE    * mailmess;
  1022. #endif
  1023.  
  1024.  
  1025.     aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
  1026.     if (suppress_mail)
  1027.         return true;
  1028.     if (!yesorno(false, "Do you want to break the lock? [ny](n): "))
  1029.         return false;
  1030.  
  1031.         /* go ahead with breaking  */
  1032. #ifdef SENDMAIL
  1033.     messagefile = maketemp(0);
  1034.     if (!(mailmess = fopenSafer(messagefile, "w+"))) {
  1035.         efaterror(messagefile);
  1036.         }
  1037.  
  1038.     aprintf(mailmess, "Subject: Broken lock on %s\n\nYour lock on revision %s of file %s\nhas been broken by %s for the following reason:\n",
  1039.         basefilename(RCSname), Delta, getfullRCSname(), getcaller()
  1040.     );
  1041.     aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr);
  1042.     eflush();
  1043.  
  1044.         old1 = '\n';    old2 = ' ';
  1045.         for (; ;) {
  1046.         c = getcstdin();
  1047.         if (feof(stdin)) {
  1048.         aprintf(mailmess, "%c\n", old1);
  1049.                 break;
  1050.             }
  1051.             else if ( c == '\n' && old1 == '.' && old2 == '\n')
  1052.                 break;
  1053.             else {
  1054.         afputc(old1, mailmess);
  1055.                 old2 = old1;   old1 = c;
  1056.         if (c == '\n') {
  1057.             aputs(">> ", stderr);
  1058.             eflush();
  1059.         }
  1060.             }
  1061.         }
  1062.     Orewind(mailmess);
  1063.     aflush(mailmess);
  1064.     status = run(fileno(mailmess), (char*)0, SENDMAIL, who, (char*)0);
  1065.     Ozclose(&mailmess);
  1066.     if (status == 0)
  1067.         return true;
  1068.     warn("Mail failed.");
  1069. #endif
  1070.     warn("Mail notification of broken locks is not available.");
  1071.     warn("Please tell `%s' why you broke the lock.", who);
  1072.     return(true);
  1073. }
  1074.  
  1075.  
  1076.  
  1077.     static int
  1078. breaklock(delta)
  1079.     struct hshentry const *delta;
  1080. /* function: Finds the lock held by caller on delta,
  1081.  * and removes it.
  1082.  * Sends mail if a lock different from the caller's is broken.
  1083.  * Prints an error message if there is no such lock or error.
  1084.  */
  1085. {
  1086.     register struct rcslock *next, **trail;
  1087.     char const *num;
  1088.  
  1089.     num=delta->num;
  1090.     for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
  1091.         if (strcmp(num, next->delta->num) == 0) {
  1092.             if (
  1093.                 strcmp(getcaller(),next->login) != 0
  1094.                 &&    !sendmail(num, next->login)
  1095.             ) {
  1096.                 rcserror("revision %s still locked by %s",
  1097.                 num, next->login
  1098.                 );
  1099.                 return false;
  1100.             }
  1101.             diagnose("%s unlocked\n", next->delta->num);
  1102.             *trail = next->nextlock;
  1103.             next->delta->lockedby = 0;
  1104.             return true;
  1105.                 }
  1106.     rcserror("no lock set on revision %s", num);
  1107.     return false;
  1108. }
  1109.  
  1110.  
  1111.  
  1112.     static struct hshentry *
  1113. searchcutpt(object, length, store)
  1114.     char const *object;
  1115.     int length;
  1116.     struct hshentries *store;
  1117. /*   Function:  Search store and return entry with number being object. */
  1118. /*        cuttail = 0, if the entry is Head; otherwise, cuttail   */
  1119. /*              is the entry point to the one with number being object  */
  1120.  
  1121. {
  1122.     cuthead = 0;
  1123.     while (compartial(store->first->num, object, length)) {
  1124.         cuthead = store->first;
  1125.         store = store->rest;
  1126.     }
  1127.     return store->first;
  1128. }
  1129.  
  1130.  
  1131.  
  1132.     static int
  1133. branchpoint(strt, tail)
  1134. struct  hshentry        *strt,  *tail;
  1135. /*   Function: check whether the deltas between strt and tail    */
  1136. /*        are locked or branch point, return 1 if any is  */
  1137. /*        locked or branch point; otherwise, return 0 and */
  1138. /*        mark deleted                    */
  1139.  
  1140. {
  1141.         struct  hshentry    *pt;
  1142.     struct rcslock const *lockpt;
  1143.  
  1144.     for (pt = strt;  pt != tail;  pt = pt->next) {
  1145.             if ( pt->branches ){ /*  a branch point  */
  1146.         rcserror("can't remove branch point %s", pt->num);
  1147.         return true;
  1148.             }
  1149.         for (lockpt = Locks;  lockpt;  lockpt = lockpt->nextlock)
  1150.         if (lockpt->delta == pt) {
  1151.             rcserror("can't remove locked revision %s", pt->num);
  1152.             return true;
  1153.         }
  1154.         pt->selector = false;
  1155.         diagnose("deleting revision %s\n",pt->num);
  1156.         }
  1157.     return false;
  1158. }
  1159.  
  1160.  
  1161.  
  1162.     static int
  1163. removerevs()
  1164. /*   Function:  get the revision range to be removed, and place the     */
  1165. /*              first revision removed in delstrt, the revision before  */
  1166. /*        delstrt in cuthead (0, if delstrt is head), and the    */
  1167. /*        revision after the last removed revision in cuttail (0    */
  1168. /*              if the last is a leaf                                   */
  1169.  
  1170. {
  1171.     struct  hshentry *target, *target2, *temp;
  1172.     int length;
  1173.     int cmp;
  1174.  
  1175.     if (!expandsym(delrev.strt, &numrev)) return 0;
  1176.     target = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
  1177.         if ( ! target ) return 0;
  1178.     cmp = cmpnum(target->num, numrev.string);
  1179.     length = countnumflds(numrev.string);
  1180.  
  1181.     if (delrev.code == 0) {  /*  -o  rev    or    -o  branch   */
  1182.         if (length & 1)
  1183.         temp=searchcutpt(target->num,length+1,gendeltas);
  1184.         else if (cmp) {
  1185.         rcserror("Revision %s doesn't exist.", numrev.string);
  1186.         return 0;
  1187.         }
  1188.         else
  1189.         temp = searchcutpt(numrev.string, length, gendeltas);
  1190.         cuttail = target->next;
  1191.             if ( branchpoint(temp, cuttail) ) {
  1192.         cuttail = 0;
  1193.                 return 0;
  1194.             }
  1195.             delstrt = temp;     /* first revision to be removed   */
  1196.             return 1;
  1197.         }
  1198.  
  1199.     if (length & 1) {   /*  invalid branch after -o   */
  1200.         rcserror("invalid branch range %s after -o", numrev.string);
  1201.             return 0;
  1202.         }
  1203.  
  1204.     if (delrev.code == 1) {  /*  -o  -rev   */
  1205.             if ( length > 2 ) {
  1206.                 temp = searchcutpt( target->num, length-1, gendeltas);
  1207.                 cuttail = target->next;
  1208.             }
  1209.             else {
  1210.                 temp = searchcutpt(target->num, length, gendeltas);
  1211.                 cuttail = target;
  1212.                 while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
  1213.                     cuttail = cuttail->next;
  1214.             }
  1215.             if ( branchpoint(temp, cuttail) ){
  1216.         cuttail = 0;
  1217.                 return 0;
  1218.             }
  1219.             delstrt = temp;
  1220.             return 1;
  1221.         }
  1222.  
  1223.     if (delrev.code == 2) {   /*  -o  rev-   */
  1224.             if ( length == 2 ) {
  1225.                 temp = searchcutpt(target->num, 1,gendeltas);
  1226.         if (cmp)
  1227.                     cuttail = target;
  1228.                 else
  1229.                     cuttail = target->next;
  1230.             }
  1231.             else  {
  1232.         if (cmp) {
  1233.                     cuthead = target;
  1234.                     if ( !(temp = target->next) ) return 0;
  1235.                 }
  1236.                 else
  1237.                     temp = searchcutpt(target->num, length, gendeltas);
  1238.         getbranchno(temp->num, &numrev);  /* get branch number */
  1239.         VOID genrevs(numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas);
  1240.             }
  1241.             if ( branchpoint( temp, cuttail ) ) {
  1242.         cuttail = 0;
  1243.                 return 0;
  1244.             }
  1245.             delstrt = temp;
  1246.             return 1;
  1247.         }
  1248.  
  1249.         /*   -o   rev1-rev2   */
  1250.     if (!expandsym(delrev.end, &numrev)) return 0;
  1251.     if (
  1252.         length != countnumflds(numrev.string)
  1253.         ||    (length>2 && compartial(numrev.string, target->num, length-1))
  1254.     ) {
  1255.         rcserror("invalid revision range %s-%s",
  1256.         target->num, numrev.string
  1257.         );
  1258.             return 0;
  1259.         }
  1260.  
  1261.     target2 = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
  1262.         if ( ! target2 ) return 0;
  1263.  
  1264.         if ( length > 2) {  /* delete revisions on branches  */
  1265.             if ( cmpnum(target->num, target2->num) > 0) {
  1266.         cmp = cmpnum(target2->num, numrev.string);
  1267.                 temp = target;
  1268.                 target = target2;
  1269.                 target2 = temp;
  1270.             }
  1271.         if (cmp) {
  1272.                 if ( ! cmpnum(target->num, target2->num) ) {
  1273.             rcserror("Revisions %s-%s don't exist.",
  1274.             delrev.strt, delrev.end
  1275.             );
  1276.                     return 0;
  1277.                 }
  1278.                 cuthead = target;
  1279.                 temp = target->next;
  1280.             }
  1281.             else
  1282.                 temp = searchcutpt(target->num, length, gendeltas);
  1283.             cuttail = target2->next;
  1284.         }
  1285.         else { /*  delete revisions on trunk  */
  1286.             if ( cmpnum( target->num, target2->num) < 0 ) {
  1287.                 temp = target;
  1288.                 target = target2;
  1289.                 target2 = temp;
  1290.             }
  1291.             else
  1292.         cmp = cmpnum(target2->num, numrev.string);
  1293.         if (cmp) {
  1294.                 if ( ! cmpnum(target->num, target2->num) ) {
  1295.             rcserror("Revisions %s-%s don't exist.",
  1296.             delrev.strt, delrev.end
  1297.             );
  1298.                     return 0;
  1299.                 }
  1300.                 cuttail = target2;
  1301.             }
  1302.             else
  1303.                 cuttail = target2->next;
  1304.             temp = searchcutpt(target->num, length, gendeltas);
  1305.         }
  1306.         if ( branchpoint(temp, cuttail) )  {
  1307.         cuttail = 0;
  1308.             return 0;
  1309.         }
  1310.         delstrt = temp;
  1311.         return 1;
  1312. }
  1313.  
  1314.  
  1315.  
  1316.     static int
  1317. doassoc()
  1318. /* Add or delete (if !revno) association that is stored in assoclst.  */
  1319. {
  1320.     char const *p;
  1321.     int changed = false;
  1322.     struct Symrev const *curassoc;
  1323.     struct assoc **pre, *pt;
  1324.  
  1325.         /*  add new associations   */
  1326.     for (curassoc = assoclst;  curassoc;  curassoc = curassoc->nextsym) {
  1327.         char const *ssymbol = curassoc->ssymbol;
  1328.  
  1329.         if (!curassoc->revno) {  /* delete symbol  */
  1330.         for (pre = &Symbols;  ;  pre = &pt->nextassoc)
  1331.             if (!(pt = *pre)) {
  1332.             rcswarn("can't delete nonexisting symbol %s", ssymbol);
  1333.             break;
  1334.             } else if (strcmp(pt->symbol, ssymbol) == 0) {
  1335.             *pre = pt->nextassoc;
  1336.             changed = true;
  1337.             break;
  1338.             }
  1339.         }
  1340.         else {
  1341.         if (curassoc->revno[0]) {
  1342.             p = 0;
  1343.             if (expandsym(curassoc->revno, &numrev))
  1344.             p = fstr_save(numrev.string);
  1345.         } else if (!(p = tiprev()))
  1346.             rcserror("no latest revision to associate with symbol %s",
  1347.                 ssymbol
  1348.             );
  1349.         if (p)
  1350.             changed |= addsymbol(p, ssymbol, curassoc->override);
  1351.         }
  1352.         }
  1353.     return changed;
  1354. }
  1355.  
  1356.  
  1357.  
  1358.     static int
  1359. dolocks()
  1360. /* Function: remove lock for caller or first lock if unlockcaller is set;
  1361.  *           remove locks which are stored in rmvlocklst,
  1362.  *           add new locks which are stored in newlocklst,
  1363.  *           add lock for Dbranch or Head if lockhead is set.
  1364.  */
  1365. {
  1366.     struct Lockrev const *lockpt;
  1367.     struct hshentry *target;
  1368.     int changed = false;
  1369.  
  1370.     if (unlockcaller) { /*  find lock for caller  */
  1371.             if ( Head ) {
  1372.         if (Locks) {
  1373.             switch (findlock(true, &target)) {
  1374.               case 0:
  1375.             /* remove most recent lock */
  1376.             changed |= breaklock(Locks->delta);
  1377.             break;
  1378.               case 1:
  1379.             diagnose("%s unlocked\n",target->num);
  1380.             changed = true;
  1381.             break;
  1382.             }
  1383.         } else {
  1384.             rcswarn("No locks are set.");
  1385.         }
  1386.             } else {
  1387.         rcswarn("can't unlock an empty tree");
  1388.             }
  1389.         }
  1390.  
  1391.         /*  remove locks which are stored in rmvlocklst   */
  1392.     for (lockpt = rmvlocklst;  lockpt;  lockpt = lockpt->nextrev)
  1393.         if (expandsym(lockpt->revno, &numrev)) {
  1394.         target = genrevs(numrev.string, (char *)0, (char *)0, (char *)0, &gendeltas);
  1395.                 if ( target )
  1396.            if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
  1397.             rcserror("can't unlock nonexisting revision %s",
  1398.                 lockpt->revno
  1399.             );
  1400.                    else
  1401.             changed |= breaklock(target);
  1402.                         /* breaklock does its own diagnose */
  1403.             }
  1404.  
  1405.         /*  add new locks which stored in newlocklst  */
  1406.     for (lockpt = newlocklst;  lockpt;  lockpt = lockpt->nextrev)
  1407.         changed |= setlock(lockpt->revno);
  1408.  
  1409.     if (lockhead) /*  lock default branch or head  */
  1410.         if (Dbranch)
  1411.         changed |= setlock(Dbranch);
  1412.         else if (Head)
  1413.         changed |= setlock(Head->num);
  1414.         else
  1415.         rcswarn("can't lock an empty tree");
  1416.     return changed;
  1417. }
  1418.  
  1419.  
  1420.  
  1421.     static int
  1422. setlock(rev)
  1423.     char const *rev;
  1424. /* Function: Given a revision or branch number, finds the corresponding
  1425.  * delta and locks it for caller.
  1426.  */
  1427. {
  1428.         struct  hshentry *target;
  1429.     int r;
  1430.  
  1431.     if (expandsym(rev, &numrev)) {
  1432.         target = genrevs(numrev.string, (char*)0, (char*)0,
  1433.                  (char*)0, &gendeltas);
  1434.             if ( target )
  1435.            if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
  1436.             rcserror("can't lock nonexisting revision %s",
  1437.             numrev.string
  1438.             );
  1439.            else {
  1440.             if ((r = addlock(target, false)) < 0  &&  breaklock(target))
  1441.             r = addlock(target, true);
  1442.             if (0 <= r) {
  1443.             if (r)
  1444.                 diagnose("%s locked\n", target->num);
  1445.             return r;
  1446.             }
  1447.            }
  1448.     }
  1449.     return 0;
  1450. }
  1451.  
  1452.  
  1453.     static int
  1454. domessages()
  1455. {
  1456.     struct hshentry *target;
  1457.     struct Message *p;
  1458.     int changed = false;
  1459.  
  1460.     for (p = messagelst;  p;  p = p->nextmessage)
  1461.         if (
  1462.         expandsym(p->revno, &numrev)  &&
  1463.         (target = genrevs(
  1464.             numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas
  1465.         ))
  1466.         ) {
  1467.         /*
  1468.          * We can't check the old log -- it's much later in the file.
  1469.          * We pessimistically assume that it changed.
  1470.          */
  1471.         target->log = p->message;
  1472.         changed = true;
  1473.         }
  1474.     return changed;
  1475. }
  1476.  
  1477.  
  1478.     static int
  1479. rcs_setstate(rev,status)
  1480.     char const *rev, *status;
  1481. /* Function: Given a revision or branch number, finds the corresponding delta
  1482.  * and sets its state to status.
  1483.  */
  1484. {
  1485.         struct  hshentry *target;
  1486.  
  1487.     if (expandsym(rev, &numrev)) {
  1488.         target = genrevs(numrev.string, (char*)0, (char*)0,
  1489.                  (char*)0, &gendeltas);
  1490.             if ( target )
  1491.            if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
  1492.             rcserror("can't set state of nonexisting revision %s",
  1493.             numrev.string
  1494.             );
  1495.            else if (strcmp(target->state, status) != 0) {
  1496.                     target->state = status;
  1497.             return true;
  1498.            }
  1499.     }
  1500.     return false;
  1501. }
  1502.  
  1503.  
  1504.  
  1505.  
  1506.  
  1507.     static int
  1508. buildeltatext(deltas)
  1509.     struct hshentries const *deltas;
  1510. /*   Function:  put the delta text on frewrite and make necessary   */
  1511. /*              change to delta text                                */
  1512. {
  1513.     register FILE *fcut;    /* temporary file to rebuild delta tree */
  1514.     char const *cutname;
  1515.  
  1516.     fcut = 0;
  1517.     cuttail->selector = false;
  1518.     scanlogtext(deltas->first, false);
  1519.         if ( cuthead )  {
  1520.         cutname = maketemp(3);
  1521.         if (!(fcut = fopenSafer(cutname, FOPEN_WPLUS_WORK))) {
  1522.         efaterror(cutname);
  1523.             }
  1524.  
  1525.         while (deltas->first != cuthead) {
  1526.         deltas = deltas->rest;
  1527.         scanlogtext(deltas->first, true);
  1528.             }
  1529.  
  1530.         snapshotedit(fcut);
  1531.         Orewind(fcut);
  1532.         aflush(fcut);
  1533.         }
  1534.  
  1535.     while (deltas->first != cuttail)
  1536.         scanlogtext((deltas = deltas->rest)->first, true);
  1537.     finishedit((struct hshentry*)0, (FILE*)0, true);
  1538.     Ozclose(&fcopy);
  1539.  
  1540.     if (fcut) {
  1541.         char const *diffname = maketemp(0);
  1542.         char const *diffv[6 + !!OPEN_O_BINARY];
  1543.         char const **diffp = diffv;
  1544.         *++diffp = DIFF;
  1545.         *++diffp = DIFFFLAGS;
  1546. #        if OPEN_O_BINARY
  1547.         if (Expand == BINARY_EXPAND)
  1548.             *++diffp = "--binary";
  1549. #        endif
  1550.         *++diffp = "-";
  1551.         *++diffp = resultname;
  1552.         *++diffp = 0;
  1553.         switch (runv(fileno(fcut), diffname, diffv)) {
  1554.         case DIFF_FAILURE: case DIFF_SUCCESS: break;
  1555.         default: rcsfaterror("diff failed");
  1556.         }
  1557.         Ofclose(fcut);
  1558.         return putdtext(cuttail,diffname,frewrite,true);
  1559.     } else
  1560.         return putdtext(cuttail,resultname,frewrite,false);
  1561. }
  1562.  
  1563.  
  1564.  
  1565.     static void
  1566. buildtree()
  1567. /*   Function:  actually removes revisions whose selector field  */
  1568. /*        is false, and rebuilds the linkage of deltas.     */
  1569. /*              asks for reconfirmation if deleting last revision*/
  1570. {
  1571.     struct    hshentry   * Delta;
  1572.         struct  branchhead      *pt, *pre;
  1573.  
  1574.         if ( cuthead )
  1575.            if ( cuthead->next == delstrt )
  1576.                 cuthead->next = cuttail;
  1577.            else {
  1578.                 pre = pt = cuthead->branches;
  1579.                 while( pt && pt->hsh != delstrt )  {
  1580.                     pre = pt;
  1581.                     pt = pt->nextbranch;
  1582.                 }
  1583.                 if ( cuttail )
  1584.                     pt->hsh = cuttail;
  1585.                 else if ( pt == pre )
  1586.                     cuthead->branches = pt->nextbranch;
  1587.                 else
  1588.                     pre->nextbranch = pt->nextbranch;
  1589.             }
  1590.     else {
  1591.         if (!cuttail && !quietflag) {
  1592.         if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) {
  1593.             rcserror("No revision deleted");
  1594.             Delta = delstrt;
  1595.             while( Delta) {
  1596.             Delta->selector = true;
  1597.             Delta = Delta->next;
  1598.             }
  1599.             return;
  1600.         }
  1601.         }
  1602.             Head = cuttail;
  1603.     }
  1604.         return;
  1605. }
  1606.  
  1607. #if RCS_lint
  1608. /* This lets us lint everything all at once. */
  1609.  
  1610. char const cmdid[] = "";
  1611.  
  1612. #define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();}
  1613.  
  1614.     int
  1615. main(argc, argv)
  1616.     int argc;
  1617.     char **argv;
  1618. {
  1619.     go(ciId,    ciExit);
  1620.     go(coId,    coExit);
  1621.     go(identId,    identExit);
  1622.     go(mergeId,    mergeExit);
  1623.     go(rcsId,    exiterr);
  1624.     go(rcscleanId,    rcscleanExit);
  1625.     go(rcsdiffId,    rdiffExit);
  1626.     go(rcsmergeId,    rmergeExit);
  1627.     go(rlogId,    rlogExit);
  1628.     return 0;
  1629. }
  1630. #endif
  1631.