Blame build-aux/gitlog-to-changelog

Packit eba2e2
eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}'
Packit eba2e2
  & eval 'exec perl -wS "$0" $argv:q'
Packit eba2e2
    if 0;
Packit eba2e2
# Convert git log output to ChangeLog format.
Packit eba2e2
Packit eba2e2
my $VERSION = '2012-07-29 06:11'; # UTC
Packit eba2e2
# The definition above must lie within the first 8 lines in order
Packit eba2e2
# for the Emacs time-stamp write hook (at end) to update it.
Packit eba2e2
# If you change this file with Emacs, please let the write hook
Packit eba2e2
# do its job.  Otherwise, update this string manually.
Packit eba2e2
Packit eba2e2
# Copyright (C) 2008-2014 Free Software Foundation, Inc.
Packit eba2e2
Packit eba2e2
# This program is free software: you can redistribute it and/or modify
Packit eba2e2
# it under the terms of the GNU General Public License as published by
Packit eba2e2
# the Free Software Foundation, either version 3 of the License, or
Packit eba2e2
# (at your option) any later version.
Packit eba2e2
Packit eba2e2
# This program is distributed in the hope that it will be useful,
Packit eba2e2
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit eba2e2
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit eba2e2
# GNU General Public License for more details.
Packit eba2e2
Packit eba2e2
# You should have received a copy of the GNU General Public License
Packit eba2e2
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit eba2e2
Packit eba2e2
# Written by Jim Meyering
Packit eba2e2
Packit eba2e2
use strict;
Packit eba2e2
use warnings;
Packit eba2e2
use Getopt::Long;
Packit eba2e2
use POSIX qw(strftime);
Packit eba2e2
Packit eba2e2
(my $ME = $0) =~ s|.*/||;
Packit eba2e2
Packit eba2e2
# use File::Coda; # http://meyering.net/code/Coda/
Packit eba2e2
END {
Packit eba2e2
  defined fileno STDOUT or return;
Packit eba2e2
  close STDOUT and return;
Packit eba2e2
  warn "$ME: failed to close standard output: $!\n";
Packit eba2e2
  $? ||= 1;
Packit eba2e2
}
Packit eba2e2
Packit eba2e2
sub usage ($)
Packit eba2e2
{
Packit eba2e2
  my ($exit_code) = @_;
Packit eba2e2
  my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
Packit eba2e2
  if ($exit_code != 0)
Packit eba2e2
    {
Packit eba2e2
      print $STREAM "Try '$ME --help' for more information.\n";
Packit eba2e2
    }
Packit eba2e2
  else
Packit eba2e2
    {
Packit eba2e2
      print $STREAM <
Packit eba2e2
Usage: $ME [OPTIONS] [ARGS]
Packit eba2e2
Packit eba2e2
Convert git log output to ChangeLog format.  If present, any ARGS
Packit eba2e2
are passed to "git log".  To avoid ARGS being parsed as options to
Packit eba2e2
$ME, they may be preceded by '--'.
Packit eba2e2
Packit eba2e2
OPTIONS:
Packit eba2e2
Packit eba2e2
   --amend=FILE FILE maps from an SHA1 to perl code (i.e., s/old/new/) that
Packit eba2e2
                  makes a change to SHA1's commit log text or metadata.
Packit eba2e2
   --append-dot append a dot to the first line of each commit message if
Packit eba2e2
                  there is no other punctuation or blank at the end.
Packit eba2e2
   --no-cluster never cluster commit messages under the same date/author
Packit eba2e2
                  header; the default is to cluster adjacent commit messages
Packit eba2e2
                  if their headers are the same and neither commit message
Packit eba2e2
                  contains multiple paragraphs.
Packit eba2e2
   --srcdir=DIR the root of the source tree, from which the .git/
Packit eba2e2
                  directory can be derived.
Packit eba2e2
   --since=DATE convert only the logs since DATE;
Packit eba2e2
                  the default is to convert all log entries.
Packit eba2e2
   --format=FMT set format string for commit subject and body;
Packit eba2e2
                  see 'man git-log' for the list of format metacharacters;
Packit eba2e2
                  the default is '%s%n%b%n'
Packit eba2e2
   --strip-tab  remove one additional leading TAB from commit message lines.
Packit eba2e2
   --strip-cherry-pick  remove data inserted by "git cherry-pick";
Packit eba2e2
                  this includes the "cherry picked from commit ..." line,
Packit eba2e2
                  and the possible final "Conflicts:" paragraph.
Packit eba2e2
   --help       display this help and exit
Packit eba2e2
   --version    output version information and exit
Packit eba2e2
Packit eba2e2
EXAMPLE:
Packit eba2e2
Packit eba2e2
  $ME --since=2008-01-01 > ChangeLog
Packit eba2e2
  $ME -- -n 5 foo > last-5-commits-to-branch-foo
Packit eba2e2
Packit eba2e2
SPECIAL SYNTAX:
Packit eba2e2
Packit eba2e2
The following types of strings are interpreted specially when they appear
Packit eba2e2
at the beginning of a log message line.  They are not copied to the output.
Packit eba2e2
Packit eba2e2
  Copyright-paperwork-exempt: Yes
Packit eba2e2
    Append the "(tiny change)" notation to the usual "date name email"
Packit eba2e2
    ChangeLog header to mark a change that does not require a copyright
Packit eba2e2
    assignment.
Packit eba2e2
  Co-authored-by: Joe User <user\@example.com>
Packit eba2e2
    List the specified name and email address on a second
Packit eba2e2
    ChangeLog header, denoting a co-author.
Packit eba2e2
  Signed-off-by: Joe User <user\@example.com>
Packit eba2e2
    These lines are simply elided.
Packit eba2e2
Packit eba2e2
In a FILE specified via --amend, comment lines (starting with "#") are ignored.
Packit eba2e2
FILE must consist of <SHA,CODE+> pairs where SHA is a 40-byte SHA1 (alone on
Packit eba2e2
a line) referring to a commit in the current project, and CODE refers to one
Packit eba2e2
or more consecutive lines of Perl code.  Pairs must be separated by one or
Packit eba2e2
more blank line.
Packit eba2e2
Packit eba2e2
Here is sample input for use with --amend=FILE, from coreutils:
Packit eba2e2
Packit eba2e2
3a169f4c5d9159283548178668d2fae6fced3030
Packit eba2e2
# fix typo in title:
Packit eba2e2
s/all tile types/all file types/
Packit eba2e2
Packit eba2e2
1379ed974f1fa39b12e2ffab18b3f7a607082202
Packit eba2e2
# Due to a bug in vc-dwim, I mis-attributed a patch by Paul to myself.
Packit eba2e2
# Change the author to be Paul.  Note the escaped "@":
Packit eba2e2
s,Jim .*>,Paul Eggert <eggert\\\@cs.ucla.edu>,
Packit eba2e2
Packit eba2e2
EOF
Packit eba2e2
    }
Packit eba2e2
  exit $exit_code;
Packit eba2e2
}
Packit eba2e2
Packit eba2e2
# If the string $S is a well-behaved file name, simply return it.
Packit eba2e2
# If it contains white space, quotes, etc., quote it, and return the new string.
Packit eba2e2
sub shell_quote($)
Packit eba2e2
{
Packit eba2e2
  my ($s) = @_;
Packit eba2e2
  if ($s =~ m![^\w+/.,-]!)
Packit eba2e2
    {
Packit eba2e2
      # Convert each single quote to '\''
Packit eba2e2
      $s =~ s/\'/\'\\\'\'/g;
Packit eba2e2
      # Then single quote the string.
Packit eba2e2
      $s = "'$s'";
Packit eba2e2
    }
Packit eba2e2
  return $s;
Packit eba2e2
}
Packit eba2e2
Packit eba2e2
sub quoted_cmd(@)
Packit eba2e2
{
Packit eba2e2
  return join (' ', map {shell_quote $_} @_);
Packit eba2e2
}
Packit eba2e2
Packit eba2e2
# Parse file F.
Packit eba2e2
# Comment lines (starting with "#") are ignored.
Packit eba2e2
# F must consist of <SHA,CODE+> pairs where SHA is a 40-byte SHA1
Packit eba2e2
# (alone on a line) referring to a commit in the current project, and
Packit eba2e2
# CODE refers to one or more consecutive lines of Perl code.
Packit eba2e2
# Pairs must be separated by one or more blank line.
Packit eba2e2
sub parse_amend_file($)
Packit eba2e2
{
Packit eba2e2
  my ($f) = @_;
Packit eba2e2
Packit eba2e2
  open F, '<', $f
Packit eba2e2
    or die "$ME: $f: failed to open for reading: $!\n";
Packit eba2e2
Packit eba2e2
  my $fail;
Packit eba2e2
  my $h = {};
Packit eba2e2
  my $in_code = 0;
Packit eba2e2
  my $sha;
Packit eba2e2
  while (defined (my $line = <F>))
Packit eba2e2
    {
Packit eba2e2
      $line =~ /^\#/
Packit eba2e2
        and next;
Packit eba2e2
      chomp $line;
Packit eba2e2
      $line eq ''
Packit eba2e2
        and $in_code = 0, next;
Packit eba2e2
Packit eba2e2
      if (!$in_code)
Packit eba2e2
        {
Packit eba2e2
          $line =~ /^([0-9a-fA-F]{40})$/
Packit eba2e2
            or (warn "$ME: $f:$.: invalid line; expected an SHA1\n"),
Packit eba2e2
              $fail = 1, next;
Packit eba2e2
          $sha = lc $1;
Packit eba2e2
          $in_code = 1;
Packit eba2e2
          exists $h->{$sha}
Packit eba2e2
            and (warn "$ME: $f:$.: duplicate SHA1\n"),
Packit eba2e2
              $fail = 1, next;
Packit eba2e2
        }
Packit eba2e2
      else
Packit eba2e2
        {
Packit eba2e2
          $h->{$sha} ||= '';
Packit eba2e2
          $h->{$sha} .= "$line\n";
Packit eba2e2
        }
Packit eba2e2
    }
Packit eba2e2
  close F;
Packit eba2e2
Packit eba2e2
  $fail
Packit eba2e2
    and exit 1;
Packit eba2e2
Packit eba2e2
  return $h;
Packit eba2e2
}
Packit eba2e2
Packit eba2e2
# git_dir_option $SRCDIR
Packit eba2e2
#
Packit eba2e2
# From $SRCDIR, the --git-dir option to pass to git (none if $SRCDIR
Packit eba2e2
# is undef).  Return as a list (0 or 1 element).
Packit eba2e2
sub git_dir_option($)
Packit eba2e2
{
Packit eba2e2
  my ($srcdir) = @_;
Packit eba2e2
  my @res = ();
Packit eba2e2
  if (defined $srcdir)
Packit eba2e2
    {
Packit eba2e2
      my $qdir = shell_quote $srcdir;
Packit eba2e2
      my $cmd = "cd $qdir && git rev-parse --show-toplevel";
Packit eba2e2
      my $qcmd = shell_quote $cmd;
Packit eba2e2
      my $git_dir = qx($cmd);
Packit eba2e2
      defined $git_dir
Packit eba2e2
        or die "$ME: cannot run $qcmd: $!\n";
Packit eba2e2
      $? == 0
Packit eba2e2
        or die "$ME: $qcmd had unexpected exit code or signal ($?)\n";
Packit eba2e2
      chomp $git_dir;
Packit eba2e2
      push @res, "--git-dir=$git_dir/.git";
Packit eba2e2
    }
Packit eba2e2
  @res;
Packit eba2e2
}
Packit eba2e2
Packit eba2e2
{
Packit eba2e2
  my $since_date;
Packit eba2e2
  my $format_string = '%s%n%b%n';
Packit eba2e2
  my $amend_file;
Packit eba2e2
  my $append_dot = 0;
Packit eba2e2
  my $cluster = 1;
Packit eba2e2
  my $strip_tab = 0;
Packit eba2e2
  my $strip_cherry_pick = 0;
Packit eba2e2
  my $srcdir;
Packit eba2e2
  GetOptions
Packit eba2e2
    (
Packit eba2e2
     help => sub { usage 0 },
Packit eba2e2
     version => sub { print "$ME version $VERSION\n"; exit },
Packit eba2e2
     'since=s' => \$since_date,
Packit eba2e2
     'format=s' => \$format_string,
Packit eba2e2
     'amend=s' => \$amend_file,
Packit eba2e2
     'append-dot' => \$append_dot,
Packit eba2e2
     'cluster!' => \$cluster,
Packit eba2e2
     'strip-tab' => \$strip_tab,
Packit eba2e2
     'strip-cherry-pick' => \$strip_cherry_pick,
Packit eba2e2
     'srcdir=s' => \$srcdir,
Packit eba2e2
    ) or usage 1;
Packit eba2e2
Packit eba2e2
  defined $since_date
Packit eba2e2
    and unshift @ARGV, "--since=$since_date";
Packit eba2e2
Packit eba2e2
  # This is a hash that maps an SHA1 to perl code (i.e., s/old/new/)
Packit eba2e2
  # that makes a correction in the log or attribution of that commit.
Packit eba2e2
  my $amend_code = defined $amend_file ? parse_amend_file $amend_file : {};
Packit eba2e2
Packit eba2e2
  my @cmd = ('git',
Packit eba2e2
             git_dir_option $srcdir,
Packit eba2e2
             qw(log --log-size),
Packit eba2e2
             '--pretty=format:%H:%ct  %an  <%ae>%n%n'.$format_string, @ARGV);
Packit eba2e2
  open PIPE, '-|', @cmd
Packit eba2e2
    or die ("$ME: failed to run '". quoted_cmd (@cmd) ."': $!\n"
Packit eba2e2
            . "(Is your Git too old?  Version 1.5.1 or later is required.)\n");
Packit eba2e2
Packit eba2e2
  my $prev_multi_paragraph;
Packit eba2e2
  my $prev_date_line = '';
Packit eba2e2
  my @prev_coauthors = ();
Packit eba2e2
  while (1)
Packit eba2e2
    {
Packit eba2e2
      defined (my $in = <PIPE>)
Packit eba2e2
        or last;
Packit eba2e2
      $in =~ /^log size (\d+)$/
Packit eba2e2
        or die "$ME:$.: Invalid line (expected log size):\n$in";
Packit eba2e2
      my $log_nbytes = $1;
Packit eba2e2
Packit eba2e2
      my $log;
Packit eba2e2
      my $n_read = read PIPE, $log, $log_nbytes;
Packit eba2e2
      $n_read == $log_nbytes
Packit eba2e2
        or die "$ME:$.: unexpected EOF\n";
Packit eba2e2
Packit eba2e2
      # Extract leading hash.
Packit eba2e2
      my ($sha, $rest) = split ':', $log, 2;
Packit eba2e2
      defined $sha
Packit eba2e2
        or die "$ME:$.: malformed log entry\n";
Packit eba2e2
      $sha =~ /^[0-9a-fA-F]{40}$/
Packit eba2e2
        or die "$ME:$.: invalid SHA1: $sha\n";
Packit eba2e2
Packit eba2e2
      # If this commit's log requires any transformation, do it now.
Packit eba2e2
      my $code = $amend_code->{$sha};
Packit eba2e2
      if (defined $code)
Packit eba2e2
        {
Packit eba2e2
          eval 'use Safe';
Packit eba2e2
          my $s = new Safe;
Packit eba2e2
          # Put the unpreprocessed entry into "$_".
Packit eba2e2
          $_ = $rest;
Packit eba2e2
Packit eba2e2
          # Let $code operate on it, safely.
Packit eba2e2
          my $r = $s->reval("$code")
Packit eba2e2
            or die "$ME:$.:$sha: failed to eval \"$code\":\n$@\n";
Packit eba2e2
Packit eba2e2
          # Note that we've used this entry.
Packit eba2e2
          delete $amend_code->{$sha};
Packit eba2e2
Packit eba2e2
          # Update $rest upon success.
Packit eba2e2
          $rest = $_;
Packit eba2e2
        }
Packit eba2e2
Packit eba2e2
      # Remove lines inserted by "git cherry-pick".
Packit eba2e2
      if ($strip_cherry_pick)
Packit eba2e2
        {
Packit eba2e2
          $rest =~ s/^\s*Conflicts:\n.*//sm;
Packit eba2e2
          $rest =~ s/^\s*\(cherry picked from commit [\da-f]+\)\n//m;
Packit eba2e2
        }
Packit eba2e2
Packit eba2e2
      my @line = split "\n", $rest;
Packit eba2e2
      my $author_line = shift @line;
Packit eba2e2
      defined $author_line
Packit eba2e2
        or die "$ME:$.: unexpected EOF\n";
Packit eba2e2
      $author_line =~ /^(\d+)  (.*>)$/
Packit eba2e2
        or die "$ME:$.: Invalid line "
Packit eba2e2
          . "(expected date/author/email):\n$author_line\n";
Packit eba2e2
Packit eba2e2
      # Format 'Copyright-paperwork-exempt: Yes' as a standard ChangeLog
Packit eba2e2
      # `(tiny change)' annotation.
Packit eba2e2
      my $tiny = (grep (/^Copyright-paperwork-exempt:\s+[Yy]es$/, @line)
Packit eba2e2
                  ? '  (tiny change)' : '');
Packit eba2e2
Packit eba2e2
      my $date_line = sprintf "%s  %s$tiny\n",
Packit eba2e2
        strftime ("%F", localtime ($1)), $2;
Packit eba2e2
Packit eba2e2
      my @coauthors = grep /^Co-authored-by:.*$/, @line;
Packit eba2e2
      # Omit meta-data lines we've already interpreted.
Packit eba2e2
      @line = grep !/^(?:Signed-off-by:[ ].*>$
Packit eba2e2
                       |Co-authored-by:[ ]
Packit eba2e2
                       |Copyright-paperwork-exempt:[ ]
Packit eba2e2
                       )/x, @line;
Packit eba2e2
Packit eba2e2
      # Remove leading and trailing blank lines.
Packit eba2e2
      if (@line)
Packit eba2e2
        {
Packit eba2e2
          while ($line[0] =~ /^\s*$/) { shift @line; }
Packit eba2e2
          while ($line[$#line] =~ /^\s*$/) { pop @line; }
Packit eba2e2
        }
Packit eba2e2
Packit eba2e2
      # Record whether there are two or more paragraphs.
Packit eba2e2
      my $multi_paragraph = grep /^\s*$/, @line;
Packit eba2e2
Packit eba2e2
      # Format 'Co-authored-by: A U Thor <email@example.com>' lines in
Packit eba2e2
      # standard multi-author ChangeLog format.
Packit eba2e2
      for (@coauthors)
Packit eba2e2
        {
Packit eba2e2
          s/^Co-authored-by:\s*/\t    /;
Packit eba2e2
          s/\s*</  </;
Packit eba2e2
Packit eba2e2
          /<.*?@.*\..*>/
Packit eba2e2
            or warn "$ME: warning: missing email address for "
Packit eba2e2
              . substr ($_, 5) . "\n";
Packit eba2e2
        }
Packit eba2e2
Packit eba2e2
      # If clustering of commit messages has been disabled, if this header
Packit eba2e2
      # would be different from the previous date/name/email/coauthors header,
Packit eba2e2
      # or if this or the previous entry consists of two or more paragraphs,
Packit eba2e2
      # then print the header.
Packit eba2e2
      if ( ! $cluster
Packit eba2e2
          || $date_line ne $prev_date_line
Packit eba2e2
          || "@coauthors" ne "@prev_coauthors"
Packit eba2e2
          || $multi_paragraph
Packit eba2e2
          || $prev_multi_paragraph)
Packit eba2e2
        {
Packit eba2e2
          $prev_date_line eq ''
Packit eba2e2
            or print "\n";
Packit eba2e2
          print $date_line;
Packit eba2e2
          @coauthors
Packit eba2e2
            and print join ("\n", @coauthors), "\n";
Packit eba2e2
        }
Packit eba2e2
      $prev_date_line = $date_line;
Packit eba2e2
      @prev_coauthors = @coauthors;
Packit eba2e2
      $prev_multi_paragraph = $multi_paragraph;
Packit eba2e2
Packit eba2e2
      # If there were any lines
Packit eba2e2
      if (@line == 0)
Packit eba2e2
        {
Packit eba2e2
          warn "$ME: warning: empty commit message:\n  $date_line\n";
Packit eba2e2
        }
Packit eba2e2
      else
Packit eba2e2
        {
Packit eba2e2
          if ($append_dot)
Packit eba2e2
            {
Packit eba2e2
              # If the first line of the message has enough room, then
Packit eba2e2
              if (length $line[0] < 72)
Packit eba2e2
                {
Packit eba2e2
                  # append a dot if there is no other punctuation or blank
Packit eba2e2
                  # at the end.
Packit eba2e2
                  $line[0] =~ /[[:punct:]\s]$/
Packit eba2e2
                    or $line[0] .= '.';
Packit eba2e2
                }
Packit eba2e2
            }
Packit eba2e2
Packit eba2e2
          # Remove one additional leading TAB from each line.
Packit eba2e2
          $strip_tab
Packit eba2e2
            and map { s/^\t// } @line;
Packit eba2e2
Packit eba2e2
          # Prefix each non-empty line with a TAB.
Packit eba2e2
          @line = map { length $_ ? "\t$_" : '' } @line;
Packit eba2e2
Packit eba2e2
          print "\n", join ("\n", @line), "\n";
Packit eba2e2
        }
Packit eba2e2
Packit eba2e2
      defined ($in = <PIPE>)
Packit eba2e2
        or last;
Packit eba2e2
      $in ne "\n"
Packit eba2e2
        and die "$ME:$.: unexpected line:\n$in";
Packit eba2e2
    }
Packit eba2e2
Packit eba2e2
  close PIPE
Packit eba2e2
    or die "$ME: error closing pipe from " . quoted_cmd (@cmd) . "\n";
Packit eba2e2
  # FIXME-someday: include $PROCESS_STATUS in the diagnostic
Packit eba2e2
Packit eba2e2
  # Complain about any unused entry in the --amend=F specified file.
Packit eba2e2
  my $fail = 0;
Packit eba2e2
  foreach my $sha (keys %$amend_code)
Packit eba2e2
    {
Packit eba2e2
      warn "$ME:$amend_file: unused entry: $sha\n";
Packit eba2e2
      $fail = 1;
Packit eba2e2
    }
Packit eba2e2
Packit eba2e2
  exit $fail;
Packit eba2e2
}
Packit eba2e2
Packit eba2e2
# Local Variables:
Packit eba2e2
# mode: perl
Packit eba2e2
# indent-tabs-mode: nil
Packit eba2e2
# eval: (add-hook 'write-file-hooks 'time-stamp)
Packit eba2e2
# time-stamp-start: "my $VERSION = '"
Packit eba2e2
# time-stamp-format: "%:y-%02m-%02d %02H:%02M"
Packit eba2e2
# time-stamp-time-zone: "UTC"
Packit eba2e2
# time-stamp-end: "'; # UTC"
Packit eba2e2
# End: