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