1 eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}' 2 & eval 'exec perl -wS "$0" $argv:q' 3 if 0; 4 # Convert git log output to ChangeLog format. 5 6 my $VERSION = '2011-10-31 16:06'; # UTC 7 # The definition above must lie within the first 8 lines in order 8 # for the Emacs time-stamp write hook (at end) to update it. 9 # If you change this file with Emacs, please let the write hook 10 # do its job. Otherwise, update this string manually. 11 12 # Copyright (C) 2008-2011 Free Software Foundation, Inc. 13 14 # This program is free software: you can redistribute it and/or modify 15 # it under the terms of the GNU General Public License as published by 16 # the Free Software Foundation, either version 3 of the License, or 17 # (at your option) any later version. 18 19 # This program is distributed in the hope that it will be useful, 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 # GNU General Public License for more details. 23 24 # You should have received a copy of the GNU General Public License 25 # along with this program. If not, see <http://www.gnu.org/licenses/>. 26 27 # Written by Jim Meyering 28 29 use strict; 30 use warnings; 31 use Getopt::Long; 32 use POSIX qw(strftime); 33 34 (my $ME = $0) =~ s|.*/||; 35 36 # use File::Coda; # http://meyering.net/code/Coda/ 37 END { 38 defined fileno STDOUT or return; 39 close STDOUT and return; 40 warn "$ME: failed to close standard output: $!\n"; 41 $? ||= 1; 42 } 43 44 sub usage ($) 45 { 46 my ($exit_code) = @_; 47 my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); 48 if ($exit_code != 0) 49 { 50 print $STREAM "Try `$ME --help' for more information.\n"; 51 } 52 else 53 { 54 print $STREAM <<EOF; 55 Usage: $ME [OPTIONS] [ARGS] 56 57 Convert git log output to ChangeLog format. If present, any ARGS 58 are passed to "git log". To avoid ARGS being parsed as options to 59 $ME, they may be preceded by '--'. 60 61 OPTIONS: 62 63 --since=DATE convert only the logs since DATE; 64 the default is to convert all log entries. 65 --format=FMT set format string for commit subject and body; 66 see 'man git-log' for the list of format metacharacters; 67 the default is '%s%n%b%n' 68 --append-dot append a dot to the first line of each commit message if 69 there is no other punctuation or blank at the end. 70 71 --help display this help and exit 72 --version output version information and exit 73 74 EXAMPLE: 75 76 $ME --since=2008-01-01 > ChangeLog 77 $ME -- -n 5 foo > last-5-commits-to-branch-foo 78 79 EOF 80 } 81 exit $exit_code; 82 } 83 84 # If the string $S is a well-behaved file name, simply return it. 85 # If it contains white space, quotes, etc., quote it, and return the new string. 86 sub shell_quote($) 87 { 88 my ($s) = @_; 89 if ($s =~ m![^\w+/.,-]!) 90 { 91 # Convert each single quote to '\'' 92 $s =~ s/\'/\'\\\'\'/g; 93 # Then single quote the string. 94 $s = "'$s'"; 95 } 96 return $s; 97 } 98 99 sub quoted_cmd(@) 100 { 101 return join (' ', map {shell_quote $_} @_); 102 } 103 104 { 105 my $since_date; 106 my $format_string = '%s%n%b%n'; 107 my $append_dot = 0; 108 GetOptions 109 ( 110 help => sub { usage 0 }, 111 version => sub { print "$ME version $VERSION\n"; exit }, 112 'since=s' => \$since_date, 113 'format=s' => \$format_string, 114 'append-dot' => \$append_dot, 115 ) or usage 1; 116 117 defined $since_date 118 and unshift @ARGV, "--since=$since_date"; 119 120 my @cmd = (qw (git log --log-size), 121 '--pretty=format:%ct %an <%ae>%n%n'.$format_string, @ARGV); 122 open PIPE, '-|', @cmd 123 or die ("$ME: failed to run `". quoted_cmd (@cmd) ."': $!\n" 124 . "(Is your Git too old? Version 1.5.1 or later is required.)\n"); 125 126 my $prev_date_line = ''; 127 while (1) 128 { 129 defined (my $in = <PIPE>) 130 or last; 131 $in =~ /^log size (\d+)$/ 132 or die "$ME:$.: Invalid line (expected log size):\n$in"; 133 my $log_nbytes = $1; 134 135 my $log; 136 my $n_read = read PIPE, $log, $log_nbytes; 137 $n_read == $log_nbytes 138 or die "$ME:$.: unexpected EOF\n"; 139 140 my @line = split "\n", $log; 141 my $author_line = shift @line; 142 defined $author_line 143 or die "$ME:$.: unexpected EOF\n"; 144 $author_line =~ /^(\d+) (.*>)$/ 145 or die "$ME:$.: Invalid line " 146 . "(expected date/author/email):\n$author_line\n"; 147 148 my $date_line = sprintf "%s $2\n", strftime ("%F", localtime ($1)); 149 # If this line would be the same as the previous date/name/email 150 # line, then arrange not to print it. 151 if ($date_line ne $prev_date_line) 152 { 153 $prev_date_line eq '' 154 or print "\n"; 155 print $date_line; 156 } 157 $prev_date_line = $date_line; 158 159 # Omit "Signed-off-by..." lines. 160 @line = grep !/^Signed-off-by: .*>$/, @line; 161 162 # Remove leading and trailing blank lines. 163 if (@line) 164 { 165 while ($line[0] =~ /^\s*$/) { shift @line; } 166 while ($line[$#line] =~ /^\s*$/) { pop @line; } 167 } 168 169 # If there were any lines 170 if (@line == 0) 171 { 172 warn "$ME: warning: empty commit message:\n $date_line\n"; 173 } 174 else 175 { 176 if ($append_dot) 177 { 178 # If the first line of the message has enough room, then 179 if (length $line[0] < 72) 180 { 181 # append a dot if there is no other punctuation or blank 182 # at the end. 183 $line[0] =~ /[[:punct:]\s]$/ 184 or $line[0] .= '.'; 185 } 186 } 187 188 # Prefix each non-empty line with a TAB. 189 @line = map { length $_ ? "\t$_" : '' } @line; 190 191 print "\n", join ("\n", @line), "\n"; 192 } 193 194 defined ($in = <PIPE>) 195 or last; 196 $in ne "\n" 197 and die "$ME:$.: unexpected line:\n$in"; 198 } 199 200 close PIPE 201 or die "$ME: error closing pipe from " . quoted_cmd (@cmd) . "\n"; 202 # FIXME-someday: include $PROCESS_STATUS in the diagnostic 203 } 204 205 # Local Variables: 206 # mode: perl 207 # indent-tabs-mode: nil 208 # eval: (add-hook 'write-file-hooks 'time-stamp) 209 # time-stamp-start: "my $VERSION = '" 210 # time-stamp-format: "%:y-%02m-%02d %02H:%02M" 211 # time-stamp-time-zone: "UTC" 212 # time-stamp-end: "'; # UTC" 213 # End: 214