Optionally work without python
[git.git] / git-fmt-merge-msg.perl
1 #!/usr/bin/perl -w
2 #
3 # Copyright (c) 2005 Junio C Hamano
4 #
5 # Read .git/FETCH_HEAD and make a human readable merge message
6 # by grouping branches and tags together to form a single line.
7
8 use strict;
9
10 my @src;
11 my %src;
12 sub andjoin {
13         my ($label, $labels, $stuff) = @_;
14         my $l = scalar @$stuff;
15         my $m = '';
16         if ($l == 0) {
17                 return ();
18         }
19         if ($l == 1) {
20                 $m = "$label$stuff->[0]";
21         }
22         else {
23                 $m = ("$labels" .
24                       join (', ', @{$stuff}[0..$l-2]) .
25                       " and $stuff->[-1]");
26         }
27         return ($m);
28 }
29
30 sub repoconfig {
31         my $fh;
32         my $val;
33         eval {
34                 open $fh, '-|', 'git-repo-config', '--get', 'merge.summary'
35                     or die "$!";
36                 ($val) = <$fh>;
37                 close $fh;
38         };
39         return $val;
40 }
41
42 sub mergebase {
43         my ($other) = @_;
44         my $fh;
45         open $fh, '-|', 'git-merge-base', '--all', 'HEAD', $other or die "$!";
46         my (@mb) = map { chomp; $_ } <$fh>;
47         close $fh or die "$!";
48         return @mb;
49 }
50
51 sub shortlog {
52         my ($tip, $limit, @base) = @_;
53         my ($fh, @result);
54         open $fh, '-|', ('git-log', "--max-count=$limit", '--topo-order',
55                          '--pretty=oneline', $tip, map { "^$_" } @base)
56             or die "$!";
57         while (<$fh>) {
58                 s/^[0-9a-f]{40}\s+//;
59                 push @result, $_;
60         }
61         close $fh or die "$!";
62         return @result;
63 }
64
65 my @origin = ();
66 while (<>) {
67         my ($bname, $tname, $gname, $src, $sha1, $origin);
68         chomp;
69         s/^([0-9a-f]*)  //;
70         $sha1 = $1;
71         next if (/^not-for-merge/);
72         s/^     //;
73         if (s/ of (.*)$//) {
74                 $src = $1;
75         } else {
76                 # Pulling HEAD
77                 $src = $_;
78                 $_ = 'HEAD';
79         }
80         if (! exists $src{$src}) {
81                 push @src, $src;
82                 $src{$src} = {
83                         BRANCH => [],
84                         TAG => [],
85                         GENERIC => [],
86                         # &1 == has HEAD.
87                         # &2 == has others.
88                         HEAD_STATUS => 0,
89                 };
90         }
91         if (/^branch (.*)$/) {
92                 $origin = $1;
93                 push @{$src{$src}{BRANCH}}, $1;
94                 $src{$src}{HEAD_STATUS} |= 2;
95         }
96         elsif (/^tag (.*)$/) {
97                 $origin = $_;
98                 push @{$src{$src}{TAG}}, $1;
99                 $src{$src}{HEAD_STATUS} |= 2;
100         }
101         elsif (/^HEAD$/) {
102                 $origin = $src;
103                 $src{$src}{HEAD_STATUS} |= 1;
104         }
105         else {
106                 push @{$src{$src}{GENERIC}}, $_;
107                 $src{$src}{HEAD_STATUS} |= 2;
108                 $origin = $src;
109         }
110         if ($src eq '.' || $src eq $origin) {
111                 $origin =~ s/^'(.*)'$/$1/;
112                 push @origin, [$sha1, "$origin"];
113         }
114         else {
115                 push @origin, [$sha1, "$origin of $src"];
116         }
117 }
118
119 my @msg;
120 for my $src (@src) {
121         if ($src{$src}{HEAD_STATUS} == 1) {
122                 # Only HEAD is fetched, nothing else.
123                 push @msg, $src;
124                 next;
125         }
126         my @this;
127         if ($src{$src}{HEAD_STATUS} == 3) {
128                 # HEAD is fetched among others.
129                 push @this, andjoin('', '', ['HEAD']);
130         }
131         push @this, andjoin("branch ", "branches ",
132                            $src{$src}{BRANCH});
133         push @this, andjoin("tag ", "tags ",
134                            $src{$src}{TAG});
135         push @this, andjoin("commit ", "commits ",
136                             $src{$src}{GENERIC});
137         my $this = join(', ', @this);
138         if ($src ne '.') {
139                 $this .= " of $src";
140         }
141         push @msg, $this;
142 }
143 print "Merge ", join("; ", @msg), "\n";
144
145 if (!repoconfig) {
146         exit(0);
147 }
148
149 # We limit the merge message to the latst 20 or so per each branch.
150 my $limit = 20;
151
152 for (@origin) {
153         my ($sha1, $name) = @$_;
154         my @mb = mergebase($sha1);
155         my @log = shortlog($sha1, $limit + 1, @mb);
156         if ($limit + 1 <= @log) {
157                 print "\n* $name: (" . scalar(@log) . " commits)\n";
158         }
159         else {
160                 print "\n* $name:\n";
161         }
162         my $cnt = 0;
163         for my $log (@log) {
164                 if ($limit < ++$cnt) {
165                         print "  ...\n";
166                         last;
167                 }
168                 print "  $log";
169         }
170 }