xref: /openbsd-src/gnu/usr.bin/cvs/contrib/rcs2log.sh (revision 68ccc4cd4fcd46b761698c90bb1d612aed667c58)
11e72d8d2Sderaadt#! /bin/sh
21e72d8d2Sderaadt
31e72d8d2Sderaadt# RCS to ChangeLog generator
41e72d8d2Sderaadt
5c71bc7e2Stholo# Generate a change log prefix from RCS files (perhaps in the CVS repository)
6c71bc7e2Stholo# and the ChangeLog (if any).
71e72d8d2Sderaadt# Output the new prefix to standard output.
81e72d8d2Sderaadt# You can edit this prefix by hand, and then prepend it to ChangeLog.
91e72d8d2Sderaadt
101e72d8d2Sderaadt# Ignore log entries that start with `#'.
111e72d8d2Sderaadt# Clump together log entries that start with `{topic} ',
121e72d8d2Sderaadt# where `topic' contains neither white space nor `}'.
131e72d8d2Sderaadt
14c71bc7e2StholoHelp='The default FILEs are the files registered under the working directory.
15c71bc7e2StholoOptions:
161e72d8d2Sderaadt
17c71bc7e2Stholo  -c CHANGELOG  Output a change log prefix to CHANGELOG (default ChangeLog).
18c71bc7e2Stholo  -h HOSTNAME  Use HOSTNAME in change log entries (default current host).
19c71bc7e2Stholo  -i INDENT  Indent change log lines by INDENT spaces (default 8).
20c71bc7e2Stholo  -l LENGTH  Try to limit log lines to LENGTH characters (default 79).
21c71bc7e2Stholo  -R  If no FILEs are given and RCS is used, recurse through working directory.
22c71bc7e2Stholo  -r OPTION  Pass OPTION to subsidiary log command.
23c71bc7e2Stholo  -t TABWIDTH  Tab stops are every TABWIDTH characters (default 8).
24c71bc7e2Stholo  -u "LOGIN<tab>FULLNAME<tab>MAILADDR"  Assume LOGIN has FULLNAME and MAILADDR.
25c71bc7e2Stholo  -v  Append RCS revision to file names in log lines.
26c71bc7e2Stholo  --help  Output help.
27c71bc7e2Stholo  --version  Output version number.
28c71bc7e2Stholo
29c71bc7e2StholoReport bugs to <bug-gnu-emacs@gnu.org>.'
30c71bc7e2Stholo
31*68ccc4cdSmillertId='$Id: rcs2log.sh,v 1.2 2001/08/07 22:00:56 millert Exp $'
32c71bc7e2Stholo
33c71bc7e2Stholo# Copyright 1992, 93, 94, 95, 96, 97, 1998 Free Software Foundation, Inc.
341e72d8d2Sderaadt
351e72d8d2Sderaadt# This program is free software; you can redistribute it and/or modify
361e72d8d2Sderaadt# it under the terms of the GNU General Public License as published by
371e72d8d2Sderaadt# the Free Software Foundation; either version 2, or (at your option)
381e72d8d2Sderaadt# any later version.
391e72d8d2Sderaadt#
401e72d8d2Sderaadt# This program is distributed in the hope that it will be useful,
411e72d8d2Sderaadt# but WITHOUT ANY WARRANTY; without even the implied warranty of
421e72d8d2Sderaadt# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
431e72d8d2Sderaadt# GNU General Public License for more details.
441e72d8d2Sderaadt#
451e72d8d2Sderaadt# You should have received a copy of the GNU General Public License
4650bf276cStholo# along with this program; see the file COPYING.  If not, write to the
4750bf276cStholo# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
4850bf276cStholo# Boston, MA 02111-1307, USA.
491e72d8d2Sderaadt
50c71bc7e2StholoCopyright='Copyright 1998 Free Software Foundation, Inc.
51c71bc7e2StholoThis program comes with NO WARRANTY, to the extent permitted by law.
52c71bc7e2StholoYou may redistribute copies of this program
53c71bc7e2Stholounder the terms of the GNU General Public License.
54c71bc7e2StholoFor more information about these matters, see the files named COPYING.
55c71bc7e2StholoAuthor: Paul Eggert <eggert@twinsun.com>'
56c71bc7e2Stholo
571e72d8d2Sderaadttab='	'
581e72d8d2Sderaadtnl='
591e72d8d2Sderaadt'
601e72d8d2Sderaadt
611e72d8d2Sderaadt# Parse options.
621e72d8d2Sderaadt
631e72d8d2Sderaadt# defaults
641e72d8d2Sderaadt: ${AWK=awk}
651e72d8d2Sderaadt: ${TMPDIR=/tmp}
66c71bc7e2Stholochangelog=ChangeLog # change log file name
67c71bc7e2Stholodatearg= # rlog date option
681e72d8d2Sderaadthostname= # name of local host (if empty, will deduce it later)
691e72d8d2Sderaadtindent=8 # indent of log line
701e72d8d2Sderaadtlength=79 # suggested max width of log line
711e72d8d2Sderaadtlogins= # login names for people we know fullnames and mailaddrs of
721e72d8d2SderaadtloginFullnameMailaddrs= # login<tab>fullname<tab>mailaddr triplets
73c71bc7e2StholologTZ= # time zone for log dates (if empty, use local time)
741e72d8d2Sderaadtrecursive= # t if we want recursive rlog
75c71bc7e2Stholorevision= # t if we want revision numbers
761e72d8d2Sderaadtrlog_options= # options to pass to rlog
771e72d8d2Sderaadttabwidth=8 # width of horizontal tab
781e72d8d2Sderaadt
791e72d8d2Sderaadtwhile :
801e72d8d2Sderaadtdo
811e72d8d2Sderaadt	case $1 in
82c71bc7e2Stholo	-c)	changelog=${2?}; shift;;
831e72d8d2Sderaadt	-i)	indent=${2?}; shift;;
841e72d8d2Sderaadt	-h)	hostname=${2?}; shift;;
851e72d8d2Sderaadt	-l)	length=${2?}; shift;;
861e72d8d2Sderaadt	-[nu])	# -n is obsolescent; it is replaced by -u.
871e72d8d2Sderaadt		case $1 in
881e72d8d2Sderaadt		-n)	case ${2?}${3?}${4?} in
891e72d8d2Sderaadt			*"$tab"* | *"$nl"*)
901e72d8d2Sderaadt				echo >&2 "$0: -n '$2' '$3' '$4': tabs, newlines not allowed"
911e72d8d2Sderaadt				exit 1
921e72d8d2Sderaadt			esac
93c71bc7e2Stholo			case $loginFullnameMailaddrs in
94c71bc7e2Stholo			'') loginFullnameMailaddrs=$2$tab$3$tab$4;;
95c71bc7e2Stholo			?*) loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2$tab$3$tab$4
96c71bc7e2Stholo			esac
971e72d8d2Sderaadt			shift; shift; shift;;
981e72d8d2Sderaadt		-u)
991e72d8d2Sderaadt			# If $2 is not tab-separated, use colon for separator.
1001e72d8d2Sderaadt			case ${2?} in
1011e72d8d2Sderaadt			*"$nl"*)
1021e72d8d2Sderaadt				echo >&2 "$0: -u '$2': newlines not allowed"
1031e72d8d2Sderaadt				exit 1;;
1041e72d8d2Sderaadt			*"$tab"*)
1051e72d8d2Sderaadt				t=$tab;;
1061e72d8d2Sderaadt			*)
1071e72d8d2Sderaadt				t=:
1081e72d8d2Sderaadt			esac
1091e72d8d2Sderaadt			case $2 in
1101e72d8d2Sderaadt			*"$t"*"$t"*"$t"*)
1111e72d8d2Sderaadt				echo >&2 "$0: -u '$2': too many fields"
1121e72d8d2Sderaadt				exit 1;;
1131e72d8d2Sderaadt			*"$t"*"$t"*)
1141e72d8d2Sderaadt				;;
1151e72d8d2Sderaadt			*)
1161e72d8d2Sderaadt				echo >&2 "$0: -u '$2': not enough fields"
1171e72d8d2Sderaadt				exit 1
1181e72d8d2Sderaadt			esac
119c71bc7e2Stholo			case $loginFullnameMailaddrs in
120c71bc7e2Stholo			'') loginFullnameMailaddrs=$2;;
121c71bc7e2Stholo			?*) loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2
122c71bc7e2Stholo			esac
1231e72d8d2Sderaadt			shift
1241e72d8d2Sderaadt		esac
125c71bc7e2Stholo		case $logins in
126c71bc7e2Stholo		'') logins=$login;;
127c71bc7e2Stholo		?*) logins=$logins$nl$login
128c71bc7e2Stholo		esac
1291e72d8d2Sderaadt		;;
130c71bc7e2Stholo	-r)
131c71bc7e2Stholo		case $rlog_options in
132c71bc7e2Stholo		'') rlog_options=${2?};;
133c71bc7e2Stholo		?*) rlog_options=$rlog_options$nl${2?}
134c71bc7e2Stholo		esac
135c71bc7e2Stholo		shift;;
1361e72d8d2Sderaadt	-R)	recursive=t;;
1371e72d8d2Sderaadt	-t)	tabwidth=${2?}; shift;;
138c71bc7e2Stholo	-v)	revision=t;;
139c71bc7e2Stholo	--version)
140c71bc7e2Stholo		set $Id
141c71bc7e2Stholo		rcs2logVersion=$3
142c71bc7e2Stholo		echo >&2 "rcs2log (GNU Emacs) $rcs2logVersion$nl$Copyright"
143c71bc7e2Stholo		exit 0;;
144c71bc7e2Stholo	-*)	echo >&2 "Usage: $0 [OPTION]... [FILE ...]$nl$Help"
145c71bc7e2Stholo		case $1 in
146c71bc7e2Stholo		--help) exit 0;;
147c71bc7e2Stholo		*) exit 1
148c71bc7e2Stholo		esac;;
1491e72d8d2Sderaadt	*)	break
1501e72d8d2Sderaadt	esac
1511e72d8d2Sderaadt	shift
1521e72d8d2Sderaadtdone
1531e72d8d2Sderaadt
1541e72d8d2Sderaadtmonth_data='
1551e72d8d2Sderaadt	m[0]="Jan"; m[1]="Feb"; m[2]="Mar"
1561e72d8d2Sderaadt	m[3]="Apr"; m[4]="May"; m[5]="Jun"
1571e72d8d2Sderaadt	m[6]="Jul"; m[7]="Aug"; m[8]="Sep"
1581e72d8d2Sderaadt	m[9]="Oct"; m[10]="Nov"; m[11]="Dec"
1591e72d8d2Sderaadt'
1601e72d8d2Sderaadt
1611e72d8d2Sderaadt
1621e72d8d2Sderaadt# Put rlog output into $rlogout.
1631e72d8d2Sderaadt
1641e72d8d2Sderaadt# If no rlog options are given,
1651e72d8d2Sderaadt# log the revisions checked in since the first ChangeLog entry.
166c71bc7e2Stholo# Since ChangeLog is only by date, some of these revisions may be duplicates of
167c71bc7e2Stholo# what's already in ChangeLog; it's the user's responsibility to remove them.
1681e72d8d2Sderaadtcase $rlog_options in
1691e72d8d2Sderaadt'')
170c71bc7e2Stholo	if test -s "$changelog"
1711e72d8d2Sderaadt	then
1721e72d8d2Sderaadt		e='
173c71bc7e2Stholo			/^[0-9]+-[0-9][0-9]-[0-9][0-9]/{
174c71bc7e2Stholo				# ISO 8601 date
175c71bc7e2Stholo				print $1
176c71bc7e2Stholo				exit
177c71bc7e2Stholo			}
1781e72d8d2Sderaadt			/^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{
179c71bc7e2Stholo				# old-fashioned date and time (Emacs 19.31 and earlier)
1801e72d8d2Sderaadt				'"$month_data"'
1811e72d8d2Sderaadt				year = $5
1821e72d8d2Sderaadt				for (i=0; i<=11; i++) if (m[i] == $2) break
1831e72d8d2Sderaadt				dd = $3
184c71bc7e2Stholo				printf "%d-%02d-%02d\n", year, i+1, dd
1851e72d8d2Sderaadt				exit
1861e72d8d2Sderaadt			}
1871e72d8d2Sderaadt		'
188c71bc7e2Stholo		d=`$AWK "$e" <"$changelog"` || exit
1891e72d8d2Sderaadt		case $d in
190c71bc7e2Stholo		?*) datearg="-d>$d"
1911e72d8d2Sderaadt		esac
1921e72d8d2Sderaadt	fi
1931e72d8d2Sderaadtesac
1941e72d8d2Sderaadt
195c71bc7e2Stholo# Use TZ specified by ChangeLog local variable, if any.
196c71bc7e2Stholoif test -s "$changelog"
197c71bc7e2Stholothen
198c71bc7e2Stholo	extractTZ='
199c71bc7e2Stholo		/^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*"\([^"]*\)".*/{
200c71bc7e2Stholo			s//\1/; p; q
201c71bc7e2Stholo		}
202c71bc7e2Stholo		/^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*t.*/{
203c71bc7e2Stholo			s//UTC0/; p; q
204c71bc7e2Stholo		}
205c71bc7e2Stholo	'
206c71bc7e2Stholo	logTZ=`tail "$changelog" | sed -n "$extractTZ"`
207c71bc7e2Stholo	case $logTZ in
208c71bc7e2Stholo	?*) TZ=$logTZ; export TZ
209c71bc7e2Stholo	esac
210c71bc7e2Stholofi
211c71bc7e2Stholo
2121e72d8d2Sderaadt# If CVS is in use, examine its repository, not the normal RCS files.
2131e72d8d2Sderaadtif test ! -f CVS/Repository
2141e72d8d2Sderaadtthen
2151e72d8d2Sderaadt	rlog=rlog
2161e72d8d2Sderaadt	repository=
2171e72d8d2Sderaadtelse
218c71bc7e2Stholo	rlog='cvs -q log'
2191e72d8d2Sderaadt	repository=`sed 1q <CVS/Repository` || exit
2201e72d8d2Sderaadt	test ! -f CVS/Root || CVSROOT=`cat <CVS/Root` || exit
2211e72d8d2Sderaadt	case $CVSROOT in
2221e72d8d2Sderaadt	*:/*)
2231e72d8d2Sderaadt		# remote repository
2241e72d8d2Sderaadt		;;
2251e72d8d2Sderaadt	*)
2261e72d8d2Sderaadt		# local repository
2271e72d8d2Sderaadt		case $repository in
2281e72d8d2Sderaadt		/*) ;;
2291e72d8d2Sderaadt		*) repository=${CVSROOT?}/$repository
2301e72d8d2Sderaadt		esac
2311e72d8d2Sderaadt		if test ! -d "$repository"
2321e72d8d2Sderaadt		then
2331e72d8d2Sderaadt			echo >&2 "$0: $repository: bad repository (see CVS/Repository)"
2341e72d8d2Sderaadt			exit 1
2351e72d8d2Sderaadt		fi
2361e72d8d2Sderaadt	esac
2371e72d8d2Sderaadtfi
2381e72d8d2Sderaadt
239c71bc7e2Stholo# Use $rlog's -zLT option, if $rlog supports it.
240c71bc7e2Stholocase `$rlog -zLT 2>&1` in
241c71bc7e2Stholo*' option'*) ;;
242c71bc7e2Stholo*)
243c71bc7e2Stholo	case $rlog_options in
244c71bc7e2Stholo	'') rlog_options=-zLT;;
245c71bc7e2Stholo	?*) rlog_options=-zLT$nl$rlog_options
246c71bc7e2Stholo	esac
247c71bc7e2Stholoesac
248c71bc7e2Stholo
2491e72d8d2Sderaadt# With no arguments, examine all files under the RCS directory.
2501e72d8d2Sderaadtcase $# in
2511e72d8d2Sderaadt0)
2521e72d8d2Sderaadt	case $repository in
2531e72d8d2Sderaadt	'')
2541e72d8d2Sderaadt		oldIFS=$IFS
2551e72d8d2Sderaadt		IFS=$nl
2561e72d8d2Sderaadt		case $recursive in
2571e72d8d2Sderaadt		t)
2581e72d8d2Sderaadt			RCSdirs=`find . -name RCS -type d -print`
2591e72d8d2Sderaadt			filesFromRCSfiles='s|,v$||; s|/RCS/|/|; s|^\./||'
2601e72d8d2Sderaadt			files=`
2611e72d8d2Sderaadt				{
2621e72d8d2Sderaadt					case $RCSdirs in
263c71bc7e2Stholo					?*) find $RCSdirs \
264c71bc7e2Stholo							-type f \
265c71bc7e2Stholo							! -name '*_' \
266c71bc7e2Stholo							! -name ',*,' \
267c71bc7e2Stholo							! -name '.*_' \
268c71bc7e2Stholo							! -name .rcsfreeze.log \
269c71bc7e2Stholo							! -name .rcsfreeze.ver \
270c71bc7e2Stholo							-print
2711e72d8d2Sderaadt					esac
2721e72d8d2Sderaadt					find . -name '*,v' -print
2731e72d8d2Sderaadt				} |
2741e72d8d2Sderaadt				sort -u |
2751e72d8d2Sderaadt				sed "$filesFromRCSfiles"
2761e72d8d2Sderaadt			`;;
2771e72d8d2Sderaadt		*)
2781e72d8d2Sderaadt			files=
2791e72d8d2Sderaadt			for file in RCS/.* RCS/* .*,v *,v
2801e72d8d2Sderaadt			do
2811e72d8d2Sderaadt				case $file in
282c71bc7e2Stholo				RCS/. | RCS/.. | RCS/,*, | RCS/*_) continue;;
283c71bc7e2Stholo				RCS/.rcsfreeze.log | RCS/.rcsfreeze.ver) continue;;
284c71bc7e2Stholo				RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue;;
285c71bc7e2Stholo				RCS/*,v | RCS/.*,v) ;;
286c71bc7e2Stholo				RCS/* | RCS/.*) test -f "$file" || continue
2871e72d8d2Sderaadt				esac
288c71bc7e2Stholo				case $files in
289c71bc7e2Stholo				'') files=$file;;
290c71bc7e2Stholo				?*) files=$files$nl$file
291c71bc7e2Stholo				esac
2921e72d8d2Sderaadt			done
2931e72d8d2Sderaadt			case $files in
2941e72d8d2Sderaadt			'') exit 0
2951e72d8d2Sderaadt			esac
2961e72d8d2Sderaadt		esac
2971e72d8d2Sderaadt		set x $files
2981e72d8d2Sderaadt		shift
2991e72d8d2Sderaadt		IFS=$oldIFS
3001e72d8d2Sderaadt	esac
3011e72d8d2Sderaadtesac
3021e72d8d2Sderaadt
303*68ccc4cdSmillertllogout=`mktemp $TMPDIR/rcs2log_l.XXXXXXXXXX` || exit 1
304*68ccc4cdSmillertrlogout=`mktemp $TMPDIR/rcs2log_r.XXXXXXXXXX` || {
305*68ccc4cdSmillert	rm -f $llogout
306*68ccc4cdSmillert	exit 1
307*68ccc4cdSmillert}
3081e72d8d2Sderaadttrap exit 1 2 13 15
3091e72d8d2Sderaadttrap "rm -f $llogout $rlogout; exit 1" 0
3101e72d8d2Sderaadt
311c71bc7e2Stholocase $datearg in
312c71bc7e2Stholo?*) $rlog $rlog_options "$datearg" ${1+"$@"} >$rlogout;;
313c71bc7e2Stholo'') $rlog $rlog_options ${1+"$@"} >$rlogout
3141e72d8d2Sderaadtesac || exit
3151e72d8d2Sderaadt
3161e72d8d2Sderaadt
3171e72d8d2Sderaadt# Get the full name of each author the logs mention, and set initialize_fullname
3181e72d8d2Sderaadt# to awk code that initializes the `fullname' awk associative array.
3191e72d8d2Sderaadt# Warning: foreign authors (i.e. not known in the passwd file) are mishandled;
3201e72d8d2Sderaadt# you have to fix the resulting output by hand.
3211e72d8d2Sderaadt
3221e72d8d2Sderaadtinitialize_fullname=
3231e72d8d2Sderaadtinitialize_mailaddr=
3241e72d8d2Sderaadt
3251e72d8d2Sderaadtcase $loginFullnameMailaddrs in
3261e72d8d2Sderaadt?*)
3271e72d8d2Sderaadt	case $loginFullnameMailaddrs in
3281e72d8d2Sderaadt	*\"* | *\\*)
3291e72d8d2Sderaadt		sed 's/["\\]/\\&/g' >$llogout <<EOF || exit
3301e72d8d2Sderaadt$loginFullnameMailaddrs
3311e72d8d2SderaadtEOF
3321e72d8d2Sderaadt		loginFullnameMailaddrs=`cat $llogout`
3331e72d8d2Sderaadt	esac
3341e72d8d2Sderaadt
3351e72d8d2Sderaadt	oldIFS=$IFS
3361e72d8d2Sderaadt	IFS=$nl
3371e72d8d2Sderaadt	for loginFullnameMailaddr in $loginFullnameMailaddrs
3381e72d8d2Sderaadt	do
3391e72d8d2Sderaadt		case $loginFullnameMailaddr in
3401e72d8d2Sderaadt		*"$tab"*) IFS=$tab;;
3411e72d8d2Sderaadt		*) IFS=:
3421e72d8d2Sderaadt		esac
3431e72d8d2Sderaadt		set x $loginFullnameMailaddr
3441e72d8d2Sderaadt		login=$2
3451e72d8d2Sderaadt		fullname=$3
3461e72d8d2Sderaadt		mailaddr=$4
3471e72d8d2Sderaadt		initialize_fullname="$initialize_fullname
3481e72d8d2Sderaadt			fullname[\"$login\"] = \"$fullname\""
3491e72d8d2Sderaadt		initialize_mailaddr="$initialize_mailaddr
3501e72d8d2Sderaadt			mailaddr[\"$login\"] = \"$mailaddr\""
3511e72d8d2Sderaadt	done
3521e72d8d2Sderaadt	IFS=$oldIFS
3531e72d8d2Sderaadtesac
3541e72d8d2Sderaadt
3551e72d8d2Sderaadtcase $llogout in
3561e72d8d2Sderaadt?*) sort -u -o $llogout <<EOF || exit
3571e72d8d2Sderaadt$logins
3581e72d8d2SderaadtEOF
3591e72d8d2Sderaadtesac
3601e72d8d2Sderaadtoutput_authors='/^date: / {
3611e72d8d2Sderaadt	if ($2 ~ /^[0-9]*[-\/][0-9][0-9][-\/][0-9][0-9]$/ && $3 ~ /^[0-9][0-9]:[0-9][0-9]:[0-9][0-9][-+0-9:]*;$/ && $4 == "author:" && $5 ~ /^[^;]*;$/) {
3621e72d8d2Sderaadt		print substr($5, 1, length($5)-1)
3631e72d8d2Sderaadt	}
3641e72d8d2Sderaadt}'
3651e72d8d2Sderaadtauthors=`
3661e72d8d2Sderaadt	$AWK "$output_authors" <$rlogout |
3671e72d8d2Sderaadt	case $llogout in
3681e72d8d2Sderaadt	'') sort -u;;
3691e72d8d2Sderaadt	?*) sort -u | comm -23 - $llogout
3701e72d8d2Sderaadt	esac
3711e72d8d2Sderaadt`
3721e72d8d2Sderaadtcase $authors in
3731e72d8d2Sderaadt?*)
3741e72d8d2Sderaadt	cat >$llogout <<EOF || exit
3751e72d8d2Sderaadt$authors
3761e72d8d2SderaadtEOF
3771e72d8d2Sderaadt	initialize_author_script='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/'
3781e72d8d2Sderaadt	initialize_author=`sed -e "$initialize_author_script" <$llogout`
3791e72d8d2Sderaadt	awkscript='
3801e72d8d2Sderaadt		BEGIN {
3811e72d8d2Sderaadt			alphabet = "abcdefghijklmnopqrstuvwxyz"
3821e72d8d2Sderaadt			ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3831e72d8d2Sderaadt			'"$initialize_author"'
3841e72d8d2Sderaadt		}
3851e72d8d2Sderaadt		{
3861e72d8d2Sderaadt			if (author[$1]) {
3871e72d8d2Sderaadt				fullname = $5
3881e72d8d2Sderaadt				if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) {
3891e72d8d2Sderaadt					# Remove the junk from fullnames like "0000-Admin(0000)".
3901e72d8d2Sderaadt					fullname = substr(fullname, index(fullname, "-") + 1)
3911e72d8d2Sderaadt					fullname = substr(fullname, 1, index(fullname, "(") - 1)
3921e72d8d2Sderaadt				}
3931e72d8d2Sderaadt				if (fullname ~ /,[^ ]/) {
3941e72d8d2Sderaadt					# Some sites put comma-separated junk after the fullname.
3951e72d8d2Sderaadt					# Remove it, but leave "Bill Gates, Jr" alone.
3961e72d8d2Sderaadt					fullname = substr(fullname, 1, index(fullname, ",") - 1)
3971e72d8d2Sderaadt				}
3981e72d8d2Sderaadt				abbr = index(fullname, "&")
3991e72d8d2Sderaadt				if (abbr) {
4001e72d8d2Sderaadt					a = substr($1, 1, 1)
4011e72d8d2Sderaadt					A = a
4021e72d8d2Sderaadt					i = index(alphabet, a)
4031e72d8d2Sderaadt					if (i) A = substr(ALPHABET, i, 1)
4041e72d8d2Sderaadt					fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1)
4051e72d8d2Sderaadt				}
4061e72d8d2Sderaadt
4071e72d8d2Sderaadt				# Quote quotes and backslashes properly in full names.
4081e72d8d2Sderaadt				# Do not use gsub; traditional awk lacks it.
4091e72d8d2Sderaadt				quoted = ""
4101e72d8d2Sderaadt				rest = fullname
4111e72d8d2Sderaadt				for (;;) {
4121e72d8d2Sderaadt					p = index(rest, "\\")
4131e72d8d2Sderaadt					q = index(rest, "\"")
4141e72d8d2Sderaadt					if (p) {
4151e72d8d2Sderaadt						if (q && q<p) p = q
4161e72d8d2Sderaadt					} else {
4171e72d8d2Sderaadt						if (!q) break
4181e72d8d2Sderaadt						p = q
4191e72d8d2Sderaadt					}
4201e72d8d2Sderaadt					quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1)
4211e72d8d2Sderaadt					rest = substr(rest, p+1)
4221e72d8d2Sderaadt				}
4231e72d8d2Sderaadt
4241e72d8d2Sderaadt				printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest
4251e72d8d2Sderaadt				author[$1] = 0
4261e72d8d2Sderaadt			}
4271e72d8d2Sderaadt		}
4281e72d8d2Sderaadt	'
4291e72d8d2Sderaadt
4301e72d8d2Sderaadt	initialize_fullname=`
431c71bc7e2Stholo		{
432c71bc7e2Stholo			(getent passwd $authors) ||
43350bf276cStholo			(
43450bf276cStholo				cat /etc/passwd
43550bf276cStholo				for author in $authors
436c71bc7e2Stholo				do NIS_PATH= nismatch $author passwd.org_dir
43750bf276cStholo				done
43850bf276cStholo				ypmatch $authors passwd
439c71bc7e2Stholo			)
440c71bc7e2Stholo		} 2>/dev/null |
4411e72d8d2Sderaadt		$AWK -F: "$awkscript"
4421e72d8d2Sderaadt	`$initialize_fullname
4431e72d8d2Sderaadtesac
4441e72d8d2Sderaadt
4451e72d8d2Sderaadt
4461e72d8d2Sderaadt# Function to print a single log line.
4471e72d8d2Sderaadt# We don't use awk functions, to stay compatible with old awk versions.
448c71bc7e2Stholo# `Log' is the log message (with \n replaced by \001).
4491e72d8d2Sderaadt# `files' contains the affected files.
4501e72d8d2Sderaadtprintlogline='{
4511e72d8d2Sderaadt
4521e72d8d2Sderaadt	# Following the GNU coding standards, rewrite
4531e72d8d2Sderaadt	#	* file: (function): comment
4541e72d8d2Sderaadt	# to
4551e72d8d2Sderaadt	#	* file (function): comment
4561e72d8d2Sderaadt	if (Log ~ /^\([^)]*\): /) {
4571e72d8d2Sderaadt		i = index(Log, ")")
4581e72d8d2Sderaadt		files = files " " substr(Log, 1, i)
4591e72d8d2Sderaadt		Log = substr(Log, i+3)
4601e72d8d2Sderaadt	}
4611e72d8d2Sderaadt
4621e72d8d2Sderaadt	# If "label: comment" is too long, break the line after the ":".
4631e72d8d2Sderaadt	sep = " "
464c71bc7e2Stholo	if ('"$length"' <= '"$indent"' + 1 + length(files) + index(Log, SOH)) sep = "\n" indent_string
4651e72d8d2Sderaadt
4661e72d8d2Sderaadt	# Print the label.
4671e72d8d2Sderaadt	printf "%s*%s:", indent_string, files
4681e72d8d2Sderaadt
469c71bc7e2Stholo	# Print each line of the log, transliterating \001 to \n.
470c71bc7e2Stholo	while ((i = index(Log, SOH)) != 0) {
4711e72d8d2Sderaadt		logline = substr(Log, 1, i-1)
4721e72d8d2Sderaadt		if (logline ~ /[^'"$tab"' ]/) {
4731e72d8d2Sderaadt			printf "%s%s\n", sep, logline
4741e72d8d2Sderaadt		} else {
4751e72d8d2Sderaadt			print ""
4761e72d8d2Sderaadt		}
4771e72d8d2Sderaadt		sep = indent_string
4781e72d8d2Sderaadt		Log = substr(Log, i+1)
4791e72d8d2Sderaadt	}
4801e72d8d2Sderaadt}'
4811e72d8d2Sderaadt
482c71bc7e2Stholo# Pattern to match the `revision' line of rlog output.
483c71bc7e2Stholorlog_revision_pattern='^revision [0-9]+\.[0-9]+(\.[0-9]+\.[0-9]+)*(['"$tab"' ]+locked by: [^'"$tab"' $,.0-9:;@]*[^'"$tab"' $,:;@][^'"$tab"' $,.0-9:;@]*;)?['"$tab"' ]*$'
484c71bc7e2Stholo
4851e72d8d2Sderaadtcase $hostname in
4861e72d8d2Sderaadt'')
4871e72d8d2Sderaadt	hostname=`(
4881e72d8d2Sderaadt		hostname || uname -n || uuname -l || cat /etc/whoami
4891e72d8d2Sderaadt	) 2>/dev/null` || {
4901e72d8d2Sderaadt		echo >&2 "$0: cannot deduce hostname"
4911e72d8d2Sderaadt		exit 1
4921e72d8d2Sderaadt	}
49350bf276cStholo
49450bf276cStholo	case $hostname in
49550bf276cStholo	*.*) ;;
49650bf276cStholo	*)
49750bf276cStholo		domainname=`(domainname) 2>/dev/null` &&
49850bf276cStholo		case $domainname in
49950bf276cStholo		*.*) hostname=$hostname.$domainname
50050bf276cStholo		esac
50150bf276cStholo	esac
5021e72d8d2Sderaadtesac
5031e72d8d2Sderaadt
5041e72d8d2Sderaadt
5051e72d8d2Sderaadt# Process the rlog output, generating ChangeLog style entries.
5061e72d8d2Sderaadt
5071e72d8d2Sderaadt# First, reformat the rlog output so that each line contains one log entry.
508c71bc7e2Stholo# Transliterate \n to \001 so that multiline entries fit on a single line.
5091e72d8d2Sderaadt# Discard irrelevant rlog output.
5101e72d8d2Sderaadt$AWK <$rlogout '
5111e72d8d2Sderaadt	BEGIN { repository = "'"$repository"'" }
5121e72d8d2Sderaadt	/^RCS file:/ {
5131e72d8d2Sderaadt		if (repository != "") {
5141e72d8d2Sderaadt			filename = $3
5151e72d8d2Sderaadt			if (substr(filename, 1, length(repository) + 1) == repository "/") {
5161e72d8d2Sderaadt				filename = substr(filename, length(repository) + 2)
5171e72d8d2Sderaadt			}
5181e72d8d2Sderaadt			if (filename ~ /,v$/) {
5191e72d8d2Sderaadt				filename = substr(filename, 1, length(filename) - 2)
5201e72d8d2Sderaadt			}
521c71bc7e2Stholo			if (filename ~ /(^|\/)Attic\/[^\/]*$/) {
522c71bc7e2Stholo				i = length(filename)
523c71bc7e2Stholo				while (substr(filename, i, 1) != "/") i--
524c71bc7e2Stholo				filename = substr(filename, 1, i - 6) substr(filename, i + 1)
5251e72d8d2Sderaadt			}
5261e72d8d2Sderaadt		}
527c71bc7e2Stholo		rev = "?"
528c71bc7e2Stholo	}
5291e72d8d2Sderaadt	/^Working file:/ { if (repository == "") filename = $3 }
530c71bc7e2Stholo	/'"$rlog_revision_pattern"'/, /^(-----------*|===========*)$/ {
531c71bc7e2Stholo		line = $0
532c71bc7e2Stholo		if (line ~ /'"$rlog_revision_pattern"'/) {
533c71bc7e2Stholo			rev = $2
534c71bc7e2Stholo			next
535c71bc7e2Stholo		}
536c71bc7e2Stholo		if (line ~ /^date: [0-9][- +\/0-9:]*;/) {
5371e72d8d2Sderaadt			date = $2
538c71bc7e2Stholo			if (date ~ /\//) {
539c71bc7e2Stholo				# This is a traditional RCS format date YYYY/MM/DD.
540c71bc7e2Stholo				# Replace "/"s with "-"s to get ISO format.
5411e72d8d2Sderaadt				newdate = ""
542c71bc7e2Stholo				while ((i = index(date, "/")) != 0) {
543c71bc7e2Stholo					newdate = newdate substr(date, 1, i-1) "-"
5441e72d8d2Sderaadt					date = substr(date, i+1)
5451e72d8d2Sderaadt				}
5461e72d8d2Sderaadt				date = newdate date
5471e72d8d2Sderaadt			}
548c71bc7e2Stholo			time = substr($3, 1, length($3) - 1)
5491e72d8d2Sderaadt			author = substr($5, 1, length($5)-1)
550c71bc7e2Stholo			printf "%s %s %s %s %s %c", filename, rev, date, time, author, 1
551c71bc7e2Stholo			rev = "?"
5521e72d8d2Sderaadt			next
5531e72d8d2Sderaadt		}
554c71bc7e2Stholo		if (line ~ /^branches: /) { next }
555c71bc7e2Stholo		if (line ~ /^(-----------*|===========*)$/) { print ""; next }
556c71bc7e2Stholo		if (line == "Initial revision" || line ~ /^file .+ was initially added on branch .+\.$/) {
557c71bc7e2Stholo			line = "New file."
558c71bc7e2Stholo		}
559c71bc7e2Stholo		printf "%s%c", line, 1
5601e72d8d2Sderaadt	}
5611e72d8d2Sderaadt' |
5621e72d8d2Sderaadt
5631e72d8d2Sderaadt# Now each line is of the form
564c71bc7e2Stholo# FILENAME REVISION YYYY-MM-DD HH:MM:SS[+-TIMEZONE] AUTHOR \001LOG
565c71bc7e2Stholo#	where \001 stands for a carriage return,
566c71bc7e2Stholo#	and each line of the log is terminated by \001 instead of \n.
5671e72d8d2Sderaadt# Sort the log entries, first by date+time (in reverse order),
568c71bc7e2Stholo# then by author, then by log entry, and finally by file name and revision
569c71bc7e2Stholo# (just in case).
570c71bc7e2Stholosort +2 -4r +4 +0 |
5711e72d8d2Sderaadt
5721e72d8d2Sderaadt# Finally, reformat the sorted log entries.
5731e72d8d2Sderaadt$AWK '
5741e72d8d2Sderaadt	BEGIN {
575c71bc7e2Stholo		logTZ = "'"$logTZ"'"
576c71bc7e2Stholo		revision = "'"$revision"'"
577c71bc7e2Stholo
578c71bc7e2Stholo		# Some awk variants do not understand "\001", so we have to
579c71bc7e2Stholo		# put the char directly in the file.
580c71bc7e2Stholo		SOH="" # <-- There is a single SOH (octal code 001) here.
5811e72d8d2Sderaadt
5821e72d8d2Sderaadt		# Initialize the fullname and mailaddr associative arrays.
5831e72d8d2Sderaadt		'"$initialize_fullname"'
5841e72d8d2Sderaadt		'"$initialize_mailaddr"'
5851e72d8d2Sderaadt
5861e72d8d2Sderaadt		# Initialize indent string.
5871e72d8d2Sderaadt		indent_string = ""
5881e72d8d2Sderaadt		i = '"$indent"'
5891e72d8d2Sderaadt		if (0 < '"$tabwidth"')
5901e72d8d2Sderaadt			for (;  '"$tabwidth"' <= i;  i -= '"$tabwidth"')
5911e72d8d2Sderaadt				indent_string = indent_string "\t"
5921e72d8d2Sderaadt		while (1 <= i--)
5931e72d8d2Sderaadt			indent_string = indent_string " "
5941e72d8d2Sderaadt	}
5951e72d8d2Sderaadt
5961e72d8d2Sderaadt	{
597c71bc7e2Stholo		newlog = substr($0, 1 + index($0, SOH))
5981e72d8d2Sderaadt
5991e72d8d2Sderaadt		# Ignore log entries prefixed by "#".
6001e72d8d2Sderaadt		if (newlog ~ /^#/) { next }
6011e72d8d2Sderaadt
602c71bc7e2Stholo		if (Log != newlog || date != $3 || author != $5) {
6031e72d8d2Sderaadt
6041e72d8d2Sderaadt			# The previous log and this log differ.
6051e72d8d2Sderaadt
6061e72d8d2Sderaadt			# Print the old log.
6071e72d8d2Sderaadt			if (date != "") '"$printlogline"'
6081e72d8d2Sderaadt
6091e72d8d2Sderaadt			# Logs that begin with "{clumpname} " should be grouped together,
6101e72d8d2Sderaadt			# and the clumpname should be removed.
6111e72d8d2Sderaadt			# Extract the new clumpname from the log header,
6121e72d8d2Sderaadt			# and use it to decide whether to output a blank line.
6131e72d8d2Sderaadt			newclumpname = ""
6141e72d8d2Sderaadt			sep = "\n"
6151e72d8d2Sderaadt			if (date == "") sep = ""
6161e72d8d2Sderaadt			if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) {
6171e72d8d2Sderaadt				i = index(newlog, "}")
6181e72d8d2Sderaadt				newclumpname = substr(newlog, 1, i)
6191e72d8d2Sderaadt				while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++
6201e72d8d2Sderaadt				newlog = substr(newlog, i+1)
6211e72d8d2Sderaadt				if (clumpname == newclumpname) sep = ""
6221e72d8d2Sderaadt			}
6231e72d8d2Sderaadt			printf sep
6241e72d8d2Sderaadt			clumpname = newclumpname
6251e72d8d2Sderaadt
6261e72d8d2Sderaadt			# Get ready for the next log.
6271e72d8d2Sderaadt			Log = newlog
6281e72d8d2Sderaadt			if (files != "")
6291e72d8d2Sderaadt				for (i in filesknown)
6301e72d8d2Sderaadt					filesknown[i] = 0
6311e72d8d2Sderaadt			files = ""
6321e72d8d2Sderaadt		}
633c71bc7e2Stholo		if (date != $3  ||  author != $5) {
6341e72d8d2Sderaadt			# The previous date+author and this date+author differ.
6351e72d8d2Sderaadt			# Print the new one.
636c71bc7e2Stholo			date = $3
637c71bc7e2Stholo			time = $4
638c71bc7e2Stholo			author = $5
6391e72d8d2Sderaadt
640c71bc7e2Stholo			zone = ""
641c71bc7e2Stholo			if (logTZ && ((i = index(time, "-")) || (i = index(time, "+"))))
642c71bc7e2Stholo				zone = " " substr(time, i)
6431e72d8d2Sderaadt
644c71bc7e2Stholo			# Print "date[ timezone]  fullname  <email address>".
6451e72d8d2Sderaadt			# Get fullname and email address from associative arrays;
6461e72d8d2Sderaadt			# default to author and author@hostname if not in arrays.
6471e72d8d2Sderaadt			if (fullname[author])
6481e72d8d2Sderaadt				auth = fullname[author]
6491e72d8d2Sderaadt			else
6501e72d8d2Sderaadt				auth = author
651c71bc7e2Stholo			printf "%s%s  %s  ", date, zone, auth
6521e72d8d2Sderaadt			if (mailaddr[author])
6531e72d8d2Sderaadt				printf "<%s>\n\n", mailaddr[author]
6541e72d8d2Sderaadt			else
6551e72d8d2Sderaadt				printf "<%s@%s>\n\n", author, "'"$hostname"'"
6561e72d8d2Sderaadt		}
6571e72d8d2Sderaadt		if (! filesknown[$1]) {
6581e72d8d2Sderaadt			filesknown[$1] = 1
6591e72d8d2Sderaadt			if (files == "") files = " " $1
6601e72d8d2Sderaadt			else files = files ", " $1
661c71bc7e2Stholo			if (revision && $2 != "?") files = files " " $2
6621e72d8d2Sderaadt		}
6631e72d8d2Sderaadt	}
6641e72d8d2Sderaadt	END {
6651e72d8d2Sderaadt		# Print the last log.
6661e72d8d2Sderaadt		if (date != "") {
6671e72d8d2Sderaadt			'"$printlogline"'
6681e72d8d2Sderaadt			printf "\n"
6691e72d8d2Sderaadt		}
6701e72d8d2Sderaadt	}
6711e72d8d2Sderaadt' &&
6721e72d8d2Sderaadt
6731e72d8d2Sderaadt
6741e72d8d2Sderaadt# Exit successfully.
6751e72d8d2Sderaadt
6761e72d8d2Sderaadtexec rm -f $llogout $rlogout
677c71bc7e2Stholo
678c71bc7e2Stholo# Local Variables:
679c71bc7e2Stholo# tab-width:4
680c71bc7e2Stholo# End:
681