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.
8 use strict;
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 }
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 }
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 }
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 }
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 }
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";
145 if (!repoconfig) {
146 exit(0);
147 }
149 # We limit the merge message to the latst 20 or so per each branch.
150 my $limit = 20;
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 }