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