1 1.1 christos #!/bin/sh 2 1.1 christos #! -*-perl-*- 3 1.1 christos 4 1.1 christos # Convert git log output to ChangeLog format. 5 1.1 christos 6 1.1 christos # Copyright (C) 2008-2022 Free Software Foundation, Inc. 7 1.1 christos # 8 1.1 christos # This program is free software: you can redistribute it and/or modify 9 1.1 christos # it under the terms of the GNU General Public License as published by 10 1.1 christos # the Free Software Foundation, either version 3 of the License, or 11 1.1 christos # (at your option) any later version. 12 1.1 christos # 13 1.1 christos # This program is distributed in the hope that it will be useful, 14 1.1 christos # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 1.1 christos # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 1.1 christos # GNU General Public License for more details. 17 1.1 christos # 18 1.1 christos # You should have received a copy of the GNU General Public License 19 1.1 christos # along with this program. If not, see <https://www.gnu.org/licenses/>. 20 1.1 christos # 21 1.1 christos # Written by Jim Meyering 22 1.1 christos 23 1.1 christos # This is a prologue that allows to run a perl script as an executable 24 1.1 christos # on systems that are compliant to a POSIX version before POSIX:2017. 25 1.1 christos # On such systems, the usual invocation of an executable through execlp() 26 1.1 christos # or execvp() fails with ENOEXEC if it is a script that does not start 27 1.1 christos # with a #! line. The script interpreter mentioned in the #! line has 28 1.1 christos # to be /bin/sh, because on GuixSD systems that is the only program that 29 1.1 christos # has a fixed file name. The second line is essential for perl and is 30 1.1 christos # also useful for editing this file in Emacs. The next two lines below 31 1.1 christos # are valid code in both sh and perl. When executed by sh, they re-execute 32 1.1 christos # the script through the perl program found in $PATH. The '-x' option 33 1.1 christos # is essential as well; without it, perl would re-execute the script 34 1.1 christos # through /bin/sh. When executed by perl, the next two lines are a no-op. 35 1.1 christos eval 'exec perl -wSx "$0" "$@"' 36 1.1 christos if 0; 37 1.1 christos 38 1.1 christos my $VERSION = '2022-01-27 18:49'; # UTC 39 1.1 christos # The definition above must lie within the first 8 lines in order 40 1.1 christos # for the Emacs time-stamp write hook (at end) to update it. 41 1.1 christos # If you change this file with Emacs, please let the write hook 42 1.1 christos # do its job. Otherwise, update this string manually. 43 1.1 christos 44 1.1 christos use strict; 45 1.1 christos use warnings; 46 1.1 christos use Getopt::Long; 47 1.1 christos use POSIX qw(strftime); 48 1.1 christos 49 1.1 christos (my $ME = $0) =~ s|.*/||; 50 1.1 christos 51 1.1 christos # use File::Coda; # https://meyering.net/code/Coda/ 52 1.1 christos END { 53 1.1 christos defined fileno STDOUT or return; 54 1.1 christos close STDOUT and return; 55 1.1 christos warn "$ME: failed to close standard output: $!\n"; 56 1.1 christos $? ||= 1; 57 1.1 christos } 58 1.1 christos 59 1.1 christos sub usage ($) 60 1.1 christos { 61 1.1 christos my ($exit_code) = @_; 62 1.1 christos my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); 63 1.1 christos if ($exit_code != 0) 64 1.1 christos { 65 1.1 christos print $STREAM "Try '$ME --help' for more information.\n"; 66 1.1 christos } 67 1.1 christos else 68 1.1 christos { 69 1.1 christos print $STREAM <<EOF; 70 1.1 christos Usage: $ME [OPTIONS] [ARGS] 71 1.1 christos 72 1.1 christos Convert git log output to ChangeLog format. If present, any ARGS 73 1.1 christos are passed to "git log". To avoid ARGS being parsed as options to 74 1.1 christos $ME, they may be preceded by '--'. 75 1.1 christos 76 1.1 christos OPTIONS: 77 1.1 christos 78 1.1 christos --amend=FILE FILE maps from an SHA1 to perl code (i.e., s/old/new/) that 79 1.1 christos makes a change to SHA1's commit log text or metadata. 80 1.1 christos --append-dot append a dot to the first line of each commit message if 81 1.1 christos there is no other punctuation or blank at the end. 82 1.1 christos --no-cluster never cluster commit messages under the same date/author 83 1.1 christos header; the default is to cluster adjacent commit messages 84 1.1 christos if their headers are the same and neither commit message 85 1.1 christos contains multiple paragraphs. 86 1.1 christos --srcdir=DIR the root of the source tree, from which the .git/ 87 1.1 christos directory can be derived. 88 1.1 christos --since=DATE convert only the logs since DATE; 89 1.1 christos the default is to convert all log entries. 90 1.1 christos --until=DATE convert only the logs older than DATE. 91 1.1 christos --ignore-matching=PAT ignore commit messages whose first lines match PAT. 92 1.1 christos --ignore-line=PAT ignore lines of commit messages that match PAT. 93 1.1 christos --format=FMT set format string for commit subject and body; 94 1.1 christos see 'man git-log' for the list of format metacharacters; 95 1.1 christos the default is '%s%n%b%n' 96 1.1 christos --strip-tab remove one additional leading TAB from commit message lines. 97 1.1 christos --strip-cherry-pick remove data inserted by "git cherry-pick"; 98 1.1 christos this includes the "cherry picked from commit ..." line, 99 1.1 christos and the possible final "Conflicts:" paragraph. 100 1.1 christos --help display this help and exit 101 1.1 christos --version output version information and exit 102 1.1 christos 103 1.1 christos EXAMPLE: 104 1.1 christos 105 1.1 christos $ME --since=2008-01-01 > ChangeLog 106 1.1 christos $ME -- -n 5 foo > last-5-commits-to-branch-foo 107 1.1 christos 108 1.1 christos SPECIAL SYNTAX: 109 1.1 christos 110 1.1 christos The following types of strings are interpreted specially when they appear 111 1.1 christos at the beginning of a log message line. They are not copied to the output. 112 1.1 christos 113 1.1 christos Copyright-paperwork-exempt: Yes 114 1.1 christos Append the "(tiny change)" notation to the usual "date name email" 115 1.1 christos ChangeLog header to mark a change that does not require a copyright 116 1.1 christos assignment. 117 1.1 christos Co-authored-by: Joe User <user\@example.com> 118 1.1 christos List the specified name and email address on a second 119 1.1 christos ChangeLog header, denoting a co-author. 120 1.1 christos Signed-off-by: Joe User <user\@example.com> 121 1.1 christos These lines are simply elided. 122 1.1 christos 123 1.1 christos In a FILE specified via --amend, comment lines (starting with "#") are ignored. 124 1.1 christos FILE must consist of <SHA,CODE+> pairs where SHA is a 40-byte SHA1 (alone on 125 1.1 christos a line) referring to a commit in the current project, and CODE refers to one 126 1.1 christos or more consecutive lines of Perl code. Pairs must be separated by one or 127 1.1 christos more blank line. 128 1.1 christos 129 1.1 christos Here is sample input for use with --amend=FILE, from coreutils: 130 1.1 christos 131 1.1 christos 3a169f4c5d9159283548178668d2fae6fced3030 132 1.1 christos # fix typo in title: 133 1.1 christos s/all tile types/all file types/ 134 1.1 christos 135 1.1 christos 1379ed974f1fa39b12e2ffab18b3f7a607082202 136 1.1 christos # Due to a bug in vc-dwim, I mis-attributed a patch by Paul to myself. 137 1.1 christos # Change the author to be Paul. Note the escaped "@": 138 1.1 christos s,Jim .*>,Paul Eggert <eggert\\\@cs.ucla.edu>, 139 1.1 christos 140 1.1 christos EOF 141 1.1 christos } 142 1.1 christos exit $exit_code; 143 1.1 christos } 144 1.1 christos 145 1.1 christos # If the string $S is a well-behaved file name, simply return it. 146 1.1 christos # If it contains white space, quotes, etc., quote it, and return the new string. 147 1.1 christos sub shell_quote($) 148 1.1 christos { 149 1.1 christos my ($s) = @_; 150 1.1 christos if ($s =~ m![^\w+/.,-]!) 151 1.1 christos { 152 1.1 christos # Convert each single quote to '\'' 153 1.1 christos $s =~ s/\'/\'\\\'\'/g; 154 1.1 christos # Then single quote the string. 155 1.1 christos $s = "'$s'"; 156 1.1 christos } 157 1.1 christos return $s; 158 1.1 christos } 159 1.1 christos 160 1.1 christos sub quoted_cmd(@) 161 1.1 christos { 162 1.1 christos return join (' ', map {shell_quote $_} @_); 163 1.1 christos } 164 1.1 christos 165 1.1 christos # Parse file F. 166 1.1 christos # Comment lines (starting with "#") are ignored. 167 1.1 christos # F must consist of <SHA,CODE+> pairs where SHA is a 40-byte SHA1 168 1.1 christos # (alone on a line) referring to a commit in the current project, and 169 1.1 christos # CODE refers to one or more consecutive lines of Perl code. 170 1.1 christos # Pairs must be separated by one or more blank line. 171 1.1 christos sub parse_amend_file($) 172 1.1 christos { 173 1.1 christos my ($f) = @_; 174 1.1 christos 175 1.1 christos open F, '<', $f 176 1.1 christos or die "$ME: $f: failed to open for reading: $!\n"; 177 1.1 christos 178 1.1 christos my $fail; 179 1.1 christos my $h = {}; 180 1.1 christos my $in_code = 0; 181 1.1 christos my $sha; 182 1.1 christos while (defined (my $line = <F>)) 183 1.1 christos { 184 1.1 christos $line =~ /^\#/ 185 1.1 christos and next; 186 1.1 christos chomp $line; 187 1.1 christos $line eq '' 188 1.1 christos and $in_code = 0, next; 189 1.1 christos 190 1.1 christos if (!$in_code) 191 1.1 christos { 192 1.1 christos $line =~ /^([[:xdigit:]]{40})$/ 193 1.1 christos or (warn "$ME: $f:$.: invalid line; expected an SHA1\n"), 194 1.1 christos $fail = 1, next; 195 1.1 christos $sha = lc $1; 196 1.1 christos $in_code = 1; 197 1.1 christos exists $h->{$sha} 198 1.1 christos and (warn "$ME: $f:$.: duplicate SHA1\n"), 199 1.1 christos $fail = 1, next; 200 1.1 christos } 201 1.1 christos else 202 1.1 christos { 203 1.1 christos $h->{$sha} ||= ''; 204 1.1 christos $h->{$sha} .= "$line\n"; 205 1.1 christos } 206 1.1 christos } 207 1.1 christos close F; 208 1.1 christos 209 1.1 christos $fail 210 1.1 christos and exit 1; 211 1.1 christos 212 1.1 christos return $h; 213 1.1 christos } 214 1.1 christos 215 1.1 christos # git_dir_option $SRCDIR 216 1.1 christos # 217 1.1 christos # From $SRCDIR, the --git-dir option to pass to git (none if $SRCDIR 218 1.1 christos # is undef). Return as a list (0 or 1 element). 219 1.1 christos sub git_dir_option($) 220 1.1 christos { 221 1.1 christos my ($srcdir) = @_; 222 1.1 christos my @res = (); 223 1.1 christos if (defined $srcdir) 224 1.1 christos { 225 1.1 christos my $qdir = shell_quote $srcdir; 226 1.1 christos my $cmd = "cd $qdir && git rev-parse --show-toplevel"; 227 1.1 christos my $qcmd = shell_quote $cmd; 228 1.1 christos my $git_dir = qx($cmd); 229 1.1 christos defined $git_dir 230 1.1 christos or die "$ME: cannot run $qcmd: $!\n"; 231 1.1 christos $? == 0 232 1.1 christos or die "$ME: $qcmd had unexpected exit code or signal ($?)\n"; 233 1.1 christos chomp $git_dir; 234 1.1 christos push @res, "--git-dir=$git_dir/.git"; 235 1.1 christos } 236 1.1 christos @res; 237 1.1 christos } 238 1.1 christos 239 1.1 christos { 240 1.1 christos my $since_date; 241 1.1 christos my $until_date; 242 1.1 christos my $format_string = '%s%n%b%n'; 243 1.1 christos my $amend_file; 244 1.1 christos my $append_dot = 0; 245 1.1 christos my $cluster = 1; 246 1.1 christos my $ignore_matching; 247 1.1 christos my $ignore_line; 248 1.1 christos my $strip_tab = 0; 249 1.1 christos my $strip_cherry_pick = 0; 250 1.1 christos my $srcdir; 251 1.1 christos GetOptions 252 1.1 christos ( 253 1.1 christos help => sub { usage 0 }, 254 1.1 christos version => sub { print "$ME version $VERSION\n"; exit }, 255 1.1 christos 'since=s' => \$since_date, 256 1.1 christos 'until=s' => \$until_date, 257 1.1 christos 'format=s' => \$format_string, 258 1.1 christos 'amend=s' => \$amend_file, 259 1.1 christos 'append-dot' => \$append_dot, 260 1.1 christos 'cluster!' => \$cluster, 261 1.1 christos 'ignore-matching=s' => \$ignore_matching, 262 1.1 christos 'ignore-line=s' => \$ignore_line, 263 1.1 christos 'strip-tab' => \$strip_tab, 264 1.1 christos 'strip-cherry-pick' => \$strip_cherry_pick, 265 1.1 christos 'srcdir=s' => \$srcdir, 266 1.1 christos ) or usage 1; 267 1.1 christos 268 1.1 christos defined $since_date 269 1.1 christos and unshift @ARGV, "--since=$since_date"; 270 1.1 christos defined $until_date 271 1.1 christos and unshift @ARGV, "--until=$until_date"; 272 1.1 christos 273 1.1 christos # This is a hash that maps an SHA1 to perl code (i.e., s/old/new/) 274 1.1 christos # that makes a correction in the log or attribution of that commit. 275 1.1 christos my $amend_code = defined $amend_file ? parse_amend_file $amend_file : {}; 276 1.1 christos 277 1.1 christos my @cmd = ('git', 278 1.1 christos git_dir_option $srcdir, 279 1.1 christos qw(log --log-size), 280 1.1 christos '--pretty=format:%H:%ct %an <%ae>%n%n'.$format_string, @ARGV); 281 1.1 christos open PIPE, '-|', @cmd 282 1.1 christos or die ("$ME: failed to run '". quoted_cmd (@cmd) ."': $!\n" 283 1.1 christos . "(Is your Git too old? Version 1.5.1 or later is required.)\n"); 284 1.1 christos 285 1.1 christos my $prev_multi_paragraph; 286 1.1 christos my $prev_date_line = ''; 287 1.1 christos my @prev_coauthors = (); 288 1.1 christos my @skipshas = (); 289 1.1 christos while (1) 290 1.1 christos { 291 1.1 christos defined (my $in = <PIPE>) 292 1.1 christos or last; 293 1.1 christos $in =~ /^log size (\d+)$/ 294 1.1 christos or die "$ME:$.: Invalid line (expected log size):\n$in"; 295 1.1 christos my $log_nbytes = $1; 296 1.1 christos 297 1.1 christos my $log; 298 1.1 christos my $n_read = read PIPE, $log, $log_nbytes; 299 1.1 christos $n_read == $log_nbytes 300 1.1 christos or die "$ME:$.: unexpected EOF\n"; 301 1.1 christos 302 1.1 christos # Extract leading hash. 303 1.1 christos my ($sha, $rest) = split ':', $log, 2; 304 1.1 christos defined $sha 305 1.1 christos or die "$ME:$.: malformed log entry\n"; 306 1.1 christos $sha =~ /^[[:xdigit:]]{40}$/ 307 1.1 christos or die "$ME:$.: invalid SHA1: $sha\n"; 308 1.1 christos 309 1.1 christos my $skipflag = 0; 310 1.1 christos if (@skipshas) 311 1.1 christos { 312 1.1 christos foreach(@skipshas) 313 1.1 christos { 314 1.1 christos if ($sha =~ /^$_/) 315 1.1 christos { 316 1.1 christos $skipflag = $_; 317 1.1 christos last; 318 1.1 christos } 319 1.1 christos } 320 1.1 christos } 321 1.1 christos 322 1.1 christos # If this commit's log requires any transformation, do it now. 323 1.1 christos my $code = $amend_code->{$sha}; 324 1.1 christos if (defined $code) 325 1.1 christos { 326 1.1 christos eval 'use Safe'; 327 1.1 christos my $s = new Safe; 328 1.1 christos # Put the unpreprocessed entry into "$_". 329 1.1 christos $_ = $rest; 330 1.1 christos 331 1.1 christos # Let $code operate on it, safely. 332 1.1 christos my $r = $s->reval("$code") 333 1.1 christos or die "$ME:$.:$sha: failed to eval \"$code\":\n$@\n"; 334 1.1 christos 335 1.1 christos # Note that we've used this entry. 336 1.1 christos delete $amend_code->{$sha}; 337 1.1 christos 338 1.1 christos # Update $rest upon success. 339 1.1 christos $rest = $_; 340 1.1 christos } 341 1.1 christos 342 1.1 christos # Remove lines inserted by "git cherry-pick". 343 1.1 christos if ($strip_cherry_pick) 344 1.1 christos { 345 1.1 christos $rest =~ s/^\s*Conflicts:\n.*//sm; 346 1.1 christos $rest =~ s/^\s*\(cherry picked from commit [\da-f]+\)\n//m; 347 1.1 christos } 348 1.1 christos 349 1.1 christos my @line = split /[ \t]*\n/, $rest; 350 1.1 christos my $author_line = shift @line; 351 1.1 christos defined $author_line 352 1.1 christos or die "$ME:$.: unexpected EOF\n"; 353 1.1 christos $author_line =~ /^(\d+) (.*>)$/ 354 1.1 christos or die "$ME:$.: Invalid line " 355 1.1 christos . "(expected date/author/email):\n$author_line\n"; 356 1.1 christos 357 1.1 christos # Format 'Copyright-paperwork-exempt: Yes' as a standard ChangeLog 358 1.1 christos # `(tiny change)' annotation. 359 1.1 christos my $tiny = (grep (/^(?:Copyright-paperwork-exempt|Tiny-change):\s+[Yy]es$/, @line) 360 1.1 christos ? ' (tiny change)' : ''); 361 1.1 christos 362 1.1 christos my $date_line = sprintf "%s %s$tiny\n", 363 1.1 christos strftime ("%Y-%m-%d", localtime ($1)), $2; 364 1.1 christos 365 1.1 christos my @coauthors = grep /^Co-authored-by:.*$/, @line; 366 1.1 christos # Omit meta-data lines we've already interpreted. 367 1.1 christos @line = grep !/^(?:Signed-off-by:[ ].*>$ 368 1.1 christos |Co-authored-by:[ ] 369 1.1 christos |Copyright-paperwork-exempt:[ ] 370 1.1 christos |Tiny-change:[ ] 371 1.1 christos )/x, @line; 372 1.1 christos 373 1.1 christos # Remove leading and trailing blank lines. 374 1.1 christos if (@line) 375 1.1 christos { 376 1.1 christos while ($line[0] =~ /^\s*$/) { shift @line; } 377 1.1 christos while ($line[$#line] =~ /^\s*$/) { pop @line; } 378 1.1 christos } 379 1.1 christos 380 1.1 christos # Handle Emacs gitmerge.el "skipped" commits. 381 1.1 christos # Yes, this should be controlled by an option. So sue me. 382 1.1 christos if ( grep /^(; )?Merge from /, @line ) 383 1.1 christos { 384 1.1 christos my $found = 0; 385 1.1 christos foreach (@line) 386 1.1 christos { 387 1.1 christos if (grep /^The following commit.*skipped:$/, $_) 388 1.1 christos { 389 1.1 christos $found = 1; 390 1.1 christos ## Reset at each merge to reduce chance of false matches. 391 1.1 christos @skipshas = (); 392 1.1 christos next; 393 1.1 christos } 394 1.1 christos if ($found && $_ =~ /^([[:xdigit:]]{7,}) [^ ]/) 395 1.1 christos { 396 1.1 christos push ( @skipshas, $1 ); 397 1.1 christos } 398 1.1 christos } 399 1.1 christos } 400 1.1 christos 401 1.1 christos # Ignore commits that match the --ignore-matching pattern, if specified. 402 1.1 christos if (defined $ignore_matching && @line && $line[0] =~ /$ignore_matching/) 403 1.1 christos { 404 1.1 christos $skipflag = 1; 405 1.1 christos } 406 1.1 christos elsif ($skipflag) 407 1.1 christos { 408 1.1 christos ## Perhaps only warn if a pattern matches more than once? 409 1.1 christos warn "$ME: warning: skipping $sha due to $skipflag\n"; 410 1.1 christos } 411 1.1 christos 412 1.1 christos if (! $skipflag) 413 1.1 christos { 414 1.1 christos if (defined $ignore_line && @line) 415 1.1 christos { 416 1.1 christos @line = grep ! /$ignore_line/, @line; 417 1.1 christos while ($line[$#line] =~ /^\s*$/) { pop @line; } 418 1.1 christos } 419 1.1 christos 420 1.1 christos # Record whether there are two or more paragraphs. 421 1.1 christos my $multi_paragraph = grep /^\s*$/, @line; 422 1.1 christos 423 1.1 christos # Format 'Co-authored-by: A U Thor <email (at] example.com>' lines in 424 1.1 christos # standard multi-author ChangeLog format. 425 1.1 christos for (@coauthors) 426 1.1 christos { 427 1.1 christos s/^Co-authored-by:\s*/\t /; 428 1.1 christos s/\s*</ </; 429 1.1 christos 430 1.1 christos /<.*?@.*\..*>/ 431 1.1 christos or warn "$ME: warning: missing email address for " 432 1.1 christos . substr ($_, 5) . "\n"; 433 1.1 christos } 434 1.1 christos 435 1.1 christos # If clustering of commit messages has been disabled, if this header 436 1.1 christos # would be different from the previous date/name/etc. header, 437 1.1 christos # or if this or the previous entry consists of two or more paragraphs, 438 1.1 christos # then print the header. 439 1.1 christos if ( ! $cluster 440 1.1 christos || $date_line ne $prev_date_line 441 1.1 christos || "@coauthors" ne "@prev_coauthors" 442 1.1 christos || $multi_paragraph 443 1.1 christos || $prev_multi_paragraph) 444 1.1 christos { 445 1.1 christos $prev_date_line eq '' 446 1.1 christos or print "\n"; 447 1.1 christos print $date_line; 448 1.1 christos @coauthors 449 1.1 christos and print join ("\n", @coauthors), "\n"; 450 1.1 christos } 451 1.1 christos $prev_date_line = $date_line; 452 1.1 christos @prev_coauthors = @coauthors; 453 1.1 christos $prev_multi_paragraph = $multi_paragraph; 454 1.1 christos 455 1.1 christos # If there were any lines 456 1.1 christos if (@line == 0) 457 1.1 christos { 458 1.1 christos warn "$ME: warning: empty commit message:\n" 459 1.1 christos . " commit $sha\n $date_line\n"; 460 1.1 christos } 461 1.1 christos else 462 1.1 christos { 463 1.1 christos if ($append_dot) 464 1.1 christos { 465 1.1 christos # If the first line of the message has enough room, then 466 1.1 christos if (length $line[0] < 72) 467 1.1 christos { 468 1.1 christos # append a dot if there is no other punctuation or blank 469 1.1 christos # at the end. 470 1.1 christos $line[0] =~ /[[:punct:]\s]$/ 471 1.1 christos or $line[0] .= '.'; 472 1.1 christos } 473 1.1 christos } 474 1.1 christos 475 1.1 christos # Remove one additional leading TAB from each line. 476 1.1 christos $strip_tab 477 1.1 christos and map { s/^\t// } @line; 478 1.1 christos 479 1.1 christos # Prefix each non-empty line with a TAB. 480 1.1 christos @line = map { length $_ ? "\t$_" : '' } @line; 481 1.1 christos 482 1.1 christos print "\n", join ("\n", @line), "\n"; 483 1.1 christos } 484 1.1 christos } 485 1.1 christos 486 1.1 christos defined ($in = <PIPE>) 487 1.1 christos or last; 488 1.1 christos $in ne "\n" 489 1.1 christos and die "$ME:$.: unexpected line:\n$in"; 490 1.1 christos } 491 1.1 christos 492 1.1 christos close PIPE 493 1.1 christos or die "$ME: error closing pipe from " . quoted_cmd (@cmd) . "\n"; 494 1.1 christos # FIXME-someday: include $PROCESS_STATUS in the diagnostic 495 1.1 christos 496 1.1 christos # Complain about any unused entry in the --amend=F specified file. 497 1.1 christos my $fail = 0; 498 1.1 christos foreach my $sha (keys %$amend_code) 499 1.1 christos { 500 1.1 christos warn "$ME:$amend_file: unused entry: $sha\n"; 501 1.1 christos $fail = 1; 502 1.1 christos } 503 1.1 christos 504 1.1 christos exit $fail; 505 1.1 christos } 506 1.1 christos 507 1.1 christos # Local Variables: 508 1.1 christos # mode: perl 509 1.1 christos # indent-tabs-mode: nil 510 1.1 christos # eval: (add-hook 'before-save-hook 'time-stamp) 511 1.1 christos # time-stamp-line-limit: 50 512 1.1 christos # time-stamp-start: "my $VERSION = '" 513 1.1 christos # time-stamp-format: "%:y-%02m-%02d %02H:%02M" 514 1.1 christos # time-stamp-time-zone: "UTC0" 515 1.1 christos # time-stamp-end: "'; # UTC" 516 1.1 christos # End: 517