Add git-shortlog perl script
[git.git] / git-shortlog
1 #!/usr/bin/perl -w
2
3 use strict;
4
5 #
6 # Even with git, we don't always have name translations.
7 # So have an email->real name table to translate the
8 # (hopefully few) missing names
9 #
10 my %mailmap = (
11         'aherrman@de.ibm.com' => 'Andreas Herrmann',
12         'akpm@osdl.org' => 'Andrew Morton',
13         'andrew.vasquez@qlogic.com' => 'Andrew Vasquez',
14         'aquynh@gmail.com' => 'Nguyen Anh Quynh',
15         'axboe@suse.de' => 'Jens Axboe',
16         'blaisorblade@yahoo.it' => 'Paolo \'Blaisorblade\' Giarrusso',
17         'bunk@stusta.de' => 'Adrian Bunk',
18         'domen@coderock.org' => 'Domen Puncer',
19         'dougg@torque.net' => 'Douglas Gilbert',
20         'dwmw2@shinybook.infradead.org' => 'David Woodhouse',
21         'ecashin@coraid.com' => 'Ed L Cashin',
22         'felix@derklecks.de' => 'Felix Moeller',
23         'gregkh@suse.de' => 'Greg Kroah-Hartman',
24         'hch@lst.de' => 'Christoph Hellwig',
25         'htejun@gmail.com' => 'Tejun Heo',
26         'jejb@mulgrave.(none)' => 'James Bottomley',
27         'jejb@titanic.il.steeleye.com' => 'James Bottomley',
28         'jgarzik@pretzel.yyz.us' => 'Jeff Garzik',
29         'johnpol@2ka.mipt.ru' => 'Evgeniy Polyakov',
30         'kay.sievers@vrfy.org' => 'Kay Sievers',
31         'minyard@acm.org' => 'Corey Minyard',
32         'R.Marek@sh.cvut.cz' => 'Rudolf Marek',
33         'simon@thekelleys.org.uk' => 'Simon Kelley',
34         'ssant@in.ibm.com' => 'Sachin P Sant',
35         'tony.luck@intel.com' => 'Tony Luck',
36 );
37
38 my (%map);
39 my $pstate = 1;
40 my $n_records = 0;
41 my $n_output = 0;
42
43
44 sub shortlog_entry($$) {
45         my ($name, $desc) = @_;
46         my $key = $name;
47
48         $desc =~ s#/pub/scm/linux/kernel/git/#/.../#g;
49         $desc =~ s#\[PATCH\] ##g;
50
51         # store description in array, in email->{desc list} map
52         if (exists $map{$key}) {
53                 # grab ref
54                 my $obj = $map{$key};
55
56                 # add desc to array
57                 push(@$obj, $desc);
58         } else {
59                 # create new array, containing 1 item
60                 my @arr = ($desc);
61
62                 # store ref to array
63                 $map{$key} = \@arr;
64         }
65 }
66
67 # sort comparison function
68 sub by_name($$) {
69         my ($a, $b) = @_;
70
71         uc($a) cmp uc($b);
72 }
73
74 sub shortlog_output {
75         my ($obj, $key, $desc);
76
77         foreach $key (sort by_name keys %map) {
78                 # output author
79                 printf "%s:\n", $key;
80
81                 # output author's 1-line summaries
82                 $obj = $map{$key};
83                 foreach $desc (@$obj) {
84                         print "  $desc\n";
85                         $n_output++;
86                 }
87
88                 # blank line separating author from next author
89                 print "\n";
90         }
91 }
92
93 sub changelog_input {
94         my ($author, $desc);
95
96         while (<>) {
97                 # get author and email
98                 if ($pstate == 1) {
99                         my ($email);
100
101                         next unless /^Author: (.*)<(.*)>.*$/;
102         
103                         $n_records++;
104         
105                         $author = $1;
106                         $email = $2;
107                         $desc = undef;
108
109                         # trim trailing whitespace.
110                         # why doesn't chomp work?
111                         while ($author && ($author =~ /\s$/)) {
112                                 chop $author;
113                         }
114         
115                         # cset author fixups
116                         if (exists $mailmap{$email}) {
117                                 $author = $mailmap{$email};
118                         } elsif (exists $mailmap{$author}) {
119                                 $author = $mailmap{$author};
120                         } elsif ((!$author) || ($author eq "")) {
121                                 $author = $email;
122                         }
123         
124                         $pstate++;
125                 }
126         
127                 # skip to blank line
128                 elsif ($pstate == 2) {
129                         next unless /^\s*$/;
130                         $pstate++;
131                 }
132         
133                 # skip to non-blank line
134                 elsif ($pstate == 3) {
135                         next unless /^\s*(\S.*)$/;
136
137                         # skip lines that are obviously not
138                         # a 1-line cset description
139                         next if /^\s*From: /;
140
141                         chomp;
142                         $desc = $1;
143         
144                         &shortlog_entry($author, $desc);
145         
146                         $pstate = 1;
147                 }
148         
149                 else {
150                         die "invalid parse state $pstate";
151                 }
152         }
153 }
154
155 sub finalize {
156         #print "\n$n_records records parsed.\n";
157
158         if ($n_records != $n_output) {
159                 die "parse error: input records != output records\n";
160         }
161 }
162
163 &changelog_input;
164 &shortlog_output;
165 &finalize;
166 exit(0);
167