Tue, 28 Aug 2012 18:29:30 +0200
Update from Drupal 6.x to 7.x and introduce several new HTML5 themes. Because
many themes from Drupal 6.x have since been abandoned, left unmaintained, or
not ported to Drupal 7.x, this package has changed in size and utility.
1 #!/usr/bin/perl -w
2 #
3 # Tool to send git commit notifications
4 #
5 # Copyright 2005 Alexandre Julliard
6 #
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License as
9 # published by the Free Software Foundation; either version 2 of
10 # the License, or (at your option) any later version.
11 #
12 #
13 # This script is meant to be called from .git/hooks/post-receive.
14 #
15 # Usage: git-notify [options] [--] old-sha1 new-sha1 refname
16 #
17 # -c name Send CIA notifications under specified project name
18 # -m addr Send mail notifications to specified address
19 # -n max Set max number of individual mails to send
20 # -r name Set the git repository name
21 # -s bytes Set the maximum diff size in bytes (-1 for no limit)
22 # -u url Set the URL to the gitweb browser
23 # -i branch If at least one -i is given, report only for specified branches
24 # -x branch Exclude changes to the specified branch from reports
25 # -X Exclude merge commits
26 #
28 use strict;
29 use open ':utf8';
30 use Encode 'encode';
31 use Cwd 'realpath';
33 binmode STDIN, ':utf8';
34 binmode STDOUT, ':utf8';
36 sub git_config($);
37 sub get_repos_name();
39 # some parameters you may want to change
41 # CIA notification address
42 my $cia_address = "cia\@cia.navi.cx";
44 # debug mode
45 my $debug = 0;
47 # configuration parameters
49 # base URL of the gitweb repository browser (can be set with the -u option)
50 my $gitweb_url = git_config( "notify.baseurl" );
52 # default repository name (can be changed with the -r option)
53 my $repos_name = git_config( "notify.repository" ) || get_repos_name();
55 # max size of diffs in bytes (can be changed with the -s option)
56 my $max_diff_size = git_config( "notify.maxdiff" ) || 10000;
58 # address for mail notices (can be set with -m option)
59 my $commitlist_address = git_config( "notify.mail" );
61 # project name for CIA notices (can be set with -c option)
62 my $cia_project_name = git_config( "notify.cia" );
64 # max number of individual notices before falling back to a single global notice (can be set with -n option)
65 my $max_individual_notices = git_config( "notify.maxnotices" ) || 100;
67 # branches to include
68 my @include_list = split /\s+/, git_config( "notify.include" ) || "";
70 # branches to exclude
71 my @exclude_list = split /\s+/, git_config( "notify.exclude" ) || "";
73 # set this to something that takes "-s"
74 my $mailer = git_config( "notify.mailer" ) || "/usr/bin/mail";
76 # Extra options to git rev-list
77 my @revlist_options;
79 sub usage()
80 {
81 print "Usage: $0 [options] [--] old-sha1 new-sha1 refname\n";
82 print " -c name Send CIA notifications under specified project name\n";
83 print " -m addr Send mail notifications to specified address\n";
84 print " -n max Set max number of individual mails to send\n";
85 print " -r name Set the git repository name\n";
86 print " -s bytes Set the maximum diff size in bytes (-1 for no limit)\n";
87 print " -u url Set the URL to the gitweb browser\n";
88 print " -i branch If at least one -i is given, report only for specified branches\n";
89 print " -x branch Exclude changes to the specified branch from reports\n";
90 print " -X Exclude merge commits\n";
91 exit 1;
92 }
94 sub xml_escape($)
95 {
96 my $str = shift;
97 $str =~ s/&/&/g;
98 $str =~ s/</</g;
99 $str =~ s/>/>/g;
100 my @chars = unpack "U*", $str;
101 $str = join "", map { ($_ > 127) ? sprintf "&#%u;", $_ : chr($_); } @chars;
102 return $str;
103 }
105 # format an integer date + timezone as string
106 # algorithm taken from git's date.c
107 sub format_date($$)
108 {
109 my ($time,$tz) = @_;
111 if ($tz < 0)
112 {
113 my $minutes = (-$tz / 100) * 60 + (-$tz % 100);
114 $time -= $minutes * 60;
115 }
116 else
117 {
118 my $minutes = ($tz / 100) * 60 + ($tz % 100);
119 $time += $minutes * 60;
120 }
121 return gmtime($time) . sprintf " %+05d", $tz;
122 }
124 # fetch a parameter from the git config file
125 sub git_config($)
126 {
127 my ($param) = @_;
129 open CONFIG, "-|" or exec "git", "config", $param;
130 my $ret = <CONFIG>;
131 chomp $ret if $ret;
132 close CONFIG or $ret = undef;
133 return $ret;
134 }
136 # parse command line options
137 sub parse_options()
138 {
139 while (@ARGV && $ARGV[0] =~ /^-/)
140 {
141 my $arg = shift @ARGV;
143 if ($arg eq '--') { last; }
144 elsif ($arg eq '-c') { $cia_project_name = shift @ARGV; }
145 elsif ($arg eq '-m') { $commitlist_address = shift @ARGV; }
146 elsif ($arg eq '-n') { $max_individual_notices = shift @ARGV; }
147 elsif ($arg eq '-r') { $repos_name = shift @ARGV; }
148 elsif ($arg eq '-s') { $max_diff_size = shift @ARGV; }
149 elsif ($arg eq '-u') { $gitweb_url = shift @ARGV; }
150 elsif ($arg eq '-i') { push @include_list, shift @ARGV; }
151 elsif ($arg eq '-x') { push @exclude_list, shift @ARGV; }
152 elsif ($arg eq '-X') { push @revlist_options, "--no-merges"; }
153 elsif ($arg eq '-d') { $debug++; }
154 else { usage(); }
155 }
156 if (@ARGV && $#ARGV != 2) { usage(); }
157 @exclude_list = map { "^$_"; } @exclude_list;
158 }
160 # send an email notification
161 sub mail_notification($$$@)
162 {
163 my ($name, $subject, $content_type, @text) = @_;
164 $subject = encode("MIME-Q",$subject);
165 if ($debug)
166 {
167 print "---------------------\n";
168 print "To: $name\n";
169 print "Subject: $subject\n";
170 print "Content-Type: $content_type\n";
171 print "\n", join("\n", @text), "\n";
172 }
173 else
174 {
175 my $pid = open MAIL, "|-";
176 return unless defined $pid;
177 if (!$pid)
178 {
179 exec $mailer, "-s", $subject, "-a", "Content-Type: $content_type", $name or die "Cannot exec $mailer";
180 }
181 print MAIL join("\n", @text), "\n";
182 close MAIL;
183 }
184 }
186 # get the default repository name
187 sub get_repos_name()
188 {
189 my $dir = `git rev-parse --git-dir`;
190 chomp $dir;
191 my $repos = realpath($dir);
192 $repos =~ s/(.*?)((\.git\/)?\.git)$/$1/;
193 $repos =~ s/(.*)\/([^\/]+)\/?$/$2/;
194 return $repos;
195 }
197 # extract the information from a commit or tag object and return a hash containing the various fields
198 sub get_object_info($)
199 {
200 my $obj = shift;
201 my %info = ();
202 my @log = ();
203 my $do_log = 0;
205 open TYPE, "-|" or exec "git", "cat-file", "-t", $obj or die "cannot run git-cat-file";
206 my $type = <TYPE>;
207 chomp $type;
208 close TYPE;
210 open OBJ, "-|" or exec "git", "cat-file", $type, $obj or die "cannot run git-cat-file";
211 while (<OBJ>)
212 {
213 chomp;
214 if ($do_log)
215 {
216 last if /^-----BEGIN PGP SIGNATURE-----/;
217 push @log, $_;
218 }
219 elsif (/^(author|committer|tagger) ((.*)(<.*>)) (\d+) ([+-]\d+)$/)
220 {
221 $info{$1} = $2;
222 $info{$1 . "_name"} = $3;
223 $info{$1 . "_email"} = $4;
224 $info{$1 . "_date"} = $5;
225 $info{$1 . "_tz"} = $6;
226 }
227 elsif (/^tag (.*)$/)
228 {
229 $info{"tag"} = $1;
230 }
231 elsif (/^$/) { $do_log = 1; }
232 }
233 close OBJ;
235 $info{"type"} = $type;
236 $info{"log"} = \@log;
237 return %info;
238 }
240 # send a commit notice to a mailing list
241 sub send_commit_notice($$)
242 {
243 my ($ref,$obj) = @_;
244 my %info = get_object_info($obj);
245 my @notice = ();
246 my $subject;
248 if ($info{"type"} eq "tag")
249 {
250 push @notice,
251 "Module: $repos_name",
252 "Branch: $ref",
253 "Tag: $obj",
254 $gitweb_url ? "URL: $gitweb_url/?a=tag;h=$obj\n" : "",
255 "Tagger: " . $info{"tagger"},
256 "Date: " . format_date($info{"tagger_date"},$info{"tagger_tz"}),
257 "",
258 join "\n", @{$info{"log"}};
259 $subject = "Tag " . $info{"tag"} . " : " . $info{"tagger_name"} . ": " . ${$info{"log"}}[0];
260 }
261 else
262 {
263 push @notice,
264 "Module: $repos_name",
265 "Branch: $ref",
266 "Commit: $obj",
267 $gitweb_url ? "URL: $gitweb_url/?a=commit;h=$obj\n" : "",
268 "Author: " . $info{"author"},
269 "Date: " . format_date($info{"author_date"},$info{"author_tz"}),
270 "",
271 join "\n", @{$info{"log"}},
272 "",
273 "---",
274 "";
276 open STAT, "-|" or exec "git", "diff-tree", "--stat", "-M", "--no-commit-id", $obj or die "cannot exec git-diff-tree";
277 push @notice, join("", <STAT>);
278 close STAT;
280 open DIFF, "-|" or exec "git", "diff-tree", "-p", "-M", "--no-commit-id", $obj or die "cannot exec git-diff-tree";
281 my $diff = join( "", <DIFF> );
282 close DIFF;
284 if (($max_diff_size == -1) || (length($diff) < $max_diff_size))
285 {
286 push @notice, $diff;
287 }
288 else
289 {
290 push @notice, "Diff: $gitweb_url/?a=commitdiff;h=$obj" if $gitweb_url;
291 }
293 $subject = $info{"author_name"} . ": " . ${$info{"log"}}[0];
294 }
296 mail_notification($commitlist_address, $subject, "text/plain; charset=UTF-8", @notice);
297 }
299 # send a commit notice to the CIA server
300 sub send_cia_notice($$)
301 {
302 my ($ref,$commit) = @_;
303 my %info = get_object_info($commit);
304 my @cia_text = ();
306 return if $info{"type"} ne "commit";
308 push @cia_text,
309 "<message>",
310 " <generator>",
311 " <name>git-notify script for CIA</name>",
312 " </generator>",
313 " <source>",
314 " <project>" . xml_escape($cia_project_name) . "</project>",
315 " <module>" . xml_escape($repos_name) . "</module>",
316 " <branch>" . xml_escape($ref). "</branch>",
317 " </source>",
318 " <body>",
319 " <commit>",
320 " <revision>" . substr($commit,0,10) . "</revision>",
321 " <author>" . xml_escape($info{"author"}) . "</author>",
322 " <log>" . xml_escape(join "\n", @{$info{"log"}}) . "</log>",
323 " <files>";
325 open COMMIT, "-|" or exec "git", "diff-tree", "--name-status", "-r", "-M", $commit or die "cannot run git-diff-tree";
326 while (<COMMIT>)
327 {
328 chomp;
329 if (/^([AMD])\t(.*)$/)
330 {
331 my ($action, $file) = ($1, $2);
332 my %actions = ( "A" => "add", "M" => "modify", "D" => "remove" );
333 next unless defined $actions{$action};
334 push @cia_text, " <file action=\"$actions{$action}\">" . xml_escape($file) . "</file>";
335 }
336 elsif (/^R\d+\t(.*)\t(.*)$/)
337 {
338 my ($old, $new) = ($1, $2);
339 push @cia_text, " <file action=\"rename\" to=\"" . xml_escape($new) . "\">" . xml_escape($old) . "</file>";
340 }
341 }
342 close COMMIT;
344 push @cia_text,
345 " </files>",
346 $gitweb_url ? " <url>" . xml_escape("$gitweb_url/?a=commit;h=$commit") . "</url>" : "",
347 " </commit>",
348 " </body>",
349 " <timestamp>" . $info{"author_date"} . "</timestamp>",
350 "</message>";
352 mail_notification($cia_address, "DeliverXML", "text/xml", @cia_text);
353 }
355 # send a global commit notice when there are too many commits for individual mails
356 sub send_global_notice($$$)
357 {
358 my ($ref, $old_sha1, $new_sha1) = @_;
359 my @notice = ();
361 push @revlist_options, "--pretty";
362 open LIST, "-|" or exec "git", "rev-list", @revlist_options, "^$old_sha1", "$new_sha1", @exclude_list or die "cannot exec git-rev-list";
363 while (<LIST>)
364 {
365 chomp;
366 s/^commit /URL: $gitweb_url\/?a=commit;h=/ if $gitweb_url;
367 push @notice, $_;
368 }
369 close LIST;
371 mail_notification($commitlist_address, "New commits on branch $ref", "text/plain; charset=UTF-8", @notice);
372 }
374 # send all the notices
375 sub send_all_notices($$$)
376 {
377 my ($old_sha1, $new_sha1, $ref) = @_;
379 $ref =~ s/^refs\/heads\///;
381 return if (@include_list && !grep {$_ eq $ref} @include_list);
383 if ($old_sha1 eq '0' x 40) # new ref
384 {
385 send_commit_notice( $ref, $new_sha1 ) if $commitlist_address;
386 return;
387 }
389 my @commits = ();
391 open LIST, "-|" or exec "git", "rev-list", @revlist_options, "^$old_sha1", "$new_sha1", @exclude_list or die "cannot exec git-rev-list";
392 while (<LIST>)
393 {
394 chomp;
395 die "invalid commit $_" unless /^[0-9a-f]{40}$/;
396 unshift @commits, $_;
397 }
398 close LIST;
400 if (@commits > $max_individual_notices)
401 {
402 send_global_notice( $ref, $old_sha1, $new_sha1 ) if $commitlist_address;
403 return;
404 }
406 foreach my $commit (@commits)
407 {
408 send_commit_notice( $ref, $commit ) if $commitlist_address;
409 send_cia_notice( $ref, $commit ) if $cia_project_name;
410 }
411 }
413 parse_options();
415 # append repository path to URL
416 $gitweb_url .= "/$repos_name.git" if $gitweb_url;
418 if (@ARGV)
419 {
420 send_all_notices( $ARGV[0], $ARGV[1], $ARGV[2] );
421 }
422 else # read them from stdin
423 {
424 while (<>)
425 {
426 chomp;
427 if (/^([0-9a-f]{40}) ([0-9a-f]{40}) (.*)$/) { send_all_notices( $1, $2, $3 ); }
428 }
429 }
431 exit 0;