1#!/usr/bin/perl
2
3=head1 NAME
4
5gitlog2changelog - tiny tool to convert Git commit logs to GNU-style ChangeLog
6
7=head1 SYNOPSIS
8
9git log | gitlog2changelog > ChangeLog
10
11TZ=UTC git log --date=local --no-merges --numstat --pretty=fuller | gitlog2changelog > ChangeLog
12
13(Use TZ=UTC and --date=local to use UTC instead of the original time zone.
14 Use --no-merges to ignore merge commits.
15 Use --numstat to show changed files.
16 Use --pretty=fuller to show committer date instead of author date.)
17
18=head1 SEE ALSO
19
20GNU Coding Standards:
21    <http://www.gnu.org/prep/standards/html_node/index.html>
22    <http://www.gnu.org/prep/standards/html_node/Change-Logs.html>
23
24=head1 COPYRIGHT
25
26Copyright (c) 2013 Tatsuya Kinoshita
27
28Redistribution and use in source and binary forms, with or without
29modification, are permitted without restriction, with NO WARRANTY.
30You may regard this as a work that is placed in the public domain.
31
32=cut
33
34use strict;
35use warnings;
36
37my $author = ""; my $date = ""; my $comment = ""; my @files = ();
38my $pre_header = "";
39
40sub print_entry {
41    my $header = "$date  $author";
42    $header =~ s/ </  </;
43    if ($header ne $pre_header) {
44	print "$header\n\n";
45	$pre_header = $header;
46    }
47    print "\t* ";
48
49    my $files_line_len = 10;
50    my $files_lines = "";
51    foreach my $file (@files) {
52	my $len = length($file);
53	if ($files_line_len > 10 && ($files_line_len + $len > 78)) {
54	    $files_lines =~ s/, $/:\n\t* /;
55	    $files_line_len = 10;
56	}
57	$files_lines .= "$file, ";
58	$files_line_len += $len + 2;
59    }
60
61    if ($files_lines) {
62	my $short_comment = "";
63	if ($comment =~ /^([^\n]+)/) {
64	    $short_comment = $1;
65	}
66	if ($short_comment =~ /:[ \t]|[^\(\[][:,\)\]][ \t]*$/ ||
67	    $files_line_len + length($short_comment) > 78) {
68	    $files_lines =~ s/, $/:\n\t/;
69	} else {
70	    $files_lines =~ s/, $/: /;
71	}
72	print "$files_lines";
73    }
74
75    $comment =~ s/\.?[ \t]*\n/.\n/;
76    $comment =~ s/\n([ \t]*\n)+/\n/g;
77    $comment =~ s/\n+$//;
78    $comment =~ s/\n/\n\t/g;
79    $comment =~ s/^\* //;
80    print "$comment\n\n";
81
82    $author = ""; $date = ""; $comment = ""; @files = ();
83}
84
85while (<>) {
86    if (/^Author:[ \t]+([^\n]+)/) {
87	$author = $1;
88    } elsif (/^(Commit|)Date:[ \t]+(Sun|Mon|Tue|Wed|Thu|Fri|Sat) +(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) +(\d+) +\d+:\d+:\d+ +(\d+)/) {  # Assume git log --date=default or --date=local
89	my $mm = $3;
90	my $dd = $4;
91	my $yyyy = $5;
92	$mm =~ s/Jan/01/; $mm =~ s/Feb/02/; $mm =~ s/Mar/03/; $mm =~ s/Apr/04/;
93	$mm =~ s/May/05/; $mm =~ s/Jun/06/; $mm =~ s/Jul/07/; $mm =~ s/Aug/08/;
94	$mm =~ s/Sep/09/; $mm =~ s/Oct/10/; $mm =~ s/Nov/11/; $mm =~ s/Dec/12/;
95	$dd =~ s/^(\d)$/0$1/;
96	$date = "$yyyy-$mm-$dd";
97    } elsif (/^(Commit|)Date:[ \t]+(\d+-\d+-\d+)/) {  # Assume git log --date=short or --date=iso
98	$date = $2;
99    } elsif (/^(Commit|)Date:[ \t]+(.+)$/) {
100	$date = $2;
101    } elsif (/^    (.*)$/) {
102	$comment .= "$1\n";
103    } elsif (/^[-0-9]+\t[-0-9]+\t(.*)$/) {  # Assume git log --numstat
104	push @files, $1;
105    } elsif (/^commit / && $author && $date) {
106	&print_entry;
107    }
108}
109if ($author && $date) {
110    &print_entry;
111}
112