]> mj.ucw.cz Git - git-tools.git/blob - update2
Update2: An attempt at recognition of merges
[git-tools.git] / update2
1 #!/usr/bin/perl
2 # This is a generic update hook script for GIT repositories.
3 # Written by Martin Mares <mj@ucw.cz> and placed into public domain.
4
5 use strict;
6 use warnings;
7
8 use Getopt::Long;
9 use IO::File;
10 use File::Temp;
11 use POSIX;
12
13 my $mail_to;
14 my $subject_prefix = "GIT";
15 my $max_diff_size;
16
17 GetOptions(
18         'mail-to=s' => \$mail_to,
19         'subject-prefix=s' => \$subject_prefix,
20         'max-diff-size=s' => \$max_diff_size,
21 ) and @ARGV == 3 or die <<AMEN ;
22 Usage: $0 [<options>] <refname> <sha1-old> <sha1-new>
23
24 Options:
25 --mail-to=<address>     Send mail to the given address
26 --max-diff-size=<bytes> If the diff is too long, send just a summary
27 --subject-prefix=<px>   Prefix subjects with [<px>] (default: GIT)
28 AMEN
29
30 my ($ref, $old, $new) = @ARGV;
31 $ref =~ s{^refs/heads/}{} or exit 0;
32 $old ne $new or exit 0;
33
34 my $repo = POSIX::getcwd();
35 $repo =~ s{.*/}{};
36
37 my $subj = '[' . $subject_prefix . ($ref eq 'master' ? '' : "/$ref") . ']';
38 my $out = File::Temp->new() or die;
39 my $outname = $out->filename;
40 $out->autoflush(1);
41 open ORIG_STDOUT, '>&', \*STDOUT;
42 close STDOUT;
43 open STDOUT, '>&', $out or die;
44
45 my @rev_list_options = ('--pretty', '--no-abbrev', '--date=iso');
46 my @diff_options = ('-C');
47
48 sub scan_branches() {
49         # Is there any branch pointing to $new ?
50         for (`git branch -v --no-abbrev`) {
51                 chomp;
52                 my ($name, $sha) = /^..(\S+)\s+(\S+)/ or die;
53                 if ($name ne $ref && $sha eq $new) {
54                         return $name;
55                 }
56         }
57         return;
58 }
59
60 sub scan_commits() {
61         my @commits = ();
62         for (`git rev-list $old..$new --pretty=format:"# %H (%P) %s"`) {
63                 chomp;
64                 /^# / or next;
65                 my ($hash, $parents, $subject) = m{^# (\S+) \(([^)]*)\) (.*)} or die;
66                 push @commits, {
67                         hash => $hash,
68                         parents => [ split /\s+/, $parents ],
69                         subject => $subject,
70                 };
71         }
72         return @commits;
73 }
74
75 sub most_recent() {
76         print $out "Most recent commits:\n\n";
77         system 'git', 'rev-list', @rev_list_options, '--max-count=20', $new;
78 }
79
80 sub output_size() {
81         $out->seek(0, 2);
82         return $out->tell;
83 }
84
85 if ($old =~ /^0+$/) {
86         # Creation of a branch
87         $subj .= ' Created branch';
88         my $copy_of = scan_branches();
89         if (defined $copy_of) {
90                 $subj .= " as a copy of $copy_of";
91                 print $out "Created branch $ref as a copy of $copy_of ($new).\n";
92         } else {
93                 print $out "Created branch $ref ($new).\n\n";
94                 most_recent();
95         }
96 } elsif ($new =~ /^0+$/) {
97         # Deletion of a branch
98         $subj .= ' Branch deleted';
99         print $out "Deleted branch $ref ($old).\n";
100 } else {
101         my $lca = `git merge-base $old $new`; die if $?;
102         chomp $lca;
103         if ($lca eq $old) {
104                 # Fast forward ... scan all objects
105                 my @commits = scan_commits();
106                 my @nonmerges = grep { @{$_->{parents}} == 1 } @commits;
107                 @commits or exit;
108
109                 # Construct subject
110                 # Try to recognize simple merges and display them as such
111                 my $c0 = $commits[0];
112                 my $n0 = $nonmerges[0];
113                 my $c0p = $c0->{parents};
114                 if (@{$c0p} == 2 &&
115                     $c0->{subject} =~ m{^\s*Merge branch '([^']*)'} &&
116                     $1 ne $ref &&
117                     ($c0p->[0] eq $old || $c0p->[1] eq $old)) {
118                         # Pushed a merge of a foreign branch to the current one
119                         $subj .= ' ' . $c0->{subject};
120                 } elsif ($n0) {
121                         # Otherwise take the subject of the first non-merge commit
122                         $subj .= ' ' . $n0->{subject};
123                 } else {
124                         # If there is none, take the first merge
125                         $subj .= ' ' . $c0->{subject};
126                 }
127
128                 print $out "Push to branch $ref ($old -> $new)\n\n";
129
130                 # If there are multiple commits, print an overall diffstat first
131                 if (@nonmerges > 1) {
132                         $subj .= ' [' . (scalar @commits) . ' commits]';
133                         print $out 'Pushed ', (scalar @commits), " commits. Overall diffstat:\n\n";
134                         system 'git', 'diff', '--stat', $old, $new;
135                         print $out "\n";
136                 }
137                 my $pos_after_header = output_size();
138
139                 # Show individual commits with diffs and stats
140                 system 'git', 'log', @rev_list_options, @diff_options, '-p', '--stat', "$old..$new";
141
142                 # If the file is too long, truncate it and print just a summary
143                 if (defined($max_diff_size) && output_size() > $max_diff_size) {
144                         $out->truncate($pos_after_header);
145                         output_size();
146                         print $out "Diff was too long, printing just a summary.\n\n";
147                         system 'git', 'log', @rev_list_options, "$old..$new";
148                 }
149         } elsif ($lca eq $new) {
150                 # Rewind
151                 $subj .= ' Branch rewound';
152                 print $out "Rewound branch $ref ($old -> $new).\n\n";
153                 most_recent();
154         } else {
155                 # Otherwise it is a rebase
156                 $subj .= ' Branch rebased';
157                 print $out "Rebased branch $ref ($old -> $new).\n\n";
158                 print $out "Commits from common ancestor:\n\n";
159                 system 'git', 'rev-list', @rev_list_options, $new, "^$old";
160         }
161 }
162
163 $out->close();
164 if (defined $mail_to) {
165         close STDIN;
166         open STDIN, '<', $outname;
167         system 'mutt',
168                 '-F/dev/null',
169                 '-x',
170                 '-e', 'set charset="utf-8"',
171                 '-e', 'set send_charset="us-ascii:iso-8859-2:utf-8"',
172                 '-e', "my_hdr X-Git-Repo: $repo",
173                 '-e', "my_hdr X-Git-Branch: $ref",
174                 '-e', "my_hdr X-Git-Old-SHA: $old",
175                 '-e', "my_hdr X-Git-New-SHA: $new",
176                 '-s', $subj,
177                 $mail_to;
178 } else {
179         open STDOUT, '>&', \*ORIG_STDOUT;
180         print "Subject: $subj\n\n";
181         system 'cat', $outname;
182 }