xref: /netbsd-src/external/gpl3/gdb/dist/gnulib/import/extra/gitlog-to-changelog (revision 4b169a6ba595ae283ca507b26b15fdff40495b1c)
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