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