home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume24 / rcs / part03 < prev    next >
Encoding:
Internet Message Format  |  1991-03-05  |  53.5 KB

  1. Subject:  v24i003:  RCS source control system, Part03/12
  2. Newsgroups: comp.sources.unix
  3. Approved: rsalz@uunet.UU.NET
  4. X-Checksum-Snefru: db32cfc0 fb888ade 0a683c96 f996a50b
  5.  
  6. Submitted-by: Adam Hammer <hammer@cs.purdue.edu>
  7. Posting-number: Volume 24, Issue 3
  8. Archive-name: rcs/part03
  9.  
  10. #! /bin/sh
  11. # This is a shell archive.  Remove anything before this line, then feed it
  12. # into a shell via "sh file" or similar.  To overwrite existing files,
  13. # type "sh file -c".
  14. # The tool that generated this appeared in the comp.sources.unix newsgroup;
  15. # send mail to comp-sources-unix@uunet.uu.net if you want that tool.
  16. # Contents:  src/Makefile src/rcs.c
  17. # Wrapped by rsalz@litchi.bbn.com on Thu Feb 21 14:36:55 1991
  18. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  19. echo If this archive is complete, you will see the following message:
  20. echo '          "shar: End of archive 3 (of 12)."'
  21. if test -f 'src/Makefile' -a "${1}" != "-c" ; then 
  22.   echo shar: Will not clobber existing file \"'src/Makefile'\"
  23. else
  24.   echo shar: Extracting \"'src/Makefile'\" \(8655 characters\)
  25.   sed "s/^X//" >'src/Makefile' <<'END_OF_FILE'
  26. X# $Id: Makefile,v 5.8 1990/12/13 06:54:06 eggert Exp $
  27. X# Copyright (C) 1982, 1988, 1989 Walter Tichy
  28. X#   Copyright 1990 by Paul Eggert
  29. X#   Distributed under license by the Free Software Foundation, Inc.
  30. X#
  31. X# This file is part of RCS.
  32. X#
  33. X# RCS is free software; you can redistribute it and/or modify
  34. X# it under the terms of the GNU General Public License as published by
  35. X# the Free Software Foundation; either version 1, or (at your option)
  36. X# any later version.
  37. X#
  38. X# RCS is distributed in the hope that it will be useful,
  39. X# but WITHOUT ANY WARRANTY; without even the implied warranty of
  40. X# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  41. X# GNU General Public License for more details.
  42. X#
  43. X# You should have received a copy of the GNU General Public License
  44. X# along with RCS; see the file COPYING.  If not, write to
  45. X# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  46. X#
  47. X# Report problems and direct all questions to:
  48. X#
  49. X#    rcs-bugs@cs.purdue.edu
  50. X#
  51. X#               INSTRUCTIONS
  52. X#               ============
  53. X
  54. X
  55. X#    Figure out where to put the RCS commands; define RCSDIR accordingly.
  56. X
  57. XRCSDIR = /usr/local/bin
  58. X
  59. X
  60. X#    Define INSTALL_SETID_FLAGS as needed to install RCS setgid or setuid.
  61. X#    This makes sense only when setegid() and seteuid() work
  62. X#    Setgid is better than setuid because it mixes with nonstrict locking.
  63. X#INSTALL_SETID_FLAGS = ${INSTALL_NORMAL_FLAGS}
  64. X#INSTALL_SETID_FLAGS = -g rcs -o root -m 2555
  65. X INSTALL_SETID_FLAGS = ${INSTALL_NORMAL_FLAGS}
  66. X
  67. X
  68. X#    Define RCSPREFIX to be empty if you want RCS to search the PATH for
  69. X#    subsidiary RCS commands like co.  This lets you move RCS commands
  70. X#    after building them, and permits multiple instances of setgid RCS
  71. X#    commands on the same host for different groups.
  72. X#
  73. X#    Define RCSPREFIX to a path followed by / if you want RCS to look in
  74. X#    just one place.  This makes execution faster.  Also, if your host's
  75. X#    execvp() system call does not understand the BSD #!/bin/sh convention
  76. X#    for starting shell files, you must use a nonempty RCSPREFIX, because
  77. X#    in this case rcsmerge invokes `/bin/sh ${RCSPREFIX}merge'.
  78. X
  79. X#RCSPREFIX =
  80. X#RCSPREFIX = ${RCSDIR}/
  81. X RCSPREFIX = ${RCSDIR}/
  82. X
  83. X#    Define DIFF and DIFF3 to be the name of your diff and diff3 programs.
  84. X#    DIFF must be an absolute path name if setgid or setuid is used.
  85. X#    Define DIFF_FLAGS to be diff's options for RCS format output.
  86. X#    If available, use the -a option for comparing arbitrary files.
  87. X#    Define DIFF_L to be 1 if your diff understands GNU diff's -L option.
  88. X#    Set DIFF3_TYPE=lib for traditional diff, =bin otherwise.
  89. X#    If DIFF3_type=bin, make sure your diff3 understands -a, -L, and and -m.
  90. X#    If DIFF3_type=lib, avoid the diff3 program visible to users, and
  91. X#    use the one in /usr/lib instead; it may be called /usr/lib/diff3prog.
  92. X
  93. X# Traditional diff
  94. X#DIFF = /bin/diff
  95. X#DIFF_FLAGS = -n
  96. X#DIFF_L = 0
  97. X#DIFF3 = /usr/lib/diff3
  98. X#DIFF3_TYPE = lib
  99. X
  100. X# GNU diff -- must be version 1.15 or later
  101. X#DIFFPREFIX = ${RCSDIR}/
  102. X#DIFF = ${DIFFPREFIX}diff
  103. X#DIFF_FLAGS = -an
  104. X#DIFF_L = 1
  105. X#DIFF3 = ${DIFF}3
  106. X#DIFF3_TYPE = bin
  107. X
  108. X DIFF = /bin/diff
  109. X DIFF_FLAGS = -n
  110. X DIFF_L = 0
  111. X DIFF3 = /usr/lib/diff3
  112. X DIFF3_TYPE = lib
  113. X
  114. X
  115. X#    Set SENDMAIL to be a comma-separated list of strings that are a command
  116. X#    to send mail.  The first string should be an absolute pathname.
  117. X#    The name of the addressee will be appended as a separate argument,
  118. X#    and the standard input will be the message (first line "Subject: xxxx",
  119. X#    second line empty).
  120. X
  121. X#SENDMAIL = "/bin/mail"
  122. X#SENDMAIL = "/etc/delivermail", "-w"
  123. X#SENDMAIL = "/usr/lib/sendmail"
  124. X SENDMAIL = "/bin/mail"
  125. X
  126. X
  127. X#    Decide what loader libraries you need.
  128. X#    Some older hosts need -lBSD, -ljobs, or -lPW.
  129. X
  130. XLDLIBS =
  131. X
  132. X
  133. X#    Decide what C compiler flags you need.
  134. X
  135. X# Optimize.  Put in options that work well for your compiler.
  136. X# Options to try with GCC include -fdelayed-branch, -finline-functions,
  137. X# -fomit-frame-pointer, and -fstrength-reduce.
  138. XCC_O = -O
  139. X
  140. X# Make all initialized data read-only (not just string literals).
  141. X# This option can improve performance by making initialized data shared.
  142. X# It's not worth worrying about if your compiler supports the `const' keyword.
  143. X# 4.3BSD-based compilers
  144. X#CC_R = -R
  145. X# most other compilers
  146. X#CC_R =
  147. X CC_R =
  148. X
  149. X# Add this for SunOS 4.1 + GCC 1.37.1.
  150. X#COMPILE.c = ${CC} ${CFLAGS} ${CPPFLAGS} -c
  151. X
  152. X
  153. X# for GCC
  154. X#CC = gcc
  155. X#CC_W = -Wall -Wcast-qual -Wpointer-arith -Wshadow -Wwrite-strings
  156. X#CFLAGS = ${CC_O} ${CC_R} ${CC_W}
  157. X
  158. X# for traditional C compilers
  159. X#CC = cc
  160. X#CFLAGS = ${CC_O} ${CC_R}
  161. X
  162. X CC = cc
  163. X CFLAGS = ${CC_O} ${CC_R}
  164. X
  165. X
  166. XLINT = lint
  167. X
  168. X# For traditional and BSD lint, use
  169. X#LINTFLAGS = -abchx
  170. X# For USG lint, use
  171. X#LINTFLAGS =
  172. X LINTFLAGS = -abchx
  173. X
  174. X
  175. X#    If you have version 2 RCS files around, define COMPAT2 to be 1.
  176. X#    (Version 2 became obsolete in 1982.)  This assures that version 2
  177. X#    RCS files can still be read.  After all version 2 RCS files have
  178. X#    been updated with later versions of ci or rcs, you can remake RCS with
  179. X#    COMPAT2=0.
  180. XCOMPAT2 = 0
  181. X#    When you have RCS installed, rename old version 2 RCS files as follows
  182. X#    (if you have any).  If the working file was "f.c" and the RCS file
  183. X#    "f.c.v", rename the RCS file to "f.c,v". If the working file was "f.c"
  184. X#    and the RCS file "f.v", rename the RCS file "f.c,v". Thus, suffixes
  185. X#    are no longer dropped and RCS files end in ",v" rather than ".v".
  186. X
  187. X
  188. X#    Now you are ready.  Try to make "conf.h".
  189. X#    Check the resulting conf.h for plausibility.
  190. X#    If it's wrong, there is a bug in conf.sh; please report it.
  191. X#    You can patch conf.h if you're in a hurry, but it's better to fix it;
  192. X#    look at a.h and conf.error for ideas.
  193. X#    If all else fails, copy conf.heg to conf.h and edit it by hand.
  194. X
  195. X#    Make "all".
  196. X#    If all went well, make "install".
  197. X#    If installation succeeds, make "installtest";
  198. X#    if this fails, make "installdebug" for detailed info.
  199. X
  200. X#    If you want to maintain RCS with itself, be sure you preserve the
  201. X#    original revision numbers, dates, etc. by checking the
  202. X#    files in with the -k option.
  203. X
  204. X# Avoid brain damage in some versions of 'make'.
  205. XSHELL = /bin/sh
  206. X
  207. X# binary commands
  208. XBCOMMANDS   =   ci ident rcs rcsdiff rcsmerge rlog co
  209. X
  210. X# all commands
  211. XRCSCOMMANDS = merge ${BCOMMANDS}
  212. X
  213. Xall :: ${RCSCOMMANDS}
  214. X
  215. XINSTALL = install -c
  216. XINSTALL_NORMAL_FLAGS = -g staff -m 775
  217. X
  218. Xinstall :: all
  219. X    ${INSTALL} ${INSTALL_SETID_FLAGS} ci ${RCSDIR}
  220. X    ${INSTALL} ${INSTALL_SETID_FLAGS} co ${RCSDIR}
  221. X    ${INSTALL} ${INSTALL_SETID_FLAGS} rcsdiff ${RCSDIR}
  222. X    ${INSTALL} ${INSTALL_SETID_FLAGS} rcsmerge ${RCSDIR}
  223. X    ${INSTALL} ${INSTALL_SETID_FLAGS} rlog ${RCSDIR}
  224. X    ${INSTALL} ${INSTALL_NORMAL_FLAGS} ident ${RCSDIR}
  225. X    ${INSTALL} ${INSTALL_NORMAL_FLAGS} rcs ${RCSDIR}
  226. X    ${INSTALL} ${INSTALL_NORMAL_FLAGS} merge ${RCSDIR}
  227. X
  228. Xinstalltest ::
  229. X    sh rcstest
  230. X
  231. Xinstalldebug ::
  232. X    sh rcstest -v
  233. X
  234. Xclean ::
  235. X    rm -f a.* *.o conf.h conf.error ${RCSCOMMANDS}
  236. X
  237. Xconf.h : conf.sh # Makefile
  238. X    C='${CC} ${CFLAGS}' \
  239. X    COMPAT2='${COMPAT2}' \
  240. X    DIFF='${DIFF}' \
  241. X    DIFF_L='${DIFF_L}' \
  242. X    DIFF_FLAGS='${DIFF_FLAGS}' \
  243. X    RCSPREFIX='${RCSPREFIX}' \
  244. X    SENDMAIL='${SENDMAIL}' \
  245. X    L='${LDLIBS}' \
  246. X    sh -x conf.sh 2>conf.error
  247. X    mv a.h $@
  248. X    rm -f a.*
  249. X
  250. XCIFILES = ci.o rcslex.o rcssyn.o rcsgen.o rcsedit.o rcskeys.o rcsmap.o \
  251. X    rcsrev.o rcsutil.o rcsfnms.o partime.o maketime.o rcskeep.o rcsfcmp.o
  252. Xci : ${CIFILES}
  253. X    ${CC} ${CFLAGS} ${CIFILES} ${LDLIBS} -o $@
  254. X
  255. XCOFILES = co.o rcslex.o rcssyn.o rcsgen.o rcsedit.o rcskeys.o rcsmap.o \
  256. X    rcsrev.o rcsutil.o rcsfnms.o partime.o maketime.o
  257. Xco : ${COFILES}
  258. X    ${CC} ${CFLAGS} ${COFILES} ${LDLIBS} -o $@
  259. X
  260. Xident : ident.o rcsmap.o
  261. X    ${CC} ${CFLAGS} ident.o rcsmap.o ${LDLIBS} -o $@
  262. X
  263. Xmerge : merge.sh
  264. X    DIFF=${DIFF} DIFF3=${DIFF3} DIFF3_TYPE=${DIFF3_TYPE} sh $@.sh >$@.o
  265. X    chmod +x $@.o
  266. X    mv $@.o $@
  267. X
  268. XRLOG = rlog.o rcslex.o rcsmap.o rcssyn.o rcsrev.o rcsutil.o partime.o \
  269. X    maketime.o rcsfnms.o
  270. Xrlog : ${RLOG}
  271. X    ${CC} ${CFLAGS} ${RLOG} ${LDLIBS} -o $@
  272. X
  273. XRCS = rcs.o rcslex.o rcssyn.o rcsrev.o rcsutil.o rcsgen.o rcsedit.o rcskeys.o \
  274. X    rcsmap.o rcsfnms.o
  275. Xrcs : ${RCS}
  276. X    ${CC} ${CFLAGS} ${RCS} ${LDLIBS} -o $@
  277. X
  278. XRCSDIFF = rcsdiff.o rcsutil.o rcsfnms.o rcsmap.o rcsrev.o rcssyn.o rcslex.o \
  279. X    maketime.o partime.o
  280. Xrcsdiff : ${RCSDIFF}
  281. X    ${CC} ${CFLAGS} ${RCSDIFF} ${LDLIBS} -o $@
  282. X
  283. XRCSMERGE = rcsmerge.o rcsutil.o rcsfnms.o rcsmap.o rcsrev.o rcssyn.o rcslex.o
  284. Xrcsmerge : ${RCSMERGE}
  285. X    ${CC} ${CFLAGS} ${RCSMERGE} ${LDLIBS} -o $@
  286. X
  287. XSOURCE=    ci.c co.c ident.c maketime.c partime.c rcs.c \
  288. X    rcsdiff.c rcsedit.c rcsfcmp.c rcsfnms.c rcsgen.c \
  289. X    rcskeep.c rcskeys.c rcslex.c rcsmap.c rcsmerge.c rcsrev.c rcssyn.c \
  290. X    rcsutil.c rlog.c
  291. XOBJECT=    ci.o co.o ident.o maketime.o partime.o rcs.o \
  292. X    rcsdiff.o rcsedit.o rcsfcmp.o rcsfnms.o rcsgen.o \
  293. X    rcskeep.o rcskeys.o rcslex.o rcsmap.o rcsmerge.o rcsrev.o rcssyn.o \
  294. X    rcsutil.o rlog.o
  295. X
  296. Xlint : conf.h
  297. X    ${LINT} ${LINTFLAGS} -Dlint=1 ${SOURCE}
  298. X
  299. X${OBJECT} : conf.h rcsbase.h
  300. END_OF_FILE
  301.   if test 8655 -ne `wc -c <'src/Makefile'`; then
  302.     echo shar: \"'src/Makefile'\" unpacked with wrong size!
  303.   fi
  304.   # end of 'src/Makefile'
  305. fi
  306. if test -f 'src/rcs.c' -a "${1}" != "-c" ; then 
  307.   echo shar: Will not clobber existing file \"'src/rcs.c'\"
  308. else
  309.   echo shar: Extracting \"'src/rcs.c'\" \(42394 characters\)
  310.   sed "s/^X//" >'src/rcs.c' <<'END_OF_FILE'
  311. X/*
  312. X *                      RCS create/change operation
  313. X */
  314. X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
  315. X   Copyright 1990 by Paul Eggert
  316. X   Distributed under license by the Free Software Foundation, Inc.
  317. X
  318. XThis file is part of RCS.
  319. X
  320. XRCS is free software; you can redistribute it and/or modify
  321. Xit under the terms of the GNU General Public License as published by
  322. Xthe Free Software Foundation; either version 1, or (at your option)
  323. Xany later version.
  324. X
  325. XRCS is distributed in the hope that it will be useful,
  326. Xbut WITHOUT ANY WARRANTY; without even the implied warranty of
  327. XMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  328. XGNU General Public License for more details.
  329. X
  330. XYou should have received a copy of the GNU General Public License
  331. Xalong with RCS; see the file COPYING.  If not, write to
  332. Xthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  333. X
  334. XReport problems and direct all questions to:
  335. X
  336. X    rcs-bugs@cs.purdue.edu
  337. X
  338. X*/
  339. X
  340. X
  341. X
  342. X
  343. X/* $Log: rcs.c,v $
  344. X * Revision 5.7  1990/12/18  17:19:21  eggert
  345. X * Fix bug with multiple -n and -N options.
  346. X *
  347. X * Revision 5.6  1990/12/04  05:18:40  eggert
  348. X * Use -I for prompts and -q for diagnostics.
  349. X *
  350. X * Revision 5.5  1990/11/11  00:06:35  eggert
  351. X * Fix `rcs -e' core dump.
  352. X *
  353. X * Revision 5.4  1990/11/01  05:03:33  eggert
  354. X * Add -I and new -t behavior.  Permit arbitrary data in logs.
  355. X *
  356. X * Revision 5.3  1990/10/04  06:30:16  eggert
  357. X * Accumulate exit status across files.
  358. X *
  359. X * Revision 5.2  1990/09/04  08:02:17  eggert
  360. X * Standardize yes-or-no procedure.
  361. X *
  362. X * Revision 5.1  1990/08/29  07:13:51  eggert
  363. X * Remove unused setuid support.  Clean old log messages too.
  364. X *
  365. X * Revision 5.0  1990/08/22  08:12:42  eggert
  366. X * Don't lose names when applying -a option to multiple files.
  367. X * Remove compile-time limits; use malloc instead.  Add setuid support.
  368. X * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
  369. X * Ansify and Posixate.  Add -V.  Fix umask bug.  Make linting easier.  Tune.
  370. X * Yield proper exit status.  Check diff's output.
  371. X *
  372. X * Revision 4.11  89/05/01  15:12:06  narten
  373. X * changed copyright header to reflect current distribution rules
  374. X * 
  375. X * Revision 4.10  88/11/08  16:01:54  narten
  376. X * didn't install previous patch correctly
  377. X * 
  378. X * Revision 4.9  88/11/08  13:56:01  narten
  379. X * removed include <sysexits.h> (not needed)
  380. X * minor fix for -A option
  381. X * 
  382. X * Revision 4.8  88/08/09  19:12:27  eggert
  383. X * Don't access freed storage.
  384. X * Use execv(), not system(); yield proper exit status; remove lint.
  385. X * 
  386. X * Revision 4.7  87/12/18  11:37:17  narten
  387. X * lint cleanups (Guy Harris)
  388. X * 
  389. X * Revision 4.6  87/10/18  10:28:48  narten
  390. X * Updating verison numbers. Changes relative to 1.1 are actually 
  391. X * relative to 4.3
  392. X * 
  393. X * Revision 1.4  87/09/24  13:58:52  narten
  394. X * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
  395. X * warnings)
  396. X * 
  397. X * Revision 1.3  87/03/27  14:21:55  jenkins
  398. X * Port to suns
  399. X * 
  400. X * Revision 1.2  85/12/17  13:59:09  albitz
  401. X * Changed setstate to rcs_setstate because of conflict with random.o.
  402. X * 
  403. X * Revision 4.3  83/12/15  12:27:33  wft
  404. X * rcs -u now breaks most recent lock if it can't find a lock by the caller.
  405. X * 
  406. X * Revision 4.2  83/12/05  10:18:20  wft
  407. X * Added conditional compilation for sending mail.
  408. X * Alternatives: V4_2BSD, V6, USG, and other.
  409. X * 
  410. X * Revision 4.1  83/05/10  16:43:02  wft
  411. X * Simplified breaklock(); added calls to findlock() and getcaller().
  412. X * Added option -b (default branch). Updated -s and -w for -b.
  413. X * Removed calls to stat(); now done by pairfilenames().
  414. X * Replaced most catchints() calls with restoreints().
  415. X * Removed check for exit status of delivermail().
  416. X * Directed all interactive output to stderr.
  417. X * 
  418. X * Revision 3.9.1.1  83/12/02  22:08:51  wft
  419. X * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
  420. X * 
  421. X * Revision 3.9  83/02/15  15:38:39  wft
  422. X * Added call to fastcopy() to copy remainder of RCS file.
  423. X *
  424. X * Revision 3.8  83/01/18  17:37:51  wft
  425. X * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
  426. X *
  427. X * Revision 3.7  83/01/15  18:04:25  wft
  428. X * Removed putree(); replaced with puttree() in rcssyn.c.
  429. X * Combined putdellog() and scanlogtext(); deleted putdellog().
  430. X * Cleaned up diagnostics and error messages. Fixed problem with
  431. X * mutilated files in case of deletions in 2 files in a single command.
  432. X * Changed marking of selector from 'D' to DELETE.
  433. X *
  434. X * Revision 3.6  83/01/14  15:37:31  wft
  435. X * Added ignoring of interrupts while new RCS file is renamed;
  436. X * Avoids deletion of RCS files by interrupts.
  437. X *
  438. X * Revision 3.5  82/12/10  21:11:39  wft
  439. X * Removed unused variables, fixed checking of return code from diff,
  440. X * introduced variant COMPAT2 for skipping Suffix on -A files.
  441. X *
  442. X * Revision 3.4  82/12/04  13:18:20  wft
  443. X * Replaced getdelta() with gettree(), changed breaklock to update
  444. X * field lockedby, added some diagnostics.
  445. X *
  446. X * Revision 3.3  82/12/03  17:08:04  wft
  447. X * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
  448. X * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
  449. X * fixed -u for missing revno. Disambiguated structure members.
  450. X *
  451. X * Revision 3.2  82/10/18  21:05:07  wft
  452. X * rcs -i now generates a file mode given by the umask minus write permission;
  453. X * otherwise, rcs keeps the mode, but removes write permission.
  454. X * I added a check for write error, fixed call to getlogin(), replaced
  455. X * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
  456. X * conflicting, long identifiers.
  457. X *
  458. X * Revision 3.1  82/10/13  16:11:07  wft
  459. X * fixed type of variables receiving from getc() (char -> int).
  460. X */
  461. X
  462. X
  463. X#include "rcsbase.h"
  464. X
  465. Xstruct  Lockrev {
  466. X    const char *revno;
  467. X        struct  Lockrev   * nextrev;
  468. X};
  469. X
  470. Xstruct  Symrev {
  471. X    const char *revno;
  472. X    const char *ssymbol;
  473. X        int     override;
  474. X        struct  Symrev  * nextsym;
  475. X};
  476. X
  477. Xstruct  Status {
  478. X    const char *revno;
  479. X    const char *status;
  480. X        struct  Status  * nextstatus;
  481. X};
  482. X
  483. Xenum changeaccess {append, erase};
  484. Xstruct chaccess {
  485. X    const char *login;
  486. X    enum changeaccess command;
  487. X    struct chaccess *nextchaccess;
  488. X};
  489. X
  490. Xstruct delrevpair {
  491. X    const char *strt;
  492. X    const char *end;
  493. X        int     code;
  494. X};
  495. X
  496. Xstatic int buildeltatext P((const struct hshentries*));
  497. Xstatic int removerevs P((void));
  498. Xstatic int sendmail P((const char*,const char*));
  499. Xstatic struct Lockrev *rmnewlocklst P((const struct Lockrev*));
  500. Xstatic void breaklock P((const struct hshentry*));
  501. Xstatic void buildtree P((void));
  502. Xstatic void cleanup P((void));
  503. Xstatic void getaccessor P((char*,enum changeaccess));
  504. Xstatic void getassoclst P((int,char*));
  505. Xstatic void getchaccess P((const char*,enum changeaccess));
  506. Xstatic void getdelrev P((char*));
  507. Xstatic void getstates P((char*));
  508. Xstatic void rcs_setstate P((const char*,const char*));
  509. Xstatic void scanlogtext P((struct hshentry*,int));
  510. Xstatic void setlock P((const char*));
  511. Xstatic void updateaccess P((void));
  512. Xstatic void updateassoc P((void));
  513. Xstatic void updatelocks P((void));
  514. X
  515. Xstatic struct buf numrev;
  516. Xstatic const char *headstate;
  517. Xstatic int chgheadstate, exitstatus, lockhead, unlockcaller;
  518. Xstatic struct Lockrev *newlocklst, *rmvlocklst;
  519. Xstatic struct Status *statelst, *laststate;
  520. Xstatic struct Symrev *assoclst, *lastassoc;
  521. Xstatic struct chaccess *chaccess, **nextchaccess;
  522. Xstatic struct delrevpair delrev;
  523. Xstatic struct hshentry *cuthead, *cuttail, *delstrt;
  524. Xstatic struct hshentries *gendeltas;
  525. X
  526. XmainProg(rcsId, "rcs", "$Id: rcs.c,v 5.7 1990/12/18 17:19:21 eggert Exp $")
  527. X{
  528. X    static const char cmdusage[] =
  529. X        "\nrcs usage: rcs -alogins -Aoldfile -{blu}[rev] -cstring -e[logins] -i -{LU} -{nN}name[:rev] -orange -sstate[:rev] -t[textfile] -Vn file ...";
  530. X
  531. X    const char *branchsym, *commsyml, *textfile;
  532. X    int branchflag, expmode, initflag;
  533. X    int r, strictlock, strict_selected, textflag;
  534. X    mode_t defaultRCSmode;    /* default mode for new RCS files */
  535. X    struct buf branchnum;
  536. X        struct  Lockrev *curlock,  * rmvlock, *lockpt;
  537. X        struct  Status  * curstate;
  538. X
  539. X    initid();
  540. X    catchints();
  541. X
  542. X    nextchaccess = &chaccess;
  543. X    branchsym = commsyml = textfile = nil;
  544. X    branchflag = strictlock = false;
  545. X    bufautobegin(&branchnum);
  546. X    curlock = rmvlock = nil;
  547. X    defaultRCSmode = 0;
  548. X    expmode = -1;
  549. X        initflag= textflag = false;
  550. X        strict_selected = 0;
  551. X
  552. X        /*  preprocessing command options    */
  553. X        while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
  554. X                switch ((*argv)[1]) {
  555. X
  556. X        case 'i':   /*  initial version  */
  557. X                        initflag = true;
  558. X                        break;
  559. X
  560. X                case 'b':  /* change default branch */
  561. X            if (branchflag) redefined('b');
  562. X                        branchflag= true;
  563. X                        branchsym = (*argv)+2;
  564. X                        break;
  565. X
  566. X                case 'c':   /*  change comment symbol   */
  567. X            if (commsyml) redefined('c');
  568. X                        commsyml = (*argv)+2;
  569. X                        break;
  570. X
  571. X                case 'a':  /*  add new accessor   */
  572. X            getaccessor(*argv+1, append);
  573. X                        break;
  574. X
  575. X                case 'A':  /*  append access list according to accessfile  */
  576. X            *argv += 2;
  577. X            if (!**argv) {
  578. X                error("missing file name after -A");
  579. X                            break;
  580. X                        }
  581. X            if (0 < pairfilenames(1,argv,rcsreadopen,true,false)) {
  582. X                while (AccessList) {
  583. X                getchaccess(strsave(AccessList->login), append);
  584. X                AccessList = AccessList->nextaccess;
  585. X                }
  586. X                ffclose(finptr);
  587. X                        }
  588. X                        break;
  589. X
  590. X                case 'e':    /*  remove accessors   */
  591. X            getaccessor(*argv+1, erase);
  592. X                        break;
  593. X
  594. X                case 'l':    /*   lock a revision if it is unlocked   */
  595. X                        if ( (*argv)[2] == '\0'){ /* lock head or def. branch */
  596. X                            lockhead = true;
  597. X                            break;
  598. X                        }
  599. X            lockpt = talloc(struct Lockrev);
  600. X                        lockpt->revno = (*argv)+2;
  601. X                        lockpt->nextrev = nil;
  602. X                        if ( curlock )
  603. X                            curlock->nextrev = lockpt;
  604. X                        else
  605. X                            newlocklst = lockpt;
  606. X                        curlock = lockpt;
  607. X                        break;
  608. X
  609. X                case 'u':   /*  release lock of a locked revision   */
  610. X                        if ( (*argv)[2] == '\0'){ /*  unlock head  */
  611. X                            unlockcaller=true;
  612. X                            break;
  613. X                        }
  614. X            lockpt = talloc(struct Lockrev);
  615. X                        lockpt->revno = (*argv)+2;
  616. X                        lockpt->nextrev = nil;
  617. X                        if (rmvlock)
  618. X                            rmvlock->nextrev = lockpt;
  619. X                        else
  620. X                            rmvlocklst = lockpt;
  621. X                        rmvlock = lockpt;
  622. X
  623. X                        curlock = rmnewlocklst(lockpt);
  624. X                        break;
  625. X
  626. X                case 'L':   /*  set strict locking */
  627. X                        if (strict_selected++) {  /* Already selected L or U? */
  628. X               if (!strictlock)      /* Already selected -U? */
  629. X                   warn("-L overrides -U.");
  630. X                        }
  631. X                        strictlock = true;
  632. X                        break;
  633. X
  634. X                case 'U':   /*  release strict locking */
  635. X                        if (strict_selected++) {  /* Already selected L or U? */
  636. X               if (strictlock)      /* Already selected -L? */
  637. X                   warn("-L overrides -U.");
  638. X                        }
  639. X            else
  640. X                strictlock = false;
  641. X                        break;
  642. X
  643. X                case 'n':    /*  add new association: error, if name exists */
  644. X                        if ( (*argv)[2] == '\0') {
  645. X                error("missing symbolic name after -n");
  646. X                            break;
  647. X                        }
  648. X                        getassoclst(false, (*argv)+1);
  649. X                        break;
  650. X
  651. X                case 'N':   /*  add or change association   */
  652. X                        if ( (*argv)[2] == '\0') {
  653. X                error("missing symbolic name after -N");
  654. X                            break;
  655. X                        }
  656. X                        getassoclst(true, (*argv)+1);
  657. X                        break;
  658. X
  659. X        case 'o':   /*  delete revisions  */
  660. X            if (delrev.strt) redefined('o');
  661. X                        if ( (*argv)[2] == '\0' ) {
  662. X                error("missing revision range after -o");
  663. X                            break;
  664. X                        }
  665. X                        getdelrev( (*argv)+1 );
  666. X                        break;
  667. X
  668. X                case 's':   /*  change state attribute of a revision  */
  669. X                        if ( (*argv)[2] == '\0') {
  670. X                error("state missing after -s");
  671. X                            break;
  672. X                        }
  673. X                        getstates( (*argv)+1);
  674. X                        break;
  675. X
  676. X                case 't':   /*  change descriptive text   */
  677. X                        textflag=true;
  678. X                        if ((*argv)[2]!='\0'){
  679. X                if (textfile) redefined('t');
  680. X                                textfile = (*argv)+2;
  681. X                        }
  682. X                        break;
  683. X
  684. X        case 'I':
  685. X            interactiveflag = true;
  686. X            break;
  687. X
  688. X                case 'q':
  689. X                        quietflag = true;
  690. X                        break;
  691. X
  692. X        case 'V':
  693. X            setRCSversion(*argv);
  694. X            break;
  695. X
  696. X        case 'k':    /*  set keyword expand mode  */
  697. X            if (0 <= expmode) redefined('k');
  698. X            if (0 <= (expmode = str2expmode(*argv+2)))
  699. X                break;
  700. X            /* fall into */
  701. X                default:
  702. X            faterror("unknown option: %s%s", *argv, cmdusage);
  703. X                };
  704. X        }  /* end processing of options */
  705. X
  706. X    if (argc<1) faterror("no input file%s", cmdusage);
  707. X        if (nerror) {
  708. X        diagnose("%s aborted\n",cmdid);
  709. X        exitmain(EXIT_FAILURE);
  710. X        }
  711. X    if (initflag) {
  712. X        defaultRCSmode = umask((mode_t)0);
  713. X        VOID umask(defaultRCSmode);
  714. X        defaultRCSmode = ~defaultRCSmode & 0444;
  715. X    }
  716. X
  717. X        /* now handle all filenames */
  718. X        do {
  719. X    foutptr = NULL;
  720. X        finptr=frewrite=NULL;
  721. X    ffree();
  722. X
  723. X        if ( initflag ) {
  724. X        switch (pairfilenames(argc, argv, rcswriteopen, false, false)) {
  725. X                case -1: break;        /*  not exist; ok */
  726. X                case  0: continue;     /*  error         */
  727. X                case  1: error("file %s exists already", RCSfilename);
  728. X                         continue;
  729. X            }
  730. X    }
  731. X        else  {
  732. X        switch (pairfilenames(argc, argv, rcswriteopen, true, false)) {
  733. X                case -1: continue;    /*  not exist      */
  734. X                case  0: continue;    /*  errors         */
  735. X                case  1: break;       /*  file exists; ok*/
  736. X            }
  737. X    }
  738. X
  739. X
  740. X        /* now RCSfilename contains the name of the RCS file, and
  741. X         * workfilename contains the name of the working file.
  742. X         * if !initflag, finptr contains the file descriptor for the
  743. X         * RCS file. The admin node is initialized.
  744. X         */
  745. X
  746. X    diagnose("RCS file: %s\n", RCSfilename);
  747. X
  748. X    if (initflag && !getworkstat())           continue; /* give up */
  749. X    if (!initflag && !checkaccesslist())       continue; /* give up */
  750. X
  751. X        gettree(); /* read in delta tree */
  752. X
  753. X        /*  update admin. node    */
  754. X        if (strict_selected) StrictLocks = strictlock;
  755. X    if (commsyml) {
  756. X        Comment.string = commsyml;
  757. X        Comment.size = strlen(commsyml);
  758. X    }
  759. X    if (0 <= expmode) Expand = expmode;
  760. X
  761. X        /* update default branch */
  762. X    if (branchflag && expandsym(branchsym, &branchnum)) {
  763. X        if (countnumflds(branchnum.string)) {
  764. X        Dbranch = branchnum.string;
  765. X            } else
  766. X                Dbranch = nil;
  767. X        }
  768. X
  769. X    updateaccess();        /*  update access list        */
  770. X
  771. X        updateassoc();          /*  update association list   */
  772. X
  773. X        updatelocks();          /*  update locks              */
  774. X
  775. X        /*  update state attribution  */
  776. X        if (chgheadstate) {
  777. X            /* change state of default branch or head */
  778. X            if (Dbranch==nil) {
  779. X                if (Head==nil)
  780. X             warn("can't change states in an empty tree");
  781. X                else Head->state = headstate;
  782. X            } else {
  783. X        rcs_setstate(Dbranch,headstate); /* Can't set directly */
  784. X            }
  785. X        }
  786. X        curstate = statelst;
  787. X        while( curstate ) {
  788. X            rcs_setstate(curstate->revno,curstate->status);
  789. X            curstate = curstate->nextstatus;
  790. X        }
  791. X
  792. X        cuthead = cuttail = nil;
  793. X    if (delrev.strt && removerevs()) {
  794. X            /*  rebuild delta tree if some deltas are deleted   */
  795. X            if ( cuttail )
  796. X        VOID genrevs(cuttail->num, (char *)nil,(char *)nil,
  797. X                 (char *)nil, &gendeltas);
  798. X            buildtree();
  799. X        }
  800. X
  801. X
  802. X        putadmin(frewrite);
  803. X        if ( Head )
  804. X           puttree(Head, frewrite);
  805. X    putdesc(textflag,textfile);
  806. X    foutptr = NULL;
  807. X
  808. X        if ( Head) {
  809. X        if (!delrev.strt) {
  810. X                /* no revision deleted */
  811. X                fastcopy(finptr,frewrite);
  812. X            } else {
  813. X        if (!cuttail || buildeltatext(gendeltas))
  814. X                    scanlogtext((struct hshentry *)nil,nil);
  815. X                    /* copy rest of delta text nodes that are not deleted      */
  816. X            }
  817. X        }
  818. X    if (finptr) {ffclose(finptr); finptr=NULL;} /* Help the file system. */
  819. X        ffclose(frewrite);   frewrite = NULL;
  820. X        if ( ! nerror ) {  /*  move temporary file to RCS file if no error */
  821. X        /* update mode */
  822. X        seteid();
  823. X        r = chmod(newRCSfilename,
  824. X             (
  825. X                   !initflag ? RCSstat.st_mode
  826. X                 : haveworkstat==0 ? workstat.st_mode
  827. X                 : defaultRCSmode
  828. X             ) & ~(S_IWUSR|S_IWGRP|S_IWOTH)
  829. X        );
  830. X        if (r == 0) {
  831. X        ignoreints();
  832. X        r = re_name(newRCSfilename,RCSfilename);
  833. X        keepdirtemp(newRCSfilename);
  834. X        restoreints();
  835. X        }
  836. X        setrid();
  837. X        if (r != 0) {
  838. X        eerror(RCSfilename);
  839. X        error("saved in %s", newRCSfilename);
  840. X        dirtempunlink();
  841. X                break;
  842. X            }
  843. X        diagnose("done\n");
  844. X        } else {
  845. X        diagnose("%s aborted; %s unchanged.\n",cmdid,RCSfilename);
  846. X        }
  847. X    } while (cleanup(),
  848. X                 ++argv, --argc >=1);
  849. X
  850. X    tempunlink();
  851. X    exitmain(exitstatus);
  852. X}       /* end of main (rcs) */
  853. X
  854. X    static void
  855. Xcleanup()
  856. X{
  857. X    if (nerror) exitstatus = EXIT_FAILURE;
  858. X    if (finptr) ffclose(finptr);
  859. X    if (frewrite) ffclose(frewrite);
  860. X    dirtempunlink();
  861. X}
  862. X
  863. X    exiting void
  864. Xexiterr()
  865. X{
  866. X    dirtempunlink();
  867. X    tempunlink();
  868. X    _exit(EXIT_FAILURE);
  869. X}
  870. X
  871. X
  872. X    static void
  873. Xgetassoclst(flag, sp)
  874. Xint     flag;
  875. Xchar    * sp;
  876. X/*  Function:   associate a symbolic name to a revision or branch,      */
  877. X/*              and store in assoclst                                   */
  878. X
  879. X{
  880. X        struct   Symrev  * pt;
  881. X    const char *temp;
  882. X        int                c;
  883. X
  884. X        while( (c=(*++sp)) == ' ' || c == '\t' || c =='\n')  ;
  885. X        temp = sp;
  886. X    sp = checkid(sp, ':');  /*  check for invalid symbolic name  */
  887. X    c = *sp;   *sp = '\0';
  888. X        while( c == ' ' || c == '\t' || c == '\n')  c = *++sp;
  889. X
  890. X        if ( c != ':' && c != '\0') {
  891. X        error("invalid string %s after option -n or -N",sp);
  892. X            return;
  893. X        }
  894. X
  895. X    pt = talloc(struct Symrev);
  896. X        pt->ssymbol = temp;
  897. X        pt->override = flag;
  898. X    if (c == '\0')  /*  delete symbol  */
  899. X            pt->revno = nil;
  900. X        else {
  901. X            while( (c = *++sp) == ' ' || c == '\n' || c == '\t')  ;
  902. X        if ( c == '\0' )
  903. X                pt->revno = nil;
  904. X        else
  905. X                pt->revno = sp;
  906. X        }
  907. X        pt->nextsym = nil;
  908. X        if (lastassoc)
  909. X            lastassoc->nextsym = pt;
  910. X        else
  911. X            assoclst = pt;
  912. X        lastassoc = pt;
  913. X        return;
  914. X}
  915. X
  916. X
  917. X    static void
  918. Xgetchaccess(login, command)
  919. X    const char *login;
  920. X    enum changeaccess command;
  921. X{
  922. X    register struct chaccess *pt;
  923. X
  924. X    *nextchaccess = pt = talloc(struct chaccess);
  925. X    pt->login = login;
  926. X    pt->command = command;
  927. X    pt->nextchaccess = nil;
  928. X    nextchaccess = &pt->nextchaccess;
  929. X}
  930. X
  931. X
  932. X
  933. X    static void
  934. Xgetaccessor(opt, command)
  935. X    char *opt;
  936. X    enum changeaccess command;
  937. X/*   Function:  get the accessor list of options -e and -a,     */
  938. X/*        and store in chaccess                */
  939. X
  940. X
  941. X{
  942. X        register c;
  943. X    register char *sp;
  944. X
  945. X    sp = opt;
  946. X        while( ( c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') ;
  947. X        if ( c == '\0') {
  948. X        if (command == erase  &&  sp-opt == 1) {
  949. X        getchaccess((const char*)nil, command);
  950. X        return;
  951. X        }
  952. X        error("missing login name after option -a or -e");
  953. X        return;
  954. X        }
  955. X
  956. X        while( c != '\0') {
  957. X        getchaccess(sp, command);
  958. X        sp = checkid(sp,',');
  959. X        c = *sp;   *sp = '\0';
  960. X                while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
  961. X        }
  962. X}
  963. X
  964. X
  965. X
  966. X    static void
  967. Xgetstates(sp)
  968. Xchar    *sp;
  969. X/*   Function:  get one state attribute and the corresponding   */
  970. X/*              revision and store in statelst                  */
  971. X
  972. X{
  973. X    const char *temp;
  974. X        struct  Status  *pt;
  975. X        register        c;
  976. X
  977. X        while( (c=(*++sp)) ==' ' || c == '\t' || c == '\n')  ;
  978. X        temp = sp;
  979. X    sp = checkid(sp,':');  /* check for invalid state attribute */
  980. X    c = *sp;   *sp = '\0';
  981. X        while( c == ' ' || c == '\t' || c == '\n' )  c = *++sp;
  982. X
  983. X        if ( c == '\0' ) {  /*  change state of def. branch or Head  */
  984. X            chgheadstate = true;
  985. X            headstate  = temp;
  986. X            return;
  987. X        }
  988. X        else if ( c != ':' ) {
  989. X        error("missing ':' after state in option -s");
  990. X            return;
  991. X        }
  992. X
  993. X        while( (c = *++sp) == ' ' || c == '\t' || c == '\n')  ;
  994. X    pt = talloc(struct Status);
  995. X        pt->status     = temp;
  996. X        pt->revno      = sp;
  997. X        pt->nextstatus = nil;
  998. X        if (laststate)
  999. X            laststate->nextstatus = pt;
  1000. X        else
  1001. X            statelst = pt;
  1002. X        laststate = pt;
  1003. X}
  1004. X
  1005. X
  1006. X
  1007. X    static void
  1008. Xgetdelrev(sp)
  1009. Xchar    *sp;
  1010. X/*   Function:  get revision range or branch to be deleted,     */
  1011. X/*              and place in delrev                             */
  1012. X{
  1013. X        int    c;
  1014. X        struct  delrevpair      *pt;
  1015. X
  1016. X    pt = &delrev;
  1017. X        while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
  1018. X
  1019. X        if ( c == '<' || c == '-' ) {  /*  -o  -rev  or <rev  */
  1020. X            while( (c = (*++sp)) == ' ' || c == '\n' || c == '\t')  ;
  1021. X            pt->strt = sp;    pt->code = 1;
  1022. X            while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
  1023. X            *sp = '\0';
  1024. X        pt->end = nil;
  1025. X            return;
  1026. X        }
  1027. X        else {
  1028. X            pt->strt = sp;
  1029. X            while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
  1030. X                   && c != '-' && c != '<' )  c = *++sp;
  1031. X            *sp = '\0';
  1032. X            while( c == ' ' || c == '\n' || c == '\t' )  c = *++sp;
  1033. X            if ( c == '\0' )  {  /*   -o rev or branch   */
  1034. X                pt->end = nil;   pt->code = 0;
  1035. X                return;
  1036. X            }
  1037. X            if ( c != '-' && c != '<') {
  1038. X        faterror("invalid range %s %s after -o", pt->strt, sp);
  1039. X            }
  1040. X            while( (c = *++sp) == ' ' || c == '\n' || c == '\t')  ;
  1041. X            if ( c == '\0') {  /*  -o   rev-   or   rev<   */
  1042. X                pt->end = nil;   pt->code = 2;
  1043. X                return;
  1044. X            }
  1045. X        }
  1046. X        /*   -o   rev1-rev2    or   rev1<rev2   */
  1047. X    pt->end = sp;  pt->code = 3;
  1048. X        while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
  1049. X        *sp = '\0';
  1050. X}
  1051. X
  1052. X
  1053. X
  1054. X
  1055. X    static void
  1056. Xscanlogtext(delta,edit)
  1057. X    struct hshentry *delta;
  1058. X    int edit;
  1059. X/* Function: Scans delta text nodes up to and including the one given
  1060. X * by delta, or up to last one present, if delta==nil.
  1061. X * For the one given by delta (if delta!=nil), the log message is saved into
  1062. X * curlogmsg and the text is edited if 'edit' is set, copied otherwise.
  1063. X * Assumes the initial lexeme must be read in first.
  1064. X * Does not advance nexttok after it is finished, except if delta==nil.
  1065. X */
  1066. X{
  1067. X    const struct hshentry *nextdelta;
  1068. X    struct cbuf cb;
  1069. X
  1070. X    for (;;) {
  1071. X        foutptr = NULL;
  1072. X                nextlex();
  1073. X                if (!(nextdelta=getnum())) {
  1074. X                    if(delta)
  1075. X            faterror("can't find delta for revision %s", delta->num);
  1076. X            if (nexttok != EOFILE)
  1077. X            fatserror("expecting EOF");
  1078. X            return; /* no more delta text nodes */
  1079. X                }
  1080. X        if (nextdelta->selector) {
  1081. X            foutptr = frewrite;
  1082. X            aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
  1083. X                }
  1084. X        getkeystring(Klog);
  1085. X        if (delta==nextdelta) {
  1086. X            cb = savestring(&curlogbuf);
  1087. X            delta->log = curlogmsg =
  1088. X                cleanlogmsg(curlogbuf.string, cb.size);
  1089. X                } else {readstring();
  1090. X                }
  1091. X                nextlex();
  1092. X        while (nexttok==ID && strcmp(NextString,Ktext)!=0)
  1093. X            ignorephrase();
  1094. X        getkeystring(Ktext);
  1095. X
  1096. X        if (delta==nextdelta)
  1097. X            break;
  1098. X        readstring(); /* skip over it */
  1099. X
  1100. X    }
  1101. X    /* got the one we're looking for */
  1102. X    if (edit)
  1103. X        editstring((struct hshentry *)nil);
  1104. X    else
  1105. X        copystring();
  1106. X}
  1107. X
  1108. X
  1109. X
  1110. X    static struct Lockrev *
  1111. Xrmnewlocklst(which)
  1112. X    const struct Lockrev *which;
  1113. X/*   Function:  remove lock to revision which->revno from newlocklst   */
  1114. X
  1115. X{
  1116. X        struct  Lockrev   * pt, *pre;
  1117. X
  1118. X        while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){
  1119. X        struct Lockrev *pn = newlocklst->nextrev;
  1120. X        tfree(newlocklst);
  1121. X        newlocklst = pn;
  1122. X        }
  1123. X
  1124. X        pt = pre = newlocklst;
  1125. X        while( pt ) {
  1126. X            if ( ! strcmp(pt->revno, which->revno) ) {
  1127. X                pre->nextrev = pt->nextrev;
  1128. X        tfree(pt);
  1129. X        pt = pre->nextrev;
  1130. X            }
  1131. X            else {
  1132. X                pre = pt;
  1133. X                pt = pt->nextrev;
  1134. X            }
  1135. X        }
  1136. X        return pre;
  1137. X}
  1138. X
  1139. X
  1140. X
  1141. X    static void
  1142. Xupdateaccess()
  1143. X{
  1144. X    register struct chaccess *ch;
  1145. X    register struct access **p, *t;
  1146. X
  1147. X    for (ch = chaccess;  ch;  ch = ch->nextchaccess) {
  1148. X        switch (ch->command) {
  1149. X        case erase:
  1150. X            if (!ch->login)
  1151. X                AccessList = nil;
  1152. X            else
  1153. X                for (p = &AccessList;  (t = *p);  )
  1154. X                if (strcmp(ch->login, t->login) == 0)
  1155. X                    *p = t->nextaccess;
  1156. X                else
  1157. X                    p = &t->nextaccess;
  1158. X            break;
  1159. X        case append:
  1160. X            for (p = &AccessList;  ;  p = &t->nextaccess)
  1161. X                if (!(t = *p)) {
  1162. X                    *p = t = ftalloc(struct access);
  1163. X                    t->login = ch->login;
  1164. X                    t->nextaccess = nil;
  1165. X                    break;
  1166. X                } else if (strcmp(ch->login, t->login) == 0)
  1167. X                    break;
  1168. X            break;
  1169. X        }
  1170. X    }
  1171. X}
  1172. X
  1173. X
  1174. X    static int
  1175. Xsendmail(Delta, who)
  1176. X    const char *Delta, *who;
  1177. X/*   Function:  mail to who, informing him that his lock on delta was
  1178. X *   broken by caller. Ask first whether to go ahead. Return false on
  1179. X *   error or if user decides not to break the lock.
  1180. X */
  1181. X{
  1182. X    const char *messagefile;
  1183. X    int old1, old2, c;
  1184. X        FILE    * mailmess;
  1185. X
  1186. X
  1187. X    aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
  1188. X    if (!yesorno(false, "Do you want to break the lock? [ny](n): "))
  1189. X        return false;
  1190. X
  1191. X        /* go ahead with breaking  */
  1192. X    messagefile = maketemp(0);
  1193. X    errno = 0;
  1194. X        if ( (mailmess = fopen(messagefile, "w")) == NULL) {
  1195. X        efaterror(messagefile);
  1196. X        }
  1197. X
  1198. X    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",
  1199. X        bindex(RCSfilename,SLASH), Delta, getfullRCSname(), getcaller()
  1200. X    );
  1201. X    aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr);
  1202. X
  1203. X        old1 = '\n';    old2 = ' ';
  1204. X        for (; ;) {
  1205. X        c = getcstdin();
  1206. X            if ( c == EOF ) {
  1207. X        aprintf(mailmess, "%c\n", old1);
  1208. X                break;
  1209. X            }
  1210. X            else if ( c == '\n' && old1 == '.' && old2 == '\n')
  1211. X                break;
  1212. X            else {
  1213. X        afputc(old1, mailmess);
  1214. X                old2 = old1;   old1 = c;
  1215. X        if (c=='\n') aputs(">> ", stderr);
  1216. X            }
  1217. X        }
  1218. X        ffclose(mailmess);
  1219. X
  1220. X    /* ignore the exit status, even if delivermail unsuccessful */
  1221. X    VOID run(messagefile, (char*)nil, SENDMAIL, who, (char*)nil);
  1222. X    return(true);
  1223. X}
  1224. X
  1225. X
  1226. X
  1227. X    static void
  1228. Xbreaklock(delta)
  1229. X    const struct hshentry *delta;
  1230. X/* function: Finds the lock held by caller on delta,
  1231. X * and removes it.
  1232. X * Sends mail if a lock different from the caller's is broken.
  1233. X * Prints an error message if there is no such lock or error.
  1234. X */
  1235. X{
  1236. X        register struct lock * next, * trail;
  1237. X    const char *num;
  1238. X        struct lock dummy;
  1239. X
  1240. X    num=delta->num;
  1241. X        dummy.nextlock=next=Locks;
  1242. X        trail = &dummy;
  1243. X        while (next!=nil) {
  1244. X        if (strcmp(num, next->delta->num) == 0) {
  1245. X            if (
  1246. X                strcmp(getcaller(),next->login) != 0
  1247. X                &&    !sendmail(num, next->login)
  1248. X            ) {
  1249. X                error("%s still locked by %s", num, next->login);
  1250. X                return;
  1251. X            }
  1252. X            break; /* exact match */
  1253. X                }
  1254. X                trail=next;
  1255. X                next=next->nextlock;
  1256. X        }
  1257. X        if (next!=nil) {
  1258. X                /*found one */
  1259. X        diagnose("%s unlocked\n",next->delta->num);
  1260. X                trail->nextlock=next->nextlock;
  1261. X                next->delta->lockedby=nil;
  1262. X                Locks=dummy.nextlock;
  1263. X        } else  {
  1264. X        error("no lock set on revision %s", num);
  1265. X        }
  1266. X}
  1267. X
  1268. X
  1269. X
  1270. X    static struct hshentry *
  1271. Xsearchcutpt(object, length, store)
  1272. X    const char *object;
  1273. X    unsigned length;
  1274. X    struct hshentries *store;
  1275. X/*   Function:  Search store and return entry with number being object. */
  1276. X/*              cuttail = nil, if the entry is Head; otherwise, cuttail */
  1277. X/*              is the entry point to the one with number being object  */
  1278. X
  1279. X{
  1280. X    cuthead = nil;
  1281. X    while (compartial(store->first->num, object, length)) {
  1282. X        cuthead = store->first;
  1283. X        store = store->rest;
  1284. X    }
  1285. X    return store->first;
  1286. X}
  1287. X
  1288. X
  1289. X
  1290. X    static int
  1291. Xbranchpoint(strt, tail)
  1292. Xstruct  hshentry        *strt,  *tail;
  1293. X/*   Function: check whether the deltas between strt and tail    */
  1294. X/*        are locked or branch point, return 1 if any is  */
  1295. X/*        locked or branch point; otherwise, return 0 and */
  1296. X/*        mark deleted                    */
  1297. X
  1298. X{
  1299. X        struct  hshentry    *pt;
  1300. X    const struct lock *lockpt;
  1301. X        int     flag;
  1302. X
  1303. X
  1304. X        pt = strt;
  1305. X        flag = false;
  1306. X        while( pt != tail) {
  1307. X            if ( pt->branches ){ /*  a branch point  */
  1308. X                flag = true;
  1309. X        error("can't remove branch point %s", pt->num);
  1310. X            }
  1311. X        lockpt = Locks;
  1312. X        while(lockpt && lockpt->delta != pt)
  1313. X        lockpt = lockpt->nextlock;
  1314. X        if ( lockpt ) {
  1315. X        flag = true;
  1316. X        error("can't remove locked revision %s",pt->num);
  1317. X        }
  1318. X            pt = pt->next;
  1319. X        }
  1320. X
  1321. X        if ( ! flag ) {
  1322. X            pt = strt;
  1323. X            while( pt != tail ) {
  1324. X        pt->selector = false;
  1325. X        diagnose("deleting revision %s\n",pt->num);
  1326. X                pt = pt->next;
  1327. X            }
  1328. X        }
  1329. X        return flag;
  1330. X}
  1331. X
  1332. X
  1333. X
  1334. X    static int
  1335. Xremoverevs()
  1336. X/*   Function:  get the revision range to be removed, and place the     */
  1337. X/*              first revision removed in delstrt, the revision before  */
  1338. X/*              delstrt in cuthead( nil, if delstrt is head), and the   */
  1339. X/*              revision after the last removed revision in cuttail(nil */
  1340. X/*              if the last is a leaf                                   */
  1341. X
  1342. X{
  1343. X    struct  hshentry *target, *target2, *temp;
  1344. X    unsigned length;
  1345. X    int flag;
  1346. X
  1347. X        flag = false;
  1348. X    if (!expandsym(delrev.strt, &numrev)) return 0;
  1349. X    target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas);
  1350. X        if ( ! target ) return 0;
  1351. X    if (cmpnum(target->num, numrev.string)) flag = true;
  1352. X    length = countnumflds(numrev.string);
  1353. X
  1354. X    if (delrev.code == 0) {  /*  -o  rev    or    -o  branch   */
  1355. X        if (length & 1)
  1356. X        temp=searchcutpt(target->num,length+1,gendeltas);
  1357. X        else if (flag) {
  1358. X        error("Revision %s doesn't exist.", numrev.string);
  1359. X        return 0;
  1360. X        }
  1361. X        else
  1362. X        temp = searchcutpt(numrev.string, length, gendeltas);
  1363. X        cuttail = target->next;
  1364. X            if ( branchpoint(temp, cuttail) ) {
  1365. X                cuttail = nil;
  1366. X                return 0;
  1367. X            }
  1368. X            delstrt = temp;     /* first revision to be removed   */
  1369. X            return 1;
  1370. X        }
  1371. X
  1372. X    if (length & 1) {   /*  invalid branch after -o   */
  1373. X        error("invalid branch range %s after -o", numrev.string);
  1374. X            return 0;
  1375. X        }
  1376. X
  1377. X    if (delrev.code == 1) {  /*  -o  -rev   */
  1378. X            if ( length > 2 ) {
  1379. X                temp = searchcutpt( target->num, length-1, gendeltas);
  1380. X                cuttail = target->next;
  1381. X            }
  1382. X            else {
  1383. X                temp = searchcutpt(target->num, length, gendeltas);
  1384. X                cuttail = target;
  1385. X                while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
  1386. X                    cuttail = cuttail->next;
  1387. X            }
  1388. X            if ( branchpoint(temp, cuttail) ){
  1389. X                cuttail = nil;
  1390. X                return 0;
  1391. X            }
  1392. X            delstrt = temp;
  1393. X            return 1;
  1394. X        }
  1395. X
  1396. X    if (delrev.code == 2) {   /*  -o  rev-   */
  1397. X            if ( length == 2 ) {
  1398. X                temp = searchcutpt(target->num, 1,gendeltas);
  1399. X                if ( flag)
  1400. X                    cuttail = target;
  1401. X                else
  1402. X                    cuttail = target->next;
  1403. X            }
  1404. X            else  {
  1405. X                if ( flag){
  1406. X                    cuthead = target;
  1407. X                    if ( !(temp = target->next) ) return 0;
  1408. X                }
  1409. X                else
  1410. X                    temp = searchcutpt(target->num, length, gendeltas);
  1411. X        getbranchno(temp->num, &numrev);  /* get branch number */
  1412. X        target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas);
  1413. X            }
  1414. X            if ( branchpoint( temp, cuttail ) ) {
  1415. X                cuttail = nil;
  1416. X                return 0;
  1417. X            }
  1418. X            delstrt = temp;
  1419. X            return 1;
  1420. X        }
  1421. X
  1422. X        /*   -o   rev1-rev2   */
  1423. X    if (!expandsym(delrev.end, &numrev)) return 0;
  1424. X    if (
  1425. X        length != countnumflds(numrev.string)
  1426. X        ||    length>2 && compartial(numrev.string, target->num, length-1)
  1427. X    ) {
  1428. X        error("invalid revision range %s-%s", target->num, numrev.string);
  1429. X            return 0;
  1430. X        }
  1431. X
  1432. X    target2 = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas);
  1433. X        if ( ! target2 ) return 0;
  1434. X
  1435. X        if ( length > 2) {  /* delete revisions on branches  */
  1436. X            if ( cmpnum(target->num, target2->num) > 0) {
  1437. X        if (cmpnum(target2->num, numrev.string))
  1438. X                    flag = true;
  1439. X                else
  1440. X                    flag = false;
  1441. X                temp = target;
  1442. X                target = target2;
  1443. X                target2 = temp;
  1444. X            }
  1445. X            if ( flag ) {
  1446. X                if ( ! cmpnum(target->num, target2->num) ) {
  1447. X            error("Revisions %s-%s don't exist.", delrev.strt,delrev.end);
  1448. X                    return 0;
  1449. X                }
  1450. X                cuthead = target;
  1451. X                temp = target->next;
  1452. X            }
  1453. X            else
  1454. X                temp = searchcutpt(target->num, length, gendeltas);
  1455. X            cuttail = target2->next;
  1456. X        }
  1457. X        else { /*  delete revisions on trunk  */
  1458. X            if ( cmpnum( target->num, target2->num) < 0 ) {
  1459. X                temp = target;
  1460. X                target = target2;
  1461. X                target2 = temp;
  1462. X            }
  1463. X            else
  1464. X        if (cmpnum(target2->num, numrev.string))
  1465. X                    flag = true;
  1466. X                else
  1467. X                    flag = false;
  1468. X            if ( flag ) {
  1469. X                if ( ! cmpnum(target->num, target2->num) ) {
  1470. X            error("Revisions %s-%s don't exist.", delrev.strt, delrev.end);
  1471. X                    return 0;
  1472. X                }
  1473. X                cuttail = target2;
  1474. X            }
  1475. X            else
  1476. X                cuttail = target2->next;
  1477. X            temp = searchcutpt(target->num, length, gendeltas);
  1478. X        }
  1479. X        if ( branchpoint(temp, cuttail) )  {
  1480. X            cuttail = nil;
  1481. X            return 0;
  1482. X        }
  1483. X        delstrt = temp;
  1484. X        return 1;
  1485. X}
  1486. X
  1487. X
  1488. X
  1489. X    static void
  1490. Xupdateassoc()
  1491. X/*   Function: add or delete(if revno is nil) association    */
  1492. X/*        which is stored in assoclst            */
  1493. X
  1494. X{
  1495. X    const struct Symrev *curassoc;
  1496. X    struct  assoc   * pre,  * pt;
  1497. X
  1498. X        /*  add new associations   */
  1499. X        curassoc = assoclst;
  1500. X        while( curassoc ) {
  1501. X            if ( curassoc->revno == nil ) {  /* delete symbol  */
  1502. X        pre = pt = Symbols;
  1503. X                while( pt && strcmp(pt->symbol,curassoc->ssymbol) ) {
  1504. X            pre = pt;
  1505. X            pt = pt->nextassoc;
  1506. X        }
  1507. X        if ( pt )
  1508. X            if ( pre == pt )
  1509. X            Symbols = pt->nextassoc;
  1510. X            else
  1511. X            pre->nextassoc = pt->nextassoc;
  1512. X        else
  1513. X            warn("can't delete nonexisting symbol %s",curassoc->ssymbol);
  1514. X        }
  1515. X        else if (expandsym(curassoc->revno, &numrev)) {
  1516. X        /*   add symbol  */
  1517. X        VOID addsymbol(fstrsave(numrev.string), curassoc->ssymbol, curassoc->override);
  1518. X            }
  1519. X            curassoc = curassoc->nextsym;
  1520. X        }
  1521. X
  1522. X}
  1523. X
  1524. X
  1525. X
  1526. X    static void
  1527. Xupdatelocks()
  1528. X/* Function: remove lock for caller or first lock if unlockcaller is set;
  1529. X *           remove locks which are stored in rmvlocklst,
  1530. X *           add new locks which are stored in newlocklst,
  1531. X *           add lock for Dbranch or Head if lockhead is set.
  1532. X */
  1533. X{
  1534. X    const struct Lockrev *lockpt;
  1535. X    struct hshentry *target;
  1536. X
  1537. X    if (unlockcaller) { /*  find lock for caller  */
  1538. X            if ( Head ) {
  1539. X        if (Locks) {
  1540. X            switch (findlock(true, &target)) {
  1541. X              case 0:
  1542. X            breaklock(Locks->delta); /* remove most recent lock */
  1543. X            break;
  1544. X              case 1:
  1545. X            diagnose("%s unlocked\n",target->num);
  1546. X            break;
  1547. X            }
  1548. X        } else {
  1549. X            warn("No locks are set.");
  1550. X        }
  1551. X            } else {
  1552. X        warn("can't unlock an empty tree");
  1553. X            }
  1554. X        }
  1555. X
  1556. X        /*  remove locks which are stored in rmvlocklst   */
  1557. X        lockpt = rmvlocklst;
  1558. X        while( lockpt ) {
  1559. X        if (expandsym(lockpt->revno, &numrev)) {
  1560. X        target = genrevs(numrev.string, (char *)nil, (char *)nil, (char *)nil, &gendeltas);
  1561. X                if ( target )
  1562. X           if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
  1563. X            error("can't unlock nonexisting revision %s",lockpt->revno);
  1564. X                   else
  1565. X            breaklock(target);
  1566. X                        /* breaklock does its own diagnose */
  1567. X            }
  1568. X            lockpt = lockpt->nextrev;
  1569. X        }
  1570. X
  1571. X        /*  add new locks which stored in newlocklst  */
  1572. X        lockpt = newlocklst;
  1573. X        while( lockpt ) {
  1574. X        setlock(lockpt->revno);
  1575. X            lockpt = lockpt->nextrev;
  1576. X        }
  1577. X
  1578. X    if (lockhead) {  /*  lock default branch or head  */
  1579. X            if (Dbranch) {
  1580. X        setlock(Dbranch);
  1581. X        } else if (Head) {
  1582. X        if (0 <= addlock(Head))
  1583. X            diagnose("%s locked\n",Head->num);
  1584. X            } else {
  1585. X        warn("can't lock an empty tree");
  1586. X            }
  1587. X        }
  1588. X
  1589. X}
  1590. X
  1591. X
  1592. X
  1593. X    static void
  1594. Xsetlock(rev)
  1595. X    const char *rev;
  1596. X/* Function: Given a revision or branch number, finds the corresponding
  1597. X * delta and locks it for caller.
  1598. X */
  1599. X{
  1600. X        struct  hshentry *target;
  1601. X
  1602. X    if (expandsym(rev, &numrev)) {
  1603. X        target = genrevs(numrev.string, (char*)nil, (char*)nil,
  1604. X                 (char*)nil, &gendeltas);
  1605. X            if ( target )
  1606. X           if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
  1607. X            error("can't lock nonexisting revision %s", numrev.string);
  1608. X               else
  1609. X            if (0 <= addlock(target))
  1610. X            diagnose("%s locked\n", target->num);
  1611. X        }
  1612. X}
  1613. X
  1614. X
  1615. X
  1616. X    static void
  1617. Xrcs_setstate(rev,status)
  1618. X    const char *rev, *status;
  1619. X/* Function: Given a revision or branch number, finds the corresponding delta
  1620. X * and sets its state to status.
  1621. X */
  1622. X{
  1623. X        struct  hshentry *target;
  1624. X
  1625. X    if (expandsym(rev, &numrev)) {
  1626. X        target = genrevs(numrev.string, (char*)nil, (char*)nil,
  1627. X                 (char*)nil, &gendeltas);
  1628. X            if ( target )
  1629. X           if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
  1630. X            error("can't set state of nonexisting revision %s to %s",
  1631. X              numrev.string, status);
  1632. X               else
  1633. X                    target->state = status;
  1634. X        }
  1635. X}
  1636. X
  1637. X
  1638. X
  1639. X
  1640. X
  1641. X    static int
  1642. Xbuildeltatext(deltas)
  1643. X    const struct hshentries *deltas;
  1644. X/*   Function:  put the delta text on frewrite and make necessary   */
  1645. X/*              change to delta text                                */
  1646. X{
  1647. X    int exit_stats;
  1648. X    register FILE *fcut;    /* temporary file to rebuild delta tree */
  1649. X    const char *cutfilename, *diffilename;
  1650. X
  1651. X    cutfilename = nil;
  1652. X    cuttail->selector = false;
  1653. X    inittmpeditfiles();
  1654. X    scanlogtext(deltas->first, false);
  1655. X        if ( cuthead )  {
  1656. X        cutfilename = maketemp(3);
  1657. X        errno = 0;
  1658. X            if ( (fcut = fopen(cutfilename, "w")) == NULL) {
  1659. X        efaterror(cutfilename);
  1660. X            }
  1661. X
  1662. X        while (deltas->first != cuthead) {
  1663. X        deltas = deltas->rest;
  1664. X        scanlogtext(deltas->first, true);
  1665. X            }
  1666. X
  1667. X        finishedit((struct hshentry *)nil);
  1668. X        arewind(fcopy);
  1669. X        fastcopy(fcopy, fcut);
  1670. X            swapeditfiles(false);
  1671. X            ffclose(fcut);
  1672. X        }
  1673. X
  1674. X    while (deltas->first != cuttail)
  1675. X        scanlogtext((deltas = deltas->rest)->first, true);
  1676. X        finishedit((struct hshentry *)nil);    ffclose(fcopy);
  1677. X
  1678. X        if ( cuthead ) {
  1679. X        diffilename = maketemp(0);
  1680. X            exit_stats = run((char*)nil,diffilename,
  1681. X            DIFF DIFF_FLAGS, cutfilename, resultfile, (char*)nil);
  1682. X        if (!WIFEXITED(exit_stats) || 1<WEXITSTATUS(exit_stats))
  1683. X                faterror ("diff failed");
  1684. X        return putdtext(cuttail->num,curlogmsg,diffilename,frewrite,true);
  1685. X    } else
  1686. X        return putdtext(cuttail->num,curlogmsg,resultfile,frewrite,false);
  1687. X}
  1688. X
  1689. X
  1690. X
  1691. X    static void
  1692. Xbuildtree()
  1693. X/*   Function:  actually removes revisions whose selector field  */
  1694. X/*        is false, and rebuilds the linkage of deltas.     */
  1695. X/*              asks for reconfirmation if deleting last revision*/
  1696. X{
  1697. X    struct    hshentry   * Delta;
  1698. X        struct  branchhead      *pt, *pre;
  1699. X
  1700. X        if ( cuthead )
  1701. X           if ( cuthead->next == delstrt )
  1702. X                cuthead->next = cuttail;
  1703. X           else {
  1704. X                pre = pt = cuthead->branches;
  1705. X                while( pt && pt->hsh != delstrt )  {
  1706. X                    pre = pt;
  1707. X                    pt = pt->nextbranch;
  1708. X                }
  1709. X                if ( cuttail )
  1710. X                    pt->hsh = cuttail;
  1711. X                else if ( pt == pre )
  1712. X                    cuthead->branches = pt->nextbranch;
  1713. X                else
  1714. X                    pre->nextbranch = pt->nextbranch;
  1715. X            }
  1716. X    else {
  1717. X            if ( cuttail == nil && !quietflag) {
  1718. X        if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) {
  1719. X            error("No revision deleted");
  1720. X            Delta = delstrt;
  1721. X            while( Delta) {
  1722. X            Delta->selector = true;
  1723. X            Delta = Delta->next;
  1724. X            }
  1725. X            return;
  1726. X        }
  1727. X        }
  1728. X            Head = cuttail;
  1729. X    }
  1730. X        return;
  1731. X}
  1732. X
  1733. X#if lint
  1734. X/* This lets us lint everything all at once. */
  1735. X
  1736. Xconst char cmdid[] = "";
  1737. X
  1738. X#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();return 0;}
  1739. X
  1740. X    int
  1741. Xmain(argc, argv)
  1742. X    int argc;
  1743. X    char **argv;
  1744. X{
  1745. X    switch (argc) {
  1746. X        case 0:    go(ciId,    ciExit);
  1747. X        case 1:    go(coId,    coExit);
  1748. X        case 2:    go(identId,    identExit);
  1749. X        case 3:    go(rcsdiffId,    rdiffExit);
  1750. X        case 4:    go(rcsmergeId,    rmergeExit);
  1751. X        case 5:    go(rlogId,    rlogExit);
  1752. X        case 6:    go(rcsId,    exiterr);
  1753. X        default: return 0;
  1754. X    }
  1755. X}
  1756. X#endif
  1757. END_OF_FILE
  1758.   if test 42394 -ne `wc -c <'src/rcs.c'`; then
  1759.     echo shar: \"'src/rcs.c'\" unpacked with wrong size!
  1760.   fi
  1761.   # end of 'src/rcs.c'
  1762. fi
  1763. echo shar: End of archive 3 \(of 12\).
  1764. cp /dev/null ark3isdone
  1765. MISSING=""
  1766. for I in 1 2 3 4 5 6 7 8 9 10 11 12 ; do
  1767.     if test ! -f ark${I}isdone ; then
  1768.     MISSING="${MISSING} ${I}"
  1769.     fi
  1770. done
  1771. if test "${MISSING}" = "" ; then
  1772.     echo You have unpacked all 12 archives.
  1773.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1774. else
  1775.     echo You still must unpack the following archives:
  1776.     echo "        " ${MISSING}
  1777. fi
  1778. exit 0
  1779. exit 0 # Just in case...
  1780.