Fix whitespace error in Makefile.am
[synfig.git] / autobuild / git2cl
1 #!/usr/bin/perl
2
3 # Copyright (C) 2007, 2008 Simon Josefsson <simon@josefsson.org>
4 # Copyright (C) 2007 Luis Mondesi <lemsx1@gmail.com>
5 # * calls git directly. To use it just: 
6 #   cd ~/Project/my_git_repo; git2cl > ChangeLog
7 # * implements strptime()
8 # * fixes bugs in $comment parsing
9 #   - copy input before we remove leading spaces
10 #   - skip "merge branch" statements as they don't
11 #     have information about files (i.e. we never
12 #     go into $state 2)
13 #   - behaves like a pipe/filter if input is given from the CLI
14 #     else it calls git log by itself
15 #
16 # The functions mywrap, last_line_len, wrap_log_entry are derived from
17 # the cvs2cl tool, see <http://www.red-bean.com/cvs2cl/>:
18 # Copyright (C) 2001,2002,2003,2004 Martyn J. Pearce <fluffy@cpan.org>
19 # Copyright (C) 1999 Karl Fogel <kfogel@red-bean.com>
20 #
21 # git2cl is free software; you can redistribute it and/or modify it
22 # under the terms of the GNU General Public License as published by
23 # the Free Software Foundation; either version 2, or (at your option)
24 # any later version.
25 #
26 # git2cl is distributed in the hope that it will be useful, but
27 # WITHOUT ANY WARRANTY; without even the implied warranty of
28 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
29 # General Public License for more details.
30 #
31 # You should have received a copy of the GNU General Public License
32 # along with git2cl; see the file COPYING.  If not, write to the Free
33 # Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
34 # 02111-1307, USA.
35
36 use strict;
37 use POSIX qw(strftime);
38 use Text::Wrap qw(wrap);
39 use FileHandle;
40
41 use constant EMPTY_LOG_MESSAGE => '*** empty log message ***';
42
43 # this is a helper hash for stptime.
44 # Assumes you are calling 'git log ...' with LC_ALL=C
45 my %month = (
46     'Jan'=>0,
47     'Feb'=>1,
48     'Mar'=>2,
49     'Apr'=>3,
50     'May'=>4,
51     'Jun'=>5,
52     'Jul'=>6,
53     'Aug'=>7,
54     'Sep'=>8,
55     'Oct'=>9,
56     'Nov'=>10,
57     'Dec'=>11,
58 );
59
60 my $fh = new FileHandle;
61
62 sub key_ready
63 {
64     my ($rin, $nfd);
65     vec($rin, fileno(STDIN), 1) = 1;
66     return $nfd = select($rin, undef, undef, 0);
67 }
68
69 sub strptime {
70     my $str = shift;
71     return undef if not defined $str;
72
73     # we are parsing this format
74     # Fri Oct 26 00:42:56 2007 -0400
75     # to these fields
76     # sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1
77     # Luis Mondesi <lemsx1@gmail.com>
78     my @date;
79     if ($str =~ /([[:alpha:]]{3})\s+([[:alpha:]]{3})\s+([[:digit:]]{1,2})\s+([[:digit:]]{1,2}):([[:digit:]]{1,2}):([[:digit:]]{1,2})\s+([[:digit:]]{4})/){
80         push(@date,$6,$5,$4,$3,$month{$2},($7 - 1900),-1,-1,-1);
81     } else {
82         die ("Cannot parse date '$str'\n'");
83     }
84     return @date;
85 }
86
87 sub mywrap {
88     my ($indent1, $indent2, @text) = @_;
89     # If incoming text looks preformatted, don't get clever
90     my $text = Text::Wrap::wrap($indent1, $indent2, @text);
91     if ( grep /^\s+/m, @text ) {
92         return $text;
93     }
94     my @lines = split /\n/, $text;
95     $indent2 =~ s!^((?: {8})+)!"\t" x (length($1)/8)!e;
96     $lines[0] =~ s/^$indent1\s+/$indent1/;
97     s/^$indent2\s+/$indent2/
98         for @lines[1..$#lines];
99     my $newtext = join "\n", @lines;
100     $newtext .= "\n"
101         if substr($text, -1) eq "\n";
102     return $newtext;
103 }
104
105 sub last_line_len {
106     my $files_list = shift;
107     my @lines = split (/\n/, $files_list);
108     my $last_line = pop (@lines);
109     return length ($last_line);
110 }
111
112 # A custom wrap function, sensitive to some common constructs used in
113 # log entries.
114 sub wrap_log_entry {
115     my $text = shift;                  # The text to wrap.
116     my $left_pad_str = shift;          # String to pad with on the left.
117
118     # These do NOT take left_pad_str into account:
119     my $length_remaining = shift;      # Amount left on current line.
120     my $max_line_length  = shift;      # Amount left for a blank line.
121
122     my $wrapped_text = '';             # The accumulating wrapped entry.
123     my $user_indent = '';              # Inherited user_indent from prev line.
124
125     my $first_time = 1;                # First iteration of the loop?
126     my $suppress_line_start_match = 0; # Set to disable line start checks.
127
128     my @lines = split (/\n/, $text);
129     while (@lines)   # Don't use `foreach' here, it won't work.
130     {
131         my $this_line = shift (@lines);
132         chomp $this_line;
133
134         if ($this_line =~ /^(\s+)/) {
135             $user_indent = $1;
136         }
137         else {
138             $user_indent = '';
139         }
140
141         # If it matches any of the line-start regexps, print a newline now...
142         if ($suppress_line_start_match)
143         {
144             $suppress_line_start_match = 0;
145         }
146         elsif (($this_line =~ /^(\s*)\*\s+[a-zA-Z0-9]/)
147                || ($this_line =~ /^(\s*)\* [a-zA-Z0-9_\.\/\+-]+/)
148                || ($this_line =~ /^(\s*)\([a-zA-Z0-9_\.\/\+-]+(\)|,\s*)/)
149                || ($this_line =~ /^(\s+)(\S+)/)
150                || ($this_line =~ /^(\s*)- +/)
151                || ($this_line =~ /^()\s*$/)
152                || ($this_line =~ /^(\s*)\*\) +/)
153                || ($this_line =~ /^(\s*)[a-zA-Z0-9](\)|\.|\:) +/))
154         {
155             $length_remaining = $max_line_length - (length ($user_indent));
156         }
157
158         # Now that any user_indent has been preserved, strip off leading
159         # whitespace, so up-folding has no ugly side-effects.
160         $this_line =~ s/^\s*//;
161
162         # Accumulate the line, and adjust parameters for next line.
163         my $this_len = length ($this_line);
164         if ($this_len == 0)
165         {
166             # Blank lines should cancel any user_indent level.
167             $user_indent = '';
168             $length_remaining = $max_line_length;
169         }
170         elsif ($this_len >= $length_remaining) # Line too long, try breaking it.
171         {
172             # Walk backwards from the end.  At first acceptable spot, break
173             # a new line.
174             my $idx = $length_remaining - 1;
175             if ($idx < 0) { $idx = 0 };
176             while ($idx > 0)
177             {
178                 if (substr ($this_line, $idx, 1) =~ /\s/)
179                 {
180                     my $line_now = substr ($this_line, 0, $idx);
181                     my $next_line = substr ($this_line, $idx);
182                     $this_line = $line_now;
183
184                     # Clean whitespace off the end.
185                     chomp $this_line;
186
187                     # The current line is ready to be printed.
188                     $this_line .= "\n${left_pad_str}";
189
190                     # Make sure the next line is allowed full room.
191                     $length_remaining = $max_line_length - (length ($user_indent));
192
193                     # Strip next_line, but then preserve any user_indent.
194                     $next_line =~ s/^\s*//;
195
196                     # Sneak a peek at the user_indent of the upcoming line, so
197                     # $next_line (which will now precede it) can inherit that
198                     # indent level.  Otherwise, use whatever user_indent level
199                     # we currently have, which might be none.
200                     my $next_next_line = shift (@lines);
201                     if ((defined ($next_next_line)) && ($next_next_line =~ /^(\s+)/)) {
202                         $next_line = $1 . $next_line if (defined ($1));
203                         # $length_remaining = $max_line_length - (length ($1));
204                         $next_next_line =~ s/^\s*//;
205                     }
206                     else {
207                         $next_line = $user_indent . $next_line;
208                     }
209                     if (defined ($next_next_line)) {
210                         unshift (@lines, $next_next_line);
211                     }
212                     unshift (@lines, $next_line);
213
214                     # Our new next line might, coincidentally, begin with one of
215                     # the line-start regexps, so we temporarily turn off
216                     # sensitivity to that until we're past the line.
217                     $suppress_line_start_match = 1;
218
219                     last;
220                 }
221                 else
222                 {
223                     $idx--;
224                 }
225             }
226
227             if ($idx == 0)
228             {
229                 # We bottomed out because the line is longer than the
230                 # available space.  But that could be because the space is
231                 # small, or because the line is longer than even the maximum
232                 # possible space.  Handle both cases below.
233
234                 if ($length_remaining == ($max_line_length - (length ($user_indent))))
235                 {
236                     # The line is simply too long -- there is no hope of ever
237                     # breaking it nicely, so just insert it verbatim, with
238                     # appropriate padding.
239                     $this_line = "\n${left_pad_str}${this_line}";
240                 }
241                 else
242                 {
243                     # Can't break it here, but may be able to on the next round...
244                     unshift (@lines, $this_line);
245                     $length_remaining = $max_line_length - (length ($user_indent));
246                     $this_line = "\n${left_pad_str}";
247                 }
248             }
249         }
250         else  # $this_len < $length_remaining, so tack on what we can.
251         {
252             # Leave a note for the next iteration.
253             $length_remaining = $length_remaining - $this_len;
254
255             if ($this_line =~ /\.$/)
256             {
257                 $this_line .= "  ";
258                 $length_remaining -= 2;
259             }
260             else  # not a sentence end
261             {
262                 $this_line .= " ";
263                 $length_remaining -= 1;
264             }
265         }
266
267         # Unconditionally indicate that loop has run at least once.
268         $first_time = 0;
269
270         $wrapped_text .= "${user_indent}${this_line}";
271     }
272
273     # One last bit of padding.
274     $wrapped_text .= "\n";
275
276     return $wrapped_text;
277 }
278
279 # main
280
281 my @date;
282 my $author;
283 my @files;
284 my $comment;
285
286 my $state; # 0-header 1-comment 2-files
287 my $done = 0;
288
289 $state = 0;
290
291 # if reading from STDIN, we assume that we are
292 # getting git log as input
293 if (key_ready())
294 {
295
296     #my $dummyfh; # don't care about writing
297     #($fh,$dummyfh) = FileHandle::pipe;
298     $fh->fdopen(*STDIN, 'r');
299 }
300 else
301 {
302     $fh->open("LC_ALL=C git log --pretty --numstat --summary . |")
303         or die("Cannot execute git log...$!\n");
304 }
305
306 while (my $_l = <$fh>) {
307     #print STDERR "debug ($state, " . (@date ? (strftime "%Y-%m-%d", @date) : "") . "): `$_'\n";
308     if ($state == 0) {
309         if ($_l =~ m,^Author: (.*),) {
310             $author = $1;
311         }
312         if ($_l =~ m,^Date: (.*),) {
313             @date = strptime($1);
314         }
315         $state = 1 if ($_l =~ m,^$, and $author and (@date+0>0));
316     } elsif ($state == 1) {
317         # * modifying our input text is a bad choice
318         #   let's make a copy of it first, then we remove spaces 
319         # * if we meet a "merge branch" statement, we need to start
320         #   over and find a real entry
321         # Luis Mondesi <lemsx1@gmail.com>
322         my $_s = $_l;
323         $_s =~ s/^    //g;
324         if ($_s =~ m/^Merge branch/)
325         {
326             $state=0;
327             next;
328         }
329         $comment = $comment . $_s;
330         $state = 2 if ($_l =~ m,^$,);
331     } elsif ($state == 2) {
332         if ($_l =~ m,^([0-9]+)\t([0-9]+)\t(.*)$,) {
333             push @files, $3;
334         }
335         $done = 1 if ($_l =~ m,^$,);
336     }
337
338     if ($done) {
339         print (strftime "%Y-%m-%d  $author\n\n", @date);
340
341         my $files = join (", ", @files);
342         $files = mywrap ("\t", "\t", "* $files"), ": ";
343
344         if (index($comment, EMPTY_LOG_MESSAGE) > -1 ) {
345             $comment = "[no log message]\n";
346         }
347
348         my $files_last_line_len = 0;
349         $files_last_line_len = last_line_len($files) + 1;
350         my $msg = wrap_log_entry($comment, "\t", 69-$files_last_line_len, 69);
351
352         $msg =~ s/[ \t]+\n/\n/g;
353
354         print "$files: $msg\n";
355
356         @date = ();
357         $author = "";
358         @files = ();
359         $comment = "";
360
361         $state = 0;
362         $done = 0;
363     }
364 }
365
366 if (@date + 0)
367 {
368     print (strftime "%Y-%m-%d  $author\n\n", @date);
369     my $msg = wrap_log_entry($comment, "\t", 69, 69);
370     $msg =~ s/[ \t]+\n/\n/g;
371     print "\t* $msg\n";
372 }