[comp.sources.unix] v22i019: Brian Berliner's concurrent RCS system, Part05/07

rsalz@uunet.uu.net (Rich Salz) (05/04/90)

Submitted-by: Brian Berliner <berliner@prisma.com>
Posting-number: Volume 22, Issue 19
Archive-name: cvs-berliner/part05

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 5 (of 7)."
# Contents:  examples/modules src/commit.c src/partime.c
# Wrapped by rsalz@litchi.bbn.com on Thu May  3 16:59:05 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'examples/modules' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'examples/modules'\"
else
echo shar: Extracting \"'examples/modules'\" \(14454 characters\)
sed "s/^X//" >'examples/modules' <<'END_OF_FILE'
X#
X# CVS Modules file for Prisma sources
X# $Id: modules,v 1.1 89/08/25 00:00:15 berliner Exp $
X#
X# Three differnt line formats are valid:
X#	key     -a    aliases...
X#	key [options] directory
X#	key [options] directory files...
X#
X# Where "options" are composed of:
X#	-i prog		Run "prog" on checkin of files
X#	-o prog		Run "prog" on "checkout" of files
X#	-t prog		Run "prog" on tagging of files
X#
X
X# Convenient aliases
Xworld		-a .
Xkernel		-a sys lang/adb sparcsim
X
X# CVSROOT.adm support
XCVSROOT		-i /usr/local/bin/mkmodules CVSROOT.adm
XCVSROOT.adm	-i /usr/local/bin/mkmodules CVSROOT.adm
Xmodules		-i /usr/local/bin/mkmodules CVSROOT.adm modules
Xloginfo		-i /usr/local/bin/mkmodules CVSROOT.adm loginfo
X
X# The "sys" entry exists only to make symbolic links after checkout
Xsys		-o sys/tools/make_links sys
X
X# Sub-directories of "bin"
Xawk		bin/awk
Xcsh		bin/csh
Xdiff		bin/diff
Xmake		bin/make
Xsed		bin/sed
Xsh		bin/sh
X
X# Programs that live in "bin"
Xcat		bin Makefile cat.c
Xchgrp		bin Makefile chgrp.c
Xchmod		bin Makefile chmod.c
Xcmp		bin Makefile cmp.c
Xcp		bin Makefile cp.c
Xdate		bin Makefile date.c
Xdd		bin Makefile dd.c
Xdf		bin Makefile df.c
Xdomainname	bin Makefile domainname.c
Xdu		bin Makefile du.c
Xecho		bin Makefile echo.c
Xed		bin Makefile ed.c
Xenv		bin Makefile env.c
Xexpr		bin Makefile expr.c
Xgrep		bin Makefile grep.c
Xhostid		bin Makefile hostid.c
Xhostname	bin Makefile hostname.c
Xkill		bin Makefile kill.c
Xldd		bin Makefile ldd.c
Xline		bin Makefile line.c
Xln		bin Makefile ln.c
Xlogin		bin Makefile login.c
Xls		bin Makefile ls.c
Xmail		bin Makefile mail.c
Xmkdir		bin Makefile mkdir.c
Xmt		bin Makefile mt.c
Xmv		bin Makefile mv.c
Xnewgrp		bin Makefile newgrp.c
Xnice		bin Makefile nice.c
Xod		bin Makefile od.c
Xpagesize	bin Makefile pagesize.c
Xpasswd		bin Makefile passwd.c
Xpr		bin Makefile pr.c
Xps		bin Makefile ps.c
Xpwd		bin Makefile pwd.c
Xrm		bin Makefile rm.c
Xrmail		bin Makefile rmail.c
Xrmdir		bin Makefile rmdir.c
Xstty		bin Makefile stty.c
Xsu		bin Makefile su.c
Xsync		bin Makefile sync.c
Xtar		bin Makefile tar.c
Xtee		bin Makefile tee.c
Xtest		bin Makefile test.c
Xtime		bin Makefile time.c
Xwall		bin Makefile wall.c
Xwho		bin Makefile who.c
Xwrite		bin Makefile write.c
X
X# Sub-directories of "etc"
Xdump		etc/dump
Xfiles		etc/files
Xfsck		etc/fsck
Xgetty		etc/getty
Xin.routed	etc/in.routed
Xrestore		etc/restore
Xrpc.lockd	etc/rpc.lockd
Xrpc.statd	etc/rpc.statd
X
X# Programs that live in "etc"
Xarp		etc Makefile arp.c
Xbiod		etc Makefile biod.c
Xchown		etc Makefile chown.c
Xclri		etc Makefile clri.c
Xdkinfo		etc Makefile dkinfo.c
Xdmesg		etc Makefile dmesg.c
Xfsirand		etc Makefile fsirand.c
Xhalt		etc Makefile halt.c
Xifconfig	etc Makefile ifconfig.c
Xin.rlogind	etc Makefile in.rlogind.c
Xin.rshd		etc Makefile in.rshd.c
Xinetd		etc Makefile inetd.c
Xinit		etc Makefile init.c
Xmkfs		etc Makefile mkfs.c
Xmknod		etc Makefile mknod.c
Xmount		etc Makefile mount.c
Xnewfs		etc Makefile newfs.c
Xnfsd		etc Makefile nfsd.c
Xportmap		etc Makefile portmap.c
Xpstat		etc Makefile pstat.c
Xreboot		etc Makefile reboot.c
Xrenice		etc Makefile renice.c
Xrmt		etc Makefile rmt.c
Xshutdown	etc Makefile shutdown.c
Xsyslogd		etc Makefile syslogd.c
Xumount		etc Makefile umount.c
Xupdate		etc Makefile update.c
Xvipw		etc Makefile vipw.c
Xypbind		etc Makefile ypbind.c
X
X# Sub-directories of "games"
Xadventure	games/adventure
Xbackgammon	games/backgammon
Xbattlestar	games/battlestar
Xboggle		games/boggle
Xchess		games/chess
Xching		games/ching
Xcribbage	games/cribbage
Xfortune		games/fortune
Xhack		games/hack
Xhangman		games/hangman
Xhunt		games/hunt
Xlife		games/life
Xmille		games/mille
Xmonop		games/monop
Xquiz		games/quiz
Xrobots		games/robots
Xsail		games/sail
Xsnake		games/snake
Xtrek		games/trek
X
X# Programs that live in "games"
Xarithmetic	games Makefile arithmetic.c
Xbanner		games Makefile banner.c
Xbcd		games Makefile bcd.c
Xbj		games Makefile bj.c
Xbtlgammon	games Makefile btlgammon.c
Xcanfield	games Makefile canfield.c
Xcfscores	games Makefile cfscores.c
Xcraps		games Makefile craps.c
Xfactor		games Makefile factor.c
Xfish		games Makefile fish.c
Xmoo		games Makefile moo.c
Xnumber		games Makefile number.c
Xprimes		games Makefile primes.c
Xrain		games Makefile rain.c
Xrandom		games Makefile random.c
Xworm		games Makefile worm.c
Xworms		games Makefile worms.c
Xwump		games Makefile wump.c
X
X# Sub-directories of "lang"
Xadb		lang/adb
Xas		lang/as
Xboot		lang/boot
Xc2		lang/c2
Xcgrdr		lang/cgrdr
Xcompile		lang/compile
Xcpp		lang/cpp
Xdbx		lang/dbx
Xf77		lang/f77
Xinline		lang/inline
Xiropt		lang/iropt
Xld		lang/ld
Xlint		lang/lint
Xm4		lang/m4
Xpascal		lang/pascal
Xpcc		lang/pcc
Xratfor		lang/ratfor
Xrtld		lang/rtld
Xtcov		lang/tcov
Xvroot		lang/vroot
X
X# Programs that live in "lang"
Xar		lang Makefile ar.c
Xnm		lang Makefile nm.c
Xranlib		lang Makefile ranlib.c
Xsize		lang Makefile size.c
Xstrip		lang Makefile strip.c
Xsymorder	lang Makefile symorder.c
X
X# Sub-directories of "lib"
Xcsu		lib/csu
Xlibc		lib/libc
X
X# Programs that live in "lib"
X# NONE
X
X# Sub-directories of "lib/libc"
Xlibc_compat	lib/libc/compat
Xlibc_crt	lib/libc/crt
Xlibc_des	lib/libc/des
Xlibc_gen	lib/libc/gen
Xlibc_net	lib/libc/net
Xlibc_inet	lib/libc/inet
Xlibc_rpc	lib/libc/rpc
Xlibc_stdio	lib/libc/stdio
Xlibc_sun	lib/libc/sun
Xlibc_sys	lib/libc/sys
Xlibc_yp		lib/libc/yp
X
X# Programs that live in "lib/libc"
X# NONE
X
X#Sub-directories of "local"
Xnotes		local/notes
X
X# Sub-directories of "man"
Xman1		man/man1
Xman2		man/man2
Xman3		man/man3
Xman4		man/man4
Xman5		man/man5
Xman6		man/man6
Xman7		man/man7
Xman8		man/man8
Xmanl		man/manl
X
X# Programs that live in "man"
X# NONE
X
X# Sub-directories of "old"
Xold_compact	old/compact
Xold_eyacc	old/eyacc
Xold_filemerge	old/filemerge
Xold_make	old/make
X
X# Programs that live in "old"
Xold_analyze	old Makefile analyze.c
Xold_prmail	old Makefile prmail.c
Xold_pti		old Makefile pti.c
Xold_syslog	old Makefile syslog.c
X
X# Sub-directories of "ucb"
XMail		ucb/Mail
Xcompress	ucb/compress
Xerror		ucb/error
Xex		ucb/ex
Xftp		ucb/ftp
Xgprof		ucb/gprof
Xindent		ucb/indent
Xlpr		ucb/lpr
Xmore		ucb/more
Xmsgs		ucb/msgs
Xnetstat		ucb/netstat
Xrdist		ucb/rdist
Xtalk		ucb/talk
Xtftp		ucb/tftp
Xtset		ucb/tset
Xvgrind		ucb/vgrind
X
X# Programs that live in "ucb"
Xbiff		ucb Makefile biff.c
Xchecknr		ucb Makefile checknr.c
Xclear		ucb Makefile clear.c
Xcolcrt		ucb Makefile colcrt.c
Xcolrm		ucb Makefile colrm.c
Xctags		ucb Makefile ctags.c
Xexpand		ucb Makefile expand.c
Xfinger		ucb Makefile finger.c
Xfold		ucb Makefile fold.c
Xfrom		ucb Makefile from.c
Xfsplit		ucb Makefile fsplit.c
Xgcore		ucb Makefile gcore.c
Xgroups		ucb Makefile groups.c
Xhead		ucb Makefile head.c
Xlast		ucb Makefile last.c
Xlastcomm	ucb Makefile lastcomm.c
Xleave		ucb Makefile leave.c
Xlogger		ucb Makefile logger.c
Xman_prog	ucb Makefile man.c
Xmkstr		ucb Makefile mkstr.c
Xprintenv	ucb Makefile printenv.c
Xquota		ucb Makefile quota.c
Xrcp		ucb Makefile rcp.c
Xrdate		ucb Makefile rdate.c
Xrlogin		ucb Makefile rlogin.c
Xrsh		ucb Makefile rsh.c
Xrup		ucb Makefile rup.c
Xruptime		ucb Makefile ruptime.c
Xrusers		ucb Makefile rusers.c
Xrwho		ucb Makefile rwho.c
Xsccs		ucb Makefile sccs.c
Xscript		ucb Makefile script.c
Xsoelim		ucb Makefile soelim.c
Xstrings		ucb Makefile strings.c
Xtail		ucb Makefile tail.c
Xtcopy		ucb Makefile tcopy.c
Xtelnet		ucb Makefile telnet.c
Xul		ucb Makefile ul.c
Xunexpand	ucb Makefile unexpand.c
Xunifdef		ucb Makefile unifdef.c
Xusers		ucb Makefile users.c
Xvmstat		ucb Makefile vmstat.c
Xw		ucb Makefile w.c
Xwc		ucb Makefile wc.c
Xwhat		ucb Makefile what.c
Xwhatis		ucb Makefile whatis.c
Xwhereis		ucb Makefile whereis.c
Xwhoami		ucb Makefile whoami.c
Xwhois		ucb Makefile whois.c
Xxstr		ucb Makefile xstr.c
Xyes		ucb Makefile yes.c
X
X# Sub-directories of "usr.bin"
Xcalendar	usr.bin/calendar
Xcflow		usr.bin/cflow
Xctrace		usr.bin/ctrace
Xcxref		usr.bin/cxref
Xdc		usr.bin/dc
Xdes		usr.bin/des
Xdiff3		usr.bin/diff3
Xsun_eqn		usr.bin/eqn
Xfile		usr.bin/file
Xfind		usr.bin/find
Xgraph		usr.bin/graph
Xlex		usr.bin/lex
Xsun_neqn	usr.bin/neqn
Xsun_nroff	usr.bin/nroff
Xsun_plot	usr.bin/plot
Xprof		usr.bin/prof
Xrefer		usr.bin/refer
Xrpcgen		usr.bin/rpcgen
Xspell		usr.bin/spell
Xsun_tbl		usr.bin/tbl
Xtip		usr.bin/tip
Xtrace		usr.bin/trace
Xsun_troff	usr.bin/troff
Xuucp		usr.bin/uucp
Xxsend		usr.bin/xsend
Xyacc		usr.bin/yacc
X
X# Programs that live in "usr.bin"
Xbasename	usr.bin Makefile basename.c
Xbc		usr.bin Makefile bc.c
Xcal		usr.bin Makefile cal.c
Xcb		usr.bin Makefile cb.c
Xcheckeq		usr.bin Makefile checkeq.c
Xchkey		usr.bin Makefile chkey.c
Xclick		usr.bin Makefile click.c
Xcol		usr.bin Makefile col.c
Xcomm		usr.bin Makefile comm.c
Xcpio		usr.bin Makefile cpio.c
Xcrypt		usr.bin Makefile crypt.c
Xcsplit		usr.bin Makefile csplit.c
Xcut		usr.bin Makefile cut.c
Xderoff		usr.bin Makefile deroff.c
Xegrep		usr.bin Makefile egrep.c
Xfgrep		usr.bin Makefile fgrep.c
Xgetopt		usr.bin Makefile getopt.c
Xid		usr.bin Makefile id.c
Xinstallcmd	usr.bin Makefile installcmd.c
Xiostat		usr.bin Makefile iostat.c
Xipcrm		usr.bin Makefile ipcrm.c
Xipcs		usr.bin Makefile ipcs.c
Xjoin		usr.bin Makefile join.c
Xkeylogin	usr.bin Makefile keylogin.c
Xlogname		usr.bin Makefile logname.c
Xlook		usr.bin Makefile look.c
Xmesg		usr.bin Makefile mesg.c
Xnl		usr.bin Makefile nl.c
Xpack		usr.bin Makefile pack.c
Xpaste		usr.bin Makefile paste.c
Xptx		usr.bin Makefile ptx.c
Xrev		usr.bin Makefile rev.c
Xscreenblank	usr.bin Makefile screenblank.c
Xsdiff		usr.bin Makefile sdiff.c
Xsleep		usr.bin Makefile sleep.c
Xsort		usr.bin Makefile sort.c
Xspline		usr.bin Makefile spline.c
Xsplit		usr.bin Makefile split.c
Xsum		usr.bin Makefile sum.c
Xtouch		usr.bin Makefile touch.c
Xtr		usr.bin Makefile tr.c
Xtsort		usr.bin Makefile tsort.c
Xtty		usr.bin Makefile tty.c
Xuniq		usr.bin Makefile uniq.c
Xunits		usr.bin Makefile units.c
Xunpack		usr.bin Makefile unpack.c
Xxargs		usr.bin Makefile xargs.c
Xypcat		usr.bin Makefile ypcat.c
Xypmatch		usr.bin Makefile ypmatch.c
Xyppasswd	usr.bin Makefile yppasswd.c
Xypwhich		usr.bin Makefile ypwhich.c
X
X# Sub-directories of "usr.etc"
Xautomount	usr.etc/automount
Xc2convert	usr.etc/c2convert
Xconfig		usr.etc/config
Xcron		usr.etc/cron
Xeeprom		usr.etc/eeprom
Xetherfind	usr.etc/etherfind
Xformat		usr.etc/format
Xhtable		usr.etc/htable
Ximplog		usr.etc/implog
Xin.ftpd		-a usr.etc/in.ftpd ucb/ftp
Xin.named	usr.etc/in.named
Xin.rwhod	usr.etc/in.rwhod
Xkeyserv		usr.etc/keyserv
Xndbootd		usr.etc/ndbootd
Xpraudit		usr.etc/praudit
Xrexd		usr.etc/rexd
Xrpc.bootparamd	usr.etc/rpc.bootparamd
Xtermcap		usr.etc/termcap
Xupgrade		usr.etc/upgrade
Xyp		usr.etc/yp
Xzic		usr.etc/zic
X
X# Programs that live in "usr.etc"
Xac		usr.etc Makefile ac.c
Xaccton		usr.etc Makefile accton.c
Xaudit		usr.etc Makefile audit.c
Xauditd		usr.etc Makefile auditd.c
Xcatman		usr.etc Makefile catman.c
Xchroot		usr.etc Makefile chroot.c
Xdcheck		usr.etc Makefile dcheck.c
Xdevnm		usr.etc Makefile devnm.c
Xdumpfs		usr.etc Makefile dumpfs.c
Xedquota		usr.etc Makefile edquota.c
Xexportfs	usr.etc Makefile exportfs.c
Xfoption		usr.etc Makefile foption.c
Xgettable	usr.etc Makefile gettable.c
Xgrpck		usr.etc Makefile grpck.c
Xicheck		usr.etc Makefile icheck.c
Xin.comsat	usr.etc Makefile in.comsat.c
Xin.fingerd	usr.etc Makefile in.fingerd.c
Xin.rexecd	usr.etc Makefile in.rexecd.c
Xin.telnetd	usr.etc Makefile in.telnetd.c
Xin.tnamed	usr.etc Makefile in.tnamed.c
Xkgmon		usr.etc Makefile kgmon.c
Xlink		usr.etc Makefile link.c
Xmkfile		usr.etc Makefile mkfile.c
Xmkproto		usr.etc Makefile mkproto.c
Xmount_lo	usr.etc Makefile mount_lo.c
Xncheck		usr.etc Makefile ncheck.c
Xnfsstat		usr.etc Makefile nfsstat.c
Xping		usr.etc Makefile ping.c
Xpwck		usr.etc Makefile pwck.c
Xquot		usr.etc Makefile quot.c
Xquotacheck	usr.etc Makefile quotacheck.c
Xquotaon		usr.etc Makefile quotaon.c
Xrarpd		usr.etc Makefile rarpd.c
Xrepquota	usr.etc Makefile repquota.c
Xroute		usr.etc Makefile route.c
Xrpc.etherd	usr.etc Makefile rpc.etherd.c
Xrpc.mountd	usr.etc Makefile rpc.mountd.c
Xrpc.pwdauthd	usr.etc Makefile rpc.pwdauthd.c
Xrpc.rquotad	usr.etc Makefile rpc.rquotad.c
Xrpc.rstatd	usr.etc Makefile rpc.rstatd.c
Xrpc.rusersd	usr.etc Makefile rpc.rusersd.c
Xrpc.rwalld	usr.etc Makefile rpc.rwalld.c
Xrpc.sprayd	usr.etc Makefile rpc.sprayd.c
Xrpc.yppasswdd	usr.etc Makefile rpc.yppasswdd.c
Xrpc.ypupdated	usr.etc Makefile rpc.ypupdated.c
Xrpcinfo		usr.etc Makefile rpcinfo.c
Xrwall		usr.etc Makefile rwall.c
Xsa		usr.etc Makefile sa.c
Xsavecore	usr.etc Makefile savecore.c
Xshowmount	usr.etc Makefile showmount.c
Xspray		usr.etc Makefile spray.c
Xswapon		usr.etc Makefile swapon.c
Xtrpt		usr.etc Makefile trpt.c
Xtunefs		usr.etc Makefile tunefs.c
Xunlink		usr.etc Makefile unlink.c
X
X# Sub-directories of "usr.lib"
Xbb_count	usr.lib/bb_count
Xfixedwidthfonts	usr.lib/fixedwidthfonts
Xlibcurses	usr.lib/libcurses
Xlibdbm		usr.lib/libdbm
Xlibg		usr.lib/libg
Xlibkvm		usr.lib/libkvm
Xlibln		usr.lib/libln
Xliblwp		usr.lib/liblwp
Xlibm		usr.lib/libm
Xlibmp		usr.lib/libmp
Xlibpixrect	usr.lib/libpixrect
Xlibplot		usr.lib/libplot
Xlibresolv	usr.lib/libresolv
Xlibrpcsvc	usr.lib/librpcsvc
Xlibtermlib	usr.lib/libtermlib
Xliby		usr.lib/liby
Xme		usr.lib/me
Xms		usr.lib/ms
Xsendmail	usr.lib/sendmail
Xsun_tmac	usr.lib/tmac
Xvfont		usr.lib/vfont
X
X# Programs that live in "usr.lib"
XgetNAME		usr.lib Makefile getNAME
Xmakekey		usr.lib Makefile makekey
X
X# Sub-directories of "5bin"
X5diff3		5bin/diff3
X5m4		5bin/m4
X
X# Sub-directories of "5bin", but use sources from other places
X5cxref		-a 5bin/cxref usr.bin/cxref
X5sed		-a 5bin/sed bin/sed
X5lint		-a 5bin/lint lang/pcc lang/lint
X
X# Programs that live in "5bin"
X5banner		5bin Makefile banner.c
X5cat		5bin Makefile cat.c
X5du		5bin Makefile du.c
X5echo		5bin Makefile echo.c
X5expr		5bin Makefile expr.c
X5ls		5bin Makefile ls.c
X5nohup		5bin Makefile nohup.c
X5od		5bin Makefile od.c
X5pg		5bin Makefile pg.c
X5pr		5bin Makefile pr.c
X5sum		5bin Makefile sum.c
X5tabs		5bin Makefile tabs.c
X5time		5bin Makefile time.c
X5tr		5bin Makefile tr.c
X5uname		5bin Makefile uname.c
X
X# Programs that live in "5bin", but use sources from other places
X5chmod		-a 5bin/Makefile bin/chmod.c
X5date		-a 5bin/Makefile bin/date.c
X5grep		-a 5bin/Makefile bin/grep.c
X5stty		-a 5bin/Makefile bin/stty.c
X5col		-a 5bin/Makefile usr.bin/col.c
X5sort		-a 5bin/Makefile usr.bin/sort.c
X5touch		-a 5bin/Makefile usr.bin/touch.c
X
X# Sub-directories of "5lib"
X5compile	5lib/compile
X5libcurses	5lib/libcurses
X5liby		5lib/liby
X5terminfo	5lib/terminfo
X
X# Programs that live in "5lib"
X# NONE
X
X# Programs that live in "prisma"
Xcvs		prisma/cvs
Xmicrokernel	prisma/microkernel
Xtms		prisma/tms
X
X# Programs that live in "doctools"
Xdcan		doctools/dcan
Xditrev		doctools/ditrev
Xditsee		doctools/ditsee
Xeqn		doctools/eqn
Xgrap		doctools/grap
Xiprx		doctools/iprx
Xm3		doctools/m3
Xneqn		doctools/neqn
Xpic		doctools/pic
Xroff		doctools/roff
Xtroff		doctools/roff
Xtbl		doctools/tbl
Xtgraph		doctools/tgraph
Xtmac		doctools/tmac
END_OF_FILE
if test 14454 -ne `wc -c <'examples/modules'`; then
    echo shar: \"'examples/modules'\" unpacked with wrong size!
fi
# end of 'examples/modules'
fi
if test -f 'src/commit.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/commit.c'\"
else
echo shar: Extracting \"'src/commit.c'\" \(21136 characters\)
sed "s/^X//" >'src/commit.c' <<'END_OF_FILE'
X#ifndef lint
Xstatic char rcsid[] = "$Id: commit.c,v 1.28 89/11/19 23:40:32 berliner Exp $";
X#endif !lint
X
X/*
X *    Copyright (c) 1989, Brian Berliner
X *
X *    You may distribute under the terms of the GNU General Public License
X *    as specified in the README file that comes with the CVS 1.0 kit.
X *
X * Commit Files
X *
X *	"commit" commits the present version to the RCS repository, AFTER
X *	having done a test on conflicts.  The call is:
X *		cvs commit [options] files...
X *
X *	"commit" accepts the following options:
X *		-f		Force a commit, even if the RCS $Id string
X *				is not found
X *		-n		Causes "commit" to *not* run any commit prog
X *		-a		Commits all files in the current directory
X *				that have been modified.
X *		-m 'message'	Does not start up the editor for the
X *				log message; just gleans it from the
X *				'message' argument.
X *		-r Revision	Allows committing to a particular *numeric*
X *				revision number.
X *
X *	Note that "commit" does not do a recursive commit.  You must do
X *	"commit" in each directory where there are files that you'd
X *	like to commit.
X */
X
X#include <sys/param.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <ctype.h>
X#include "cvs.h"
X
Xstatic int force_commit_no_rcsid = 0;
X
Xextern int run_module_prog;
X
Xcommit(argc, argv)
X    int argc;
X    char *argv[];
X{
X    int commit_all = 0, err = 0;
X    char *rev = "";			/* not NULL! */
X    char line[MAXLINELEN], message[MAXMESGLEN];
X    int c;
X
X    if (argc == -1)
X	commit_usage();
X    /*
X     * For log purposes, do not allow "root" to commit files
X     */
X    if (geteuid() == 0)
X	error(0, "cannot commit files as 'root'");
X    optind = 1;
X    while ((c = getopt(argc, argv, "fnam:r:")) != -1) {
X	switch (c) {
X	case 'f':
X	    force_commit_no_rcsid = 1;
X	    break;
X	case 'n':
X	    run_module_prog = 0;
X	    break;
X	case 'a':
X	    commit_all = 1;
X	    break;
X	case 'm':
X	    use_editor = FALSE;
X	    if (strlen(optarg) >= sizeof(message)) {
X		warn(0, "warning: message too long; truncated!");
X		(void) strncpy(message, optarg, sizeof(message));
X		message[sizeof(message) - 1] = '\0';
X	    } else
X		(void) strcpy(message, optarg);
X	    break;
X	case 'r':
X	    if (!isdigit(optarg[0]))
X		error(0, "specified revision %s must be numeric!", optarg);
X	    rev = optarg;
X	    break;
X	case '?':
X	default:
X	    commit_usage();
X	    break;
X	}
X    }
X    argc -= optind;
X    argv += optind;
X    if (!commit_all && argc == 0)
X	error(0, "must specify the files you'd like to check-in");
X    if (commit_all && argc != 0)
X	error(0, "cannot specify files with the -a option");
X    Name_Repository();
X    Writer_Lock();
X    if (commit_all) {
X	Find_Names(&fileargc, fileargv, ALL);
X	argc = fileargc;
X	argv = fileargv;
X    }
X    if (rev[0] != '\0') {
X	register int i;
X	FILE *fptty;
X
X	fptty = open_file("/dev/tty", "r");
X	printf("WARNING:\n");
X	printf("\tCommitting with a specific revision number\n");
X	printf("\tbypasses all consistency checks.  Are you abosulutely\n");
X	printf("\tsure you want to continue (y/n) [n] ? ");
X	(void) fflush(stdout);
X	if (fgets(line, sizeof(line), fptty) == NULL ||
X	    (line[0] != 'y' && line[0] != 'Y')) {
X	    error(0, "commit of revision %s aborted", rev);
X	}
X	(void) fclose(fptty);
X	/*
X	 * When committing with a specific revision number, we simply
X	 * fudge the lists that Collect_Sets() would have created for
X	 * us.  This is all so gross, but sometimes useful.
X	 */
X	Clist[0] = Glist[0] = Mlist[0] = Olist[0] = Dlist[0] = '\0';
X	Alist[0] = Rlist[0] = Wlist[0] = Llist[0] = Blist[0] = '\0';
X	for (i = 0; i < argc; i++) {
X	    (void) strcat(Mlist, " ");
X	    (void) strcat(Mlist, argv[i]);
X	}
X    } else {
X	err += Collect_Sets(argc, argv);
X    }
X    if (err == 0) {
X	err += commit_process_lists(message, rev);
X	if (err == 0 && run_module_prog) {
X	    char *cp;
X	    FILE *fp;
X
X	    /*
X	     * It is not an error if Checkin.prog does not exist.
X	     */
X	    if ((fp = fopen(CVSADM_CIPROG, "r")) != NULL) {
X		if (fgets(line, sizeof(line), fp) != NULL) {
X		    if ((cp = rindex(line, '\n')) != NULL)
X			*cp = '\0';
X		    (void) sprintf(prog, "%s %s", line, Repository);
X		    printf("%s %s: Executing '%s'\n", progname, command, prog);
X		    (void) system(prog);
X		}
X		(void) fclose(fp);
X	    }
X	}
X	Update_Logfile(Repository, message);
X    }
X    Lock_Cleanup(0);
X    exit(err);
X}
X
X/*
X * Process all the lists, returning the number of errors found.
X */
Xstatic
Xcommit_process_lists(message, rev)
X    char *message;
X    char *rev;
X{
X    char line[MAXLISTLEN], fname[MAXPATHLEN], revision[50];
X    FILE *fp;
X    char *cp;
X    int first, err = 0;
X
X    /*
X     * Doesn't make much sense to commit a directory...
X     */
X    if (Dlist[0])
X	warn(0, "committing directories ignored -%s", Dlist);
X    /*
X     * Is everything up-to-date?
X     * Only if Glist, Olist, and Wlist are all NULL!
X     */
X    if (Glist[0] || Olist[0] || Wlist[0]) {
X	(void) fprintf(stderr, "%s: the following files are not ", progname);
X	(void) fprintf(stderr,
X		       "up to date; use '%s update' first:\n", progname);
X	if (Glist[0] != '\0')
X	    (void) fprintf(stderr, "\t%s\n", Glist);
X	if (Olist[0] != '\0')
X	    (void) fprintf(stderr, "\t%s\n", Olist);
X	if (Wlist[0] != '\0')
X	    (void) fprintf(stderr, "\t%s\n", Wlist);
X	Lock_Cleanup(0);
X	exit(1);
X    }
X    /*
X     * Is there anything to do in the first place?
X     */
X    if (Mlist[0] == '\0' && Rlist[0] == '\0' && Alist[0] == '\0')
X	error(0, "there is nothing to commit!");
X    /*
X     * First we make sure that the file has an RCS $Id string in it
X     * and if it does not, the user is prompted for verification to continue.
X     */
X    if (force_commit_no_rcsid == 0) {
X	(void) strcpy(line, Mlist);
X	(void) strcat(line, Alist);
X	for (first = 1, cp = strtok(line, " \t"); cp;
X	    cp = strtok((char *)NULL, " \t")) {
X	    (void) sprintf(prog, "%s -s %s %s", GREP, RCSID_PAT, cp);
X	    if (system(prog) != 0) {
X		if (first) {
X		    printf("%s %s: WARNING!\n", progname, command);
X		    printf("\tThe following file(s) do not contain an RCS $Id keyword:\n");
X		    first = 0;
X		}
X		printf("\t\t%s\n", cp);
X	    }
X	}
X	if (first == 0) {
X	    FILE *fptty = open_file("/dev/tty", "r");
X	    printf("\tAre you sure you want to continue (y/n) [n] ? ");
X	    (void) fflush(stdout);
X	    if (fgets(line, sizeof(line), fptty) == NULL ||
X		(line[0] != 'y' && line[0] != 'Y')) {
X		error(0, "commit aborted");
X	    }
X	    (void) fclose(fptty);
X	}
X    }
X    if (use_editor)
X	do_editor(message);
X    /*
X     * Mlist is the "modified, needs committing" list
X     */
X    (void) strcpy(line, Mlist);
X    for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	(void) strcpy(User, cp);
X	(void) sprintf(Rcs, "%s/%s%s", Repository, User, RCSEXT);
X	if (lock_RCS(rev) != 0)
X	    err++;
X    }
X    /*
X     * Rlist is the "to be removed" list
X     */
X    (void) strcpy(line, Rlist);
X    for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	(void) strcpy(User, cp);
X	(void) sprintf(Rcs, "%s/%s%s", Repository, User, RCSEXT);
X	if (lock_RCS(rev) != 0)
X	    err++;
X    }
X    /*
X     * Alist is the "to be added" list
X     */
X    (void) strcpy(line, Alist);
X    for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	(void) strcpy(User, cp);
X	(void) sprintf(Rcs, "%s/%s%s", Repository, User, RCSEXT);
X	(void) sprintf(prog, "%s/%s -i -t%s/%s%s", Rcsbin, RCS, CVSADM,
X		       User, CVSEXT_LOG);
X	(void) sprintf(fname, "%s/%s%s", CVSADM, User, CVSEXT_OPT);
X	fp = open_file(fname, "r");
X	while (fgets(fname, sizeof(fname), fp) != NULL) {
X	    if ((cp = rindex(fname, '\n')) != NULL)
X		*cp = '\0';
X	    (void) strcat(prog, " ");
X	    (void) strcat(prog, fname);
X	}
X	(void) fclose(fp);
X	(void) strcat(prog, " ");
X	(void) strcat(prog, Rcs);
X	if (system(prog) == 0) {
X	    fix_rcs_modes(Rcs, User);
X	} else {
X	    warn(0, "could not create %s", Rcs);
X	    err++;
X	}
X    }
X    /*
X     * If something failed, release all locks and restore the default
X     * branches
X     */
X    if (err) {
X	int didllist = 0;
X	char *branch;
X
X	for (cp = strtok(Llist, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	    didllist = 1;
X	    (void) strcpy(User, cp);
X	    (void) sprintf(Rcs, "%s/%s%s", Repository, User, RCSEXT);
X	    (void) sprintf(prog, "%s/%s -q -u %s", Rcsbin, RCS, Rcs);
X	    if (system(prog) != 0)
X		warn(0, "could not UNlock %s", Rcs);
X	}
X	if (didllist) {
X	    for (cp=strtok(Blist, " \t"); cp; cp=strtok((char *)NULL, " \t")) {
X		if ((branch = rindex(cp, ':')) == NULL)
X		    continue;
X		*branch++ = '\0';
X		(void) strcpy(User, cp);
X		(void) sprintf(Rcs, "%s/%s%s", Repository, User, RCSEXT);
X		(void) sprintf(prog, "%s/%s -q -b%s %s", Rcsbin, RCS,
X			       branch, Rcs);
X		if (system(prog) != 0)
X		    warn(0, "could not restore branch %s to %s", branch, Rcs);
X	    }
X	}
X	for (cp = strtok(Alist, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	    (void) strcpy(User, cp);
X	    (void) sprintf(Rcs, "%s/%s%s", Repository, User, RCSEXT);
X	    (void) unlink(Rcs);
X	}
X	Lock_Cleanup(0);
X	exit(1);
X    }
X    /*
X     * Got them all, now go ahead;
X     * First, add the files in the Alist
X     */
X    if (Alist[0] != '\0') {
X	int maxrev, rev;
X
X	/* scan the entries file looking for the max revision number */
X	fp = open_file(CVSADM_ENT, "r");
X	maxrev = 0;
X	while (fgets(line, sizeof(line), fp) != NULL) {
X	    rev = atoi(line);
X	    if (rev > maxrev)
X		maxrev = rev;
X	}
X	if (maxrev == 0)
X	    maxrev = 1;
X	(void) fclose(fp);
X	(void) sprintf(revision, "-r%d", maxrev);
X	(void) strcpy(line, Alist);
X	for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	    (void) strcpy(User, cp);
X	    if (Checkin(revision, message) != 0)
X		err++;
X	    (void) sprintf(fname, "%s/%s%s", CVSADM, User, CVSEXT_OPT);
X	    (void) unlink(fname);
X	    (void) sprintf(fname, "%s/%s%s", CVSADM, User, CVSEXT_LOG);
X	    (void) unlink(fname);
X	}
X    }
X    /*
X     * Everyone else uses the head as it is set in the RCS file,
X     * or the revision that was specified on the command line.
X     */
X    if (rev[0] != '\0')
X	(void) sprintf(revision, "-r%s", rev);
X    else
X	revision[0] = '\0';
X    /*
X     * Commit the user modified files in Mlist
X     */
X    (void) strcpy(line, Mlist);
X    for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	(void) strcpy(User, cp);
X	if (Checkin(revision, message) != 0)
X	    err++;
X    }
X    /*
X     * And remove the RCS files in Rlist, by placing it in the Attic
X     */
X    (void) strcpy(line, Rlist);
X    for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	int omask;
X
X	(void) strcpy(User, cp);
X	(void) sprintf(Rcs, "%s/%s%s", Repository, User, RCSEXT);
X	(void) sprintf(fname, "%s/%s", Repository, CVSATTIC);
X	omask = umask(2);
X	(void) mkdir(fname, 0777);
X	(void) umask(omask);
X	(void) sprintf(fname, "%s/%s/%s%s", Repository, CVSATTIC,
X		       User, RCSEXT);
X	(void) sprintf(prog, "%s/%s -u -q %s", Rcsbin, RCS, Rcs);
X	if ((system(prog) == 0 && rename(Rcs, fname) != -1) ||
X	    (!isreadable(Rcs) && isreadable(fname)))
X	    Scratch_Entry(User);
X	else
X	    err++;
X    }
X    return (err);
X}
X
X/*
X * Attempt to place a lock on the RCS file; returns 0 if it could and
X * 1 if it couldn't.  If the RCS file currently has a branch as the head,
X * we must move the head back to the trunk before locking the file, and
X * be sure to put the branch back as the head if there are any errors.
X */
Xstatic
Xlock_RCS(rev)
X    char *rev;
X{
X    char branch[50];
X    int err = 0;
X
X    branch[0] = '\0';
X    /*
X     * For a specified, numeric revision of the form "1" or "1.1",
X     * (or when no revision is specified ""), definitely move the
X     * branch to the trunk before locking the RCS file.
X     *
X     * The assumption is that if there is more than one revision
X     * on the trunk, the head points to the trunk, not a branch...
X     * and as such, it's not necessary to move the head in this case.
X     */
X    if (numdots(rev) < 2) {
X	branch_number(Rcs, branch);
X	if (branch[0] != '\0') {
X	    (void) sprintf(prog, "%s/%s -q -b %s", Rcsbin, RCS, Rcs);
X	    if (system(prog) != 0) {
X		warn(0, "cannot change branch to default for %s", Rcs);
X		return (1);
X	    }
X	}
X	(void) sprintf(prog, "%s/%s -q -l %s", Rcsbin, RCS, Rcs);
X	err = system(prog);
X    } else {
X	(void) sprintf(prog, "%s/%s -q -l%s %s 2>%s",
X		       Rcsbin, RCS, rev, Rcs, DEVNULL);
X	(void) system(prog);
X    }
X    if (err == 0) {
X	(void) strcat(Llist, " ");
X	(void) strcat(Llist, User);
X	(void) strcat(Blist, " ");
X	(void) strcat(Blist, User);
X	if (branch[0] != '\0') {
X	    (void) strcat(Blist, ":");
X	    (void) strcat(Blist, branch);
X	}
X	return (0);
X    }
X    if (branch[0] != '\0') {
X	(void) sprintf(prog, "%s/%s -q -b%s %s", Rcsbin, RCS, branch, Rcs);
X	if (system(prog) != 0)
X	    warn(0, "cannot restore branch to %s for %s", branch, Rcs);
X    }
X    return (1);
X}
X
X/*
X * A special function used only by lock_RCS() to determine if the current
X * head is pointed at a branch.  Returns the result in "branch" as a null
X * string if the trunk is the head, or as the branch number if the branch
X * is the head.
X */
Xstatic
Xbranch_number(rcs, branch)
X    char *rcs;
X    char *branch;
X{
X    char line[MAXLINELEN];
X    FILE *fp;
X    char *cp;
X
X    branch[0] = '\0';			/* Assume trunk is head */
X    fp = open_file(rcs, "r");
X    if (fgets(line, sizeof(line), fp) == NULL) {
X	(void) fclose(fp);
X	return;
X    }
X    if (fgets(line, sizeof(line), fp) == NULL) {
X	(void) fclose(fp);
X	return;
X    }
X    (void) fclose(fp);
X    if (strncmp(line, RCSBRANCH, sizeof(RCSBRANCH) - 1) != 0 ||
X	(cp = rindex(line, ';')) == NULL)
X	return;
X    *cp = '\0';				/* strip the ';' */
X    if ((cp = rindex(line, ' ')) == NULL &&
X	(cp = rindex(line, '\t')) == NULL)
X	return;
X    cp++;
X    if (*cp == NULL)
X	return;
X    (void) strcpy(branch, cp);
X}
X
X/*
X * Puts a standard header on the output which is either being prepared for
X * an editor session, or being sent to a logfile program.  The modified, added,
X * and removed files are included (if any) and formatted to look pretty.
X */
Xstatic
Xsetup_tmpfile(fp, prefix)
X    FILE *fp;
X    char *prefix;
X{
X    if (Mlist[0] != '\0') {
X	(void) fprintf(fp, "%sModified Files:\n", prefix);
X	fmt(fp, Mlist, prefix);
X    }
X    if (Alist[0] != '\0') {
X	(void) fprintf(fp, "%sAdded Files:\n", prefix);
X	fmt(fp, Alist, prefix);
X    }
X    if (Rlist[0] != '\0') {
X	(void) fprintf(fp, "%sRemoved Files:\n", prefix);
X	fmt(fp, Rlist, prefix);
X    }
X}
X
X/*
X * Breaks the files list into reasonable sized lines to avoid line
X * wrap...  all in the name of pretty output.
X */
Xstatic
Xfmt(fp, instring, prefix)
X    FILE *fp;
X    char *instring;
X    char *prefix;
X{
X    char line[MAXLINELEN];
X    char *cp;
X    int col;
X
X    (void) strcpy(line, instring);	/* since strtok() is destructive */
X    (void) fprintf(fp, "%s\t", prefix);
X    col = 8;				/* assumes that prefix is < 8 chars */
X    for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	if ((col + strlen(cp)) > 70) {
X	    (void) fprintf(fp, "\n%s\t", prefix);
X	    col = 8;
X	}
X	(void) fprintf(fp, "%s ", cp);
X	col += strlen(cp) + 1;
X    }
X    (void) fprintf(fp, "\n%s\n", prefix);
X}
X
X/*
X * Builds a temporary file using setup_tmpfile() and invokes the user's
X * editor on the file.  The header garbage in the resultant file is then
X * stripped and the log message is stored in the "message" argument.
X */
Xstatic
Xdo_editor(message)
X    char *message;
X{
X    FILE *fp;
X    char line[MAXLINELEN], fname[MAXPATHLEN];
X    int fd;
X
X    message[0] = '\0';
X    (void) strcpy(fname, CVSTEMP);
X    if ((fd = mkstemp(fname)) < 0)
X	error(0, "cannot create temporary file %s", fname);
X    if ((fp = fdopen(fd, "w+")) == NULL)
X	error(0, "cannot create FILE * to %s", fname);
X    setup_tmpfile(fp, CVSEDITPREFIX);
X    (void) fprintf(fp, "%sEnter Log.  Lines beginning with '%s' are removed automatically\n",
X		   CVSEDITPREFIX, CVSEDITPREFIX);
X    (void) fprintf(fp, "%s----------------------------------------------------------------------\n");
X    (void) fclose(fp);
X    (void) sprintf(prog, "%s %s", Editor, fname);
X    if (system(prog) != 0) {
X	(void) unlink(fname);
X	warn(0, "warning: editor session failed");
X    }
X    fp = open_file(fname, "r");
X    while (fgets(line, sizeof(line), fp) != NULL) {
X	if (strncmp(line, CVSEDITPREFIX, sizeof(CVSEDITPREFIX)-1) == 0)
X	    continue;
X	if ((strlen(message) + strlen(line)) >= MAXMESGLEN) {
X	    warn(0, "warning: log message truncated!");
X	    break;
X	}
X	(void) strcat(message, line);
X    }
X    (void) fclose(fp);
X    (void) unlink(fname);
X}
X
X/*
X * Uses setup_tmpfile() to pass the updated message on directly to
X * any logfile programs that have a regular expression match for the
X * checked in directory in the source repository.  The log information
X * is fed into the specified program as standard input.
X */
XUpdate_Logfile(repository, message)
X    char *repository;
X    char *message;
X{
X    FILE *fp_info;
X    char logfile[MAXPATHLEN], title[MAXLISTLEN+MAXPATHLEN], line[MAXLINELEN];
X    char path[MAXPATHLEN], default_filter[MAXLINELEN];
X    char *exp, *filter, *cp, *short_repository;
X    int filter_run, line_number;
X
X    if (CVSroot == NULL) {
X	warn(0, "CVSROOT variable not set; no log message will be sent");
X	return;
X    }
X    (void) sprintf(logfile, "%s/%s", CVSroot, CVSROOTADM_LOGINFO);
X    if ((fp_info = fopen(logfile, "r")) == NULL) {
X	warn(0, "warning: cannot open %s", logfile);
X	return;
X    }
X    if (CVSroot != NULL)
X	(void) sprintf(path, "%s/", CVSroot);
X    else
X	(void) strcpy(path, REPOS_STRIP);
X    if (strncmp(repository, path, strlen(path)) == 0)
X	short_repository = repository + strlen(path);
X    else
X	short_repository = repository;
X    (void) sprintf(title, "'%s%s'", short_repository, Llist);
X    default_filter[0] = '\0';
X    filter_run = line_number = 0;
X    while (fgets(line, sizeof(line), fp_info) != NULL) {
X	line_number++;
X	if (line[0] == '#')
X	    continue;
X	for (cp = line; *cp && isspace(*cp); cp++)
X	    ;
X	if (*cp == '\0')
X	    continue;			/* blank line */
X	for (exp = cp; *cp && !isspace(*cp); cp++)
X	    ;
X	if (*cp != '\0')
X	    *cp++ = '\0';
X	while (*cp && isspace(*cp))
X	    cp++;
X	if (*cp == '\0') {
X	    warn(0, "syntax error at line %d file %s; ignored",
X		 line_number, logfile);
X	    continue;
X	}
X	filter = cp;
X	if ((cp = rindex(filter, '\n')) != NULL)
X	    *cp = '\0';			/* strip the newline */
X	/*
X	 * At this point, exp points to the regular expression, and
X	 * filter points to the program to exec.  Evaluate the regular
X	 * expression against short_repository and exec the filter
X	 * if it matches.
X	 */
X	if (strcmp(exp, "DEFAULT") == 0) {
X	    (void) strcpy(default_filter, filter);
X	    continue;
X	}
X	/*
X	 * For a regular expression of "ALL", send the log message
X	 * to the requested filter *without* noting that a filter was run.
X	 * This allows the "DEFAULT" regular expression to be more
X	 * meaningful with all updates going to a master log file.
X	 */
X	if (strcmp(exp, "ALL") == 0) {
X	    (void) logfile_write(repository, filter, title, message);
X	    continue;
X	}
X	if ((cp = re_comp(exp)) != NULL) {
X	    warn(0, "bad regular expression at line %d file %s: %s",
X		 line_number, logfile, cp);
X	    continue;
X	}
X	if (re_exec(short_repository) == 0)
X	    continue;			/* no match */
X	if (logfile_write(repository, filter, title, message) == 0)
X	    filter_run = 1;
X    }
X    if (filter_run == 0 && default_filter[0] != '\0')
X	(void) logfile_write(repository, default_filter, title, message);
X}
X
X/*
X * Since some systems don't define this...
X */
X#ifndef MAXHOSTNAMELEN
X#define	MAXHOSTNAMELEN	64
X#endif !MAXHOSTNAMELEN
X
X/*
X * Writes some stuff to the logfile "filter" and returns the status of the
X * filter program.
X */
Xstatic
Xlogfile_write(repository, filter, title, message)
X    char *repository;
X    char *filter;
X    char *title;
X    char *message;
X{
X    char cwd[MAXPATHLEN], host[MAXHOSTNAMELEN];
X    FILE *fp;
X    char *cp;
X
X    /*
X     * A maximum of 6 %s arguments are supported in the filter
X     */
X    (void) sprintf(prog, filter, title, title, title, title, title, title);
X    if ((fp = popen(prog, "w")) == NULL) {
X	warn(0, "cannot write entry to log filter: %s", prog);
X	return (1);
X    }
X    if (gethostname(host, sizeof(host)) < 0)
X	(void) strcpy(host, "(unknown)");
X    (void) fprintf(fp, "Update of %s\n", repository);
X    (void) fprintf(fp, "In directory %s:%s\n\n", host,
X		   (cp = getwd(cwd)) ? cp : cwd);
X    setup_tmpfile(fp, "");
X    (void) fprintf(fp, "Log Message:\n%s\n", message);
X    return (pclose(fp));
X}
X
X/*
X * Called when "add"ing files to the RCS respository, as it is necessary
X * to preserve the file modes in the same fashion that RCS does.  This would
X * be automatic except that we are placing the RCS ,v file very far away from
X * the user file, and I can't seem to convince RCS of the location of the
X * user file.  So we munge it here, after the ,v file has been successfully
X * initialized with "rcs -i".
X */
Xstatic
Xfix_rcs_modes(rcs, user)
X    char *rcs;
X    char *user;
X{
X    struct stat sb;
X
X    if (stat(user, &sb) != -1) {
X	(void) chmod(rcs, (int) sb.st_mode & ~0222);
X    }
X}
X
Xstatic
Xcommit_usage()
X{
X    (void) fprintf(stderr,
X	"%s %s [-fn] [-a] [-m 'message'] [-r revision] [files...]\n",
X		   progname, command);
X    exit(1);
X}
END_OF_FILE
if test 21136 -ne `wc -c <'src/commit.c'`; then
    echo shar: \"'src/commit.c'\" unpacked with wrong size!
fi
# end of 'src/commit.c'
fi
if test -f 'src/partime.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/partime.c'\"
else
echo shar: Extracting \"'src/partime.c'\" \(14739 characters\)
sed "s/^X//" >'src/partime.c' <<'END_OF_FILE'
X#ifndef lint
Xstatic char rcsid[] = "$Id: partime.c,v 1.1 89/05/09 11:51:02 berliner Exp $";
X#endif
X
X/*
X * PARTIME		parse date/time string into a TM structure
X *
X * Usage:
X *      #include "time.h"             -- expanded tm structure
X *	char *str; struct tm *tp;
X *	partime(str,tp);
X * Returns:
X *	0 if parsing failed
X *	else time values in specified TM structure (unspecified values
X *		set to TMNULL)
X * Notes:
X *	This code is quasi-public; it may be used freely in like software.
X *	It is not to be sold, nor used in licensed software without
X *	permission of the author.
X *	For everyone's benefit, please report bugs and improvements!
X * 	Copyright 1980 by Ken Harrenstien, SRI International.
X *	(ARPANET: KLH @ SRI)
X */
X
X/* Hacknotes:
X *	If parsing changed so that no backup needed, could perhaps modify
X *		to use a FILE input stream.  Need terminator, though.
X *	Perhaps should return 0 on success, else a non-zero error val?
X *	Flush AMPM from TM structure and handle locally within PARTIME,
X *		like midnight/noon?
X */
X
X#include <stdio.h>
X#include <ctype.h>
X#include "rcstime.h"
X
X#ifndef lint
Xstatic char timeid[] = TIMEID;
X#endif
X
Xstruct tmwent {
X	char *went;
X	long wval;	/* must be big enough to hold pointer or integer */
X	char wflgs;
X	char wtype;
X};
X	/* wflgs */
X#define TWSPEC 01	/* Word wants special processing */
X#define TWTIME 02	/* Word is a time value (absence implies date) */
X#define TWDST  04	/* Word is a DST-type timezone */
X#define TW1200 010	/* Word is NOON or MIDNIGHT (sigh) */
X
Xint pt12hack();
Xint ptnoise();
Xstruct tmwent tmwords [] = {
X	{"january",      0, 0, TM_MON},
X	{"february",     1, 0, TM_MON},
X	{"march",        2, 0, TM_MON},
X	{"april",        3, 0, TM_MON},
X	{"may",          4, 0, TM_MON},
X	{"june",         5, 0, TM_MON},
X	{"july",         6, 0, TM_MON},
X	{"august",       7, 0, TM_MON},
X	{"september",    8, 0, TM_MON},
X	{"october",      9, 0, TM_MON},
X	{"november",     10, 0, TM_MON},
X	{"december",     11, 0, TM_MON},
X
X	{"sunday",       0, 0, TM_WDAY},
X	{"monday",       1, 0, TM_WDAY},
X	{"tuesday",      2, 0, TM_WDAY},
X	{"wednesday",    3, 0, TM_WDAY},
X	{"thursday",     4, 0, TM_WDAY},
X	{"friday",       5, 0, TM_WDAY},
X	{"saturday",     6, 0, TM_WDAY},
X
X	{"gmt",          0*60, TWTIME, TM_ZON},   /* Greenwich */
X	{"gst",          0*60, TWTIME, TM_ZON},
X	{"gdt",          0*60, TWTIME+TWDST, TM_ZON},     /* ?? */
X
X	{"ast",          4*60, TWTIME, TM_ZON},   /* Atlantic */
X	{"est",          5*60, TWTIME, TM_ZON},   /* Eastern */
X	{"cst",          6*60, TWTIME, TM_ZON},   /* Central */
X	{"mst",          7*60, TWTIME, TM_ZON},   /* Mountain */
X	{"pst",          8*60, TWTIME, TM_ZON},   /* Pacific */
X	{"yst",          9*60, TWTIME, TM_ZON},   /* Yukon */
X	{"hst",          10*60, TWTIME, TM_ZON},  /* Hawaii */
X	{"bst",          11*60, TWTIME, TM_ZON},  /* Bering */
X
X	{"adt",          4*60, TWTIME+TWDST, TM_ZON},     /* Atlantic */
X	{"edt",          5*60, TWTIME+TWDST, TM_ZON},     /* Eastern */
X	{"cdt",          6*60, TWTIME+TWDST, TM_ZON},     /* Central */
X	{"mdt",          7*60, TWTIME+TWDST, TM_ZON},     /* Mountain */
X	{"pdt",          8*60, TWTIME+TWDST, TM_ZON},     /* Pacific */
X	{"ydt",          9*60, TWTIME+TWDST, TM_ZON},     /* Yukon */
X	{"hdt",          10*60, TWTIME+TWDST, TM_ZON},    /* Hawaii */
X	{"bdt",          11*60, TWTIME+TWDST, TM_ZON},    /* Bering */
X
X	{"daylight",     1, TWTIME+TWDST, TM_ZON},        /* Local Daylight */
X	{"standard",     1, TWTIME, TM_ZON},      /* Local Standard */
X	{"std",          1, TWTIME, TM_ZON},      /*   "       "    */
X
X	{"am",           1, TWTIME, TM_AMPM},
X	{"pm",           2, TWTIME, TM_AMPM},
X	{"noon",         12,TWTIME+TW1200, 0},    /* Special frobs */
X	{"midnight",     0, TWTIME+TW1200, 0},
X	{"at",           (long)ptnoise, TWSPEC, 0},    /* Noise word */
X
X	{0, 0, 0, 0},             /* Zero entry to terminate searches */
X};
X
X#define TMWILD (-2)	/* Value meaning item specified as wild-card */
X			/* (May use someday...) */
X
Xstruct token {
X	char *tcp;	/* pointer to string */
X	int tcnt;	/* # chars */
X	char tbrk;	/* "break" char */
X	char tbrkl;	/* last break char */
X	char tflg;	/* 0 = alpha, 1 = numeric */
X	union {         /* Resulting value; */
X		int tnum;/* either a #, or */
X		struct tmwent *ttmw;/* ptr to a tmwent. */
X	} tval;
X};
X
Xpartime(astr, atm)
Xchar *astr;
Xstruct tm *atm;
X{	register int *tp;
X	register struct tmwent *twp;
X	register int i;
X	struct token btoken, atoken;
X	char *cp, ch;
X	int ord, midnoon;
X	int (*aproc)();
X
X	tp = (int *)atm;
X	zaptime(tp);			 /* Initialize the TM structure */
X	midnoon = TMNULL;		/* and our own temp stuff */
X	btoken.tcnt = btoken.tbrkl = 0;
X	btoken.tcp = astr;
X
Xdomore:
X	if(!ptitoken(btoken.tcp+btoken.tcnt,&btoken))	/* Get a token */
X	  {     if(btoken.tval.tnum) return(0);         /* Read error? */
X		if(midnoon != TMNULL)			/* EOF, wrap up */
X			return(pt12hack(tp, midnoon));
X		return(1);				/* Win return! */
X	  }
X	if(btoken.tflg == 0)		/* Alpha? */
X	  {     twp = btoken.tval.ttmw;         /* Yes, get ptr to entry */
X		if(twp->wflgs&TWSPEC)		/* Special alpha crock */
X		  {     aproc = (int (*) ()) (twp->wval);
X			if(!(*aproc)(tp, twp, &btoken))
X				return(0);	/* ERR: special word err */
X			goto domore;
X		  }
X		if(twp->wflgs&TW1200)
X			if(ptstash(&midnoon,(int)twp->wval))
X				return(0);	/* ERR: noon/midnite clash */
X			else goto domore;
X		if(ptstash(&tp[twp->wtype],(int)twp->wval))
X			return(0);		/* ERR: val already set */
X		if(twp->wtype == TM_ZON)	/* If was zone, hack DST */
X			if(ptstash(&tp[TM_ISDST],(twp->wflgs&TWDST)))
X				return(0);	/* ERR: DST conflict */
X		goto domore;
X	  }
X
X	/* Token is number.  Lots of hairy heuristics. */
X	if(btoken.tcnt >= 7)	/* More than 6 digits in string? */
X		return(0);	/* ERR: number too big */
X	if(btoken.tcnt == 6)	/* 6 digits = HHMMSS.  Needs special crock */
X	  {			/* since 6 digits are too big for integer! */
X		i = (btoken.tcp[0]-'0')*10	/* Gobble 1st 2 digits */
X		   + btoken.tcp[1]-'0';
X		btoken.tcnt = 2;		/* re-read last 4 chars */
X		goto coltime;
X	  }
X
X	i = btoken.tval.tnum;   /* Value now known to be valid; get it. */
X	if( btoken.tcnt == 5	/*  5 digits = HMMSS */
X	 || btoken.tcnt == 3)	/*  3 digits = HMM   */
X	  {	if(btoken.tcnt != 3)
X			if(ptstash(&tp[TM_SEC], i%100))
X				return(0);	/* ERR: sec conflict */
X			else i /= 100;
Xhhmm4:		if(ptstash(&tp[TM_MIN], i%100))
X			return(0);		/* ERR: min conflict */
X		i /= 100;
Xhh2:            if(ptstash(&tp[TM_HOUR], i))
X			return(0);		/* ERR: hour conflict */
X		goto domore;
X	  }
X
X	if(btoken.tcnt == 4)	/* 4 digits = YEAR or HHMM */
X	  {	if(tp[TM_YEAR] != TMNULL) goto hhmm4;	/* Already got yr? */
X		if(tp[TM_HOUR] != TMNULL) goto year4;	/* Already got hr? */
X		if((i%100) > 59) goto year4;		/* MM >= 60? */
X		if(btoken.tbrk == ':')			/* HHMM:SS ? */
X			if( ptstash(&tp[TM_HOUR],i/100)
X			 || ptstash(&tp[TM_MIN], i%100))
X				return(0);		/* ERR: hr/min clash */
X			else goto coltm2;		/* Go handle SS */
X		if(btoken.tbrk != ',' && btoken.tbrk != '/'
X		  && ptitoken(btoken.tcp+btoken.tcnt,&atoken)	/* Peek */
X		  && atoken.tflg == 0			/* alpha */
X		  && (atoken.tval.ttmw->wflgs&TWTIME))  /* HHMM-ZON */
X			goto hhmm4;
X		if(btoken.tbrkl == '-'		/* DD-Mon-YYYY */
X		  || btoken.tbrkl == ','	/* Mon DD, YYYY */
X		  || btoken.tbrkl == '/'	/* MM/DD/YYYY */
X		  || btoken.tbrkl == '.'	/* DD.MM.YYYY */
X		  || btoken.tbrk == '-'		/* YYYY-MM-DD */
X			) goto year4;
X		goto hhmm4;			/* Give up, assume HHMM. */
X	  }
X
X	/* From this point on, assume tcnt == 1 or 2 */
X	/* 2 digits = YY, MM, DD, or HH (MM and SS caught at coltime) */
X	if(btoken.tbrk == ':')		/* HH:MM[:SS] */
X		goto coltime;		/*  must be part of time. */
X	if(i > 31) goto yy2;		/* If >= 32, only YY poss. */
X
X	/* Check for numerical-format date */
X	for (cp = "/-."; ch = *cp++;)
X	  {	ord = (ch == '.' ? 0 : 1);	/* n/m = D/M or M/D */
X		if(btoken.tbrk == ch)			/* "NN-" */
X		  {	if(btoken.tbrkl != ch)
X			  {	if(ptitoken(btoken.tcp+btoken.tcnt,&atoken)
X				  && atoken.tflg == 0
X				  && atoken.tval.ttmw->wtype == TM_MON)
X					goto dd2;
X				if(ord)goto mm2; else goto dd2; /* "NN-" */
X			  }				/* "-NN-" */
X			if(tp[TM_DAY] == TMNULL
X			&& tp[TM_YEAR] != TMNULL)	/* If "YY-NN-" */
X				goto mm2;		/* then always MM */
X			if(ord)goto dd2; else goto mm2;
X		  }
X		if(btoken.tbrkl == ch			/* "-NN" */
X		  && tp[ord ? TM_MON : TM_DAY] != TMNULL)
X			if(tp[ord ? TM_DAY : TM_MON] == TMNULL)	/* MM/DD */
X				if(ord)goto dd2; else goto mm2;
X			else goto yy2;			/* "-YY" */
X	  }
X
X	/* At this point only YY, DD, and HH are left.
X	 * YY is very unlikely since value is <= 32 and there was
X	 * no numerical format date.  Make one last try at YY
X	 * before dropping through to DD vs HH code.
X	 */
X	if(btoken.tcnt == 2		/* If 2 digits */
X	  && tp[TM_HOUR] != TMNULL	/* and already have hour */
X	  && tp[TM_DAY] != TMNULL	/* and day, but  */
X	  && tp[TM_YEAR] == TMNULL)	/* no year, then assume */
X		goto yy2;		/* that's what we have. */
X
X	/* Now reduced to choice between HH and DD */
X	if(tp[TM_HOUR] != TMNULL) goto dd2;	/* Have hour? Assume day. */
X	if(tp[TM_DAY] != TMNULL) goto hh2;	/* Have day? Assume hour. */
X	if(i > 24) goto dd2;			/* Impossible HH means DD */
X	if(!ptitoken(btoken.tcp+btoken.tcnt, &atoken))	/* Read ahead! */
X		if(atoken.tval.tnum) return(0); /* ERR: bad token */
X		else goto dd2;			/* EOF, assume day. */
X	if( atoken.tflg == 0		/* If next token is an alpha */
X	 && atoken.tval.ttmw->wflgs&TWTIME)  /* time-spec, assume hour */
X		goto hh2;		/* e.g. "3 PM", "11-EDT"  */
X
Xdd2:	if(ptstash(&tp[TM_DAY],i))	/* Store day (1 based) */
X		return(0);
X	goto domore;
X
Xmm2:	if(ptstash(&tp[TM_MON], i-1))	/* Store month (make zero based) */
X		return(0);
X	goto domore;
X
Xyy2:	i += 1900;
Xyear4:	if(ptstash(&tp[TM_YEAR],i))	/* Store year (full number) */
X		return(0);		/* ERR: year conflict */
X	goto domore;
X
X	/* Hack HH:MM[[:]SS] */
Xcoltime:
X	if(ptstash(&tp[TM_HOUR],i)) return(0);
X	if(!ptitoken(btoken.tcp+btoken.tcnt,&btoken))
X		return(!btoken.tval.tnum);
X	if(!btoken.tflg) return(0);	/* ERR: HH:<alpha> */
X	if(btoken.tcnt == 4)		/* MMSS */
X		if(ptstash(&tp[TM_MIN],btoken.tval.tnum/100)
X		  || ptstash(&tp[TM_SEC],btoken.tval.tnum%100))
X			return(0);
X		else goto domore;
X	if(btoken.tcnt != 2
X	  || ptstash(&tp[TM_MIN],btoken.tval.tnum))
X		return(0);		/* ERR: MM bad */
X	if(btoken.tbrk != ':') goto domore;	/* Seconds follow? */
Xcoltm2:	if(!ptitoken(btoken.tcp+btoken.tcnt,&btoken))
X		return(!btoken.tval.tnum);
X	if(!btoken.tflg || btoken.tcnt != 2	/* Verify SS */
X	  || ptstash(&tp[TM_SEC], btoken.tval.tnum))
X		return(0);		/* ERR: SS bad */
X	goto domore;
X}
X
X/* Store date/time value, return 0 if successful.
X * Fails if entry already set to a different value.
X */
Xptstash(adr,val)
Xint *adr;
X{	register int *a;
X	if( *(a=adr) != TMNULL)
X		return(*a != val);
X	*a = val;
X	return(0);
X}
X
X/* This subroutine is invoked for NOON or MIDNIGHT when wrapping up
X * just prior to returning from partime.
X */
Xpt12hack(atp, aval)
Xint *atp, aval;
X{	register int *tp, i, h;
X	tp = atp;
X	if (((i=tp[TM_MIN]) && i != TMNULL)	/* Ensure mins, secs */
X	 || ((i=tp[TM_SEC]) && i != TMNULL))	/* are 0 or unspec'd */
X		return(0);			/* ERR: MM:SS not 00:00 */
X	i = aval;			/* Get 0 or 12 (midnite or noon) */
X	if ((h = tp[TM_HOUR]) == TMNULL	/* If hour unspec'd, win */
X	 || h == 12)			/* or if 12:00 (matches either) */
X		tp[TM_HOUR] = i;	/* Then set time */
X	else if(!(i == 0		/* Nope, but if midnight and */
X		&&(h == 0 || h == 24)))	/* time matches, can pass. */
X			return(0);	/* ERR: HH conflicts */
X	tp[TM_AMPM] = TMNULL;		/* Always reset this value if won */
X	return(1);
X}
X
X/* Null routine for no-op tokens */
X
Xptnoise() { return(1); }
X
X/* Get a token and identify it to some degree.
X * Returns 0 on failure; token.tval will be 0 for normal EOF, otherwise
X * hit error of some sort
X */
X
Xptitoken(astr, tkp)
Xregister struct token *tkp;
Xchar *astr;
X{
X	register char *cp;
X	register int i;
X
X	tkp->tval.tnum = 0;
X	if(pttoken(astr,tkp) == 0)
X#ifdef DEBUG
X	VOID printf("EOF\n");
X#endif DEBUG
X		return(0);
X	cp = tkp->tcp;
X
X#ifdef DEBUG
X	i = cp[tkp->tcnt];
X	cp[tkp->tcnt] = 0;
X	VOID printf("Token: \"%s\" ",cp);
X	cp[tkp->tcnt] = i;
X#endif DEBUG
X
X	if(tkp->tflg)
X		for(i = tkp->tcnt; i > 0; i--)
X			tkp->tval.tnum = (int)tkp->tval.tnum*10 + ((*cp++)-'0');
X	else
X	  {     i = ptmatchstr(cp, tkp->tcnt, tmwords);
X		tkp->tval.tnum = i ? i : -1;         /* Set -1 for error */
X
X#ifdef DEBUG
X		if(!i) VOID printf("Not found!\n");
X#endif DEBUG
X
X		if(!i) return(0);
X	  }
X
X#ifdef DEBUG
X	if(tkp->tflg)
X		VOID printf("Val: %d.\n",tkp->tval.tnum);
X	else VOID printf("Found: \"%s\", val: %d., type %d\n",
X		tkp->tval.ttmw->went,tkp->tval.ttmw->wval,tkp->tval.ttmw->wtype);
X#endif DEBUG
X
X	return(1);
X}
X
X/* Read token from input string into token structure */
Xpttoken(astr,tkp)
Xregister struct token *tkp;
Xchar *astr;
X{
X	register char *cp;
X	register int c;
X
X	tkp->tcp = cp = astr;
X	tkp->tbrkl = tkp->tbrk;		/* Set "last break" */
X	tkp->tcnt = tkp->tbrk = tkp->tflg = 0;
X
X	while(c = *cp++)
X	  {	switch(c)
X		  {	case ' ': case '\t':	/* Flush all whitespace */
X				while((c = *cp++) && isspace(c));
X				cp--;		/* Drop thru to handle brk */
X			case '(': case ')':	/* Perhaps any non-alphanum */
X			case '-': case ',':	/* shd qualify as break? */
X			case '/': case ':': case '.':	/* Break chars */
X				if(tkp->tcnt == 0)	/* If no token yet */
X				  {	tkp->tcp = cp;	/* ignore the brk */
X					tkp->tbrkl = c;
X				  	continue;	/* and go on. */
X				  }
X				tkp->tbrk = c;
X				return(tkp->tcnt);
X		  }
X		if(tkp->tcnt == 0)		/* If first char of token, */
X			tkp->tflg = isdigit(c);	/*    determine type */
X	  	if(( isdigit(c) &&  tkp->tflg)	/* If not first, make sure */
X		 ||(!isdigit(c) && !tkp->tflg))	/*    char matches type */
X			tkp->tcnt++;		/* Win, add to token. */
X		else {
X			cp--;			/* Wrong type, back up */
X			tkp->tbrk = c;
X			return(tkp->tcnt);
X		  }
X	  }
X	return(tkp->tcnt);		/* When hit EOF */
X}
X
X
Xptmatchstr(astr,cnt,astruc)
Xchar *astr;
Xint cnt;
Xstruct tmwent *astruc;
X{	register char *cp, *mp;
X	register int c;
X	struct tmwent *lastptr;
X	struct integ { int word; };   /* For getting at array ptr */
X	int i;
X
X	lastptr = 0;
X	for(;mp = (char *)((struct integ *)astruc)->word; astruc += 1)
X	  {	cp = astr;
X		for(i = cnt; i > 0; i--)
X		  {	switch((c = *cp++) ^ *mp++)	/* XOR the chars */
X			  {	case 0: continue;	/* Exact match */
X				case 040: if(isalpha(c))
X					continue;
X			  }
X			break;
X		  }
X		if(i==0)
X			if(*mp == 0) return((unsigned int)astruc);    /* Exact match */
X			else if(lastptr) return(0);	/* Ambiguous */
X			else lastptr = astruc;		/* 1st ambig */
X	  }
X	return((unsigned int)lastptr);
X}
X
X
X
Xzaptime(tp)
Xregister int *tp;
X/* clears tm structure pointed to by tp */
X{	register int i;
X	i = (sizeof (struct tm))/(sizeof (int));
X	do *tp++ = TMNULL;		/* Set entry to "unspecified" */
X	while(--i);			/* Faster than FOR */
X}
END_OF_FILE
if test 14739 -ne `wc -c <'src/partime.c'`; then
    echo shar: \"'src/partime.c'\" unpacked with wrong size!
fi
# end of 'src/partime.c'
fi
echo shar: End of archive 5 \(of 7\).
cp /dev/null ark5isdone
MISSING=""
for I in 1 2 3 4 5 6 7 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 7 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
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.