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 BEGIN {
9 unless (exists $ENV{'RUNNING_GIT_TESTS'}) {
10 unshift @INC, '@@INSTLIBDIR@@';
11 }
12 }
13 use strict;
14 use Git;
15 use Error qw(:try);
17 my $repo = Git->repository();
19 my @src;
20 my %src;
21 sub andjoin {
22 my ($label, $labels, $stuff) = @_;
23 my $l = scalar @$stuff;
24 my $m = '';
25 if ($l == 0) {
26 return ();
27 }
28 if ($l == 1) {
29 $m = "$label$stuff->[0]";
30 }
31 else {
32 $m = ("$labels" .
33 join (', ', @{$stuff}[0..$l-2]) .
34 " and $stuff->[-1]");
35 }
36 return ($m);
37 }
39 sub repoconfig {
40 my $val;
41 try {
42 $val = $repo->command_oneline('repo-config', '--get', 'merge.summary');
43 } catch Git::Error::Command with {
44 my ($E) = shift;
45 if ($E->value() == 1) {
46 return undef;
47 } else {
48 throw $E;
49 }
50 };
51 return $val;
52 }
54 sub current_branch {
55 my ($bra) = $repo->command_oneline('symbolic-ref', 'HEAD');
56 $bra =~ s|^refs/heads/||;
57 if ($bra ne 'master') {
58 $bra = " into $bra";
59 } else {
60 $bra = "";
61 }
62 return $bra;
63 }
65 sub shortlog {
66 my ($tip) = @_;
67 my @result;
68 foreach ($repo->command('log', '--no-merges', '--topo-order', '--pretty=oneline', $tip, '^HEAD')) {
69 s/^[0-9a-f]{40}\s+//;
70 push @result, $_;
71 }
72 return @result;
73 }
75 my @origin = ();
76 while (<>) {
77 my ($bname, $tname, $gname, $src, $sha1, $origin);
78 chomp;
79 s/^([0-9a-f]*) //;
80 $sha1 = $1;
81 next if (/^not-for-merge/);
82 s/^ //;
83 if (s/ of (.*)$//) {
84 $src = $1;
85 } else {
86 # Pulling HEAD
87 $src = $_;
88 $_ = 'HEAD';
89 }
90 if (! exists $src{$src}) {
91 push @src, $src;
92 $src{$src} = {
93 BRANCH => [],
94 TAG => [],
95 R_BRANCH => [],
96 GENERIC => [],
97 # &1 == has HEAD.
98 # &2 == has others.
99 HEAD_STATUS => 0,
100 };
101 }
102 if (/^branch (.*)$/) {
103 $origin = $1;
104 push @{$src{$src}{BRANCH}}, $1;
105 $src{$src}{HEAD_STATUS} |= 2;
106 }
107 elsif (/^tag (.*)$/) {
108 $origin = $_;
109 push @{$src{$src}{TAG}}, $1;
110 $src{$src}{HEAD_STATUS} |= 2;
111 }
112 elsif (/^remote branch (.*)$/) {
113 $origin = $1;
114 push @{$src{$src}{R_BRANCH}}, $1;
115 $src{$src}{HEAD_STATUS} |= 2;
116 }
117 elsif (/^HEAD$/) {
118 $origin = $src;
119 $src{$src}{HEAD_STATUS} |= 1;
120 }
121 else {
122 push @{$src{$src}{GENERIC}}, $_;
123 $src{$src}{HEAD_STATUS} |= 2;
124 $origin = $src;
125 }
126 if ($src eq '.' || $src eq $origin) {
127 $origin =~ s/^'(.*)'$/$1/;
128 push @origin, [$sha1, "$origin"];
129 }
130 else {
131 push @origin, [$sha1, "$origin of $src"];
132 }
133 }
135 my @msg;
136 for my $src (@src) {
137 if ($src{$src}{HEAD_STATUS} == 1) {
138 # Only HEAD is fetched, nothing else.
139 push @msg, $src;
140 next;
141 }
142 my @this;
143 if ($src{$src}{HEAD_STATUS} == 3) {
144 # HEAD is fetched among others.
145 push @this, andjoin('', '', ['HEAD']);
146 }
147 push @this, andjoin("branch ", "branches ",
148 $src{$src}{BRANCH});
149 push @this, andjoin("remote branch ", "remote branches ",
150 $src{$src}{R_BRANCH});
151 push @this, andjoin("tag ", "tags ",
152 $src{$src}{TAG});
153 push @this, andjoin("commit ", "commits ",
154 $src{$src}{GENERIC});
155 my $this = join(', ', @this);
156 if ($src ne '.') {
157 $this .= " of $src";
158 }
159 push @msg, $this;
160 }
162 my $into = current_branch();
164 print "Merge ", join("; ", @msg), $into, "\n";
166 if (!repoconfig) {
167 exit(0);
168 }
170 # We limit the merge message to the latst 20 or so per each branch.
171 my $limit = 20;
173 for (@origin) {
174 my ($sha1, $name) = @$_;
175 my @log = shortlog($sha1);
176 if ($limit + 1 <= @log) {
177 print "\n* $name: (" . scalar(@log) . " commits)\n";
178 }
179 else {
180 print "\n* $name:\n";
181 }
182 my $cnt = 0;
183 for my $log (@log) {
184 if ($limit < ++$cnt) {
185 print " ...\n";
186 last;
187 }
188 print " $log\n";
189 }
190 }