xref: /netbsd-src/external/gpl2/xcvs/dist/contrib/rcs2log.sh (revision a7c918477dd5f12c1da816ba05caf44eab2d06d6)
1*a7c91847Schristos#! /bin/sh
2*a7c91847Schristos
3*a7c91847Schristos# Copyright (C) 1995-2005 The Free Software Foundation, Inc.
4*a7c91847Schristos
5*a7c91847Schristos# This program is free software; you can redistribute it and/or modify
6*a7c91847Schristos# it under the terms of the GNU General Public License as published by
7*a7c91847Schristos# the Free Software Foundation; either version 2, or (at your option)
8*a7c91847Schristos# any later version.
9*a7c91847Schristos#
10*a7c91847Schristos# This program is distributed in the hope that it will be useful,
11*a7c91847Schristos# but WITHOUT ANY WARRANTY; without even the implied warranty of
12*a7c91847Schristos# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13*a7c91847Schristos# GNU General Public License for more details.
14*a7c91847Schristos
15*a7c91847Schristos# RCS to ChangeLog generator
16*a7c91847Schristos
17*a7c91847Schristos# Generate a change log prefix from RCS files (perhaps in the CVS repository)
18*a7c91847Schristos# and the ChangeLog (if any).
19*a7c91847Schristos# Output the new prefix to standard output.
20*a7c91847Schristos# You can edit this prefix by hand, and then prepend it to ChangeLog.
21*a7c91847Schristos
22*a7c91847Schristos# Ignore log entries that start with `#'.
23*a7c91847Schristos# Clump together log entries that start with `{topic} ',
24*a7c91847Schristos# where `topic' contains neither white space nor `}'.
25*a7c91847Schristos
26*a7c91847SchristosHelp='The default FILEs are the files registered under the working directory.
27*a7c91847SchristosOptions:
28*a7c91847Schristos
29*a7c91847Schristos  -c CHANGELOG  Output a change log prefix to CHANGELOG (default ChangeLog).
30*a7c91847Schristos  -h HOSTNAME  Use HOSTNAME in change log entries (default current host).
31*a7c91847Schristos  -i INDENT  Indent change log lines by INDENT spaces (default 8).
32*a7c91847Schristos  -l LENGTH  Try to limit log lines to LENGTH characters (default 79).
33*a7c91847Schristos  -L FILE  Use rlog-format FILE for source of logs.
34*a7c91847Schristos  -R  If no FILEs are given and RCS is used, recurse through working directory.
35*a7c91847Schristos  -r OPTION  Pass OPTION to subsidiary log command.
36*a7c91847Schristos  -t TABWIDTH  Tab stops are every TABWIDTH characters (default 8).
37*a7c91847Schristos  -u "LOGIN<tab>FULLNAME<tab>MAILADDR"  Assume LOGIN has FULLNAME and MAILADDR.
38*a7c91847Schristos  -v  Append RCS revision to file names in log lines.
39*a7c91847Schristos  --help  Output help.
40*a7c91847Schristos  --version  Output version number.
41*a7c91847Schristos
42*a7c91847SchristosReport bugs to <bug-gnu-emacs@gnu.org>.'
43*a7c91847Schristos
44*a7c91847SchristosId='Id: rcs2log,v 1.48 2001/09/05 23:07:46 eggert Exp'
45*a7c91847Schristos
46*a7c91847Schristos# Copyright 1992, 1993, 1994, 1995, 1996, 1997, 1998, 2001, 2003
47*a7c91847Schristos#  Free Software Foundation, Inc.
48*a7c91847Schristos
49*a7c91847Schristos# This program is free software; you can redistribute it and/or modify
50*a7c91847Schristos# it under the terms of the GNU General Public License as published by
51*a7c91847Schristos# the Free Software Foundation; either version 2, or (at your option)
52*a7c91847Schristos# any later version.
53*a7c91847Schristos#
54*a7c91847Schristos# This program is distributed in the hope that it will be useful,
55*a7c91847Schristos# but WITHOUT ANY WARRANTY; without even the implied warranty of
56*a7c91847Schristos# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
57*a7c91847Schristos# GNU General Public License for more details.
58*a7c91847Schristos#
59*a7c91847Schristos# You should have received a copy of the GNU General Public License
60*a7c91847Schristos# along with this program; see the file COPYING.  If not, write to the
61*a7c91847Schristos# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
62*a7c91847Schristos# Boston, MA 02111-1307, USA.
63*a7c91847Schristos
64*a7c91847SchristosCopyright='Copyright 1992-2003 Free Software Foundation, Inc.
65*a7c91847SchristosThis program comes with NO WARRANTY, to the extent permitted by law.
66*a7c91847SchristosYou may redistribute copies of this program
67*a7c91847Schristosunder the terms of the GNU General Public License.
68*a7c91847SchristosFor more information about these matters, see the files named COPYING.
69*a7c91847SchristosAuthor: Paul Eggert <eggert@twinsun.com>'
70*a7c91847Schristos
71*a7c91847Schristos# functions
72*a7c91847Schristos@MKTEMP_SH_FUNCTION@
73*a7c91847Schristos
74*a7c91847Schristos# Use the traditional C locale.
75*a7c91847SchristosLANG=C
76*a7c91847SchristosLANGUAGE=C
77*a7c91847SchristosLC_ALL=C
78*a7c91847SchristosLC_COLLATE=C
79*a7c91847SchristosLC_CTYPE=C
80*a7c91847SchristosLC_MESSAGES=C
81*a7c91847SchristosLC_NUMERIC=C
82*a7c91847SchristosLC_TIME=C
83*a7c91847Schristosexport LANG LANGUAGE LC_ALL LC_COLLATE LC_CTYPE LC_MESSAGES LC_NUMERIC LC_TIME
84*a7c91847Schristos
85*a7c91847Schristos# These variables each contain a single ASCII character.
86*a7c91847Schristos# Unfortunately, there's no portable way of writing these characters
87*a7c91847Schristos# in older Unix implementations, other than putting them directly into
88*a7c91847Schristos# this text file.
89*a7c91847SchristosSOH='' # SOH, octal code 001
90*a7c91847Schristostab='	'
91*a7c91847Schristosnl='
92*a7c91847Schristos'
93*a7c91847Schristos
94*a7c91847Schristos# Parse options.
95*a7c91847Schristos
96*a7c91847Schristos# defaults
97*a7c91847Schristos: ${MKTEMP="@MKTEMP@"}
98*a7c91847Schristos: ${AWK=awk}
99*a7c91847Schristos: ${TMPDIR=/tmp}
100*a7c91847Schristos
101*a7c91847Schristoschangelog=ChangeLog # change log file name
102*a7c91847Schristosdatearg= # rlog date option
103*a7c91847Schristoshostname= # name of local host (if empty, will deduce it later)
104*a7c91847Schristosindent=8 # indent of log line
105*a7c91847Schristoslength=79 # suggested max width of log line
106*a7c91847Schristoslogins= # login names for people we know fullnames and mailaddrs of
107*a7c91847SchristosloginFullnameMailaddrs= # login<tab>fullname<tab>mailaddr triplets
108*a7c91847SchristoslogTZ= # time zone for log dates (if empty, use local time)
109*a7c91847Schristosrecursive= # t if we want recursive rlog
110*a7c91847Schristosrevision= # t if we want revision numbers
111*a7c91847Schristosrlog_options= # options to pass to rlog
112*a7c91847Schristosrlogfile= # log file to read from
113*a7c91847Schristostabwidth=8 # width of horizontal tab
114*a7c91847Schristos
115*a7c91847Schristoswhile :
116*a7c91847Schristosdo
117*a7c91847Schristos	case $1 in
118*a7c91847Schristos	-c)	changelog=${2?}; shift;;
119*a7c91847Schristos	-i)	indent=${2?}; shift;;
120*a7c91847Schristos	-h)	hostname=${2?}; shift;;
121*a7c91847Schristos	-l)	length=${2?}; shift;;
122*a7c91847Schristos	-L)	rlogfile=${2?}; shift;;
123*a7c91847Schristos	-[nu])	# -n is obsolescent; it is replaced by -u.
124*a7c91847Schristos		case $1 in
125*a7c91847Schristos		-n)	case ${2?}${3?}${4?} in
126*a7c91847Schristos			*"$tab"* | *"$nl"*)
127*a7c91847Schristos				echo >&2 "$0: -n '$2' '$3' '$4': tabs, newlines not allowed"
128*a7c91847Schristos				exit 1;;
129*a7c91847Schristos			esac
130*a7c91847Schristos			login=$2
131*a7c91847Schristos			lfm=$2$tab$3$tab$4
132*a7c91847Schristos			shift; shift; shift;;
133*a7c91847Schristos		-u)
134*a7c91847Schristos			# If $2 is not tab-separated, use colon for separator.
135*a7c91847Schristos			case ${2?} in
136*a7c91847Schristos			*"$nl"*)
137*a7c91847Schristos				echo >&2 "$0: -u '$2': newlines not allowed"
138*a7c91847Schristos				exit 1;;
139*a7c91847Schristos			*"$tab"*)
140*a7c91847Schristos				t=$tab;;
141*a7c91847Schristos			*)
142*a7c91847Schristos				t=':';;
143*a7c91847Schristos			esac
144*a7c91847Schristos			case $2 in
145*a7c91847Schristos			*"$t"*"$t"*"$t"*)
146*a7c91847Schristos				echo >&2 "$0: -u '$2': too many fields"
147*a7c91847Schristos				exit 1;;
148*a7c91847Schristos			*"$t"*"$t"*)
149*a7c91847Schristos				uf="[^$t]*$t" # An unselected field, followed by a separator.
150*a7c91847Schristos				sf="\\([^$t]*\\)" # The selected field.
151*a7c91847Schristos				login=`expr "X$2" : "X$sf"`
152*a7c91847Schristos				lfm="$login$tab"`
153*a7c91847Schristos					expr "X$2" : "$uf$sf"
154*a7c91847Schristos				  `"$tab"`
155*a7c91847Schristos					expr "X$2" : "$uf$uf$sf"
156*a7c91847Schristos				`;;
157*a7c91847Schristos			*)
158*a7c91847Schristos				echo >&2 "$0: -u '$2': not enough fields"
159*a7c91847Schristos				exit 1;;
160*a7c91847Schristos			esac
161*a7c91847Schristos			shift;;
162*a7c91847Schristos		esac
163*a7c91847Schristos		case $logins in
164*a7c91847Schristos		'') logins=$login;;
165*a7c91847Schristos		?*) logins=$logins$nl$login;;
166*a7c91847Schristos		esac
167*a7c91847Schristos		case $loginFullnameMailaddrs in
168*a7c91847Schristos		'') loginFullnameMailaddrs=$lfm;;
169*a7c91847Schristos		?*) loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$lfm;;
170*a7c91847Schristos		esac;;
171*a7c91847Schristos	-r)
172*a7c91847Schristos		case $rlog_options in
173*a7c91847Schristos		'') rlog_options=${2?};;
174*a7c91847Schristos		?*) rlog_options=$rlog_options$nl${2?};;
175*a7c91847Schristos		esac
176*a7c91847Schristos		shift;;
177*a7c91847Schristos	-R)	recursive=t;;
178*a7c91847Schristos	-t)	tabwidth=${2?}; shift;;
179*a7c91847Schristos	-v)	revision=t;;
180*a7c91847Schristos	--version)
181*a7c91847Schristos		set $Id
182*a7c91847Schristos		rcs2logVersion=$3
183*a7c91847Schristos		echo >&2 "rcs2log (GNU Emacs) $rcs2logVersion$nl$Copyright"
184*a7c91847Schristos		exit 0;;
185*a7c91847Schristos	-*)	echo >&2 "Usage: $0 [OPTION]... [FILE ...]$nl$Help"
186*a7c91847Schristos		case $1 in
187*a7c91847Schristos		--help) exit 0;;
188*a7c91847Schristos		*) exit 1;;
189*a7c91847Schristos		esac;;
190*a7c91847Schristos	*)	break;;
191*a7c91847Schristos	esac
192*a7c91847Schristos	shift
193*a7c91847Schristosdone
194*a7c91847Schristos
195*a7c91847Schristosmonth_data='
196*a7c91847Schristos	m[0]="Jan"; m[1]="Feb"; m[2]="Mar"
197*a7c91847Schristos	m[3]="Apr"; m[4]="May"; m[5]="Jun"
198*a7c91847Schristos	m[6]="Jul"; m[7]="Aug"; m[8]="Sep"
199*a7c91847Schristos	m[9]="Oct"; m[10]="Nov"; m[11]="Dec"
200*a7c91847Schristos'
201*a7c91847Schristos
202*a7c91847Schristoslogdir=`$MKTEMP -d $TMPDIR/rcs2log.XXXXXX`
203*a7c91847Schristostest -n "$logdir" || exit
204*a7c91847Schristosllogout=$logdir/l
205*a7c91847Schristostrap exit 1 2 13 15
206*a7c91847Schristostrap "rm -fr $logdir 2>/dev/null" 0
207*a7c91847Schristos
208*a7c91847Schristos# If no rlog-format log file is given, generate one into $rlogfile.
209*a7c91847Schristoscase $rlogfile in
210*a7c91847Schristos'')
211*a7c91847Schristos	rlogfile=$logdir/r
212*a7c91847Schristos
213*a7c91847Schristos	# If no rlog options are given,
214*a7c91847Schristos	# log the revisions checked in since the first ChangeLog entry.
215*a7c91847Schristos	# Since ChangeLog is only by date, some of these revisions may be duplicates of
216*a7c91847Schristos	# what's already in ChangeLog; it's the user's responsibility to remove them.
217*a7c91847Schristos	case $rlog_options in
218*a7c91847Schristos	'')
219*a7c91847Schristos		if test -s "$changelog"
220*a7c91847Schristos		then
221*a7c91847Schristos			e='
222*a7c91847Schristos				/^[0-9]+-[0-9][0-9]-[0-9][0-9]/{
223*a7c91847Schristos					# ISO 8601 date
224*a7c91847Schristos					print $1
225*a7c91847Schristos					exit
226*a7c91847Schristos				}
227*a7c91847Schristos				/^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{
228*a7c91847Schristos					# old-fashioned date and time (Emacs 19.31 and earlier)
229*a7c91847Schristos					'"$month_data"'
230*a7c91847Schristos					year = $5
231*a7c91847Schristos					for (i=0; i<=11; i++) if (m[i] == $2) break
232*a7c91847Schristos					dd = $3
233*a7c91847Schristos					printf "%d-%02d-%02d\n", year, i+1, dd
234*a7c91847Schristos					exit
235*a7c91847Schristos				}
236*a7c91847Schristos			'
237*a7c91847Schristos			d=`$AWK "$e" <"$changelog"` || exit
238*a7c91847Schristos			case $d in
239*a7c91847Schristos			?*) datearg="-d>$d";;
240*a7c91847Schristos			esac
241*a7c91847Schristos		fi;;
242*a7c91847Schristos	esac
243*a7c91847Schristos
244*a7c91847Schristos	# Use TZ specified by ChangeLog local variable, if any.
245*a7c91847Schristos	if test -s "$changelog"
246*a7c91847Schristos	then
247*a7c91847Schristos		extractTZ='
248*a7c91847Schristos			/^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*"\([^"]*\)".*/{
249*a7c91847Schristos				s//\1/; p; q
250*a7c91847Schristos			}
251*a7c91847Schristos			/^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*t.*/{
252*a7c91847Schristos				s//UTC0/; p; q
253*a7c91847Schristos			}
254*a7c91847Schristos		'
255*a7c91847Schristos		logTZ=`tail "$changelog" | sed -n "$extractTZ"`
256*a7c91847Schristos		case $logTZ in
257*a7c91847Schristos		?*) TZ=$logTZ; export TZ;;
258*a7c91847Schristos		esac
259*a7c91847Schristos	fi
260*a7c91847Schristos
261*a7c91847Schristos	# If CVS is in use, examine its repository, not the normal RCS files.
262*a7c91847Schristos	if test ! -f CVS/Repository
263*a7c91847Schristos	then
264*a7c91847Schristos		rlog=rlog
265*a7c91847Schristos		repository=
266*a7c91847Schristos	else
267*a7c91847Schristos		rlog='cvs -q log'
268*a7c91847Schristos		repository=`sed 1q <CVS/Repository` || exit
269*a7c91847Schristos		test ! -f CVS/Root || CVSROOT=`cat <CVS/Root` || exit
270*a7c91847Schristos		case $CVSROOT in
271*a7c91847Schristos		*:/*:/*)
272*a7c91847Schristos			echo >&2 "$0: $CVSROOT: CVSROOT has multiple ':/'s"
273*a7c91847Schristos			exit 1;;
274*a7c91847Schristos		*:/*)
275*a7c91847Schristos			# remote repository
276*a7c91847Schristos			pository=`expr "X$repository" : '.*:\(/.*\)'`;;
277*a7c91847Schristos		*)
278*a7c91847Schristos			# local repository
279*a7c91847Schristos			case $repository in
280*a7c91847Schristos			/*) ;;
281*a7c91847Schristos			*) repository=${CVSROOT?}/$repository;;
282*a7c91847Schristos			esac
283*a7c91847Schristos			if test ! -d "$repository"
284*a7c91847Schristos			then
285*a7c91847Schristos				echo >&2 "$0: $repository: bad repository (see CVS/Repository)"
286*a7c91847Schristos				exit 1
287*a7c91847Schristos			fi
288*a7c91847Schristos			pository=$repository;;
289*a7c91847Schristos		esac
290*a7c91847Schristos
291*a7c91847Schristos		# Ensure that $pository ends in exactly one slash.
292*a7c91847Schristos		while :
293*a7c91847Schristos		do
294*a7c91847Schristos			case $pository in
295*a7c91847Schristos			*//) pository=`expr "X$pository" : 'X\(.*\)/'`;;
296*a7c91847Schristos			*/) break;;
297*a7c91847Schristos			*) pository=$pository/; break;;
298*a7c91847Schristos			esac
299*a7c91847Schristos		done
300*a7c91847Schristos
301*a7c91847Schristos	fi
302*a7c91847Schristos
303*a7c91847Schristos	# Use $rlog's -zLT option, if $rlog supports it.
304*a7c91847Schristos	case `$rlog -zLT 2>&1` in
305*a7c91847Schristos	*' option'*) ;;
306*a7c91847Schristos	*)
307*a7c91847Schristos		case $rlog_options in
308*a7c91847Schristos		'') rlog_options=-zLT;;
309*a7c91847Schristos		?*) rlog_options=-zLT$nl$rlog_options;;
310*a7c91847Schristos		esac;;
311*a7c91847Schristos	esac
312*a7c91847Schristos
313*a7c91847Schristos	# With no arguments, examine all files under the RCS directory.
314*a7c91847Schristos	case $# in
315*a7c91847Schristos	0)
316*a7c91847Schristos		case $repository in
317*a7c91847Schristos		'')
318*a7c91847Schristos			oldIFS=$IFS
319*a7c91847Schristos			IFS=$nl
320*a7c91847Schristos			case $recursive in
321*a7c91847Schristos			t)
322*a7c91847Schristos				RCSdirs=`find . -name RCS -type d -print`
323*a7c91847Schristos				filesFromRCSfiles='s|,v$||; s|/RCS/|/|; s|^\./||'
324*a7c91847Schristos				files=`
325*a7c91847Schristos					{
326*a7c91847Schristos						case $RCSdirs in
327*a7c91847Schristos						?*) find $RCSdirs \
328*a7c91847Schristos								-type f \
329*a7c91847Schristos								! -name '*_' \
330*a7c91847Schristos								! -name ',*,' \
331*a7c91847Schristos								! -name '.*_' \
332*a7c91847Schristos								! -name .rcsfreeze.log \
333*a7c91847Schristos								! -name .rcsfreeze.ver \
334*a7c91847Schristos								-print;;
335*a7c91847Schristos						esac
336*a7c91847Schristos						find . -name '*,v' -print
337*a7c91847Schristos					} |
338*a7c91847Schristos					sort -u |
339*a7c91847Schristos					sed "$filesFromRCSfiles"
340*a7c91847Schristos				`;;
341*a7c91847Schristos			*)
342*a7c91847Schristos				files=
343*a7c91847Schristos				for file in RCS/.* RCS/* .*,v *,v
344*a7c91847Schristos				do
345*a7c91847Schristos					case $file in
346*a7c91847Schristos					RCS/. | RCS/.. | RCS/,*, | RCS/*_) continue;;
347*a7c91847Schristos					RCS/.rcsfreeze.log | RCS/.rcsfreeze.ver) continue;;
348*a7c91847Schristos					RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue;;
349*a7c91847Schristos					RCS/*,v | RCS/.*,v) ;;
350*a7c91847Schristos					RCS/* | RCS/.*) test -f "$file" || continue;;
351*a7c91847Schristos					esac
352*a7c91847Schristos					case $files in
353*a7c91847Schristos					'') files=$file;;
354*a7c91847Schristos					?*) files=$files$nl$file;;
355*a7c91847Schristos					esac
356*a7c91847Schristos				done
357*a7c91847Schristos				case $files in
358*a7c91847Schristos				'') exit 0;;
359*a7c91847Schristos				esac;;
360*a7c91847Schristos			esac
361*a7c91847Schristos			set x $files
362*a7c91847Schristos			shift
363*a7c91847Schristos			IFS=$oldIFS;;
364*a7c91847Schristos		esac;;
365*a7c91847Schristos	esac
366*a7c91847Schristos
367*a7c91847Schristos	case $datearg in
368*a7c91847Schristos	?*) $rlog $rlog_options "$datearg" ${1+"$@"} >$rlogfile;;
369*a7c91847Schristos	'') $rlog $rlog_options ${1+"$@"} >$rlogfile;;
370*a7c91847Schristos	esac || exit;;
371*a7c91847Schristosesac
372*a7c91847Schristos
373*a7c91847Schristos
374*a7c91847Schristos# Get the full name of each author the logs mention, and set initialize_fullname
375*a7c91847Schristos# to awk code that initializes the `fullname' awk associative array.
376*a7c91847Schristos# Warning: foreign authors (i.e. not known in the passwd file) are mishandled;
377*a7c91847Schristos# you have to fix the resulting output by hand.
378*a7c91847Schristos
379*a7c91847Schristosinitialize_fullname=
380*a7c91847Schristosinitialize_mailaddr=
381*a7c91847Schristos
382*a7c91847Schristoscase $loginFullnameMailaddrs in
383*a7c91847Schristos?*)
384*a7c91847Schristos	case $loginFullnameMailaddrs in
385*a7c91847Schristos	*\"* | *\\*)
386*a7c91847Schristos		sed 's/["\\]/\\&/g' >$llogout <<EOF || exit
387*a7c91847Schristos$loginFullnameMailaddrs
388*a7c91847SchristosEOF
389*a7c91847Schristos		loginFullnameMailaddrs=`cat $llogout`;;
390*a7c91847Schristos	esac
391*a7c91847Schristos
392*a7c91847Schristos	oldIFS=$IFS
393*a7c91847Schristos	IFS=$nl
394*a7c91847Schristos	for loginFullnameMailaddr in $loginFullnameMailaddrs
395*a7c91847Schristos	do
396*a7c91847Schristos		IFS=$tab
397*a7c91847Schristos		set x $loginFullnameMailaddr
398*a7c91847Schristos		login=$2
399*a7c91847Schristos		fullname=$3
400*a7c91847Schristos		mailaddr=$4
401*a7c91847Schristos		initialize_fullname="$initialize_fullname
402*a7c91847Schristos			fullname[\"$login\"] = \"$fullname\""
403*a7c91847Schristos		initialize_mailaddr="$initialize_mailaddr
404*a7c91847Schristos			mailaddr[\"$login\"] = \"$mailaddr\""
405*a7c91847Schristos	done
406*a7c91847Schristos	IFS=$oldIFS;;
407*a7c91847Schristosesac
408*a7c91847Schristos
409*a7c91847Schristoscase $logins in
410*a7c91847Schristos?*)
411*a7c91847Schristos	sort -u -o $llogout <<EOF
412*a7c91847Schristos$logins
413*a7c91847SchristosEOF
414*a7c91847Schristos	;;
415*a7c91847Schristos'')
416*a7c91847Schristos	: ;;
417*a7c91847Schristosesac >$llogout || exit
418*a7c91847Schristos
419*a7c91847Schristosoutput_authors='/^date: / {
420*a7c91847Schristos	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 ~ /^[^;]*;$/) {
421*a7c91847Schristos		print substr($5, 1, length($5)-1)
422*a7c91847Schristos	}
423*a7c91847Schristos}'
424*a7c91847Schristosauthors=`
425*a7c91847Schristos	$AWK "$output_authors" <"$rlogfile" | sort -u | comm -23 - $llogout
426*a7c91847Schristos`
427*a7c91847Schristoscase $authors in
428*a7c91847Schristos?*)
429*a7c91847Schristos	cat >$llogout <<EOF || exit
430*a7c91847Schristos$authors
431*a7c91847SchristosEOF
432*a7c91847Schristos	initialize_author_script='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/'
433*a7c91847Schristos	initialize_author=`sed -e "$initialize_author_script" <$llogout`
434*a7c91847Schristos	awkscript='
435*a7c91847Schristos		BEGIN {
436*a7c91847Schristos			alphabet = "abcdefghijklmnopqrstuvwxyz"
437*a7c91847Schristos			ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
438*a7c91847Schristos			'"$initialize_author"'
439*a7c91847Schristos		}
440*a7c91847Schristos		{
441*a7c91847Schristos			if (author[$1]) {
442*a7c91847Schristos				fullname = $5
443*a7c91847Schristos				if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) {
444*a7c91847Schristos					# Remove the junk from fullnames like "0000-Admin(0000)".
445*a7c91847Schristos					fullname = substr(fullname, index(fullname, "-") + 1)
446*a7c91847Schristos					fullname = substr(fullname, 1, index(fullname, "(") - 1)
447*a7c91847Schristos				}
448*a7c91847Schristos				if (fullname ~ /,[^ ]/) {
449*a7c91847Schristos					# Some sites put comma-separated junk after the fullname.
450*a7c91847Schristos					# Remove it, but leave "Bill Gates, Jr" alone.
451*a7c91847Schristos					fullname = substr(fullname, 1, index(fullname, ",") - 1)
452*a7c91847Schristos				}
453*a7c91847Schristos				abbr = index(fullname, "&")
454*a7c91847Schristos				if (abbr) {
455*a7c91847Schristos					a = substr($1, 1, 1)
456*a7c91847Schristos					A = a
457*a7c91847Schristos					i = index(alphabet, a)
458*a7c91847Schristos					if (i) A = substr(ALPHABET, i, 1)
459*a7c91847Schristos					fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1)
460*a7c91847Schristos				}
461*a7c91847Schristos
462*a7c91847Schristos				# Quote quotes and backslashes properly in full names.
463*a7c91847Schristos				# Do not use gsub; traditional awk lacks it.
464*a7c91847Schristos				quoted = ""
465*a7c91847Schristos				rest = fullname
466*a7c91847Schristos				for (;;) {
467*a7c91847Schristos					p = index(rest, "\\")
468*a7c91847Schristos					q = index(rest, "\"")
469*a7c91847Schristos					if (p) {
470*a7c91847Schristos						if (q && q<p) p = q
471*a7c91847Schristos					} else {
472*a7c91847Schristos						if (!q) break
473*a7c91847Schristos						p = q
474*a7c91847Schristos					}
475*a7c91847Schristos					quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1)
476*a7c91847Schristos					rest = substr(rest, p+1)
477*a7c91847Schristos				}
478*a7c91847Schristos
479*a7c91847Schristos				printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest
480*a7c91847Schristos				author[$1] = 0
481*a7c91847Schristos			}
482*a7c91847Schristos		}
483*a7c91847Schristos	'
484*a7c91847Schristos
485*a7c91847Schristos	initialize_fullname=`
486*a7c91847Schristos		{
487*a7c91847Schristos			(getent passwd $authors) ||
488*a7c91847Schristos			(
489*a7c91847Schristos				cat /etc/passwd
490*a7c91847Schristos				for author in $authors
491*a7c91847Schristos				do NIS_PATH= nismatch $author passwd.org_dir
492*a7c91847Schristos				done
493*a7c91847Schristos				ypmatch $authors passwd
494*a7c91847Schristos			)
495*a7c91847Schristos		} 2>/dev/null |
496*a7c91847Schristos		$AWK -F: "$awkscript"
497*a7c91847Schristos	`$initialize_fullname;;
498*a7c91847Schristosesac
499*a7c91847Schristos
500*a7c91847Schristos
501*a7c91847Schristos# Function to print a single log line.
502*a7c91847Schristos# We don't use awk functions, to stay compatible with old awk versions.
503*a7c91847Schristos# `Log' is the log message.
504*a7c91847Schristos# `files' contains the affected files.
505*a7c91847Schristosprintlogline='{
506*a7c91847Schristos
507*a7c91847Schristos	# Following the GNU coding standards, rewrite
508*a7c91847Schristos	#	* file: (function): comment
509*a7c91847Schristos	# to
510*a7c91847Schristos	#	* file (function): comment
511*a7c91847Schristos	if (Log ~ /^\([^)]*\): /) {
512*a7c91847Schristos		i = index(Log, ")")
513*a7c91847Schristos		filefunc = substr(Log, 1, i)
514*a7c91847Schristos		while ((j = index(filefunc, "\n"))) {
515*a7c91847Schristos			files = files " " substr(filefunc, 1, j-1)
516*a7c91847Schristos			filefunc = substr(filefunc, j+1)
517*a7c91847Schristos		}
518*a7c91847Schristos		files = files " " filefunc
519*a7c91847Schristos		Log = substr(Log, i+3)
520*a7c91847Schristos	}
521*a7c91847Schristos
522*a7c91847Schristos	# If "label: comment" is too long, break the line after the ":".
523*a7c91847Schristos	sep = " "
524*a7c91847Schristos	i = index(Log, "\n")
525*a7c91847Schristos	if ('"$length"' <= '"$indent"' + 1 + length(files) + i) sep = "\n" indent_string
526*a7c91847Schristos
527*a7c91847Schristos	# Print the label.
528*a7c91847Schristos	printf "%s*%s:", indent_string, files
529*a7c91847Schristos
530*a7c91847Schristos	# Print each line of the log.
531*a7c91847Schristos	while (i) {
532*a7c91847Schristos		logline = substr(Log, 1, i-1)
533*a7c91847Schristos		if (logline ~ /[^'"$tab"' ]/) {
534*a7c91847Schristos			printf "%s%s\n", sep, logline
535*a7c91847Schristos		} else {
536*a7c91847Schristos			print ""
537*a7c91847Schristos		}
538*a7c91847Schristos		sep = indent_string
539*a7c91847Schristos		Log = substr(Log, i+1)
540*a7c91847Schristos		i = index(Log, "\n")
541*a7c91847Schristos	}
542*a7c91847Schristos}'
543*a7c91847Schristos
544*a7c91847Schristos# Pattern to match the `revision' line of rlog output.
545*a7c91847Schristosrlog_revision_pattern='^revision [0-9]+\.[0-9]+(\.[0-9]+\.[0-9]+)*(['"$tab"' ]+locked by: [^'"$tab"' $,.0-9:;@]*[^'"$tab"' $,:;@][^'"$tab"' $,.0-9:;@]*;)?['"$tab"' ]*$'
546*a7c91847Schristos
547*a7c91847Schristoscase $hostname in
548*a7c91847Schristos'')
549*a7c91847Schristos	hostname=`(
550*a7c91847Schristos		hostname || uname -n || uuname -l || cat /etc/whoami
551*a7c91847Schristos	) 2>/dev/null` || {
552*a7c91847Schristos		echo >&2 "$0: cannot deduce hostname"
553*a7c91847Schristos		exit 1
554*a7c91847Schristos	}
555*a7c91847Schristos
556*a7c91847Schristos	case $hostname in
557*a7c91847Schristos	*.*) ;;
558*a7c91847Schristos	*)
559*a7c91847Schristos		domainname=`(domainname) 2>/dev/null` &&
560*a7c91847Schristos		case $domainname in
561*a7c91847Schristos		*.*) hostname=$hostname.$domainname;;
562*a7c91847Schristos		esac;;
563*a7c91847Schristos	esac;;
564*a7c91847Schristosesac
565*a7c91847Schristos
566*a7c91847Schristos
567*a7c91847Schristos# Process the rlog output, generating ChangeLog style entries.
568*a7c91847Schristos
569*a7c91847Schristos# First, reformat the rlog output so that each line contains one log entry.
570*a7c91847Schristos# Transliterate \n to SOH so that multiline entries fit on a single line.
571*a7c91847Schristos# Discard irrelevant rlog output.
572*a7c91847Schristos$AWK '
573*a7c91847Schristos	BEGIN {
574*a7c91847Schristos		pository = "'"$pository"'"
575*a7c91847Schristos		SOH="'"$SOH"'"
576*a7c91847Schristos	}
577*a7c91847Schristos	/^RCS file: / {
578*a7c91847Schristos		if (pository != "") {
579*a7c91847Schristos			filename = substr($0, 11)
580*a7c91847Schristos			if (substr(filename, 1, length(pository)) == pository) {
581*a7c91847Schristos				filename = substr(filename, length(pository) + 1)
582*a7c91847Schristos			}
583*a7c91847Schristos			if (filename ~ /,v$/) {
584*a7c91847Schristos				filename = substr(filename, 1, length(filename) - 2)
585*a7c91847Schristos			}
586*a7c91847Schristos			if (filename ~ /(^|\/)Attic\/[^\/]*$/) {
587*a7c91847Schristos				i = length(filename)
588*a7c91847Schristos				while (substr(filename, i, 1) != "/") i--
589*a7c91847Schristos				filename = substr(filename, 1, i - 6) substr(filename, i + 1)
590*a7c91847Schristos			}
591*a7c91847Schristos		}
592*a7c91847Schristos		rev = "?"
593*a7c91847Schristos	}
594*a7c91847Schristos	/^Working file: / { if (repository == "") filename = substr($0, 15) }
595*a7c91847Schristos	/'"$rlog_revision_pattern"'/, /^(-----------*|===========*)$/ {
596*a7c91847Schristos		line = $0
597*a7c91847Schristos		if (line ~ /'"$rlog_revision_pattern"'/) {
598*a7c91847Schristos			rev = $2
599*a7c91847Schristos			next
600*a7c91847Schristos		}
601*a7c91847Schristos		if (line ~ /^date: [0-9][- +\/0-9:]*;/) {
602*a7c91847Schristos			date = $2
603*a7c91847Schristos			if (date ~ /\//) {
604*a7c91847Schristos				# This is a traditional RCS format date YYYY/MM/DD.
605*a7c91847Schristos				# Replace "/"s with "-"s to get ISO format.
606*a7c91847Schristos				newdate = ""
607*a7c91847Schristos				while ((i = index(date, "/")) != 0) {
608*a7c91847Schristos					newdate = newdate substr(date, 1, i-1) "-"
609*a7c91847Schristos					date = substr(date, i+1)
610*a7c91847Schristos				}
611*a7c91847Schristos				date = newdate date
612*a7c91847Schristos			}
613*a7c91847Schristos			time = substr($3, 1, length($3) - 1)
614*a7c91847Schristos			author = substr($5, 1, length($5)-1)
615*a7c91847Schristos			printf "%s%s%s%s%s%s%s%s%s%s", filename, SOH, rev, SOH, date, SOH, time, SOH, author, SOH
616*a7c91847Schristos			rev = "?"
617*a7c91847Schristos			next
618*a7c91847Schristos		}
619*a7c91847Schristos		if (line ~ /^branches: /) { next }
620*a7c91847Schristos		if (line ~ /^(-----------*|===========*)$/) { print ""; next }
621*a7c91847Schristos		if (line == "Initial revision" || line ~ /^file .+ was initially added on branch .+\.$/) {
622*a7c91847Schristos			line = "New file."
623*a7c91847Schristos		}
624*a7c91847Schristos		printf "%s%s", line, SOH
625*a7c91847Schristos	}
626*a7c91847Schristos' <"$rlogfile" |
627*a7c91847Schristos
628*a7c91847Schristos# Now each line is of the form
629*a7c91847Schristos# FILENAME@REVISION@YYYY-MM-DD@HH:MM:SS[+-TIMEZONE]@AUTHOR@LOG
630*a7c91847Schristos#	where @ stands for an SOH (octal code 001),
631*a7c91847Schristos#	and each line of LOG is terminated by SOH instead of \n.
632*a7c91847Schristos# Sort the log entries, first by date+time (in reverse order),
633*a7c91847Schristos# then by author, then by log entry, and finally by file name and revision
634*a7c91847Schristos# (just in case).
635*a7c91847Schristossort -t"$SOH" +2 -4r +4 +0 |
636*a7c91847Schristos
637*a7c91847Schristos# Finally, reformat the sorted log entries.
638*a7c91847Schristos$AWK -F"$SOH" '
639*a7c91847Schristos	BEGIN {
640*a7c91847Schristos		logTZ = "'"$logTZ"'"
641*a7c91847Schristos		revision = "'"$revision"'"
642*a7c91847Schristos
643*a7c91847Schristos		# Initialize the fullname and mailaddr associative arrays.
644*a7c91847Schristos		'"$initialize_fullname"'
645*a7c91847Schristos		'"$initialize_mailaddr"'
646*a7c91847Schristos
647*a7c91847Schristos		# Initialize indent string.
648*a7c91847Schristos		indent_string = ""
649*a7c91847Schristos		i = '"$indent"'
650*a7c91847Schristos		if (0 < '"$tabwidth"')
651*a7c91847Schristos			for (;  '"$tabwidth"' <= i;  i -= '"$tabwidth"')
652*a7c91847Schristos				indent_string = indent_string "\t"
653*a7c91847Schristos		while (1 <= i--)
654*a7c91847Schristos			indent_string = indent_string " "
655*a7c91847Schristos	}
656*a7c91847Schristos
657*a7c91847Schristos	{
658*a7c91847Schristos		newlog = ""
659*a7c91847Schristos		for (i = 6; i < NF; i++) newlog = newlog $i "\n"
660*a7c91847Schristos
661*a7c91847Schristos		# Ignore log entries prefixed by "#".
662*a7c91847Schristos		if (newlog ~ /^#/) { next }
663*a7c91847Schristos
664*a7c91847Schristos		if (Log != newlog || date != $3 || author != $5) {
665*a7c91847Schristos
666*a7c91847Schristos			# The previous log and this log differ.
667*a7c91847Schristos
668*a7c91847Schristos			# Print the old log.
669*a7c91847Schristos			if (date != "") '"$printlogline"'
670*a7c91847Schristos
671*a7c91847Schristos			# Logs that begin with "{clumpname} " should be grouped together,
672*a7c91847Schristos			# and the clumpname should be removed.
673*a7c91847Schristos			# Extract the new clumpname from the log header,
674*a7c91847Schristos			# and use it to decide whether to output a blank line.
675*a7c91847Schristos			newclumpname = ""
676*a7c91847Schristos			sep = "\n"
677*a7c91847Schristos			if (date == "") sep = ""
678*a7c91847Schristos			if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) {
679*a7c91847Schristos				i = index(newlog, "}")
680*a7c91847Schristos				newclumpname = substr(newlog, 1, i)
681*a7c91847Schristos				while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++
682*a7c91847Schristos				newlog = substr(newlog, i+1)
683*a7c91847Schristos				if (clumpname == newclumpname) sep = ""
684*a7c91847Schristos			}
685*a7c91847Schristos			printf sep
686*a7c91847Schristos			clumpname = newclumpname
687*a7c91847Schristos
688*a7c91847Schristos			# Get ready for the next log.
689*a7c91847Schristos			Log = newlog
690*a7c91847Schristos			if (files != "")
691*a7c91847Schristos				for (i in filesknown)
692*a7c91847Schristos					filesknown[i] = 0
693*a7c91847Schristos			files = ""
694*a7c91847Schristos		}
695*a7c91847Schristos		if (date != $3  ||  author != $5) {
696*a7c91847Schristos			# The previous date+author and this date+author differ.
697*a7c91847Schristos			# Print the new one.
698*a7c91847Schristos			date = $3
699*a7c91847Schristos			time = $4
700*a7c91847Schristos			author = $5
701*a7c91847Schristos
702*a7c91847Schristos			zone = ""
703*a7c91847Schristos			if (logTZ && ((i = index(time, "-")) || (i = index(time, "+"))))
704*a7c91847Schristos				zone = " " substr(time, i)
705*a7c91847Schristos
706*a7c91847Schristos			# Print "date[ timezone]  fullname  <email address>".
707*a7c91847Schristos			# Get fullname and email address from associative arrays;
708*a7c91847Schristos			# default to author and author@hostname if not in arrays.
709*a7c91847Schristos			if (fullname[author])
710*a7c91847Schristos				auth = fullname[author]
711*a7c91847Schristos			else
712*a7c91847Schristos				auth = author
713*a7c91847Schristos			printf "%s%s  %s  ", date, zone, auth
714*a7c91847Schristos			if (mailaddr[author])
715*a7c91847Schristos				printf "<%s>\n\n", mailaddr[author]
716*a7c91847Schristos			else
717*a7c91847Schristos				printf "<%s@%s>\n\n", author, "'"$hostname"'"
718*a7c91847Schristos		}
719*a7c91847Schristos		if (! filesknown[$1]) {
720*a7c91847Schristos			filesknown[$1] = 1
721*a7c91847Schristos			if (files == "") files = " " $1
722*a7c91847Schristos			else files = files ", " $1
723*a7c91847Schristos			if (revision && $2 != "?") files = files " " $2
724*a7c91847Schristos		}
725*a7c91847Schristos	}
726*a7c91847Schristos	END {
727*a7c91847Schristos		# Print the last log.
728*a7c91847Schristos		if (date != "") {
729*a7c91847Schristos			'"$printlogline"'
730*a7c91847Schristos			printf "\n"
731*a7c91847Schristos		}
732*a7c91847Schristos	}
733*a7c91847Schristos' &&
734*a7c91847Schristos
735*a7c91847Schristos
736*a7c91847Schristos# Exit successfully.
737*a7c91847Schristos
738*a7c91847Schristosexec rm -fr $logdir
739*a7c91847Schristos
740*a7c91847Schristos# Local Variables:
741*a7c91847Schristos# tab-width:4
742*a7c91847Schristos# End:
743