Switch to recommended form of GPLv3 permissions notice.
[bpt/emacs.git] / lib-src / rcs2log
CommitLineData
daecaf5d
GM
1#! /bin/sh
2
3# RCS to ChangeLog generator
4
294981c7
GM
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
22Copyright='Copyright (C) 2008 Free Software Foundation, Inc.
23This program comes with NO WARRANTY, to the extent permitted by law.
24You may redistribute copies of this program
25under the terms of the GNU General Public License.
26For more information about these matters, see the files named COPYING.
27Author: Paul Eggert <eggert@twinsun.com>'
28
442a2160
PE
29Help='
30Generate ChangeLog entries from RCS files (perhaps in a CVS repository)
31and the ChangeLog file (if any). An RCS file typically has a name
32ending in ",v", and represents the entire history of a file that is
33under revision control. The ChangeLog file logs entries for changes,
34in reverse chronological order.
daecaf5d 35
442a2160
PE
36Generate entries for changes entered into RCS (or CVS) more recently
37than the newest existing entry in the ChangeLog file. You can then
38edit these entries by hand, and prepend them to the ChangeLog file.
39
40Output the resulting ChangeLog entries to standard output.
41Each entry looks something like this:
42
432004-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
49ChangeLog entries contain the current date, full name, email address
50including hostname, the name of the affected file, and commentary.
51RCS and CVS logs lack full names and email addresses, so they are
52inferred from login names using a heuristic that can be overridden
53via the -u option.
54
55Ignore log entries that start with "#".
56Clump together log entries that start with "{topic} ",
57where "topic" contains neither white space nor "}".
58
59If no FILE is specified, use all files under the working directory
60that are maintained under version control.
daecaf5d 61
daecaf5d
GM
62Options:
63
442a2160 64 -c FILE Output ChangeLog entries for FILE (default ChangeLog).
daecaf5d
GM
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).
442a2160 68 -L FILE Use FILE (same format as "rlog") for source of logs.
daecaf5d 69 -R If no FILEs are given and RCS is used, recurse through working directory.
442a2160 70 -r OPTION Pass OPTION to subsidiary command (either "rlog" or "cvs -q log").
daecaf5d 71 -t TABWIDTH Tab stops are every TABWIDTH characters (default 8).
442a2160 72 -u "LOGIN<tab>FULLNAME<tab>EMAILADDR" LOGIN has FULLNAME and EMAILADDR.
daecaf5d
GM
73 -v Append RCS revision to file names in log lines.
74 --help Output help.
75 --version Output version number.
76
77Report bugs to <bug-gnu-emacs@gnu.org>.'
78
364c38d3 79Id='$Id$'
daecaf5d 80
1bd57c39
PE
81# Use the traditional C locale.
82LANG=C
83LANGUAGE=C
84LC_ALL=C
85LC_COLLATE=C
86LC_CTYPE=C
87LC_MESSAGES=C
88LC_NUMERIC=C
89LC_TIME=C
90export 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.
96SOH='\ 1' # SOH, octal code 001
daecaf5d
GM
97tab=' '
98nl='
99'
100
101# Parse options.
102
103# defaults
b2ba2352
PE
104AWK=${AWK-awk}
105TMPDIR=${TMPDIR-/tmp}
daecaf5d
GM
106changelog=ChangeLog # change log file name
107datearg= # rlog date option
108hostname= # name of local host (if empty, will deduce it later)
109indent=8 # indent of log line
110length=79 # suggested max width of log line
111logins= # login names for people we know fullnames and mailaddrs of
112loginFullnameMailaddrs= # login<tab>fullname<tab>mailaddr triplets
113logTZ= # time zone for log dates (if empty, use local time)
114recursive= # t if we want recursive rlog
115revision= # t if we want revision numbers
116rlog_options= # options to pass to rlog
1bd57c39 117rlogfile= # log file to read from
daecaf5d
GM
118tabwidth=8 # width of horizontal tab
119
120while :
121do
122 case $1 in
123 -c) changelog=${2?}; shift;;
124 -i) indent=${2?}; shift;;
125 -h) hostname=${2?}; shift;;
126 -l) length=${2?}; shift;;
1bd57c39 127 -L) rlogfile=${2?}; shift;;
daecaf5d
GM
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"
1bd57c39 133 exit 1;;
daecaf5d 134 esac
1bd57c39
PE
135 login=$2
136 lfm=$2$tab$3$tab$4
daecaf5d
GM
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 *)
1bd57c39 147 t=':';;
daecaf5d
GM
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"*)
1bd57c39
PE
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 `;;
daecaf5d
GM
162 *)
163 echo >&2 "$0: -u '$2': not enough fields"
1bd57c39 164 exit 1;;
daecaf5d 165 esac
1bd57c39 166 shift;;
daecaf5d
GM
167 esac
168 case $logins in
169 '') logins=$login;;
1bd57c39 170 ?*) logins=$logins$nl$login;;
daecaf5d 171 esac
1bd57c39
PE
172 case $loginFullnameMailaddrs in
173 '') loginFullnameMailaddrs=$lfm;;
174 ?*) loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$lfm;;
175 esac;;
daecaf5d
GM
176 -r)
177 case $rlog_options in
178 '') rlog_options=${2?};;
1bd57c39 179 ?*) rlog_options=$rlog_options$nl${2?};;
daecaf5d
GM
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;;
1bd57c39 193 *) exit 1;;
daecaf5d 194 esac;;
1bd57c39 195 *) break;;
daecaf5d
GM
196 esac
197 shift
198done
199
200month_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
1bd57c39
PE
207logdir=$TMPDIR/rcs2log$$
208llogout=$logdir/l
209trap exit 1 2 13 15
210trap "rm -fr $logdir 2>/dev/null" 0
211(umask 077 && exec mkdir $logdir) || exit
daecaf5d 212
1bd57c39
PE
213# If no rlog-format log file is given, generate one into $rlogfile.
214case $rlogfile in
daecaf5d 215'')
1bd57c39
PE
216 rlogfile=$logdir/r
217
218 # If no rlog options are given,
219 # log the revisions checked in since the first ChangeLog entry.
c9140662
PE
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.
1bd57c39
PE
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.
daecaf5d
GM
251 if test -s "$changelog"
252 then
1bd57c39
PE
253 extractTZ='
254 /^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*"\([^"]*\)".*/{
255 s//\1/; p; q
daecaf5d 256 }
1bd57c39
PE
257 /^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*t.*/{
258 s//UTC0/; p; q
daecaf5d
GM
259 }
260 '
1bd57c39
PE
261 logTZ=`tail "$changelog" | sed -n "$extractTZ"`
262 case $logTZ in
263 ?*) TZ=$logTZ; export TZ;;
daecaf5d
GM
264 esac
265 fi
daecaf5d 266
1bd57c39
PE
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
83d48514 276 pository=
1bd57c39 277 case $CVSROOT in
83d48514
PE
278 /* | :fork:* | :local:*) ;;
279 */*)
1bd57c39 280 # remote repository
83d48514
PE
281 pository=`expr "X$CVSROOT" : '[^/]*\(.*\)'`;;
282 esac
283 case $pository in
284 '')
1bd57c39
PE
285 # local repository
286 case $repository in
287 /*) ;;
83d48514
PE
288 *)
289 repository=${CVSROOT?}/$repository
290 case $repository in
291 :fork:* | :local:*)
292 repository=`expr "$repository" : ':[^:]*:\(.*\)'`;;
293 esac;;
1bd57c39
PE
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;;
daecaf5d 301 esac
daecaf5d 302
1bd57c39
PE
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
c9140662
PE
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
1bd57c39
PE
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;;
daecaf5d 338 esac
daecaf5d 339
1bd57c39
PE
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;;
daecaf5d 378 esac
1bd57c39
PE
379 case $files in
380 '') files=$file;;
381 ?*) files=$files$nl$file;;
382 esac
383 done
daecaf5d 384 case $files in
1bd57c39
PE
385 '') exit 0;;
386 esac;;
daecaf5d 387 esac
1bd57c39
PE
388 set x $files
389 shift
390 IFS=$oldIFS;;
391 esac;;
daecaf5d 392 esac
daecaf5d 393
1bd57c39
PE
394 case $datearg in
395 ?*) $rlog $rlog_options "$datearg" ${1+"$@"} >$rlogfile;;
396 '') $rlog $rlog_options ${1+"$@"} >$rlogfile;;
397 esac || exit;;
398esac
daecaf5d
GM
399
400
b2ba2352
PE
401# Prefer the POSIX-style -k options, since POSIX 1003.1-2001 prohibits
402# support for the traditional-style +M -N options.
403SORT_K_OPTIONS='-k 3,4r -k 5 -k 1'
404sort $SORT_K_OPTIONS </dev/null 2>/dev/null || SORT_K_OPTIONS='+2 -4r +4 +0'
405
406
daecaf5d
GM
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
412initialize_fullname=
413initialize_mailaddr=
414
415case $loginFullnameMailaddrs in
416?*)
417 case $loginFullnameMailaddrs in
418 *\"* | *\\*)
419 sed 's/["\\]/\\&/g' >$llogout <<EOF || exit
420$loginFullnameMailaddrs
421EOF
1bd57c39 422 loginFullnameMailaddrs=`cat $llogout`;;
daecaf5d
GM
423 esac
424
425 oldIFS=$IFS
426 IFS=$nl
427 for loginFullnameMailaddr in $loginFullnameMailaddrs
428 do
1bd57c39 429 IFS=$tab
daecaf5d
GM
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
1bd57c39 439 IFS=$oldIFS;;
daecaf5d
GM
440esac
441
1bd57c39
PE
442case $logins in
443?*)
444 sort -u -o $llogout <<EOF
daecaf5d
GM
445$logins
446EOF
1bd57c39
PE
447 ;;
448'')
449 : ;;
450esac >$llogout || exit
451
daecaf5d
GM
452output_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}'
457authors=`
1bd57c39 458 $AWK "$output_authors" <"$rlogfile" | sort -u | comm -23 - $llogout
daecaf5d
GM
459`
460case $authors in
461?*)
462 cat >$llogout <<EOF || exit
463$authors
464EOF
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"
1bd57c39 530 `$initialize_fullname;;
daecaf5d
GM
531esac
532
533
534# Function to print a single log line.
535# We don't use awk functions, to stay compatible with old awk versions.
1bd57c39 536# `Log' is the log message.
daecaf5d
GM
537# `files' contains the affected files.
538printlogline='{
539
540 # Following the GNU coding standards, rewrite
541 # * file: (function): comment
542 # to
543 # * file (function): comment
e8d6a09b 544 if (Log ~ /^\([^)]*\):[\t\n ]/) {
daecaf5d 545 i = index(Log, ")")
1bd57c39
PE
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
daecaf5d
GM
552 Log = substr(Log, i+3)
553 }
554
555 # If "label: comment" is too long, break the line after the ":".
556 sep = " "
1bd57c39
PE
557 i = index(Log, "\n")
558 if ('"$length"' <= '"$indent"' + 1 + length(files) + i) sep = "\n" indent_string
daecaf5d
GM
559
560 # Print the label.
561 printf "%s*%s:", indent_string, files
562
1bd57c39
PE
563 # Print each line of the log.
564 while (i) {
daecaf5d
GM
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)
1bd57c39 573 i = index(Log, "\n")
daecaf5d
GM
574 }
575}'
576
577# Pattern to match the `revision' line of rlog output.
578rlog_revision_pattern='^revision [0-9]+\.[0-9]+(\.[0-9]+\.[0-9]+)*(['"$tab"' ]+locked by: [^'"$tab"' $,.0-9:;@]*[^'"$tab"' $,:;@][^'"$tab"' $,.0-9:;@]*;)?['"$tab"' ]*$'
579
580case $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
1bd57c39
PE
594 *.*) hostname=$hostname.$domainname;;
595 esac;;
596 esac;;
daecaf5d
GM
597esac
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.
1bd57c39 603# Transliterate \n to SOH so that multiline entries fit on a single line.
daecaf5d 604# Discard irrelevant rlog output.
1bd57c39
PE
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)
daecaf5d
GM
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 }
1bd57c39 627 /^Working file: / { if (repository == "") filename = substr($0, 15) }
daecaf5d
GM
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)
1bd57c39 648 printf "%s%s%s%s%s%s%s%s%s%s", filename, SOH, rev, SOH, date, SOH, time, SOH, author, SOH
daecaf5d
GM
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 }
1bd57c39 657 printf "%s%s", line, SOH
daecaf5d 658 }
1bd57c39 659' <"$rlogfile" |
daecaf5d
GM
660
661# Now each line is of the form
1bd57c39
PE
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.
daecaf5d
GM
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).
b2ba2352 668sort -t"$SOH" $SORT_K_OPTIONS |
daecaf5d
GM
669
670# Finally, reformat the sorted log entries.
1bd57c39 671$AWK -F"$SOH" '
daecaf5d
GM
672 BEGIN {
673 logTZ = "'"$logTZ"'"
674 revision = "'"$revision"'"
675
daecaf5d
GM
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 {
1bd57c39
PE
691 newlog = ""
692 for (i = 6; i < NF; i++) newlog = newlog $i "\n"
daecaf5d
GM
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)
e8d6a09b 716 if (clumpname == newclumpname && date == $3 && author == $5) sep = ""
daecaf5d
GM
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
771exec rm -fr $logdir
772
773# Local Variables:
774# tab-width:4
775# End:
ab5796a9
MB
776
777# arch-tag: cea067bd-a552-4254-ba17-078208933073