Merge part of jc/portable branch
[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 current_branch {
43         my $fh;
44         open $fh, '-|', 'git-symbolic-ref', 'HEAD' or die "$!";
45         my ($bra) = <$fh>;
46         chomp($bra);
47         $bra =~ s|^refs/heads/||;
48         if ($bra ne 'master') {
49                 $bra = " into $bra";
50         } else {
51                 $bra = "";
52         }
53
54         return $bra;
55 }
56
57 sub shortlog {
58         my ($tip) = @_;
59         my ($fh, @result);
60         open $fh, '-|', ('git-log', '--topo-order',
61                          '--pretty=oneline', $tip, '^HEAD')
62             or die "$!";
63         while (<$fh>) {
64                 s/^[0-9a-f]{40}\s+//;
65                 push @result, $_;
66         }
67         close $fh or die "$!";
68         return @result;
69 }
70
71 my @origin = ();
72 while (<>) {
73         my ($bname, $tname, $gname, $src, $sha1, $origin);
74         chomp;
75         s/^([0-9a-f]*)  //;
76         $sha1 = $1;
77         next if (/^not-for-merge/);
78         s/^     //;
79         if (s/ of (.*)$//) {
80                 $src = $1;
81         } else {
82                 # Pulling HEAD
83                 $src = $_;
84                 $_ = 'HEAD';
85         }
86         if (! exists $src{$src}) {
87                 push @src, $src;
88                 $src{$src} = {
89                         BRANCH => [],
90                         TAG => [],
91                         GENERIC => [],
92                         # &1 == has HEAD.
93                         # &2 == has others.
94                         HEAD_STATUS => 0,
95                 };
96         }
97         if (/^branch (.*)$/) {
98                 $origin = $1;
99                 push @{$src{$src}{BRANCH}}, $1;
100                 $src{$src}{HEAD_STATUS} |= 2;
101         }
102         elsif (/^tag (.*)$/) {
103                 $origin = $_;
104                 push @{$src{$src}{TAG}}, $1;
105                 $src{$src}{HEAD_STATUS} |= 2;
106         }
107         elsif (/^HEAD$/) {
108                 $origin = $src;
109                 $src{$src}{HEAD_STATUS} |= 1;
110         }
111         else {
112                 push @{$src{$src}{GENERIC}}, $_;
113                 $src{$src}{HEAD_STATUS} |= 2;
114                 $origin = $src;
115         }
116         if ($src eq '.' || $src eq $origin) {
117                 $origin =~ s/^'(.*)'$/$1/;
118                 push @origin, [$sha1, "$origin"];
119         }
120         else {
121                 push @origin, [$sha1, "$origin of $src"];
122         }
123 }
124
125 my @msg;
126 for my $src (@src) {
127         if ($src{$src}{HEAD_STATUS} == 1) {
128                 # Only HEAD is fetched, nothing else.
129                 push @msg, $src;
130                 next;
131         }
132         my @this;
133         if ($src{$src}{HEAD_STATUS} == 3) {
134                 # HEAD is fetched among others.
135                 push @this, andjoin('', '', ['HEAD']);
136         }
137         push @this, andjoin("branch ", "branches ",
138                            $src{$src}{BRANCH});
139         push @this, andjoin("tag ", "tags ",
140                            $src{$src}{TAG});
141         push @this, andjoin("commit ", "commits ",
142                             $src{$src}{GENERIC});
143         my $this = join(', ', @this);
144         if ($src ne '.') {
145                 $this .= " of $src";
146         }
147         push @msg, $this;
148 }
149
150 my $into = current_branch();
151
152 print "Merge ", join("; ", @msg), $into, "\n";
153
154 if (!repoconfig) {
155         exit(0);
156 }
157
158 # We limit the merge message to the latst 20 or so per each branch.
159 my $limit = 20;
160
161 for (@origin) {
162         my ($sha1, $name) = @$_;
163         my @log = shortlog($sha1);
164         if ($limit + 1 <= @log) {
165                 print "\n* $name: (" . scalar(@log) . " commits)\n";
166         }
167         else {
168                 print "\n* $name:\n";
169         }
170         my $cnt = 0;
171         for my $log (@log) {
172                 if ($limit < ++$cnt) {
173                         print "  ...\n";
174                         last;
175                 }
176                 print "  $log";
177         }
178 }