3 # RCS to ChangeLog generator
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.
10 # Ignore log entries that start with `#'.
11 # Clump together log entries that start with `{topic} ',
12 # where `topic' contains neither white space nor `}'.
14 Help
='The default FILEs are the files registered under the working directory.
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 -R If no FILEs are given and RCS is used, recurse through working directory.
22 -r OPTION Pass OPTION to subsidiary log command.
23 -t TABWIDTH Tab stops are every TABWIDTH characters (default 8).
24 -u "LOGIN<tab>FULLNAME<tab>MAILADDR" Assume LOGIN has FULLNAME and MAILADDR.
25 -v Append RCS revision to file names in log lines.
27 --version Output version number.
29 Report bugs to <bug-gnu-emacs@gnu.org>.'
31 Id
='$Id: rcs2log,v 1.46 2001/01/03 12:04:06 gerd Exp $'
33 # Copyright 1992, 93, 94, 95, 96, 97, 1998, 2001
34 # Free Software Foundation, Inc.
36 # This program is free software; you can redistribute it and/or modify
37 # it under the terms of the GNU General Public License as published by
38 # the Free Software Foundation; either version 2, or (at your option)
41 # This program is distributed in the hope that it will be useful,
42 # but WITHOUT ANY WARRANTY; without even the implied warranty of
43 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44 # GNU General Public License for more details.
46 # You should have received a copy of the GNU General Public License
47 # along with this program; see the file COPYING. If not, write to the
48 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
49 # Boston, MA 02111-1307, USA.
51 Copyright
='Copyright 1998 Free Software Foundation, Inc.
52 This program comes with NO WARRANTY, to the extent permitted by law.
53 You may redistribute copies of this program
54 under the terms of the GNU General Public License.
55 For more information about these matters, see the files named COPYING.
56 Author: Paul Eggert <eggert@twinsun.com>'
67 changelog
=ChangeLog
# change log file name
68 datearg
= # rlog date option
69 hostname
= # name of local host (if empty, will deduce it later)
70 indent
=8 # indent of log line
71 length
=79 # suggested max width of log line
72 logins
= # login names for people we know fullnames and mailaddrs of
73 loginFullnameMailaddrs
= # login<tab>fullname<tab>mailaddr triplets
74 logTZ
= # time zone for log dates (if empty, use local time)
75 recursive
= # t if we want recursive rlog
76 revision
= # t if we want revision numbers
77 rlog_options
= # options to pass to rlog
78 tabwidth
=8 # width of horizontal tab
83 -c) changelog
=${2?}; shift;;
84 -i) indent
=${2?}; shift;;
85 -h) hostname
=${2?}; shift;;
86 -l) length
=${2?}; shift;;
87 -[nu
]) # -n is obsolescent; it is replaced by -u.
89 -n) case ${2?}${3?}${4?} in
91 echo >&2 "$0: -n '$2' '$3' '$4': tabs, newlines not allowed"
94 case $loginFullnameMailaddrs in
95 '') loginFullnameMailaddrs
=$2$tab$3$tab$4;;
96 ?
*) loginFullnameMailaddrs
=$loginFullnameMailaddrs$nl$2$tab$3$tab$4
100 # If $2 is not tab-separated, use colon for separator.
103 echo >&2 "$0: -u '$2': newlines not allowed"
112 echo >&2 "$0: -u '$2': too many fields"
117 echo >&2 "$0: -u '$2': not enough fields"
120 case $loginFullnameMailaddrs in
121 '') loginFullnameMailaddrs
=$2;;
122 ?
*) loginFullnameMailaddrs
=$loginFullnameMailaddrs$nl$2
128 ?
*) logins
=$logins$nl$login
132 case $rlog_options in
133 '') rlog_options
=${2?};;
134 ?
*) rlog_options
=$rlog_options$nl${2?}
138 -t) tabwidth
=${2?}; shift;;
143 echo >&2 "rcs2log (GNU Emacs) $rcs2logVersion$nl$Copyright"
145 -*) echo >&2 "Usage: $0 [OPTION]... [FILE ...]$nl$Help"
156 m[0]="Jan"; m[1]="Feb"; m[2]="Mar"
157 m[3]="Apr"; m[4]="May"; m[5]="Jun"
158 m[6]="Jul"; m[7]="Aug"; m[8]="Sep"
159 m[9]="Oct"; m[10]="Nov"; m[11]="Dec"
163 # Put rlog output into $rlogout.
165 # If no rlog options are given,
166 # log the revisions checked in since the first ChangeLog entry.
167 # Since ChangeLog is only by date, some of these revisions may be duplicates of
168 # what's already in ChangeLog; it's the user's responsibility to remove them.
169 case $rlog_options in
171 if test -s "$changelog"
174 /^[0-9]+-[0-9][0-9]-[0-9][0-9]/{
179 /^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{
180 # old-fashioned date and time (Emacs 19.31 and earlier)
183 for (i=0; i<=11; i++) if (m[i] == $2) break
185 printf "%d-%02d-%02d\n", year, i+1, dd
189 d
=`$AWK "$e" <"$changelog"` ||
exit
196 # Use TZ specified by ChangeLog local variable, if any.
197 if test -s "$changelog"
200 /^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*"\([^"]*\)".*/{
203 /^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*t.*/{
207 logTZ
=`tail "$changelog" | sed -n "$extractTZ"`
209 ?
*) TZ
=$logTZ; export TZ
213 # If CVS is in use, examine its repository, not the normal RCS files.
214 if test ! -f CVS
/Repository
220 repository
=`sed 1q <CVS/Repository` ||
exit
221 test ! -f CVS
/Root || CVSROOT
=`cat <CVS/Root` ||
exit
230 *) repository
=${CVSROOT?}/$repository
232 if test ! -d "$repository"
234 echo >&2 "$0: $repository: bad repository (see CVS/Repository)"
240 # Use $rlog's -zLT option, if $rlog supports it.
241 case `$rlog -zLT 2>&1` in
244 case $rlog_options in
245 '') rlog_options
=-zLT;;
246 ?
*) rlog_options
=-zLT$nl$rlog_options
250 # With no arguments, examine all files under the RCS directory.
259 RCSdirs
=`find . -name RCS -type d -print`
260 filesFromRCSfiles
='s|,v$||; s|/RCS/|/|; s|^\./||'
269 ! -name .rcsfreeze.log \
270 ! -name .rcsfreeze.ver \
273 find . -name '*,v' -print
276 sed "$filesFromRCSfiles"
280 for file in RCS
/.
* RCS
/* .
*,v
*,v
283 RCS
/. | RCS
/.. | RCS
/,*, | RCS
/*_
) continue;;
284 RCS
/.rcsfreeze.log | RCS
/.rcsfreeze.ver
) continue;;
285 RCS
/.\
* | RCS
/\
* | .\
*,v | \
*,v
) test -f "$file" ||
continue;;
286 RCS
/*,v | RCS
/.
*,v
) ;;
287 RCS
/* | RCS
/.
*) test -f "$file" ||
continue
291 ?
*) files
=$files$nl$file
304 logdir
=$TMPDIR/rcs2log$$
308 trap "rm -fr $logdir 2>/dev/null" 0
309 (umask 077 && exec mkdir
$logdir) ||
exit
312 ?
*) $rlog $rlog_options "$datearg" ${1+"$@"} >$rlogout;;
313 '') $rlog $rlog_options ${1+"$@"} >$rlogout
317 # Get the full name of each author the logs mention, and set initialize_fullname
318 # to awk code that initializes the `fullname' awk associative array.
319 # Warning: foreign authors (i.e. not known in the passwd file) are mishandled;
320 # you have to fix the resulting output by hand.
325 case $loginFullnameMailaddrs in
327 case $loginFullnameMailaddrs in
329 sed 's/["\\]/\\&/g' >$llogout <<EOF || exit
330 $loginFullnameMailaddrs
332 loginFullnameMailaddrs
=`cat $llogout`
337 for loginFullnameMailaddr
in $loginFullnameMailaddrs
339 case $loginFullnameMailaddr in
343 set x
$loginFullnameMailaddr
347 initialize_fullname
="$initialize_fullname
348 fullname[\"$login\"] = \"$fullname\""
349 initialize_mailaddr
="$initialize_mailaddr
350 mailaddr[\"$login\"] = \"$mailaddr\""
356 ?
*) sort -u -o $llogout <<EOF || exit
360 output_authors
='/^date: / {
361 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 ~ /^[^;]*;$/) {
362 print substr($5, 1, length($5)-1)
366 $AWK "$output_authors" <$rlogout |
369 ?*) sort -u | comm -23 - $llogout
374 cat >$llogout <<EOF || exit
377 initialize_author_script
='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/'
378 initialize_author
=`sed -e "$initialize_author_script" <$llogout`
381 alphabet = "abcdefghijklmnopqrstuvwxyz"
382 ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
383 '"$initialize_author"'
388 if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) {
389 # Remove the junk from fullnames like "0000-Admin(0000)".
390 fullname = substr(fullname, index(fullname, "-") + 1)
391 fullname = substr(fullname, 1, index(fullname, "(") - 1)
393 if (fullname ~ /,[^ ]/) {
394 # Some sites put comma-separated junk after the fullname.
395 # Remove it, but leave "Bill Gates, Jr" alone.
396 fullname = substr(fullname, 1, index(fullname, ",") - 1)
398 abbr = index(fullname, "&")
402 i = index(alphabet, a)
403 if (i) A = substr(ALPHABET, i, 1)
404 fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1)
407 # Quote quotes and backslashes properly in full names.
408 # Do not use gsub; traditional awk lacks it.
412 p = index(rest, "\\")
413 q = index(rest, "\"")
420 quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1)
421 rest = substr(rest, p+1)
424 printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest
430 initialize_fullname
=`
432 (getent passwd $authors) ||
435 for author in $authors
436 do NIS_PATH= nismatch $author passwd.org_dir
438 ypmatch $authors passwd
441 $AWK -F: "$awkscript"
442 `$initialize_fullname
446 # Function to print a single log line.
447 # We don't use awk functions, to stay compatible with old awk versions.
448 # `Log' is the log message (with \n replaced by \001).
449 # `files' contains the affected files.
452 # Following the GNU coding standards, rewrite
453 # * file: (function): comment
455 # * file (function): comment
456 if (Log ~ /^\([^)]*\): /) {
458 files = files " " substr(Log, 1, i)
459 Log = substr(Log, i+3)
462 # If "label: comment" is too long, break the line after the ":".
464 if ('"$length"' <= '"$indent"' + 1 + length(files) + index(Log, SOH)) sep = "\n" indent_string
467 printf "%s*%s:", indent_string, files
469 # Print each line of the log, transliterating \001 to \n.
470 while ((i = index(Log, SOH)) != 0) {
471 logline = substr(Log, 1, i-1)
472 if (logline ~ /[^'"$tab"' ]/) {
473 printf "%s%s\n", sep, logline
478 Log = substr(Log, i+1)
482 # Pattern to match the `revision' line of rlog output.
483 rlog_revision_pattern
='^revision [0-9]+\.[0-9]+(\.[0-9]+\.[0-9]+)*(['"$tab"' ]+locked by: [^'"$tab"' $,.0-9:;@]*[^'"$tab"' $,:;@][^'"$tab"' $,.0-9:;@]*;)?['"$tab"' ]*$'
488 hostname || uname -n || uuname -l || cat /etc/whoami
490 echo >&2 "$0: cannot deduce hostname"
497 domainname
=`(domainname) 2>/dev/null` &&
499 *.
*) hostname
=$hostname.
$domainname
505 # Process the rlog output, generating ChangeLog style entries.
507 # First, reformat the rlog output so that each line contains one log entry.
508 # Transliterate \n to \001 so that multiline entries fit on a single line.
509 # Discard irrelevant rlog output.
511 BEGIN { repository = "'"$repository"'" }
513 if (repository != "") {
515 if (substr(filename, 1, length(repository) + 1) == repository "/") {
516 filename = substr(filename, length(repository) + 2)
518 if (filename ~ /,v$/) {
519 filename = substr(filename, 1, length(filename) - 2)
521 if (filename ~ /(^|\/)Attic\/[^\/]*$/) {
523 while (substr(filename, i, 1) != "/") i--
524 filename = substr(filename, 1, i - 6) substr(filename, i + 1)
529 /^Working file:/ { if (repository == "") filename = $3 }
530 /'"$rlog_revision_pattern"'/, /^(-----------*|===========*)$/ {
532 if (line ~ /'"$rlog_revision_pattern"'/) {
536 if (line ~ /^date: [0-9][- +\/0-9:]*;/) {
539 # This is a traditional RCS format date YYYY/MM/DD.
540 # Replace "/"s with "-"s to get ISO format.
542 while ((i = index(date, "/")) != 0) {
543 newdate = newdate substr(date, 1, i-1) "-"
544 date = substr(date, i+1)
548 time = substr($3, 1, length($3) - 1)
549 author = substr($5, 1, length($5)-1)
550 printf "%s %s %s %s %s %c", filename, rev, date, time, author, 1
554 if (line ~ /^branches: /) { next }
555 if (line ~ /^(-----------*|===========*)$/) { print ""; next }
556 if (line == "Initial revision" || line ~ /^file .+ was initially added on branch .+\.$/) {
559 printf "%s%c", line, 1
563 # Now each line is of the form
564 # FILENAME REVISION YYYY-MM-DD HH:MM:SS[+-TIMEZONE] AUTHOR \001LOG
565 # where \001 stands for a carriage return,
566 # and each line of the log is terminated by \001 instead of \n.
567 # Sort the log entries, first by date+time (in reverse order),
568 # then by author, then by log entry, and finally by file name and revision
572 # Finally, reformat the sorted log entries.
576 revision = "'"$revision"'"
578 # Some awk variants do not understand "\001", so we have to
579 # put the char directly in the file.
580 SOH="\x01" # <-- There is a single SOH (octal code 001) here.
582 # Initialize the fullname and mailaddr associative arrays.
583 '"$initialize_fullname"'
584 '"$initialize_mailaddr"'
586 # Initialize indent string.
589 if (0 < '"$tabwidth"')
590 for (; '"$tabwidth"' <= i; i -= '"$tabwidth"')
591 indent_string = indent_string "\t"
593 indent_string = indent_string " "
597 newlog = substr($0, 1 + index($0, SOH))
599 # Ignore log entries prefixed by "#".
600 if (newlog ~ /^#/) { next }
602 if (Log != newlog || date != $3 || author != $5) {
604 # The previous log and this log differ.
607 if (date != "") '"$printlogline"'
609 # Logs that begin with "{clumpname} " should be grouped together,
610 # and the clumpname should be removed.
611 # Extract the new clumpname from the log header,
612 # and use it to decide whether to output a blank line.
615 if (date == "") sep = ""
616 if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) {
617 i = index(newlog, "}")
618 newclumpname = substr(newlog, 1, i)
619 while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++
620 newlog = substr(newlog, i+1)
621 if (clumpname == newclumpname) sep = ""
624 clumpname = newclumpname
626 # Get ready for the next log.
629 for (i in filesknown)
633 if (date != $3 || author != $5) {
634 # The previous date+author and this date+author differ.
641 if (logTZ && ((i = index(time, "-")) || (i = index(time, "+"))))
642 zone = " " substr(time, i)
644 # Print "date[ timezone] fullname <email address>".
645 # Get fullname and email address from associative arrays;
646 # default to author and author@hostname if not in arrays.
647 if (fullname[author])
648 auth = fullname[author]
651 printf "%s%s %s ", date, zone, auth
652 if (mailaddr[author])
653 printf "<%s>\n\n", mailaddr[author]
655 printf "<%s@%s>\n\n", author, "'"$hostname"'"
657 if (! filesknown[$1]) {
659 if (files == "") files = " " $1
660 else files = files ", " $1
661 if (revision && $2 != "?") files = files " " $2
665 # Print the last log.