]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/cvs/contrib/rcs2log.sh
This commit was generated by cvs2svn to compensate for changes in r53910,
[FreeBSD/FreeBSD.git] / contrib / cvs / contrib / rcs2log.sh
1 #! /bin/sh
2
3 # RCS to ChangeLog generator
4
5 # Generate a change log prefix from RCS files and the ChangeLog (if any).
6 # Output the new prefix to standard output.
7 # You can edit this prefix by hand, and then prepend it to ChangeLog.
8
9 # Ignore log entries that start with `#'.
10 # Clump together log entries that start with `{topic} ',
11 # where `topic' contains neither white space nor `}'.
12
13 # Author: Paul Eggert <eggert@twinsun.com>
14
15 # Copyright 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
16
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation; either version 2, or (at your option)
20 # any later version.
21
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 # GNU General Public License for more details.
26
27 # You should have received a copy of the GNU General Public License
28 # along with this program; see the file COPYING.  If not, write to the
29 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
30 # Boston, MA 02111-1307, USA.
31
32 tab='   '
33 nl='
34 '
35
36 # Parse options.
37
38 # defaults
39 : ${AWK=awk}
40 : ${TMPDIR=/tmp}
41 hostname= # name of local host (if empty, will deduce it later)
42 indent=8 # indent of log line
43 length=79 # suggested max width of log line
44 logins= # login names for people we know fullnames and mailaddrs of
45 loginFullnameMailaddrs= # login<tab>fullname<tab>mailaddr triplets
46 recursive= # t if we want recursive rlog
47 rlog_options= # options to pass to rlog
48 tabwidth=8 # width of horizontal tab
49
50 while :
51 do
52         case $1 in
53         -i)     indent=${2?}; shift;;
54         -h)     hostname=${2?}; shift;;
55         -l)     length=${2?}; shift;;
56         -[nu])  # -n is obsolescent; it is replaced by -u.
57                 case $1 in
58                 -n)     case ${2?}${3?}${4?} in
59                         *"$tab"* | *"$nl"*)
60                                 echo >&2 "$0: -n '$2' '$3' '$4': tabs, newlines not allowed"
61                                 exit 1
62                         esac
63                         loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2$tab$3$tab$4
64                         shift; shift; shift;;
65                 -u)
66                         # If $2 is not tab-separated, use colon for separator.
67                         case ${2?} in
68                         *"$nl"*)
69                                 echo >&2 "$0: -u '$2': newlines not allowed"
70                                 exit 1;;
71                         *"$tab"*)
72                                 t=$tab;;
73                         *)
74                                 t=:
75                         esac
76                         case $2 in
77                         *"$t"*"$t"*"$t"*)
78                                 echo >&2 "$0: -u '$2': too many fields"
79                                 exit 1;;
80                         *"$t"*"$t"*)
81                                 ;;
82                         *)
83                                 echo >&2 "$0: -u '$2': not enough fields"
84                                 exit 1
85                         esac
86                         loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2
87                         shift
88                 esac
89                 logins=$logins$nl$login
90                 ;;
91         -r)     rlog_options=$rlog_options$nl${2?}; shift;;
92         -R)     recursive=t;;
93         -t)     tabwidth=${2?}; shift;;
94         -*)     echo >&2 "$0: usage: $0 [options] [file ...]
95 Options:
96         [-h hostname] [-i indent] [-l length] [-R] [-r rlog_option]
97         [-t tabwidth] [-u 'login<TAB>fullname<TAB>mailaddr']..."
98                 exit 1;;
99         *)      break
100         esac
101         shift
102 done
103
104 month_data='
105         m[0]="Jan"; m[1]="Feb"; m[2]="Mar"
106         m[3]="Apr"; m[4]="May"; m[5]="Jun"
107         m[6]="Jul"; m[7]="Aug"; m[8]="Sep"
108         m[9]="Oct"; m[10]="Nov"; m[11]="Dec"
109
110         # days in non-leap year thus far, indexed by month (0-12)
111         mo[0]=0; mo[1]=31; mo[2]=59; mo[3]=90
112         mo[4]=120; mo[5]=151; mo[6]=181; mo[7]=212
113         mo[8]=243; mo[9]=273; mo[10]=304; mo[11]=334
114         mo[12]=365
115 '
116
117
118 # Put rlog output into $rlogout.
119
120 # If no rlog options are given,
121 # log the revisions checked in since the first ChangeLog entry.
122 case $rlog_options in
123 '')
124         date=1970-01-01
125         if test -s ChangeLog
126         then
127                 # Add 1 to seconds to avoid duplicating most recent log.
128                 e='
129                         /^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{
130                                 '"$month_data"'
131                                 year = $5
132                                 for (i=0; i<=11; i++) if (m[i] == $2) break
133                                 dd = $3
134                                 hh = substr($0,12,2)
135                                 mm = substr($0,15,2)
136                                 ss = substr($0,18,2)
137                                 ss++
138                                 if (ss == 60) {
139                                         ss = 0
140                                         mm++
141                                         if (mm == 60) {
142                                                 mm = 0
143                                                 hh++
144                                                 if (hh == 24) {
145                                                         hh = 0
146                                                         dd++
147                                                         monthdays = mo[i+1] - mo[i]
148                                                         if (i == 1 && year%4 == 0 && (year%100 != 0 || year%400 == 0)) monthdays++
149                                                         if (dd == monthdays + 1) {
150                                                                 dd = 1
151                                                                 i++
152                                                                 if (i == 12) {
153                                                                         i = 0
154                                                                         year++
155                                                                 }
156                                                         }
157                                                 }
158                                         }
159                                 }
160                                 printf "%02d/%02d/%d %02d:%02d:%02d\n", i+1,dd,year,hh,mm,ss
161                                 exit
162                         }
163                 '
164                 d=`$AWK "$e" <ChangeLog` || exit
165                 case $d in
166                 ?*) date=$d
167                 esac
168         fi
169         datearg="-d>$date"
170 esac
171
172 # If CVS is in use, examine its repository, not the normal RCS files.
173 if test ! -f CVS/Repository
174 then
175         rlog=rlog
176         repository=
177 else
178         rlog='cvs log'
179         repository=`sed 1q <CVS/Repository` || exit
180         test ! -f CVS/Root || CVSROOT=`cat <CVS/Root` || exit
181         case $CVSROOT in
182         *:/*)
183                 # remote repository
184                 ;;
185         *)
186                 # local repository
187                 case $repository in
188                 /*) ;;
189                 *) repository=${CVSROOT?}/$repository
190                 esac
191                 if test ! -d "$repository"
192                 then
193                         echo >&2 "$0: $repository: bad repository (see CVS/Repository)"
194                         exit 1
195                 fi
196         esac
197 fi
198
199 # With no arguments, examine all files under the RCS directory.
200 case $# in
201 0)
202         case $repository in
203         '')
204                 oldIFS=$IFS
205                 IFS=$nl
206                 case $recursive in
207                 t)
208                         RCSdirs=`find . -name RCS -type d -print`
209                         filesFromRCSfiles='s|,v$||; s|/RCS/|/|; s|^\./||'
210                         files=`
211                                 {
212                                         case $RCSdirs in
213                                         ?*) find $RCSdirs -type f -print
214                                         esac
215                                         find . -name '*,v' -print
216                                 } |
217                                 sort -u |
218                                 sed "$filesFromRCSfiles"
219                         `;;
220                 *)
221                         files=
222                         for file in RCS/.* RCS/* .*,v *,v
223                         do
224                                 case $file in
225                                 RCS/. | RCS/..) continue;;
226                                 RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue
227                                 esac
228                                 files=$files$nl$file
229                         done
230                         case $files in
231                         '') exit 0
232                         esac
233                 esac
234                 set x $files
235                 shift
236                 IFS=$oldIFS
237         esac
238 esac
239
240 llogout=$TMPDIR/rcs2log$$l
241 rlogout=$TMPDIR/rcs2log$$r
242 trap exit 1 2 13 15
243 trap "rm -f $llogout $rlogout; exit 1" 0
244
245 case $rlog_options in
246 ?*) $rlog $rlog_options ${1+"$@"} >$rlogout;;
247 '') $rlog "$datearg" ${1+"$@"} >$rlogout
248 esac || exit
249
250
251 # Get the full name of each author the logs mention, and set initialize_fullname
252 # to awk code that initializes the `fullname' awk associative array.
253 # Warning: foreign authors (i.e. not known in the passwd file) are mishandled;
254 # you have to fix the resulting output by hand.
255
256 initialize_fullname=
257 initialize_mailaddr=
258
259 case $loginFullnameMailaddrs in
260 ?*)
261         case $loginFullnameMailaddrs in
262         *\"* | *\\*)
263                 sed 's/["\\]/\\&/g' >$llogout <<EOF || exit
264 $loginFullnameMailaddrs
265 EOF
266                 loginFullnameMailaddrs=`cat $llogout`
267         esac
268
269         oldIFS=$IFS
270         IFS=$nl
271         for loginFullnameMailaddr in $loginFullnameMailaddrs
272         do
273                 case $loginFullnameMailaddr in
274                 *"$tab"*) IFS=$tab;;
275                 *) IFS=:
276                 esac
277                 set x $loginFullnameMailaddr
278                 login=$2
279                 fullname=$3
280                 mailaddr=$4
281                 initialize_fullname="$initialize_fullname
282                         fullname[\"$login\"] = \"$fullname\""
283                 initialize_mailaddr="$initialize_mailaddr
284                         mailaddr[\"$login\"] = \"$mailaddr\""
285         done
286         IFS=$oldIFS
287 esac
288
289 case $llogout in
290 ?*) sort -u -o $llogout <<EOF || exit
291 $logins
292 EOF
293 esac
294 output_authors='/^date: / {
295         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 ~ /^[^;]*;$/) {
296                 print substr($5, 1, length($5)-1)
297         }
298 }'
299 authors=`
300         $AWK "$output_authors" <$rlogout |
301         case $llogout in
302         '') sort -u;;
303         ?*) sort -u | comm -23 - $llogout
304         esac
305 `
306 case $authors in
307 ?*)
308         cat >$llogout <<EOF || exit
309 $authors
310 EOF
311         initialize_author_script='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/'
312         initialize_author=`sed -e "$initialize_author_script" <$llogout`
313         awkscript='
314                 BEGIN {
315                         alphabet = "abcdefghijklmnopqrstuvwxyz"
316                         ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
317                         '"$initialize_author"'
318                 }
319                 {
320                         if (author[$1]) {
321                                 fullname = $5
322                                 if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) {
323                                         # Remove the junk from fullnames like "0000-Admin(0000)".
324                                         fullname = substr(fullname, index(fullname, "-") + 1)
325                                         fullname = substr(fullname, 1, index(fullname, "(") - 1)
326                                 }
327                                 if (fullname ~ /,[^ ]/) {
328                                         # Some sites put comma-separated junk after the fullname.
329                                         # Remove it, but leave "Bill Gates, Jr" alone.
330                                         fullname = substr(fullname, 1, index(fullname, ",") - 1)
331                                 }
332                                 abbr = index(fullname, "&")
333                                 if (abbr) {
334                                         a = substr($1, 1, 1)
335                                         A = a
336                                         i = index(alphabet, a)
337                                         if (i) A = substr(ALPHABET, i, 1)
338                                         fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1)
339                                 }
340
341                                 # Quote quotes and backslashes properly in full names.
342                                 # Do not use gsub; traditional awk lacks it.
343                                 quoted = ""
344                                 rest = fullname
345                                 for (;;) {
346                                         p = index(rest, "\\")
347                                         q = index(rest, "\"")
348                                         if (p) {
349                                                 if (q && q<p) p = q
350                                         } else {
351                                                 if (!q) break
352                                                 p = q
353                                         }
354                                         quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1)
355                                         rest = substr(rest, p+1)
356                                 }
357
358                                 printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest
359                                 author[$1] = 0
360                         }
361                 }
362         '
363
364         initialize_fullname=`
365                 (
366                         cat /etc/passwd
367                         for author in $authors
368                         do nismatch $author passwd.org_dir
369                         done
370                         ypmatch $authors passwd
371                 ) 2>/dev/null |
372                 $AWK -F: "$awkscript"
373         `$initialize_fullname
374 esac
375
376
377 # Function to print a single log line.
378 # We don't use awk functions, to stay compatible with old awk versions.
379 # `Log' is the log message (with \n replaced by \r).
380 # `files' contains the affected files.
381 printlogline='{
382
383         # Following the GNU coding standards, rewrite
384         #       * file: (function): comment
385         # to
386         #       * file (function): comment
387         if (Log ~ /^\([^)]*\): /) {
388                 i = index(Log, ")")
389                 files = files " " substr(Log, 1, i)
390                 Log = substr(Log, i+3)
391         }
392
393         # If "label: comment" is too long, break the line after the ":".
394         sep = " "
395         if ('"$length"' <= '"$indent"' + 1 + length(files) + index(Log, CR)) sep = "\n" indent_string
396
397         # Print the label.
398         printf "%s*%s:", indent_string, files
399
400         # Print each line of the log, transliterating \r to \n.
401         while ((i = index(Log, CR)) != 0) {
402                 logline = substr(Log, 1, i-1)
403                 if (logline ~ /[^'"$tab"' ]/) {
404                         printf "%s%s\n", sep, logline
405                 } else {
406                         print ""
407                 }
408                 sep = indent_string
409                 Log = substr(Log, i+1)
410         }
411 }'
412
413 case $hostname in
414 '')
415         hostname=`(
416                 hostname || uname -n || uuname -l || cat /etc/whoami
417         ) 2>/dev/null` || {
418                 echo >&2 "$0: cannot deduce hostname"
419                 exit 1
420         }
421
422         case $hostname in
423         *.*) ;;
424         *)
425                 domainname=`(domainname) 2>/dev/null` &&
426                 case $domainname in
427                 *.*) hostname=$hostname.$domainname
428                 esac
429         esac
430 esac
431
432
433 # Process the rlog output, generating ChangeLog style entries.
434
435 # First, reformat the rlog output so that each line contains one log entry.
436 # Transliterate \n to \r so that multiline entries fit on a single line.
437 # Discard irrelevant rlog output.
438 $AWK <$rlogout '
439         BEGIN { repository = "'"$repository"'" }
440         /^RCS file:/ {
441                 if (repository != "") {
442                         filename = $3
443                         if (substr(filename, 1, length(repository) + 1) == repository "/") {
444                                 filename = substr(filename, length(repository) + 2)
445                         }
446                         if (filename ~ /,v$/) {
447                                 filename = substr(filename, 1, length(filename) - 2)
448                         }
449                 }
450         }
451         /^Working file:/ { if (repository == "") filename = $3 }
452         /^date: /, /^(-----------*|===========*)$/ {
453                 if ($0 ~ /^branches: /) { next }
454                 if ($0 ~ /^date: [0-9][- +\/0-9:]*;/) {
455                         date = $2
456                         if (date ~ /-/) {
457                                 # An ISO format date.  Replace all "-"s with "/"s.
458                                 newdate = ""
459                                 while ((i = index(date, "-")) != 0) {
460                                         newdate = newdate substr(date, 1, i-1) "/"
461                                         date = substr(date, i+1)
462                                 }
463                                 date = newdate date
464                         }
465                         # Ignore any time zone; ChangeLog has no room for it.
466                         time = substr($3, 1, 8)
467                         author = substr($5, 1, length($5)-1)
468                         printf "%s %s %s %s %c", filename, date, time, author, 13
469                         next
470                 }
471                 if ($0 ~ /^(-----------*|===========*)$/) { print ""; next }
472                 printf "%s%c", $0, 13
473         }
474 ' |
475
476 # Now each line is of the form
477 # FILENAME YYYY/MM/DD HH:MM:SS AUTHOR \rLOG
478 #       where \r stands for a carriage return,
479 #       and each line of the log is terminated by \r instead of \n.
480 # Sort the log entries, first by date+time (in reverse order),
481 # then by author, then by log entry, and finally by file name (just in case).
482 sort +1 -3r +3 +0 |
483
484 # Finally, reformat the sorted log entries.
485 $AWK '
486         BEGIN {
487                 # Some awk variants do not understand "\r" or "\013", so we have to
488                 # put a carriage return directly in the file.
489                 CR="\r" # <-- There is a single CR between the " chars here.
490
491                 # Initialize the fullname and mailaddr associative arrays.
492                 '"$initialize_fullname"'
493                 '"$initialize_mailaddr"'
494
495                 # Initialize indent string.
496                 indent_string = ""
497                 i = '"$indent"'
498                 if (0 < '"$tabwidth"')
499                         for (;  '"$tabwidth"' <= i;  i -= '"$tabwidth"')
500                                 indent_string = indent_string "\t"
501                 while (1 <= i--)
502                         indent_string = indent_string " "
503
504                 # Set up date conversion tables.
505                 # RCS uses a nice, clean, sortable format,
506                 # but ChangeLog wants the traditional, ugly ctime format.
507
508                 # January 1, 0 AD (Gregorian) was Saturday = 6
509                 EPOCH_WEEKDAY = 6
510                 # Of course, there was no 0 AD, but the algorithm works anyway.
511
512                 w[0]="Sun"; w[1]="Mon"; w[2]="Tue"; w[3]="Wed"
513                 w[4]="Thu"; w[5]="Fri"; w[6]="Sat"
514
515                 '"$month_data"'
516         }
517
518         {
519                 newlog = substr($0, 1 + index($0, CR))
520
521                 # Ignore log entries prefixed by "#".
522                 if (newlog ~ /^#/) { next }
523
524                 if (Log != newlog || date != $2 || author != $4) {
525
526                         # The previous log and this log differ.
527
528                         # Print the old log.
529                         if (date != "") '"$printlogline"'
530
531                         # Logs that begin with "{clumpname} " should be grouped together,
532                         # and the clumpname should be removed.
533                         # Extract the new clumpname from the log header,
534                         # and use it to decide whether to output a blank line.
535                         newclumpname = ""
536                         sep = "\n"
537                         if (date == "") sep = ""
538                         if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) {
539                                 i = index(newlog, "}")
540                                 newclumpname = substr(newlog, 1, i)
541                                 while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++
542                                 newlog = substr(newlog, i+1)
543                                 if (clumpname == newclumpname) sep = ""
544                         }
545                         printf sep
546                         clumpname = newclumpname
547
548                         # Get ready for the next log.
549                         Log = newlog
550                         if (files != "")
551                                 for (i in filesknown)
552                                         filesknown[i] = 0
553                         files = ""
554                 }
555                 if (date != $2  ||  author != $4) {
556                         # The previous date+author and this date+author differ.
557                         # Print the new one.
558                         date = $2
559                         author = $4
560
561                         # Convert nice RCS date like "1992/01/03 00:03:44"
562                         # into ugly ctime date like "Fri Jan  3 00:03:44 1992".
563                         # Calculate day of week from Gregorian calendar.
564                         i = index($2, "/")
565                         year = substr($2, 1, i-1) + 0
566                         monthday = substr($2, i+1)
567                         i = index(monthday, "/")
568                         month = substr(monthday, 1, i-1) + 0
569                         day = substr(monthday, i+1) + 0
570                         leap = 0
571                         if (2 < month && year%4 == 0 && (year%100 != 0 || year%400 == 0)) leap = 1
572                         days_since_Sunday_before_epoch = EPOCH_WEEKDAY + year * 365 + int((year + 3) / 4) - int((year + 99) / 100) + int((year + 399) / 400) + mo[month-1] + leap + day - 1
573
574                         # Print "date  fullname  (email address)".
575                         # Get fullname and email address from associative arrays;
576                         # default to author and author@hostname if not in arrays.
577                         if (fullname[author])
578                                 auth = fullname[author]
579                         else
580                                 auth = author
581                         printf "%s %s %2d %s %d  %s  ", w[days_since_Sunday_before_epoch%7], m[month-1], day, $3, year, auth
582                         if (mailaddr[author])
583                                 printf "<%s>\n\n", mailaddr[author]
584                         else
585                                 printf "<%s@%s>\n\n", author, "'"$hostname"'"
586                 }
587                 if (! filesknown[$1]) {
588                         filesknown[$1] = 1
589                         if (files == "") files = " " $1
590                         else files = files ", " $1
591                 }
592         }
593         END {
594                 # Print the last log.
595                 if (date != "") {
596                         '"$printlogline"'
597                         printf "\n"
598                 }
599         }
600 ' &&
601
602
603 # Exit successfully.
604
605 exec rm -f $llogout $rlogout