[comp.sources.unix] v24i001: RCS source control system, Part01/12

rsalz@uunet.uu.net (Rich Salz) (02/22/91)

Submitted-by: Adam Hammer <hammer@cs.purdue.edu>
Posting-number: Volume 24, Issue 1
Archive-name: rcs/part01

RCS is a revision control system that keeps audit trails, edit histories,
and so on.  See the README file for more extensive details.  GNU diff,
useful for RCS, will follow this posting.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  PACKNOTES README MANIFEST Makefile man src src/ci.c
# Wrapped by rsalz@litchi.bbn.com on Thu Feb 21 14:36:53 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 1 (of 12)."'
if test -f 'PACKNOTES' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'PACKNOTES'\"
else
  echo shar: Extracting \"'PACKNOTES'\" \(78 characters\)
  sed "s/^X//" >'PACKNOTES' <<'END_OF_FILE'
X# "rcs.ms" was split into 2 parts; to create it, do
X	cat rcs.ms.[1-9] >rcs.ms
END_OF_FILE
  if test 78 -ne `wc -c <'PACKNOTES'`; then
    echo shar: \"'PACKNOTES'\" unpacked with wrong size!
  fi
  # end of 'PACKNOTES'
fi
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
  echo shar: Extracting \"'README'\" \(11703 characters\)
  sed "s/^X//" >'README' <<'END_OF_FILE'
X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
X   Copyright 1990 by Paul Eggert
X   Distributed under license by the Free Software Foundation, Inc.
X
XThis file is part of RCS.
X
XRCS is free software; you can redistribute it and/or modify
Xit under the terms of the GNU General Public License as published by
Xthe Free Software Foundation; either version 1, or (at your option)
Xany later version.
X
XRCS is distributed in the hope that it will be useful,
Xbut WITHOUT ANY WARRANTY; without even the implied warranty of
XMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
XGNU General Public License for more details.
X
XYou should have received a copy of the GNU General Public License
Xalong with RCS; see the file COPYING.  If not, write to
Xthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
X
XReport problems and direct all questions to:
X
X    rcs-bugs@cs.purdue.edu
X
X*/
X
X$Id: README,v 5.6 1990/12/13 06:54:04 eggert Exp $
X
XThis directory contains complete sources for RCS version 5.5.
X
X
XInstallation notes:
X
X  RCS requires a diff that supports the -n option.
X  Get GNU diff (version 1.15 or later) if your diff lacks -n.
X
X  RCS works best with a diff that supports -a, -L, and (diff3 only) -m.
X  GNU diff supports these options.
X
X  Sources for RCS are in the src directory.
X  Read the directions in src/Makefile to set up the options
X  for building RCS on your system.
X  If `make' fails to build src/conf.h, look in src/conf.error
X  to see what went wrong in the src/conf.sh shell file.
X  If all else fails, create src/conf.h manually by editing a
X  copy of src/conf.heg.
X
X  Manual entries reside in man.
X
X  To test your installation of RCS, run the shell file src/rcstest.
X
X  Troff source for the paper `RCS--A System for Version Control', which
X  appeared in _Software--Practice & Experience_, is in rcs.ms.
X
X
XRCS compatibility notes:
X
X  RCS version 5 reads RCS files written by any RCS version released since 1982.
X  It also writes RCS files that these older versions of RCS can read,
X  unless you use one of the following new features:
X
X	checkin times after 1999/12/31 23:59:59 GMT
X	checking in non-text files
X	non-Ascii symbolic names
X	rcs -bX, where X is nonempty
X	rcs -kX, where X is not `kv'
X	RCS files that exceed hardcoded limits in older RCS versions
X
X
XFeatures new to RCS version 5 include:
X
X  RCS can check in arbitrary files, not just text files, if diff -a works.
X  RCS can merge lines containing just a single `.' if diff3 -m works.
X  GNU diff supports the -a and -m options.
X
X  RCS can now be installed as a setgid or setuid program
X  if the setegid() and seteuid() system calls work.
X  Setid privileges yield extra security if RCS files are protected so that
X  only the effective group or user can write RCS directories.
X  RCS uses the real group and user for all accesses other than to RCS files.
X  On older hosts lacking setegid() and seteuid(), RCS uses the effective group
X  and user for all accesses; formerly it was inconsistent.
X
X  New options to co, rcsdiff, and rcsmerge give more flexibility to keyword
X  substitution.
X
X    -kkv substitutes the default `$Keyword: value $' for keyword strings.
X    However, a locker's name is inserted only as a file is being locked,
X    i.e. by `ci -l' and `co -l'.  This is normally the default.
X
X    -kkvl acts like -kkv, except that a locker's name is always inserted
X    if the given revision is currently locked.  This was the default in
X    version 4.  It is now the default only with when using rcsdiff to
X    compare a revision to a working file whose mode is that of a file
X    checked out for changes.
X
X    -kk substitutes just `$Keyword$', which helps to ignore keyword values
X    when comparing revisions.
X
X    -ko retrieves the old revision's keyword string, thus bypassing keyword
X    substitution.
X
X    -kv retrieves just `value'.  This can ease the use of keyword values, but
X    it is dangerous because it causes RCS to lose track of where the keywords
X    are, so for safety the owner write permission of the working file is
X    turned off when -kv is used; to edit the file later, check it out again
X    without -kv.
X
X  rcs -ko sets the default keyword substitution to be in the style of co -ko,
X  and similarly for the other -k options.  This can be useful with binary file
X  formats that cannot tolerate changing the lengths of keyword strings.
X  However it also renders a RCS file readable only by RCS version 5 or later.
X  Use rcs -kkv to restore the usual default substitution.
X
X  RCS can now be used by development groups that span timezone boundaries.
X  All times are now displayed in GMT, and GMT is the default timezone.
X  To use local time with co -d, append ` LT' to the time.
X  When interchanging RCS files with sites running older versions of RCS,
X  users may encounter discrepancies of up to 13 hours in old time stamps.
X  The list of timezone names has been modernized.
X
X  Dates are now displayed using four-digit years, not two-digit years.
X  Years given in -d options must now have four digits.
X  This change is required for RCS to continue to work after 1999/12/31.
X  The form of dates in version 5 RCS files will not change until 2000/01/01,
X  so in the meantime RCS files can still be interchanged with sites
X  running older versions of RCS.  To make room for the longer dates,
X  rlog now outputs `lines: +A -D' instead of `lines added/del: A/D'.
X
X  To help prevent diff programs that are broken or have run out of memory
X  from trashing an RCS file, ci now checks diff output more carefully.
X
X  ci -k now handles the Log keyword, so that checking in a file
X  with -k does not normally alter the file's contents.
X
X  RCS no longer outputs white space at the ends of lines
X  unless the original working file had it.
X  For consistency with other keywords,
X  a space, not a tab, is now output after `$Log:'.
X  Rlog now puts lockers and symbolic names on separate lines in the output
X  to avoid generating lines that are too long.
X  A similar fix has been made to lists in the RCS files themselves.
X
X  RCS no longer outputs the string `Locker: ' when expanding Header or Id
X  keywords.  This saves space and reverts back to version 3 behavior.
X
X  The default branch is not put into the RCS file unless it is nonempty.
X  Therefore, files generated by RCS version 5 can be read by RCS version 3
X  unless they use the default branch feature introduced in version 4.
X  This fixes a compatibility problem introduced by version 4.
X
X  RCS can now emulate older versions of RCS; see `co -V'.
X  This may be useful to overcome compatibility problems
X  due to the above changes.
X
X  Programs like Emacs can now interact with RCS commands via a pipe:
X  the new -I option causes ci, co, and rcs to run interactively,
X  even if standard input is not a terminal.
X  These commands now accept multiple inputs from stdin separated by `.' lines.
X
X  ci now silently ignores the -t option if the RCS file already exists.
X  This simplifies some shell scripts and improves security in setuid sites.
X
X  Descriptive text may be given directly in an argument of the form -t-string.
X
X  The character set for symbolic names has been upgraded
X  from Ascii to ISO 8859.
X
X  rcsdiff now passes through all options used by GNU diff;
X  this is a longer list than 4.3BSD diff.
X
X  merge's new -L option gives tags for merge's overlap report lines.
X  This ability used to be present in a different, undocumented form;
X  the new form is chosen for compatibility with GNU diff3's -L option.
X
X  rcsmerge and merge now have a -q option, just like their siblings do.
X
X  RCS now attempts to ignore parts of an RCS file that look like they come
X  from a future version of RCS.
X
X  When properly configured, RCS now strictly conforms with Posix 1003.1-1988.
X  Normally, RCS file names contain `,', which is outside the Posix portable
X  filename character set; but in impoverished Posix environments, you can
X  compile RCS so that the RCS file for Foo is named just RCS/Foo.
X  RCS can still be compiled in non-Posix traditional Unix environments,
X  and can use common BSD and USG extensions to Posix.
X  RCS is a conforming ANSI C program, and also compiles under traditional C.
X
X  Arbitrary limits on internal table sizes have been removed.
X  The only limit now is the amount of memory available via malloc().
X
X  File temporaries, lock files, signals, and system call return codes
X  are now handled more cleanly, portably, and quickly.
X  Some race conditions have been removed.
X
X  A new compile-time option RCSPREFIX lets administrators avoid absolute path
X  names for subsidiary programs, trading speed for flexibility.
X
X  The configuration procedure is now more automatic.
X
X  Snooping has been removed; it did not work in version 4.
X
X
XVersion 4 was the first version distributed by FSF.
XBeside bug fixes, features new to RCS version 4 include:
X
X  The notion of default branch has been added; see rcs -b.
X
X
XVersion 3 was included in the 4.3BSD distribution.
X
X
XFurther projects:
X
X  Improve performance when checking out branch revisions;
X  see the `piece table' comments in rcs.ms.
X  Joe Berkovitz of Stratus has written some fast revision extraction code;
X  unfortunately there wasn't enough time to integrate it into RCS version 5.
X  It's probably best to use mmap() here if available.
X
X  Let the user mark an RCS revision as deleted; checking out such a revision
X  would result in no working file.  Similarly, using `co -d' with a date either
X  before the initial revision or after the file was marked deleted should
X  remove the working file.  For extra credit, extend the notion of `deleted' to
X  include `renamed', i.e. when an RCS file gets renamed.
X
X  Use a better scheme for locking revisions; the current scheme requires
X  changing the RCS file just to lock or unlock a revision.
X  The new scheme should coexist as well as possible with older versions of RCS.
X
X  Permit multiple option-filename pairs, e.g. co -r1.4 a -r1.5 b.
X
X  Add rcs options for changing keyword names, e.g. XConsortium instead of Id.
X
X  If there are multiple locks by a user, ci should fall back on ci -k's
X  method to figure out which version it is.
X
X  Add frozen branches a la SCCS.  In general, be able to emulate all of
X  SCCS, so that an SCCS-to-RCS program can be practical.
X
X  Improve RCS's method for storing binary files.
X  Although it is more efficient than SCCS's,
X  the diff algorithm is still line oriented,
X  and often generates long output for minor changes to an executable file.
X
X  Port binary file handling to non-Unix hosts where fopen(F,"r") and
X  fopen(F,"rb") are quite different beasts.
X
X  Extend the grammar of RCS files so that keywords need not be in a fixed order.
X
X  Clean up the source code with a consistent indenting style.
X
X  Update the date parser to use the more modern getdate.y by Bellovin, Salz,
X  and Berets.
X
X  Internationalize messages; unfortunately, there's no common standard yet.
X
X  Prune the unnecessary keyword substitution baggage from the rcs command.
X
X  Break up the code into a library so that it's easier to write new programs
X  that manipulate RCS files.
X
X
XCredits:
X
X  RCS was designed and built by Walter F. Tichy of Purdue University.
X  RCS version 3 was released in 1983.
X
X  Thomas Narten, Dan Trinkle, and others of Purdue supported RCS through
X  version 4.2, released in 1989.  Guy Harris of Sun contributed many porting
X  fixes.  Paul Eggert of System Development Corporation contributed bug fixes
X  and tuneups.  Jay Lepreau contributed 4.3BSD support.
X
X  Paul Eggert of Twin Sun wrote the changes for RCS version 5, released in
X  1990.  Ideas for setgid support were contributed by Bill Hahn of Stratus.
X  Test case ideas were contributed by Matt Cross of Stratus.
X  Adam Hammer of Purdue QAed.
END_OF_FILE
  if test 11703 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
  fi
  # end of 'README'
fi
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
  echo shar: Extracting \"'MANIFEST'\" \(1641 characters\)
  sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X----------------------------------------------------------
XPACKNOTES                  1	Warnings about long lines, etc
XREADME                     1	
XMANIFEST                   1	
XCOPYING                    8	
XMakefile                   1	
Xman                        1	
Xman/Makefile              12	
Xman/ci.1                   9	
Xman/co.1                   9	
Xman/ident.1               12	
Xman/merge.1               11	
Xman/rcs.1                 11	
Xman/rcsclean.1            11	
Xman/rcsdiff.1             11	
Xman/rcsfile.5             11	
Xman/rcsfreeze.1           12	
Xman/rcsintro.1            10	
Xman/rcsmerge.1            11	
Xman/rlog.1                 6	
Xrcs.ms.01                  2	(part 1)
Xrcs.ms.02                 11	(part 2)
Xrcs_func.ms               10	
Xsrc                        1	
Xsrc/Makefile               3	
Xsrc/ci.c                   1	
Xsrc/co.c                   7	
Xsrc/conf.heg              11	
Xsrc/conf.sh                9	
Xsrc/ident.c               11	
Xsrc/maketime.c            10	
Xsrc/merge.sh              11	
Xsrc/partime.c              8	
Xsrc/rcs.c                  3	
Xsrc/rcsbase.h              6	
Xsrc/rcsclean.sh           12	
Xsrc/rcsdiff.c             10	
Xsrc/rcsedit.c              6	
Xsrc/rcsfcmp.c              7	
Xsrc/rcsfnms.c              5	
Xsrc/rcsfreeze.sh          11	
Xsrc/rcsgen.c               9	
Xsrc/rcskeep.c             10	
Xsrc/rcskeys.c              2	
Xsrc/rcslex.c               5	
Xsrc/rcsmap.c              11	
Xsrc/rcsmerge.c            10	
Xsrc/rcsrev.c               7	
Xsrc/rcssyn.c               8	
Xsrc/rcsutil.c              4	
Xsrc/rlog.c                 4	
END_OF_FILE
  if test 1641 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
  fi
  # end of 'MANIFEST'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
  echo shar: Extracting \"'Makefile'\" \(300 characters\)
  sed "s/^X//" >'Makefile' <<'END_OF_FILE'
XSUBDIR=	src man
XDESTDIR=
X
Xall: ${SUBDIR}
X
X${SUBDIR}: FRC
X	cd $@; make ${MFLAGS} DESTDIR=${DESTDIR}
X
Xinstall:
X	for i in ${SUBDIR}; do \
X		(cd $$i; make ${MFLAGS} DESTDIR=${DESTDIR} install); \
X	done
X
Xclean:
X	for i in ${SUBDIR}; do \
X		(cd $$i; make ${MFLAGS} DESTDIR=${DESTDIR} clean); \
X	done
X
XFRC:
X
END_OF_FILE
  if test 300 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
  fi
  # end of 'Makefile'
fi
if test ! -d 'man' ; then
    echo shar: Creating directory \"'man'\"
    mkdir 'man'
fi
if test ! -d 'src' ; then
    echo shar: Creating directory \"'src'\"
    mkdir 'src'
fi
if test -f 'src/ci.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/ci.c'\"
else
  echo shar: Extracting \"'src/ci.c'\" \(34353 characters\)
  sed "s/^X//" >'src/ci.c' <<'END_OF_FILE'
X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
X   Copyright 1990 by Paul Eggert
X   Distributed under license by the Free Software Foundation, Inc.
X
XThis file is part of RCS.
X
XRCS is free software; you can redistribute it and/or modify
Xit under the terms of the GNU General Public License as published by
Xthe Free Software Foundation; either version 1, or (at your option)
Xany later version.
X
XRCS is distributed in the hope that it will be useful,
Xbut WITHOUT ANY WARRANTY; without even the implied warranty of
XMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
XGNU General Public License for more details.
X
XYou should have received a copy of the GNU General Public License
Xalong with RCS; see the file COPYING.  If not, write to
Xthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
X
XReport problems and direct all questions to:
X
X    rcs-bugs@cs.purdue.edu
X
X*/
X
X/*
X *                     RCS checkin operation
X */
X/*******************************************************************
X *                       check revisions into RCS files
X *******************************************************************
X */
X
X
X
X/* $Log: ci.c,v $
X * Revision 5.12  1990/12/31  01:00:12  eggert
X * Don't use uninitialized storage when handling -{N,n}.
X *
X * Revision 5.11  1990/12/04  05:18:36  eggert
X * Use -I for prompts and -q for diagnostics.
X *
X * Revision 5.10  1990/11/05  20:30:10  eggert
X * Don't remove working file when aborting due to no changes.
X *
X * Revision 5.9  1990/11/01  05:03:23  eggert
X * Add -I and new -t behavior.  Permit arbitrary data in logs.
X *
X * Revision 5.8  1990/10/04  06:30:09  eggert
X * Accumulate exit status across files.
X *
X * Revision 5.7  1990/09/25  20:11:46  hammer
X * fixed another small typo
X *
X * Revision 5.6  1990/09/24  21:48:50  hammer
X * added cleanups from Paul Eggert.
X *
X * Revision 5.5  1990/09/21  06:16:38  hammer
X * made it handle multiple -{N,n}'s.  Also, made it treat re-directed stdin
X * the same as the terminal
X *
X * Revision 5.4  1990/09/20  02:38:51  eggert
X * ci -k now checks dates more thoroughly.
X *
X * Revision 5.3  1990/09/11  02:41:07  eggert
X * Fix revision bug with `ci -k file1 file2'.
X *
X * Revision 5.2  1990/09/04  08:02:10  eggert
X * Permit adjacent revisions with identical time stamps (possible on fast hosts).
X * Improve incomplete line handling.  Standardize yes-or-no procedure.
X *
X * Revision 5.1  1990/08/29  07:13:44  eggert
X * Expand locker value like co.  Clean old log messages too.
X *
X * Revision 5.0  1990/08/22  08:10:00  eggert
X * Don't require a final newline.
X * Make lock and temp files faster and safer.
X * Remove compile-time limits; use malloc instead.
X * Permit dates past 1999/12/31.  Switch to GMT.
X * Add setuid support.  Don't pass +args to diff.  Check diff's output.
X * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
X * Check diff's output.
X *
X * Revision 4.9  89/05/01  15:10:54  narten
X * changed copyright header to reflect current distribution rules
X * 
X * Revision 4.8  88/11/08  13:38:23  narten
X * changes from root@seismo.CSS.GOV (Super User)
X * -d with no arguments uses the mod time of the file it is checking in
X * 
X * Revision 4.7  88/08/09  19:12:07  eggert
X * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one.
X * Use execv(), not system(); allow cc -R; remove lint.
X * isatty(fileno(stdin)) -> ttystdin()
X * 
X * Revision 4.6  87/12/18  11:34:41  narten
X * lint cleanups (from Guy Harris)
X * 
X * Revision 4.5  87/10/18  10:18:48  narten
X * Updating version numbers. Changes relative to revision 1.1 are actually
X * relative to 4.3
X * 
X * Revision 1.3  87/09/24  13:57:19  narten
X * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
X * warnings)
X * 
X * Revision 1.2  87/03/27  14:21:33  jenkins
X * Port to suns
X * 
X * Revision 4.3  83/12/15  12:28:54  wft
X * ci -u and ci -l now set mode of working file properly.
X * 
X * Revision 4.2  83/12/05  13:40:54  wft
X * Merged with 3.9.1.1: added calls to clearerr(stdin).
X * made rewriteflag external.
X * 
X * Revision 4.1  83/05/10  17:03:06  wft
X * Added option -d and -w, and updated assingment of date, etc. to new delta.
X * Added handling of default branches.
X * Option -k generates std. log message; fixed undef. pointer in reading of log.
X * Replaced getlock() with findlock(), link--unlink with rename(),
X * getpwuid() with getcaller().
X * Moved all revision number generation to new routine addelta().
X * Removed calls to stat(); now done by pairfilenames().
X * Changed most calls to catchints() with restoreints().
X * Directed all interactive messages to stderr.
X * 
X * Revision 3.9.1.1  83/10/19  04:21:03  lepreau
X * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
X * 
X * Revision 3.9  83/02/15  15:25:44  wft
X * 4.2 prerelease
X * 
X * Revision 3.9  83/02/15  15:25:44  wft
X * Added call to fastcopy() to copy remainder of RCS file.
X *
X * Revision 3.8  83/01/14  15:34:05  wft
X * Added ignoring of interrupts while new RCS file is renamed;
X * Avoids deletion of RCS files by interrupts.
X *
X * Revision 3.7  82/12/10  16:09:20  wft
X * Corrected checking of return code from diff.
X *
X * Revision 3.6  82/12/08  21:34:49  wft
X * Using DATEFORM to prepare date of checked-in revision;
X * Fixed return from addbranch().
X *
X * Revision 3.5  82/12/04  18:32:42  wft
X * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated
X * field lockedby in removelock(), moved getlogmsg() before calling diff.
X *
X * Revision 3.4  82/12/02  13:27:13  wft
X * added option -k.
X *
X * Revision 3.3  82/11/28  20:53:31  wft
X * Added mustcheckin() to check for redundant checkins.
X * Added xpandfile() to do keyword expansion for -u and -l;
X * -m appends linefeed to log message if necessary.
X * getlogmsg() suppresses prompt if stdin is not a terminal.
X * Replaced keeplock with lockflag, fclose() with ffclose(),
X * %02d with %.2d, getlogin() with getpwuid().
X *
X * Revision 3.2  82/10/18  20:57:23  wft
X * An RCS file inherits its mode during the first ci from the working file,
X * otherwise it stays the same, except that write permission is removed.
X * Fixed ci -l, added ci -u (both do an implicit co after the ci).
X * Fixed call to getlogin(), added call to getfullRCSname(), added check
X * for write error.
X * Changed conflicting identifiers.
X *
X * Revision 3.1  82/10/13  16:04:59  wft
X * fixed type of variables receiving from getc() (char -> int).
X * added include file dbm.h for getting BYTESIZ. This is used
X * to check the return code from diff portably.
X */
X
X#include "rcsbase.h"
X
Xstruct Symrev {
X       const char *ssymbol;
X       int override;
X       struct Symrev * nextsym;
X};
X
X/* rcsfcmp */
Xint rcsfcmp P((const char*,const char*,const struct hshentry*));
X
X/* rcskeep */
Xextern char prevdate[];
Xextern struct buf prevauthor, prevrev, prevstate;
Xint getoldkeys P((FILE*));
X
Xstatic const char *xpandfile P((const char*,const char*,const struct hshentry*));
Xstatic const char *getdate P((void));
Xstatic int addbranch P((struct hshentry*,struct buf*));
Xstatic int addelta P((void));
Xstatic int mustcheckin P((const char*,const struct hshentry*));
Xstatic struct cbuf getlogmsg P((void));
Xstatic struct hshentry *removelock P((struct hshentry*));
Xstatic void cleanup P((void));
Xstatic void incnum P((const char*,struct buf*));
Xstatic void addassoclst P((int, char *));
X
Xstatic const char diff[] = DIFF;
X
Xstatic FILE *workptr;			/* working file pointer		*/
Xstatic const char *olddeltanum;		/* number of old delta		*/
Xstatic struct buf newdelnum;		/* new revision number		*/
Xstatic struct cbuf msg;
Xstatic int exitstatus;
Xstatic int forceciflag;			/* forces check in		*/
Xstatic int keepflag, keepworkingfile, rcsinitflag;
Xstatic struct hshentries *gendeltas;	/* deltas to be generated	*/
Xstatic struct hshentry *targetdelta;	/* old delta to be generated	*/
Xstatic struct hshentry newdelta;	/* new delta to be inserted	*/
Xstatic struct Symrev *assoclst, *lastassoc;
X
XmainProg(ciId, "ci", "$Id: ci.c,v 5.12 1990/12/31 01:00:12 eggert Exp $")
X{
X	static const char cmdusage[] =
X		"\nci usage: ci -{fklqru}[rev] -mmsg -{nN}name -sstate -t[textfile] -Vn file ...";
X
X	char altdate[datesize];
X	const char *author, *krev, *rev, *state, *textfile;
X	const char *diffilename, *expfilename;
X	const char *workdiffname, *newworkfilename;
X	int exit_stats;		 /* return code for command invocations     */
X	int lockflag;
X	int r;
X	int usestatdate; /* Use mod time of file for -d.  */
X	mode_t newRCSmode; /* mode for RCS file */
X	mode_t newworkmode; /* mode for working file */
X	struct Symrev *curassoc;
X	
X	initid();
X	catchints();
X
X	author = rev = state = textfile = nil;
X	curassoc = assoclst = lastassoc = (struct Symrev *) nil;
X	lockflag = false;
X	altdate[0]= '\0'; /* empty alternate date for -d */
X	usestatdate=false;
X
X        while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
X                switch ((*argv)[1]) {
X
X                case 'r':
X			keepworkingfile = lockflag = false;
X                revno:  if ((*argv)[2]!='\0') {
X				if (rev) warn("redefinition of revision number");
X                                rev = (*argv)+2;
X                        }
X                        break;
X
X                case 'l':
X                        keepworkingfile=lockflag=true;
X                        goto revno;
X
X                case 'u':
X                        keepworkingfile=true; lockflag=false;
X                        goto revno;
X
X		case 'I':
X			interactiveflag = true;
X			goto revno;
X
X                case 'q':
X                        quietflag=true;
X                        goto revno;
X
X                case 'f':
X                        forceciflag=true;
X                        goto revno;
X
X                case 'k':
X                        keepflag=true;
X                        goto revno;
X
X                case 'm':
X			if (msg.size) redefined('m');
X			msg = cleanlogmsg(*argv+2, strlen(*argv+2));
X			if (!msg.size)
X				warn("missing message for -m option");
X                        break;
X
X                case 'n':
X			if ((*argv)[2] == '\0') {
X                                error("missing symbolic name after -n");
X				break;
X            		}
X           		checksid((*argv)+2);
X            		addassoclst(false, (*argv)+2);
X		        break;
X		
X		case 'N':
X			if ((*argv)[2] == '\0') {
X                                error("missing symbolic name after -N");
X				break;
X            		}
X                	checksid((*argv)+2);
X            		addassoclst(true, (*argv)+2);
X		        break;
X
X                case 's':
X                        if ((*argv)[2]!='\0'){
X				if (state) redefined('s');
X				checksid((*argv)+2);
X                                state = (*argv)+2;
X			} else
X				warn("missing state for -s option");
X                        break;
X
X                case 't':
X                        if ((*argv)[2]!='\0'){
X				if (textfile) redefined('t');
X                                textfile = (*argv)+2;
X                        }
X                        break;
X
X		case 'd':
X			if (altdate[0] || usestatdate)
X				redefined('d');
X			altdate[0] = 0;
X			usestatdate = false;
X			if ((*argv)[2])
X				str2date(*argv+2, altdate);
X			else
X				usestatdate = true;
X                        break;
X
X		case 'w':
X                        if ((*argv)[2]!='\0'){
X				if (author) redefined('w');
X				checksid((*argv)+2);
X				author = (*argv)+2;
X			} else
X				warn("missing author for -w option");
X                        break;
X
X		case 'V':
X			setRCSversion(*argv);
X			break;
X
X
X
X                default:
X			faterror("unknown option: %s%s", *argv, cmdusage);
X                };
X        }  /* end processing of options */
X
X	if (argc<1) faterror("no input file%s", cmdusage);
X
X        /* now handle all filenames */
X        do {
X        finptr=frewrite=NULL;
X	fcopy = foutptr = NULL;
X	workptr = NULL;
X        targetdelta=nil;
X        olddeltanum=nil;
X	ffree();
X
X	switch (pairfilenames(argc, argv, rcswriteopen, false, false)) {
X
X        case -1:                /* New RCS file */
X		rcsinitflag = true;
X                break;
X
X        case 0:                 /* Error */
X                continue;
X
X        case 1:                 /* Normal checkin with prev . RCS file */
X		rcsinitflag = !Head;
X        }
X
X        /* now RCSfilename contains the name of the RCS file, and
X         * workfilename contains the name of the working file.
X	 * If the RCS file exists, finptr contains the file descriptor for the
X         * RCS file. The admin node is initialized.
X	 * RCSstat is set.
X         */
X
X	diagnose("%s  <--  %s\n", RCSfilename,workfilename);
X
X	errno = 0;
X	if (!(workptr = fopen(workfilename,"r"))) {
X		eerror(workfilename);
X		continue;
X	}
X	if (!getfworkstat(fileno(workptr))) continue;
X	newRCSmode =
X		  (rcsinitflag ? workstat.st_mode : RCSstat.st_mode)
X		& ~(S_IWUSR|S_IWGRP|S_IWOTH);
X	/* newRCSmode also adjusts mode of working file for -u and -l. */
X	if (finptr && !checkaccesslist()) continue; /* give up */
X
X	krev = rev;
X        if (keepflag) {
X                /* get keyword values from working file */
X		if (!getoldkeys(workptr)) continue;
X		if (!rev  &&  !*(krev = prevrev.string)) {
X			error("can't find a revision number in %s",workfilename);
X                        continue;
X                }
X		if (*prevdate=='\0' && *altdate=='\0' && usestatdate==false)
X			warn("can't find a date in %s", workfilename);
X		if (!*prevauthor.string && !author)
X			warn("can't find an author in %s", workfilename);
X		if (!*prevstate.string && !state)
X			warn("can't find a state in %s", workfilename);
X        } /* end processing keepflag */
X
X        gettree(); /* reads in the delta tree.*/
X
X        /* expand symbolic revision number */
X	if (!expandsym(krev,&newdelnum)) continue;
X
X        /* splice new delta into tree */
X        if (!addelta()) continue;
X
X	if (rcsinitflag) {
X		diagnose("initial revision: %s\n", newdelnum.string);
X	} else  diagnose("new revision: %s; previous revision: %s\n",
X			 newdelnum.string, olddeltanum);
X
X	newdelta.num = newdelnum.string;
X        newdelta.branches=nil;
X        newdelta.lockedby=nil; /*might be changed by addlock() */
X	newdelta.selector = true;
X	/* set author */
X	if (author!=nil)
X		newdelta.author=author;     /* set author given by -w         */
X	else if (keepflag && *prevauthor.string)
X		newdelta.author=prevauthor.string; /* preserve old author if possible*/
X	else    newdelta.author=getcaller();/* otherwise use caller's id      */
X	if (state!=nil)
X		newdelta.state=state;       /* set state given by -s          */
X	else if (keepflag && *prevstate.string)
X		newdelta.state=prevstate.string;   /* preserve old state if possible */
X	else    newdelta.state=DEFAULTSTATE;/* otherwise use default state    */
X	if (usestatdate) {
X	    time2date(workstat.st_mtime, altdate);
X	}
X	if (*altdate!='\0')
X		newdelta.date=altdate;      /* set date given by -d           */
X	else if (keepflag && *prevdate) /* preserve old date if possible  */
X		newdelta.date = prevdate;
X	else
X		newdelta.date = getdate();  /* use current date               */
X	/* now check validity of date -- needed because of -d and -k          */
X	if (targetdelta!=nil &&
X	    cmpnum(newdelta.date,targetdelta->date) < 0) {
X		error("Date %s precedes %s in existing revision %s.",
X		       newdelta.date,targetdelta->date, targetdelta->num);
X		continue;
X	}
X
X
X	if (lockflag  &&  addlock(&newdelta) < 0) continue;
X        curassoc = assoclst;
X	while (curassoc) {
X	        if (!addsymbol(newdelta.num, curassoc->ssymbol, curassoc->override))
X		        break;
X	        curassoc = curassoc->nextsym;
X	}
X	if (curassoc) continue;
X
X    
X        putadmin(frewrite);
X        puttree(Head,frewrite);
X	putdesc(false,textfile);
X
X
X        /* build rest of file */
X	if (rcsinitflag) {
X                /* get logmessage */
X                newdelta.log=getlogmsg();
X		if (!putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false)) continue;
X        } else {
X		diffilename = maketemp(0);
X		workdiffname = workfilename;
X		if (workdiffname[0] == '+') {
X			/* Some diffs have options with leading '+'. */
X			char *w = ftnalloc(char, strlen(workfilename)+3);
X			workdiffname = w;
X			*w++ = '.';
X			*w++ = SLASH;
X			VOID strcpy(w, workfilename);
X		}
X                if (&newdelta==Head) {
X                        /* prepend new one */
X			foutptr = NULL;
X                        if (!(expfilename=
X			      buildrevision(gendeltas,targetdelta,false,false))) continue;
X                        if (!mustcheckin(expfilename,targetdelta)) continue;
X                                /* don't check in files that aren't different, unless forced*/
X                        newdelta.log=getlogmsg();
X                        exit_stats = run((char*)nil,diffilename,
X				diff DIFF_FLAGS, workdiffname, expfilename,
X				(char*)nil);
X			if (!WIFEXITED(exit_stats) || 1<WEXITSTATUS(exit_stats))
X                            faterror ("diff failed");
X			/* diff status is EXIT_TROUBLE on failure. */
X			if (!putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false)) continue;
X			if (!putdtext(olddeltanum,targetdelta->log,diffilename,frewrite,true)) continue;
X                } else {
X                        /* insert new delta text */
X			foutptr = frewrite;
X                        if (!(expfilename=
X			      buildrevision(gendeltas,targetdelta,false,false))) continue;
X                        if (!mustcheckin(expfilename,targetdelta)) continue;
X                                /* don't check in files that aren't different, unless forced*/
X                        newdelta.log=getlogmsg();
X                        exit_stats = run((char*)nil, diffilename,
X				diff DIFF_FLAGS, expfilename, workdiffname,
X				(char*)nil);
X			if (!WIFEXITED(exit_stats) || 1<WEXITSTATUS(exit_stats))
X                            faterror ("diff failed");
X			if (!putdtext(newdelnum.string,newdelta.log,diffilename,frewrite,true)) continue;
X                }
X
X                /* rewrite rest of RCS file */
X                fastcopy(finptr,frewrite);
X		ffclose(finptr); finptr=NULL; /* Help the file system. */
X        }
X	ffclose(frewrite); frewrite=NULL;
X	ffclose(workptr); workptr=NULL;
X	seteid();
X	if ((r = chmod(newRCSfilename,newRCSmode)) == 0) {
X	    ignoreints();
X	    r = re_name(newRCSfilename,RCSfilename);
X	    keepdirtemp(newRCSfilename);
X	    restoreints();
X	}
X	setrid();
X	if (r != 0) {
X		eerror(RCSfilename);
X		error("saved in %s", newRCSfilename);
X		dirtempunlink();
X                break;
X        }
X
X        if (!keepworkingfile) {
X		r = unlink(workfilename); /* Get rid of old file */
X        } else {
X		newworkmode = WORKMODE(newRCSmode,
X			!(Expand == OLD_EXPAND  ||  !lockflag && StrictLocks)
X		);
X		/* Expand if !OLD_EXPAND, or if mode can't be fixed.  */
X		if (
X			Expand != OLD_EXPAND
X		||	(	workstat.st_mode != newworkmode
X			&&	(r = chmod(workfilename,newworkmode)) < 0
X			)
X		) {
X		    /* Expand keywords in file.  */
X		    locker_expansion = lockflag;
X		    newworkfilename=
X		    xpandfile(workfilename,workfilename /*for directory*/,&newdelta);
X		    if (!newworkfilename) continue; /* expand failed */
X		    if ((r = chmod(newworkfilename, newworkmode)) == 0) {
X			ignoreints();
X			r = re_name(newworkfilename,workfilename);
X			keepdirtemp(newworkfilename);
X			restoreints();
X		    }
X		}
X        }
X	if (r != 0) {
X	    eerror(workfilename);
X	    continue;
X	}
X	diagnose("done\n");
X
X        } while (cleanup(),
X                 ++argv, --argc >=1);
X
X	tempunlink();
X	exitmain(exitstatus);
X}       /* end of main (ci) */
X
X	static void
Xcleanup()
X{
X	if (nerror) exitstatus = EXIT_FAILURE;
X	if (finptr) ffclose(finptr);
X	if (frewrite) ffclose(frewrite);
X	if (workptr) ffclose(workptr);
X	dirtempunlink();
X}
X
X#if lint
X#	define exiterr ciExit
X#endif
X	exiting void
Xexiterr()
X{
X	dirtempunlink();
X	tempunlink();
X	_exit(EXIT_FAILURE);
X}
X
X/*****************************************************************/
X/* the rest are auxiliary routines                               */
X
X
X	static int
Xaddelta()
X/* Function: Appends a delta to the delta tree, whose number is
X * given by newdelnum.  Updates Head, newdelnum, newdelnumlength,
X * olddeltanum and the links in newdelta.
X * Returns false on error, true on success.
X */
X{
X	register char *tp;
X	register unsigned i;
X	unsigned newdnumlength;  /* actual length of new rev. num. */
X
X	newdnumlength = countnumflds(newdelnum.string);
X
X	if (rcsinitflag) {
X                /* this covers non-existing RCS file and a file initialized with rcs -i */
X		if ((newdnumlength==0)&&(Dbranch!=nil)) {
X			bufscpy(&newdelnum, Dbranch);
X			newdnumlength = countnumflds(Dbranch);
X		}
X		if (newdnumlength==0) bufscpy(&newdelnum, "1.1");
X		else if (newdnumlength==1) bufscat(&newdelnum, ".1");
X		else if (newdnumlength>2) {
X		    error("Branch point doesn't exist for %s.",newdelnum.string);
X                    return false;
X                } /* newdnumlength == 2 is OK;  */
X                olddeltanum=nil;
X                Head = &newdelta;
X                newdelta.next=nil;
X                return true;
X        }
X        if (newdnumlength==0) {
X                /* derive new revision number from locks */
X		switch (findlock(true, &targetdelta)) {
X
X		  default:
X		    /* found two or more old locks */
X		    return false;
X
X		  case 1:
X                    /* found an old lock */
X                    olddeltanum=targetdelta->num;
X                    /* check whether locked revision exists */
X		    if (!genrevs(olddeltanum,(char*)nil,(char*)nil,(char*)nil,&gendeltas)) return false;
X                    if (targetdelta==Head) {
X                        /* make new head */
X                        newdelta.next=Head;
X                        Head= &newdelta;
X			incnum(olddeltanum, &newdelnum);
X		    } else if (!targetdelta->next && countnumflds(olddeltanum)>2) {
X                        /* new tip revision on side branch */
X                        targetdelta->next= &newdelta;
X                        newdelta.next = nil;
X			incnum(olddeltanum, &newdelnum);
X                    } else {
X                        /* middle revision; start a new branch */
X			bufscpy(&newdelnum, "");
X			if (!addbranch(targetdelta,&newdelnum)) return false;
X                    }
X		    return true; /* successful use of existing lock */
X
X		  case 0:
X                    /* no existing lock; try Dbranch */
X                    /* update newdelnum */
X		    if (StrictLocks || !myself(RCSstat.st_uid)) {
X			error("no lock set by %s",getcaller());
X                        return false;
X                    }
X                    if (Dbranch) {
X			bufscpy(&newdelnum, Dbranch);
X                    } else {
X			incnum(Head->num, &newdelnum);
X                    }
X		    newdnumlength = countnumflds(newdelnum.string);
X                    /* now fall into next statement */
X                }
X        }
X        if (newdnumlength<=2) {
X                /* add new head per given number */
X                olddeltanum=Head->num;
X                if(newdnumlength==1) {
X                    /* make a two-field number out of it*/
X		    if (cmpnumfld(newdelnum.string,olddeltanum,1)==0)
X			incnum(olddeltanum, &newdelnum);
X		    else
X			bufscat(&newdelnum, ".1");
X                }
X		if (cmpnum(newdelnum.string,olddeltanum) <= 0) {
X                    error("deltanumber %s too low; must be higher than %s",
X			  newdelnum.string, Head->num);
X                    return false;
X                }
X		if (!(targetdelta=removelock(Head))) return false;
X		if (!genrevs(olddeltanum,(char*)nil,(char*)nil,(char*)nil,&gendeltas)) return false;
X                newdelta.next=Head;
X                Head= &newdelta;
X        } else {
X                /* put new revision on side branch */
X                /*first, get branch point */
X		tp = newdelnum.string;
X		for (i = newdnumlength - (newdnumlength&1 ^ 1);  (--i);  )
X			while (*tp++ != '.')
X				;
X		*--tp = 0; /* Kill final dot to get old delta temporarily. */
X		if (!(targetdelta=genrevs(newdelnum.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas)))
X                     return false;
X		olddeltanum = targetdelta->num;
X		if (cmpnum(olddeltanum, newdelnum.string) != 0) {
X		    error("can't find branchpoint %s", newdelnum.string);
X                    return false;
X                }
X		*tp = '.'; /* Restore final dot. */
X		if (!addbranch(targetdelta,&newdelnum)) return false;
X        }
X        return true;
X}
X
X
X
X	static int
Xaddbranch(branchpoint,num)
X	struct hshentry *branchpoint;
X	struct buf *num;
X/* adds a new branch and branch delta at branchpoint.
X * If num is the null string, appends the new branch, incrementing
X * the highest branch number (initially 1), and setting the level number to 1.
X * the new delta and branchhead are in globals newdelta and newbranch, resp.
X * the new number is placed into num.
X * returns false on error.
X */
X{
X	struct branchhead *bhead, **btrail;
X	struct buf branchnum;
X	int result;
X	unsigned field, numlength;
X	static struct branchhead newbranch;  /* new branch to be inserted */
X
X	numlength = countnumflds(num->string);
X
X        if (branchpoint->branches==nil) {
X                /* start first branch */
X                branchpoint->branches = &newbranch;
X                if (numlength==0) {
X			bufscpy(num, branchpoint->num);
X			bufscat(num, ".1.1");
X		} else if (numlength&1)
X			bufscat(num, ".1");
X                newbranch.nextbranch=nil;
X
X	} else if (numlength==0) {
X                /* append new branch to the end */
X                bhead=branchpoint->branches;
X                while (bhead->nextbranch) bhead=bhead->nextbranch;
X                bhead->nextbranch = &newbranch;
X		bufautobegin(&branchnum);
X		getbranchno(bhead->hsh->num, &branchnum);
X		incnum(branchnum.string, num);
X		bufautoend(&branchnum);
X		bufscat(num, ".1");
X                newbranch.nextbranch=nil;
X        } else {
X                /* place the branch properly */
X		field = numlength - (numlength&1 ^ 1);
X                /* field of branch number */
X		btrail = &branchpoint->branches;
X		while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) {
X			btrail = &(*btrail)->nextbranch;
X			if (!*btrail) {
X				result = -1;
X				break;
X			}
X                }
X		if (result < 0) {
X                        /* insert/append new branchhead */
X			newbranch.nextbranch = *btrail;
X			*btrail = &newbranch;
X			if (numlength&1) bufscat(num, ".1");
X                } else {
X                        /* branch exists; append to end */
X			bufautobegin(&branchnum);
X			getbranchno(num->string, &branchnum);
X			targetdelta=genrevs(branchnum.string,(char*)nil,
X					    (char*)nil,(char*)nil,&gendeltas);
X			bufautoend(&branchnum);
X			if (!targetdelta) return false;
X                        olddeltanum=targetdelta->num;
X			if (cmpnum(num->string,olddeltanum) <= 0) {
X                                error("deltanumber %s too low; must be higher than %s",
X				      num->string,olddeltanum);
X                                return false;
X                        }
X			if (!removelock(targetdelta)) return false;
X			if (numlength&1) incnum(olddeltanum,num);
X                        targetdelta->next= &newdelta;
X                        newdelta.next=nil;
X                        return true; /* Don't do anything to newbranch */
X                }
X        }
X        newbranch.hsh = &newdelta;
X        newdelta.next=nil;
X        return true;
X}
X
X
X
X	static void
Xincnum(onum,nnum)
X	const char *onum;
X	struct buf *nnum;
X/* Increment the last field of revision number onum by one and
X * place the result into nnum.
X */
X{
X	register const char *sp;
X	register char *tp;
X	register unsigned i;
X
X	sp = onum;
X	bufalloc(nnum, strlen(sp)+2);
X	tp = nnum->string;
X	for (i=countnumflds(sp);  (--i);  ) {
X		while (*sp != '.') *tp++ = *sp++;
X		*tp++ = *sp++;  /* copy dot also */
X	}
X	VOID sprintf(tp, "%d", atoi(sp)+1);
X}
X
X
X
X	static struct hshentry *
Xremovelock(delta)
Xstruct hshentry * delta;
X/* function: Finds the lock held by caller on delta,
X * removes it, and returns a pointer to the delta.
X * Prints an error message and returns nil if there is no such lock.
X * An exception is if !StrictLocks, and caller is the owner of
X * the RCS file. If caller does not have a lock in this case,
X * delta is returned.
X */
X{
X        register struct lock * next, * trail;
X	const char *num;
X        struct lock dummy;
X        int whomatch, nummatch;
X
X        num=delta->num;
X        dummy.nextlock=next=Locks;
X        trail = &dummy;
X        while (next!=nil) {
X		whomatch = strcmp(getcaller(), next->login);
X                nummatch=strcmp(num,next->delta->num);
X                if ((whomatch==0) && (nummatch==0)) break;
X			/*found a lock on delta by caller*/
X                if ((whomatch!=0)&&(nummatch==0)) {
X                    error("revision %s locked by %s",num,next->login);
X                    return nil;
X                }
X                trail=next;
X                next=next->nextlock;
X        }
X        if (next!=nil) {
X                /*found one; delete it */
X                trail->nextlock=next->nextlock;
X                Locks=dummy.nextlock;
X                next->delta->lockedby=nil; /* reset locked-by */
X                return next->delta;
X        } else {
X		if (StrictLocks || !myself(RCSstat.st_uid)) {
X		    error("no lock set by %s for revision %s",getcaller(),num);
X                    return nil;
X                } else {
X                        return delta;
X                }
X        }
X}
X
X
X
X	static const char *
Xgetdate()
X/* Return a pointer to the current date.  */
X{
X	static char buffer[datesize]; /* date buffer */
X	time_t t;
X
X	if (!buffer[0]) {
X		t = time((time_t *)0);
X		if (t == -1)
X			faterror("time not available");
X		time2date(t, buffer);
X	}
X        return buffer;
X}
X
X
X	static const char *
Xxpandfile (unexfname,dir,delta)
X	const char *unexfname, *dir;
X	const struct hshentry *delta;
X/* Function: Reads file unexpfname and copies it to a
X * file in dir, performing keyword substitution with data from delta.
X * returns the name of the expanded file if successful, nil otherwise.
X */
X{
X	const char *targetfname;
X        FILE * unexfile, *exfile;
X
X	targetfname = makedirtemp(dir,0);
X	errno = 0;
X	if (!(unexfile = fopen(unexfname, "r"))) {
X		eerror(unexfname);
X		return nil;
X	}
X	errno = 0;
X	if (!(exfile = fopen(targetfname, "w"))) {
X		eerror(targetfname);
X		error("can't expand file %s",unexfname);
X		ffclose(unexfile);
X                return nil;
X        }
X	if (Expand == OLD_EXPAND)
X		fastcopy(unexfile,exfile);
X	else
X		while (0 < expandline(unexfile,exfile,delta,false,(FILE*)nil))
X			;
X        ffclose(unexfile);ffclose(exfile);
X        return targetfname;
X}
X
X
X	static int
Xmustcheckin (unexfname,delta)
X	const char *unexfname;
X	const struct hshentry *delta;
X/* Function: determines whether checkin should proceed.
X * Compares the workfilename with unexfname, disregarding keywords.
X * If the 2 files differ, returns true. If they do not differ, asks the user
X * whether to return true or false (i.e., whether to checkin the file anyway);
X * the default answer is false.
X * Shortcut: If forceciflag is set, mustcheckin() always returns true.
X */
X{
X	int result;
X
X        if (forceciflag) return true;
X
X        if (!rcsfcmp(workfilename,unexfname,delta)) return true;
X        /* If files are different, must check them in. */
X
X        /* files are the same */
X	if (!(result = yesorno(false,
X		"File %s is unchanged with respect to revision %s\ncheckin anyway? [ny](n): ",
X		workfilename, delta->num
X	))) {
X	    error("%scheckin aborted", 
X		    !quietflag && ttystdin()  ?  ""  :  "file is unchanged; "
X	    );
X        }
X        return result;
X}
X
X
X
X
X/* --------------------- G E T L O G M S G --------------------------------*/
X
X
X	static struct cbuf
Xgetlogmsg()
X/* Function: obtains a log message and returns a pointer to it.
X * If a log message is given via the -m option, a pointer to that
X * string is returned.
X * If this is the initial revision, a standard log message is returned.
X * Otherwise, reads a character string from the terminal.
X * Stops after reading EOF or a single '.' on a
X * line. getlogmsg prompts the first time it is called for the
X * log message; during all later calls it asks whether the previous
X * log message can be reused.
X * returns a pointer to the character string; the pointer is always non-nil.
X */
X{
X	static const char
X		emptych[] = "*** empty log message ***",
X		initialch[] = "Initial revision";
X	static const struct cbuf
X		emptylog = { emptych, sizeof(emptych)-sizeof(char) },
X		initiallog = { initialch, sizeof(initialch)-sizeof(char) };
X	static struct buf logbuf;
X	static struct cbuf logmsg;
X
X	int cin;
X	register char *tp;
X	register size_t i;
X	register const char *p;
X	const char *caller, *date;
X
X	if (keepflag) {
X		/* generate std. log message */
X		caller = getcaller();
X		p = date = getdate();
X		while (*p++ != '.')
X			;
X		i = strlen(caller);
X		bufalloc(&logbuf, sizeof(ciklog)+strlen(caller)+4+datesize);
X		tp = logbuf.string;
X		VOID sprintf(tp,
X			"%s%s at %s%.*s/%.2s/%.2s %.2s:%.2s:%s",
X			ciklog, caller,
X			date[2]=='.' && VERSION(5)<=RCSversion  ?  "19"  :  "",
X			p-date-1, date,
X			p, p+3, p+6, p+9, p+12
X		);
X		logmsg.string = tp;
X		logmsg.size = strlen(tp);
X		return logmsg;
X	}
X
X	if (msg.size) return msg;
X
X	if (!olddeltanum && (
X		cmpnum(newdelnum.string,"1.1")==0 ||
X		cmpnum(newdelnum.string,"1.0")==0
X	))
X		return initiallog;
X
X	if (logmsg.size) {
X                /*previous log available*/
X	    if (yesorno(true, "reuse log message of previous file? [yn](y): "))
X		return logmsg;
X        }
X
X        /* now read string from stdin */
X	if (feof(stdin))
X	    faterror("can't reread redirected stdin for log message; use -m");
X	if (ttystdin())
X		aputs("enter log message:\n(terminate with single '.' or end of file)\n>> ",stderr);
X
X	i = 0;
X	tp = logbuf.string;
X	while ((cin = getcstdin()) != EOF) {
X		if (cin=='\n') {
X			if (i && tp[i-1]=='.' && (i==1 || tp[i-2]=='\n')) {
X				/* Remove trailing '.'. */
X				--i;
X				break;
X			}
X			if (ttystdin()) aputs(">> ", stderr);
X		}
X		bufrealloc(&logbuf, i+1);
X		tp = logbuf.string;
X		tp[i++] = cin;
X                /*SDELIM will be changed to double SDELIM by putdtext*/
X        } /* end for */
X
X        /* now check whether the log message is not empty */
X	logmsg = cleanlogmsg(tp, i);
X	if (logmsg.size)
X		return logmsg;
X	return emptylog;
X}
X
X/*  Make a linked list of Symbolic names  */
X
X        static void
Xaddassoclst(flag, sp)
Xint  flag;
Xchar * sp;
X{
X        struct Symrev *pt;
X	
X	pt = talloc(struct Symrev);
X	pt->ssymbol = sp;
X	pt->override = flag;
X	pt->nextsym = nil;
X	if (lastassoc)
X	        lastassoc->nextsym = pt;
X	else
X	        assoclst = pt;
X	lastassoc = pt;
X	return;
X}
END_OF_FILE
  if test 34353 -ne `wc -c <'src/ci.c'`; then
    echo shar: \"'src/ci.c'\" unpacked with wrong size!
  fi
  # end of 'src/ci.c'
fi
echo shar: End of archive 1 \(of 12\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 12 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0
exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.