home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-bin / lib / cvs / contrib / rcs2log < prev    next >
Encoding:
Text File  |  1996-10-13  |  15.2 KB  |  609 lines

  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. # $Id: rcs2log.sh,v 1.4 1996/09/29 20:38:31 woods Exp $
  16.  
  17. # Copyright 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
  18.  
  19. # This program is free software; you can redistribute it and/or modify
  20. # it under the terms of the GNU General Public License as published by
  21. # the Free Software Foundation; either version 2, or (at your option)
  22. # any later version.
  23. # This program is distributed in the hope that it will be useful,
  24. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  26. # GNU General Public License for more details.
  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.                 # Output comma instead of space to avoid CVS 1.5 bug.
  161.                 printf "%d/%02d/%02d,%02d:%02d:%02d\n", year,i+1,dd,hh,mm,ss
  162.                 exit
  163.             }
  164.         '
  165.         d=`$AWK "$e" <ChangeLog` || exit
  166.         case $d in
  167.         ?*) date=$d
  168.         esac
  169.     fi
  170.     datearg="-d>$date"
  171. esac
  172.  
  173. # If CVS is in use, examine its repository, not the normal RCS files.
  174. if test ! -f CVS/Repository
  175. then
  176.     rlog=rlog
  177.     repository=
  178. else
  179.     rlog='cvs log'
  180.     repository=`sed 1q <CVS/Repository` || exit
  181.     test ! -f CVS/Root || CVSROOT=`cat <CVS/Root` || exit
  182.     case $CVSROOT in
  183.     *:/*)
  184.         # remote repository
  185.         ;;
  186.     *)
  187.         # local repository
  188.         case $repository in
  189.         /*) ;;
  190.         *) repository=${CVSROOT?}/$repository
  191.         esac
  192.         if test ! -d "$repository"
  193.         then
  194.             echo >&2 "$0: $repository: bad repository (see CVS/Repository)"
  195.             exit 1
  196.         fi
  197.     esac
  198. fi
  199.  
  200. # With no arguments, examine all files under the RCS directory.
  201. case $# in
  202. 0)
  203.     case $repository in
  204.     '')
  205.         oldIFS=$IFS
  206.         IFS=$nl
  207.         case $recursive in
  208.         t)
  209.             RCSdirs=`find . -name RCS -type d -print`
  210.             filesFromRCSfiles='s|,v$||; s|/RCS/|/|; s|^\./||'
  211.             files=`
  212.                 {
  213.                     case $RCSdirs in
  214.                     ?*) find $RCSdirs -type f -print
  215.                     esac
  216.                     find . -name '*,v' -print
  217.                 } |
  218.                 sort -u |
  219.                 sed "$filesFromRCSfiles"
  220.             `;;
  221.         *)
  222.             files=
  223.             for file in RCS/.* RCS/* .*,v *,v
  224.             do
  225.                 case $file in
  226.                 RCS/. | RCS/..) continue;;
  227.                 RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue
  228.                 esac
  229.                 files=$files$nl$file
  230.             done
  231.             case $files in
  232.             '') exit 0
  233.             esac
  234.         esac
  235.         set x $files
  236.         shift
  237.         IFS=$oldIFS
  238.     esac
  239. esac
  240.  
  241. llogout=$TMPDIR/rcs2log$$l
  242. rlogout=$TMPDIR/rcs2log$$r
  243. trap exit 1 2 13 15
  244. trap "rm -f $llogout $rlogout; exit 1" 0
  245.  
  246. case $rlog_options in
  247. ?*) $rlog $rlog_options ${1+"$@"} >$rlogout;;
  248. '') $rlog "$datearg" ${1+"$@"} >$rlogout
  249. esac || exit
  250.  
  251.  
  252. # Get the full name of each author the logs mention, and set initialize_fullname
  253. # to awk code that initializes the `fullname' awk associative array.
  254. # Warning: foreign authors (i.e. not known in the passwd file) are mishandled;
  255. # you have to fix the resulting output by hand.
  256.  
  257. initialize_fullname=
  258. initialize_mailaddr=
  259.  
  260. case $loginFullnameMailaddrs in
  261. ?*)
  262.     case $loginFullnameMailaddrs in
  263.     *\"* | *\\*)
  264.         sed 's/["\\]/\\&/g' >$llogout <<EOF || exit
  265. $loginFullnameMailaddrs
  266. EOF
  267.         loginFullnameMailaddrs=`cat $llogout`
  268.     esac
  269.  
  270.     oldIFS=$IFS
  271.     IFS=$nl
  272.     for loginFullnameMailaddr in $loginFullnameMailaddrs
  273.     do
  274.         case $loginFullnameMailaddr in
  275.         *"$tab"*) IFS=$tab;;
  276.         *) IFS=:
  277.         esac
  278.         set x $loginFullnameMailaddr
  279.         login=$2
  280.         fullname=$3
  281.         mailaddr=$4
  282.         initialize_fullname="$initialize_fullname
  283.             fullname[\"$login\"] = \"$fullname\""
  284.         initialize_mailaddr="$initialize_mailaddr
  285.             mailaddr[\"$login\"] = \"$mailaddr\""
  286.     done
  287.     IFS=$oldIFS
  288. esac
  289.  
  290. case $llogout in
  291. ?*) sort -u -o $llogout <<EOF || exit
  292. $logins
  293. EOF
  294. esac
  295. output_authors='/^date: / {
  296.     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 ~ /^[^;]*;$/) {
  297.         print substr($5, 1, length($5)-1)
  298.     }
  299. }'
  300. authors=`
  301.     $AWK "$output_authors" <$rlogout |
  302.     case $llogout in
  303.     '') sort -u;;
  304.     ?*) sort -u | comm -23 - $llogout
  305.     esac
  306. `
  307. case $authors in
  308. ?*)
  309.     cat >$llogout <<EOF || exit
  310. $authors
  311. EOF
  312.     initialize_author_script='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/'
  313.     initialize_author=`sed -e "$initialize_author_script" <$llogout`
  314.     awkscript='
  315.         BEGIN {
  316.             alphabet = "abcdefghijklmnopqrstuvwxyz"
  317.             ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  318.             '"$initialize_author"'
  319.         }
  320.         {
  321.             if (author[$1]) {
  322.                 fullname = $5
  323.                 if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) {
  324.                     # Remove the junk from fullnames like "0000-Admin(0000)".
  325.                     fullname = substr(fullname, index(fullname, "-") + 1)
  326.                     fullname = substr(fullname, 1, index(fullname, "(") - 1)
  327.                 }
  328.                 if (fullname ~ /,[^ ]/) {
  329.                     # Some sites put comma-separated junk after the fullname.
  330.                     # Remove it, but leave "Bill Gates, Jr" alone.
  331.                     fullname = substr(fullname, 1, index(fullname, ",") - 1)
  332.                 }
  333.                 abbr = index(fullname, "&")
  334.                 if (abbr) {
  335.                     a = substr($1, 1, 1)
  336.                     A = a
  337.                     i = index(alphabet, a)
  338.                     if (i) A = substr(ALPHABET, i, 1)
  339.                     fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1)
  340.                 }
  341.  
  342.                 # Quote quotes and backslashes properly in full names.
  343.                 # Do not use gsub; traditional awk lacks it.
  344.                 quoted = ""
  345.                 rest = fullname
  346.                 for (;;) {
  347.                     p = index(rest, "\\")
  348.                     q = index(rest, "\"")
  349.                     if (p) {
  350.                         if (q && q<p) p = q
  351.                     } else {
  352.                         if (!q) break
  353.                         p = q
  354.                     }
  355.                     quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1)
  356.                     rest = substr(rest, p+1)
  357.                 }
  358.  
  359.                 printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest
  360.                 author[$1] = 0
  361.             }
  362.         }
  363.     '
  364.  
  365.     initialize_fullname=`
  366.         (
  367.             cat /etc/passwd
  368.             for author in $authors
  369.             do nismatch $author passwd.org_dir
  370.             done
  371.             ypmatch $authors passwd
  372.         ) 2>/dev/null |
  373.         $AWK -F: "$awkscript"
  374.     `$initialize_fullname
  375. esac
  376.  
  377.  
  378. # Function to print a single log line.
  379. # We don't use awk functions, to stay compatible with old awk versions.
  380. # `Log' is the log message (with \n replaced by \r).
  381. # `files' contains the affected files.
  382. printlogline='{
  383.  
  384.     # Following the GNU coding standards, rewrite
  385.     #    * file: (function): comment
  386.     # to
  387.     #    * file (function): comment
  388.     if (Log ~ /^\([^)]*\): /) {
  389.         i = index(Log, ")")
  390.         files = files " " substr(Log, 1, i)
  391.         Log = substr(Log, i+3)
  392.     }
  393.  
  394.     # If "label: comment" is too long, break the line after the ":".
  395.     sep = " "
  396.     if ('"$length"' <= '"$indent"' + 1 + length(files) + index(Log, CR)) sep = "\n" indent_string
  397.  
  398.     # Print the label.
  399.     printf "%s*%s:", indent_string, files
  400.  
  401.     # Print each line of the log, transliterating \r to \n.
  402.     while ((i = index(Log, CR)) != 0) {
  403.         logline = substr(Log, 1, i-1)
  404.         if (logline ~ /[^'"$tab"' ]/) {
  405.             printf "%s%s\n", sep, logline
  406.         } else {
  407.             print ""
  408.         }
  409.         sep = indent_string
  410.         Log = substr(Log, i+1)
  411.     }
  412. }'
  413.  
  414. case $hostname in
  415. '')
  416.     hostname=`(
  417.         hostname || uname -n || uuname -l || cat /etc/whoami
  418.     ) 2>/dev/null` || {
  419.         echo >&2 "$0: cannot deduce hostname"
  420.         exit 1
  421.     }
  422.  
  423.     case $hostname in
  424.     *.*) ;;
  425.     *)
  426.         domainname=`(domainname) 2>/dev/null` &&
  427.         case $domainname in
  428.         *.*) hostname=$hostname.$domainname
  429.         esac
  430.     esac
  431. esac
  432.  
  433.  
  434. # Process the rlog output, generating ChangeLog style entries.
  435.  
  436. # First, reformat the rlog output so that each line contains one log entry.
  437. # Transliterate \n to \r so that multiline entries fit on a single line.
  438. # Discard irrelevant rlog output.
  439. $AWK <$rlogout '
  440.     BEGIN { repository = "'"$repository"'" }
  441.     /^RCS file:/ {
  442.         if (repository != "") {
  443.             filename = $3
  444.             if (substr(filename, 1, length(repository) + 1) == repository "/") {
  445.                 filename = substr(filename, length(repository) + 2)
  446.             }
  447.             if (filename ~ /,v$/) {
  448.                 filename = substr(filename, 1, length(filename) - 2)
  449.             }
  450.         }
  451.     }
  452.     /^Working file:/ { if (repository == "") filename = $3 }
  453.     /^date: /, /^(-----------*|===========*)$/ {
  454.         if ($0 ~ /^branches: /) { next }
  455.         if ($0 ~ /^date: [0-9][- +\/0-9:]*;/) {
  456.             date = $2
  457.             if (date ~ /-/) {
  458.                 # An ISO format date.  Replace all "-"s with "/"s.
  459.                 newdate = ""
  460.                 while ((i = index(date, "-")) != 0) {
  461.                     newdate = newdate substr(date, 1, i-1) "/"
  462.                     date = substr(date, i+1)
  463.                 }
  464.                 date = newdate date
  465.             }
  466.             # Ignore any time zone; ChangeLog has no room for it.
  467.             time = substr($3, 1, 8)
  468.             author = substr($5, 1, length($5)-1)
  469.             printf "%s %s %s %s %c", filename, date, time, author, 13
  470.             next
  471.         }
  472.         if ($0 ~ /^(-----------*|===========*)$/) { print ""; next }
  473.         printf "%s%c", $0, 13
  474.     }
  475. ' |
  476.  
  477. # Now each line is of the form
  478. # FILENAME YYYY/MM/DD HH:MM:SS AUTHOR \rLOG
  479. #    where \r stands for a carriage return,
  480. #    and each line of the log is terminated by \r instead of \n.
  481. # Sort the log entries, first by date+time (in reverse order),
  482. # then by author, then by log entry, and finally by file name (just in case).
  483. sort +1 -3r +3 +0 |
  484.  
  485. # Finally, reformat the sorted log entries.
  486. $AWK '
  487.     BEGIN {
  488.         # Some awk variants do not understand "\r" or "\013", so we have to
  489.         # put a carriage return directly in the file.
  490.         CR="" # <-- There is a single CR between the " chars here.
  491.  
  492.         # Initialize the fullname and mailaddr associative arrays.
  493.         '"$initialize_fullname"'
  494.         '"$initialize_mailaddr"'
  495.  
  496.         # Initialize indent string.
  497.         indent_string = ""
  498.         i = '"$indent"'
  499.         if (0 < '"$tabwidth"')
  500.             for (;  '"$tabwidth"' <= i;  i -= '"$tabwidth"')
  501.                 indent_string = indent_string "\t"
  502.         while (1 <= i--)
  503.             indent_string = indent_string " "
  504.  
  505.         # Set up date conversion tables.
  506.         # RCS uses a nice, clean, sortable format,
  507.         # but ChangeLog wants the traditional, ugly ctime format.
  508.  
  509.         # January 1, 0 AD (Gregorian) was Saturday = 6
  510.         EPOCH_WEEKDAY = 6
  511.         # Of course, there was no 0 AD, but the algorithm works anyway.
  512.  
  513.         w[0]="Sun"; w[1]="Mon"; w[2]="Tue"; w[3]="Wed"
  514.         w[4]="Thu"; w[5]="Fri"; w[6]="Sat"
  515.  
  516.         '"$month_data"'
  517.     }
  518.  
  519.     {
  520.         newlog = substr($0, 1 + index($0, CR))
  521.  
  522.         # Ignore log entries prefixed by "#".
  523.         if (newlog ~ /^#/) { next }
  524.  
  525.         if (Log != newlog || date != $2 || author != $4) {
  526.  
  527.             # The previous log and this log differ.
  528.  
  529.             # Print the old log.
  530.             if (date != "") '"$printlogline"'
  531.  
  532.             # Logs that begin with "{clumpname} " should be grouped together,
  533.             # and the clumpname should be removed.
  534.             # Extract the new clumpname from the log header,
  535.             # and use it to decide whether to output a blank line.
  536.             newclumpname = ""
  537.             sep = "\n"
  538.             if (date == "") sep = ""
  539.             if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) {
  540.                 i = index(newlog, "}")
  541.                 newclumpname = substr(newlog, 1, i)
  542.                 while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++
  543.                 newlog = substr(newlog, i+1)
  544.                 if (clumpname == newclumpname) sep = ""
  545.             }
  546.             printf sep
  547.             clumpname = newclumpname
  548.  
  549.             # Get ready for the next log.
  550.             Log = newlog
  551.             if (files != "")
  552.                 for (i in filesknown)
  553.                     filesknown[i] = 0
  554.             files = ""
  555.         }
  556.         if (date != $2  ||  author != $4) {
  557.             # The previous date+author and this date+author differ.
  558.             # Print the new one.
  559.             date = $2
  560.             author = $4
  561.  
  562.             # Convert nice RCS date like "1992/01/03 00:03:44"
  563.             # into ugly ctime date like "Fri Jan  3 00:03:44 1992".
  564.             # Calculate day of week from Gregorian calendar.
  565.             i = index($2, "/")
  566.             year = substr($2, 1, i-1) + 0
  567.             monthday = substr($2, i+1)
  568.             i = index(monthday, "/")
  569.             month = substr(monthday, 1, i-1) + 0
  570.             day = substr(monthday, i+1) + 0
  571.             leap = 0
  572.             if (2 < month && year%4 == 0 && (year%100 != 0 || year%400 == 0)) leap = 1
  573.             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
  574.  
  575.             # Print "date  fullname  (email address)".
  576.             # Get fullname and email address from associative arrays;
  577.             # default to author and author@hostname if not in arrays.
  578.             if (fullname[author])
  579.                 auth = fullname[author]
  580.             else
  581.                 auth = author
  582.             printf "%s %s %2d %s %d  %s  ", w[days_since_Sunday_before_epoch%7], m[month-1], day, $3, year, auth
  583.             if (mailaddr[author])
  584.                 printf "<%s>\n\n", mailaddr[author]
  585.             else
  586.                 printf "<%s@%s>\n\n", author, "'"$hostname"'"
  587.         }
  588.         if (! filesknown[$1]) {
  589.             filesknown[$1] = 1
  590.             if (files == "") files = " " $1
  591.             else files = files ", " $1
  592.         }
  593.     }
  594.     END {
  595.         # Print the last log.
  596.         if (date != "") {
  597.             '"$printlogline"'
  598.             printf "\n"
  599.         }
  600.     }
  601. ' &&
  602.  
  603.  
  604. # Exit successfully.
  605.  
  606. exec rm -f $llogout $rlogout
  607.