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