Home | History | Annotate | Line # | Download | only in extra
      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