allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (08/08/89)
Posting-number: Volume 7, Issue 123 Submitted-by: chris@cetia.UUCP (Chris Bertin) Archive-name: sccs.sh This is a file I keep in my home directory under the name '.shrc.sccs'. It contains quite a few functions that I use to manage SCCS, to do source tree comparisons, etc... This stuff has evolved over the years and some of the functions have gotten quite large; in fact, several of them should probably be turned into separate shell scripts. All these functions assume that the SCCS files are kept in an 'SCCS' directory. If you have the BSD 'sccs' command, several of these functions will lose some of their usefulness. I have never bothered with 'sccs' because I started to use these functions before the 'sccs' command came out. ----------------------------------- cut here --------------------------------- ## ## ex:set sw=4: # set shiftwidth to 4 when editing this file ## ## ## SCCS functions ## ## Chris Bertin ## ## Company=YOUR_SITE # Used for header functions # Files matching the following patterns will be skipped by 'recdiff' RECSKIP="tags *.out llib*.ln lib.*" # admin files adm() { [ ${#} -eq 0 ] && { echo "Usage: adm files" >&2 return 1 } [ -d SCCS ] || { echo "Creating SCCS directory" mkdir SCCS || return 1 } for AFile do admin -i${AFile} SCCS/s.${AFile} && { rm -f ${AFile} get SCCS/s.${AFile} } shift done unset AFile } # admin all files in the current directory adminall() { [ -f *.mk ] && AFile=`echo *.mk` || { [ -f Makefile ] && AFile=Makefile || { [ -f makefile ] && AFile=makefile } } [ -f "${AFile}" ] && { echo "Warning: doing a 'make clobber'" sleep 5 make -f ${AFile} clobber } adm `ls | grep -v SCCS` ls -l * } # handle pending files in directory trees (default '.') using delall (see below) recdel() { [ ${#} -gt 0 ] && List="$*" || List=`pwd` CurDir=`pwd` trap "cd ${CurDir}; echo '...Aborted'; trap 1 2 3; return -1 >&- 2>&-" 1 2 3 for Tree in ${List}; do echo "Doing ${Tree}" cd ${Tree} || continue for Dir in `find . -type d ! -name SCCS -print | sed "s/^\.\///"` ; do cd ${Dir} && delall || { cd ${CurDir}; trap 1 2 3; return 1; } cd ${CurDir} && cd ${Tree} done cd ${CurDir} done unset CurDir Tree Dir List trap 1 2 3 } # handle all pending files in the current directory (delta or unget or nothing) delall() { [ ${#} -ne 0 ] && { echo "Usage: delall" >&2 return 1 } [ -z "`pend`" ] && { echo "Nothing pending in `pwd`" >&2 return 0 } for DLFile in `pend` ; do echo ">> `pwd`/${DLFile}" xdiff ${DLFile} [ ! -t 1 ] && { echo "-----------------------------"; continue; } while : ; do echo "${DLFile}: [dacpqrsuv] (or '?' for help): \c" read ANSWER < /dev/tty # Note that there is no 'shift' here. They are handled by the # functions called in each case. This is a major uglyness in 'sh'. case "${ANSWER}" in c*|C*) CoMMent="`echo ${ANSWER} | sed 's/[a-zA-Z]* //'`" echo "New comment is \"${CoMMent}\"" continue ;; a*|A*) echo "Autodelta with comment: \"${CoMMent}\"" sleep 1 chkpfile ${DLFile} || continue delta -y"${CoMMent}" SCCS/s.${DLFile} getn ${DLFile} ;; d*|D*) del ${DLFile} ;; p*|P*) echo "Current autocomment is: \"${CoMMent}\"" continue ;; u*|U*) ung ${DLFile} ;; r*|R*) xdiff ${DLFile} continue ;; v*|V*) vi ${DLFile} xdiff ${DLFile} continue ;; s*|S*) ${SHELL:-/bin/sh} echo "exited" continue ;; q*|Q*) return 1 ;; "") echo "${DLFile} unchanged..." ;; ?*|*) echo "Usage:\t\"c new auto comment\" (enter new comment)" >&2 echo "\t\"a\" (auto delta with previous auto comment)" >&2 echo "\t\"d\" (delta file manually)" >&2 echo "\t\"p\" (print autocomment)" >&2 echo "\t\"q\" (quit)" >&2 echo "\t\"r\" (redo)" >&2 echo "\t\"s\" (sub-shell)" >&2 echo "\t\"u\" (unget file)" >&2 echo "\t\"v\" (vi file)" >&2 echo "\t\"\" (do nothing)" >&2 continue ;; esac break done done return 0 } # check if there is an 'SCCS' directory. Args are passed because 'sh' doesn't # stack arguments to functions. chkSCCSdir() { [ -d SCCS ] || { echo "No SCCS directory" >&2 return 1 } return 0 } # check if file is SCCS'ed. chkSCCSfile() { [ -f SCCS/s.${1} ] && return 0 echo "${1} not SCCS'ed" >&2 return 1 } # delta files del() { [ ${#} -eq 0 ] && { echo "Usage: del files" >&2 return 1 } for DFile do chkpfile ${DFile} || continue delta SCCS/s.${DFile} && get SCCS/s.${DFile} shift done unset DFile } # check whether p file is valid chkpfile() { [ ${#} -eq 0 ] && { echo "Usage: chkpfile files" >&2 return 1 } [ $# -ne 1 ] && { echo "Arg count"; return 1; } [ -r SCCS/p.${1} ] || { echo "${1} not pending"; return 1; } WhoAmI=`whoami` WhoIsIt="`awk '{print $3}' < SCCS/p.${1}`" [ "${WhoAmI}" != "${WhoIsIt}" ] && { echo "You didn't get ${1} -- (`cat SCCS/p.${1}`)" if [ -t 1 ]; then echo "Fix it? \c" read ANSWER < /dev/tty else ANSWER="${DEFCHKP}" fi case "${ANSWER}" in y*|Y*) echo "s/${WhoIsIt}/${WhoAmI}/\nw\nq" | ed - SCCS/p.${1} [ ${?} -ne 0 ] && { echo "Can't fix SCCS/p.${1} -- Sorry"; return 1; } [ -t 1 ] || echo "Pfile fixed" return 0 ;; *) return 1 ;; esac } return 0 } # get an SCCS file in edit mode gete() { [ ${#} -eq 0 ] && { echo "Usage: gete files" >&2 return 1 } chkSCCSdir $* || return 1 for GEFile do chkSCCSfile ${GEFile} && get -e SCCS/s.${GEFile} shift done unset GEFile } # get an SCCS file without editing getn() { [ ${#} -eq 0 ] && { echo "Usage: getn files" >&2 return 1 } chkSCCSdir $* || return 1 for GNFile do chkSCCSfile ${GNFile} && get SCCS/s.${GNFile} shift done unset GNFile } # get an SCCS file into standard output getp() { [ ${#} -eq 0 ] && { echo "Usage: getp files" >&2 return 1 } chkSCCSdir $* || return 1 for GPFile do chkSCCSfile ${GPFile} && get -p SCCS/s.${GPFile} shift done unset GPFile } # get given version of an SCCS file into standard output getv() { [ ${#} -ne 2 ] && { echo "Usage: getv version file" >&2 return 1 } chkSCCSdir $* || return 1 VeRs=${1} shift chkSCCSfile ${1} && get -p -r${VeRs} SCCS/s.${1} unset VeRs } # unget SCCS files that have been get'ed but not modified (in current directory) cleanget() { [ ${#} -ne 0 ] && { echo "Usage: cleanget" >&2 return 1 } CurDir=`pwd` trap "cd ${CurDir}; echo '...Aborted'; trap 1 2 3; return -1 >&- 2>&-" 1 2 3 find . -type f -name "p.*" -print | while read CGfile; do BaseName=`basename ${CGfile} | sed "s/p.//"` cd `dirname ${CGfile}`/.. || continue echo ${BaseName}: pending in directory `pwd` get -k -p SCCS/s.${BaseName} 2>&- | ${DIFF:-diff} - ${BaseName} >&- 2>&- && { echo No differences for SCCS/s.${BaseName} CurDate=`/src/uts/m68k/bin/curdate ${BaseName}` chkpfile ${BaseName} || continue ung ${BaseName} touch -m ${CurDate} ${BaseName} } cd ${CurDir} done unset CurDir CGfile CurDate BaseName trap 1 2 3 } # SCCS history for SCCS files hist() { [ ${#} -eq 0 ] && { echo "Usage: hist files" >&2 return 1 } chkSCCSdir $* || return 1 for HFile do chkSCCSfile ${HFile} && prs -e \ -d'Delta for :M:: -- :I: --, Date: :D:\nUpdate: :C:' SCCS/s.${HFile} shift done unset HFile } # examine SCCS files m() { [ ${#} -eq 0 ] && { echo "Usage: m files" >&2 return 1 } chkSCCSdir $* || return 1 for MFile do chkSCCSfile ${MFile} && get -s -p SCCS/s.${MFile} | ${PAGER:-pg} shift done unset MFile } # show which SCCS files are pending. Optionnal args limit the name expansion pend() { chkSCCSdir $* || return 1 [ -f SCCS/p.* ] && echo `ls SCCS/p.*${1} 2>&- | sed "s/^SCCS\/p.//"` } # show all pending files in the given directory trees (default '.') pendall() { [ ${#} -gt 0 ] && List="$*" || List=. for Tree in ${List}; do find ${Tree} -type f -name 'p.*' -print | \ sed -e 's/^\.\///' -e 's/SCCS\/p\.//' done unset Tree List } # add a SCCS header to the given C files shead() { [ ${#} -eq 0 ] && { echo "Usage: shead files" >&2 return 1 } for SFile do echo '1i\n#ifndef lint\nstatic char ID[] = "%W% ${Company} %E%";\n#endif lint\n.\nw\nq' | ed - ${SFile} done unset SFile } # add a SCCS header to the given header files shhead() { [ ${#} -eq 0 ] && { echo "Usage: shhead files" >&2 return 1 } for SHFile do echo '1i\n/* %W% ${Company} %E% */\n.\nw\nq' | ed - ${SHFile} done unset SHFile } # add a SCCS header to the given FORTRAN files sfhead() { [ ${#} -eq 0 ] && { echo "Usage: sfhead files" >&2 return 1 } for SFFile do echo '1i\n* %W% ${Company} %Z%\n.\nw\nq' | ed - ${SFFile} done unset SFFile } # add a SCCS header to the given SHELL (or MAKEFILE) files sshead() { smhead $*; } smhead() { [ ${#} -eq 0 ] && { echo "Usage: smhead files" >&2 return 1 } for MHFile do echo '1i\n# %W% ${Company} %Z%\nw\nq' | ed - ${MHFile} done unset MHFile } # unget files ung() { [ ${#} -eq 0 ] && { echo "Usage: ung files" >&2 return 1 } chkSCCSdir $* || return 1 for UFile do chkSCCSfile ${UFile} && chkpfile ${UFile} && \ unget SCCS/s.${UFile} && get SCCS/s.${UFile} shift done unset UFile } # unget files without reextracting them ungn() { [ ${#} -eq 0 ] && { echo "Usage: ungn files" >&2 return 1 } chkSCCSdir $* || return 1 for UNFile do chkSCCSfile ${UNFile} && chkpfile ${UNFile} && unget SCCS/s.${UNFile} shift done unset UNFile } # show SCCS versions vs() { [ ${#} -eq 0 ] && { echo "Usage: vs files" >&2 return 1 } chkSCCSdir $* || return 1 for VFile do chkSCCSfile ${VFile} && prs -d"Current delta for :M:: -- :I: --, Date: :D:\nLast update: :C:" SCCS/s.${VFile} shift done unset VFile } # show SCCS versions of a full directory vss() { [ ${#} -eq 0 ] && { echo "Usage: vss sccs_directory" >&2 return 1 } prs -d"Current delta for :M:: -- :I: --, Date: :D:\nLast update: :C:" $1 } # diff an SCCS file and its extracted counterpart xdiff() { [ ${#} -eq 0 ] && { echo "Usage: xdiff files" >&2 return 1 } chkSCCSdir $* || return 1 for XFile do chkSCCSfile ${XFile} || continue [ ! -f ${XFile} ] && { ls ${XFile} continue } vs ${XFile} && get -p SCCS/s.${XFile} | ${DIFF:-diff} - ${XFile} 2>&1 | ${PAGER:-pg} done unset XFile } ## sccsdiff between 2 versions scdiff() { [ ${#} -lt 3 ] && { echo "Usage: scdiff rel1 rel2 files" >&2 return 1 } R1=$1; shift R2=$1; shift chkSCCSdir $* || return 1 for SFiLE do chkSCCSfile ${SFiLE} || continue sccsdiff -r${R1} -r${R2} SCCS/s.${SFiLE} | ${PAGER:-pg} done unset R1 R2 SFiLE } # recursive difference between 2 trees, using diffall (See below) recdiff() { Skip= Flag= for Opt in "$@"; do case ${Opt} in -F|-f) Flag="${Flag} -f" [ ${Opt} = "-f" ] && Fopt="-o -type l"; shift ;; -o) Flag="${Flag} -o"; shift ;; -s) Skip="! -name SCCS" ;shift ;; *) break ;; esac done [ \( ${#} -eq 1 -a ${1} = '-' \) -o ${#} -eq 2 ] && { : } || { echo "Usage:\trecdiff [-f] [-F] [-o] [-s] ['-' or dir1] dir2" >&2 echo "\t-f: follow symbolic links in destination" >&2 echo "\t-F: follow symbolic links both in source and destination" >&2 echo "\t-o: compare non ascii files" >&2 echo "\t-s: skip SCCS directories" >&2 echo "\t- : If first argument is '-', do recursive SCCS diffs" >&2 return 1 } BaseDir="." Arg1=${1} Arg2=${2} if [ ${Arg1} = "-" ]; then Skip="! -name SCCS" [ ${#} = 1 ] && set - "." || BaseDir=${2} Sdiff=true shift else Sdiff=false fi CurDir=`pwd` for Dir do [ ! -d ${Dir} ] && { echo "${Dir}: not a directory" unset Dir return 1 } done if [ ${Sdiff} = false ]; then cd ${Arg2} || return 1 Dest=`pwd` cd ${CurDir} || return 1 fi if [ ${Sdiff} = false ]; then cd ${Arg1} || return 1 fi trap "cd ${CurDir}; echo '...Aborted'; trap 1 2 3; return -1 >&- 2>&-" 1 2 3 find ${BaseDir} ${Skip} \( -type d ${Fopt} \) -print | sed "s/^\.\///" | \ while read Dir; do [ -d ${Dir} ] || continue echo "------ Directory: ${CurDir}/${Dir} ------" cd ${Dir} || continue if [ ${Sdiff} = true ]; then if [ -d SCCS ]; then Pending="`pend`" if [ "${Pending}" != "" ]; then diffall ${Flag} - ${Pending} || break fi fi cd ${CurDir} continue fi if [ ! -d ${Dest}/${Dir} ]; then diffall ${Flag} ${Dir} ${Dest} # for error handling else List="" for F in `ls -a`; do [ -z "${RECSKIP}" ] || { for FF in `echo ${RECSKIP}` ; do [ ${FF} = ${F} ] && { echo "===== ${F} ===== Skipped" continue 2 } done } [ -f ${F} ] && List="${List} ${F}" done [ -z "${List}" ] || diffall ${Flag} ${List} ${Dest}/${Dir} || break fi cd ${CurDir} cd ${Arg1} done cd ${CurDir} unset Arg1 Arg2 CurDir Skip Dir Dest Flag Fopt F List Opt Pending Sdiff trap 1 2 3 } # diff a series of files with their SCCS counterpart or another directory diffall() { Dusage() { echo "Usage:\tdiffall [-f] [-o] ['-' or files] [directory]" >&2 echo "\t-f: follow symbolic links" >&2 echo "\t-o: compare non ascii files" >&2 } Nodiff() { REDO=0; echo "$* No differences"; } Follow=false Dotos=false for Opt in "$@"; do case ${Opt} in -f) Follow=true; shift ;; -o) Dotos=true; shift ;; *) break ;; esac done [ "$1" = "" ] && { Dusage; return 1; } if [ "$1" = "-" ]; then XdIfF=true DiR="." shift chkSCCSdir $* else Args=$* XdIfF=false DiR=`echo $* | sed "s/.* //"` [ ! -d "${DiR}" ] && { echo "${DiR}: not a directory" >&2 Dusage return 1 } ls ${DiR} > /tmp/di$$ More=`ls | ${DIFF:-diff} - /tmp/di$$ | grep "^> " | sed "s/^> //"` rm -f /tmp/di$$ [ -z "${More}" ] || echo "\nWARNING: `echo ${More}` only in ${DiR}\n" set ${Args} unset More Args fi Dcheckread() { for FIle do [ -d ${FIle} -o -c ${FIle} -o -b ${FIle} -o ! -r ${FIle} ] && { echo "skipping `file ${FIle}`" unset FIle return 1 } done unset FIle return 0 } for FiLe do [ "${FiLe}" = "${DiR}" ] && break REDO=1 while [ ${REDO} -gt 0 ] ; do [ ${REDO} -eq 1 ] && echo "====== ${FiLe} ===== \c" if [ ${REDO} -eq 2 ]; then : do nothing. We got here because of wrong answer below elif [ "${XdIfF}" = "true" ] ; then [ -d ${FiLe} ] && { echo "${FiLe}: Directory" REDO=0 continue } if Dcheckread ${FiLe} SCCS/s.${FiLe} ; then get -p -s SCCS/s.${FiLe} 2>&- | ${CMP:-cmp} - ${FiLe} 2>&1 && { Nodiff continue } get -p -s SCCS/s.${FiLe}| ${DIFF:-diff} - ${FiLe} | \ ${PAGER:-pg} fi else if [ ${Follow} = false -a \ `ls -l ${FiLe} 2>&- | grep -c " -> "` -eq 1 ] ; then echo "${FiLe}: Symbolic link\c" if [ "`ls -d ${DiR}/${FiLe} 2>&-`" != "${DiR}/${FiLe}" ]; then echo "s differ: ${DiR}/${FiLe} doesn't exist" elif [ `ls -l ${DiR}/${FiLe} | grep -c " -> "` -eq 1 ]; then ls -l ${FiLe} 2>&- | sed "s/.* -> //" > /tmp/dall.$$ ls -l ${DiR}/${FiLe} 2>&- | sed "s/.* -> //" | \ ${DIFF:-diff} - /tmp/dall.$$ >&- 2>&- [ ${?} = 0 ] && { rm -f /tmp/dall.$$ Nodiff "s," continue } || { rm -f /tmp/dall.$$ echo "s differ:" ls -l ${FiLe} ${DiR}/${FiLe} } else echo " but ${FiLe} differ: `file ${DiR}/${FiLe}`" fi else if Dcheckread ${FiLe} ${DiR}/${FiLe}; then if file ${FiLe} | \ egrep " executable| archive| object| data" >&-; then echo "`file ${FiLe} | \ sed 's/[ ][ ]*/ /g'` -> \c" [ ${Dotos} = false ] && { echo "Ignored" REDO=0 continue } ${CMP:-cmp} ${FiLe} ${DiR}/${FiLe} 2>&1 && { Nodiff continue } else ${CMP:-cmp} ${FiLe} ${DiR}/`basename ${FiLe}` 2>&1 && { Nodiff continue } ${DIFF:-diff} ${FiLe} ${DiR} | ${PAGER:-pg} fi fi fi fi [ ! -t 1 ] && { REDO=0; continue; } echo "====== ${FiLe} done ===== Hit return to continue: \c" REDO=0 read a < /dev/tty [ -z "${a}" ] && continue case ${a} in vi|e|v) REDO=1 ; vi ${FiLe} ;; mod) REDO=2 ; echo "cpio: -m option will be used"; Dopt=m ;; nomod) REDO=2 ; echo "cpio: no -m option"; Dopt="" ;; redo|r) REDO=1 ;; quit|q) return 1 ;; put|PUT) REDO=1 [ ${XdIfF} = true ] && { echo "Cannot put with '-' option" >&2 REDO=2 continue; } if [ ${a} = PUT ]; then rm -r ${DiR}/${FiLe} || { REDO=2 continue } fi if [ -f ${FiLe} ] ; then find ${FiLe} -print | cpio -pduvv${Dopt} ${DiR} elif [ -d ../${FiLe} ] ; then mkdir ${DiR}/${FiLe} || { REDO=2 continue } cd ..; find ${FiLe} -print | cpio -pduvv${Dopt} ${DiR} else echo "case not handled" REDO=2 fi ;; get|GET) REDO=1 if [ ${XdIfF} = true ]; then if [ -f SCCS/p.${FiLe} ]; then if [ ${a} = GET ]; then ung ${FiLe} || REDO=2 else echo "File is out being edited. Use GET to overwrite" REDO=2 fi fi [ ${REDO} -gt 0 ] && continue get SCCS/s.${FiLe} || REDO=2 else [ ${a} = GET ] && rm -rf ${FiLe} export Dopt ( Here=`pwd`; chdir ${DiR} && \ find ${FiLe} -print | cpio -pduvv${Dopt} ${Here} ) fi ;; sh) ${SHELL:-/bin/sh} REDO=2 ;; '?'|*) echo "Usage:\tvi|e|v:\tedit local file" >&2 echo "\tr|redo:\tredo file" >&2 echo "\tq|quit:\tquit" >&2 echo "\tput:\tcopy local file to remote file" >&2 echo "\tPUT:\tsame but forcibly" >&2 echo "\t[no]mod:\t[don't] use -m option for put/get" >&2 echo "\tget:\tcopy remote file to local file (or get SCCS file)" >&2 echo "\tGET:\tsame but forcibly" >&2 echo "\tsh:\tsub-shell" >&2 echo "\t'?':\thelp" >&2 REDO=2 ;; esac unset a done done unset FiLe Opt Follow Skip Dotos Follow REDO More Dopt Nodiff Dusage return 0 } ----------------------------------- cut here --------------------------------- -- Chris Bertin | -- CETIA -- 150, Av Marcelin Berthelot, Z.I. Toulon-Est +33(94)212005 | 83088 Toulon Cedex, France | inria!cetia!chris