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