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