git/git-notify

Tue, 28 Aug 2012 18:29:30 +0200

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 28 Aug 2012 18:29:30 +0200
changeset 534
d2d0020cfafa
permissions
-rw-r--r--

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.

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

mercurial