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