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