diff options
| author | Holger Weiss <holger@zedat.fu-berlin.de> | 2013-09-30 00:03:24 +0200 |
|---|---|---|
| committer | Holger Weiss <holger@zedat.fu-berlin.de> | 2013-09-30 00:03:24 +0200 |
| commit | 0b6423f9c99d9edf8c96fefd0f6c453859395aa1 (patch) | |
| tree | 1c2b6b21704a294940f87c7892676998d8371707 /libexec/git-notify | |
| download | site-0b6423f9c99d9edf8c96fefd0f6c453859395aa1.tar.gz | |
Import Nagios Plugins site
Import the Nagios Plugins web site, Cronjobs, infrastructure scripts,
and configuration files.
Diffstat (limited to 'libexec/git-notify')
| -rwxr-xr-x | libexec/git-notify | 676 |
1 files changed, 676 insertions, 0 deletions
diff --git a/libexec/git-notify b/libexec/git-notify new file mode 100755 index 0000000..8514296 --- /dev/null +++ b/libexec/git-notify | |||
| @@ -0,0 +1,676 @@ | |||
| 1 | #!/usr/bin/perl -w | ||
| 2 | # | ||
| 3 | # Tool to send git commit notifications | ||
| 4 | # | ||
| 5 | # Copyright 2005 Alexandre Julliard | ||
| 6 | # Copyright 2009 Nagios Plugins Development Team | ||
| 7 | # | ||
| 8 | # This program is free software; you can redistribute it and/or | ||
| 9 | # modify it under the terms of the GNU General Public License as | ||
| 10 | # published by the Free Software Foundation; either version 2 of | ||
| 11 | # the License, or (at your option) any later version. | ||
| 12 | # | ||
| 13 | # | ||
| 14 | # This script is meant to be called from .git/hooks/post-receive. | ||
| 15 | # | ||
| 16 | # Usage: git-notify [options] [--] old-sha1 new-sha1 refname | ||
| 17 | # | ||
| 18 | # -A Omit the author name from the mail subject | ||
| 19 | # -C Show committer in the body if different from the author | ||
| 20 | # -c name Send CIA notifications under specified project name | ||
| 21 | # -m addr Send mail notifications to specified address | ||
| 22 | # -n max Set max number of individual mails to send | ||
| 23 | # -r name Set the git repository name | ||
| 24 | # -S Enable compatibility with SourceForge's gitweb URLs | ||
| 25 | # -s bytes Set the maximum diff size in bytes (-1 for no limit) | ||
| 26 | # -T Prefix the mail subject with a [repository name] tag | ||
| 27 | # -t file Prevent duplicate notifications by saving state to this file | ||
| 28 | # -U mask Set the umask for creating the state file | ||
| 29 | # -u url Set the URL to the gitweb browser | ||
| 30 | # -i branch If at least one -i is given, report only for specified branches | ||
| 31 | # -x branch Exclude changes to the specified branch from reports | ||
| 32 | # -X Exclude merge commits | ||
| 33 | # -z Try to abbreviate the SHA1 name within gitweb URLs (unsafe) | ||
| 34 | # | ||
| 35 | |||
| 36 | use strict; | ||
| 37 | use Fcntl ':flock'; | ||
| 38 | use Encode qw(encode decode); | ||
| 39 | use Cwd 'realpath'; | ||
| 40 | |||
| 41 | sub git_config($); | ||
| 42 | sub get_repos_name(); | ||
| 43 | |||
| 44 | # some parameters you may want to change | ||
| 45 | |||
| 46 | # sendmail's pathname | ||
| 47 | my $sendmail = "/usr/sbin/sendmail"; | ||
| 48 | |||
| 49 | # CIA notification address | ||
| 50 | my $cia_address = "cia\@cia.vc"; | ||
| 51 | |||
| 52 | # debug mode | ||
| 53 | my $debug = 0; | ||
| 54 | |||
| 55 | # configuration parameters | ||
| 56 | |||
| 57 | # omit the author from the mail subject (can be set with the -A option) | ||
| 58 | my $omit_author = git_config( "notify.omitauthor" ); | ||
| 59 | |||
| 60 | # prefix the mail subject with a [repository name] tag (can be set with the -T option) | ||
| 61 | my $emit_repo = git_config( "notify.emitrepository" ); | ||
| 62 | |||
| 63 | # show the committer if different from the author (can be set with the -C option) | ||
| 64 | my $show_committer = git_config( "notify.showcommitter" ); | ||
| 65 | |||
| 66 | # base URL of the gitweb repository browser (can be set with the -u option) | ||
| 67 | my $gitweb_url = git_config( "notify.baseurl" ); | ||
| 68 | |||
| 69 | # abbreviate the SHA1 name within gitweb URLs (can be set with the -z option) | ||
| 70 | my $abbreviate_url = git_config( "notify.shorturls" ); | ||
| 71 | |||
| 72 | # don't report merge commits (can be set with the -X option) | ||
| 73 | my $ignore_merges = git_config( "notify.ignoremerges" ); | ||
| 74 | |||
| 75 | # enable compatibility with SourceForge's gitweb (can be set with the -S option) | ||
| 76 | my $sourceforge = git_config( "notify.sourceforge" ); | ||
| 77 | |||
| 78 | # default repository name (can be changed with the -r option) | ||
| 79 | my $repos_name = git_config( "notify.repository" ) || get_repos_name(); | ||
| 80 | |||
| 81 | # max size of diffs in bytes (can be changed with the -s option) | ||
| 82 | my $max_diff_size = git_config( "notify.maxdiff" ) || 10000; | ||
| 83 | |||
| 84 | # address for mail notices (can be set with -m option) | ||
| 85 | my $commitlist_address = git_config( "notify.mail" ); | ||
| 86 | |||
| 87 | # project name for CIA notices (can be set with -c option) | ||
| 88 | my $cia_project_name = git_config( "notify.cia" ); | ||
| 89 | |||
| 90 | # max number of individual notices before falling back to a single global notice (can be set with -n option) | ||
| 91 | my $max_individual_notices = git_config( "notify.maxnotices" ) || 100; | ||
| 92 | |||
| 93 | # branches to include | ||
| 94 | my @include_list = split /\s+/, git_config( "notify.include" ) || ""; | ||
| 95 | |||
| 96 | # branches to exclude | ||
| 97 | my @exclude_list = split /\s+/, git_config( "notify.exclude" ) || ""; | ||
| 98 | |||
| 99 | # the state file we use (can be set with the -t option) | ||
| 100 | my $state_file = git_config( "notify.statefile" ); | ||
| 101 | |||
| 102 | # umask for creating the state file (can be set with -U option) | ||
| 103 | my $mode_mask = git_config( "notify.umask" ) || 002; | ||
| 104 | |||
| 105 | sub usage() | ||
| 106 | { | ||
| 107 | print "Usage: $0 [options] [--] old-sha1 new-sha1 refname\n"; | ||
| 108 | print " -A Omit the author name from the mail subject\n"; | ||
| 109 | print " -C Show committer in the body if different from the author\n"; | ||
| 110 | print " -c name Send CIA notifications under specified project name\n"; | ||
| 111 | print " -m addr Send mail notifications to specified address\n"; | ||
| 112 | print " -n max Set max number of individual mails to send\n"; | ||
| 113 | print " -r name Set the git repository name\n"; | ||
| 114 | print " -S Enable compatibility with SourceForge's gitweb URLs\n"; | ||
| 115 | print " -s bytes Set the maximum diff size in bytes (-1 for no limit)\n"; | ||
| 116 | print " -T Prefix the mail subject with a [repository name] tag\n"; | ||
| 117 | print " -t file Prevent duplicate notifications by saving state to this file\n"; | ||
| 118 | print " -U mask Set the umask for creating the state file\n"; | ||
| 119 | print " -u url Set the URL to the gitweb browser\n"; | ||
| 120 | print " -i branch If at least one -i is given, report only for specified branches\n"; | ||
| 121 | print " -x branch Exclude changes to the specified branch from reports\n"; | ||
| 122 | print " -X Exclude merge commits\n"; | ||
| 123 | print " -z Try to abbreviate the SHA1 name within gitweb URLs (unsafe)\n"; | ||
| 124 | exit 1; | ||
| 125 | } | ||
| 126 | |||
| 127 | sub xml_escape($) | ||
| 128 | { | ||
| 129 | my $str = shift; | ||
| 130 | $str =~ s/&/&/g; | ||
| 131 | $str =~ s/</</g; | ||
| 132 | $str =~ s/>/>/g; | ||
| 133 | my @chars = unpack "U*", $str; | ||
| 134 | $str = join "", map { ($_ > 127) ? sprintf "&#%u;", $_ : chr($_); } @chars; | ||
| 135 | return $str; | ||
| 136 | } | ||
| 137 | |||
| 138 | # execute git-rev-list(1) with the given parameters and return the output | ||
| 139 | sub git_rev_list(@) | ||
| 140 | { | ||
| 141 | my @args = @_; | ||
| 142 | my $revlist = []; | ||
| 143 | my $pid = open REVLIST, "-|"; | ||
| 144 | |||
| 145 | die "Cannot open pipe: $!" if not defined $pid; | ||
| 146 | if (!$pid) | ||
| 147 | { | ||
| 148 | exec "git", "rev-list", "--reverse", @args or die "Cannot execute rev-list: $!"; | ||
| 149 | } | ||
| 150 | while (<REVLIST>) | ||
| 151 | { | ||
| 152 | chomp; | ||
| 153 | unless (grep {$_ eq "--pretty"} @args) | ||
| 154 | { | ||
| 155 | die "Invalid commit: $_" if not /^[0-9a-f]{40}$/; | ||
| 156 | } | ||
| 157 | push @$revlist, $_; | ||
| 158 | } | ||
| 159 | close REVLIST or die $! ? "Cannot execute rev-list: $!" : "rev-list exited with status: $?"; | ||
| 160 | return $revlist; | ||
| 161 | } | ||
| 162 | |||
| 163 | # append the given commit hashes to the state file | ||
| 164 | sub save_commits($) | ||
| 165 | { | ||
| 166 | my $commits = shift; | ||
| 167 | |||
| 168 | open STATE, ">>", $state_file or die "Cannot open $state_file: $!"; | ||
| 169 | flock STATE, LOCK_EX or die "Cannot lock $state_file"; | ||
| 170 | print STATE "$_\n" for @$commits; | ||
| 171 | flock STATE, LOCK_UN or die "Cannot unlock $state_file"; | ||
| 172 | close STATE or die "Cannot close $state_file: $!"; | ||
| 173 | } | ||
| 174 | |||
| 175 | # for the given range, return the new hashes (and append them to the state file) | ||
| 176 | sub get_new_commits($$) | ||
| 177 | { | ||
| 178 | my ($old_sha1, $new_sha1) = @_; | ||
| 179 | my ($seen, @args); | ||
| 180 | my $newrevs = []; | ||
| 181 | |||
| 182 | @args = ( "^$old_sha1" ) unless $old_sha1 eq '0' x 40; | ||
| 183 | push @args, $new_sha1, @exclude_list; | ||
| 184 | unshift @args, "--no-merges" if $ignore_merges; | ||
| 185 | |||
| 186 | my $revlist = git_rev_list(@args); | ||
| 187 | |||
| 188 | if (not defined $state_file or not -e $state_file) | ||
| 189 | { | ||
| 190 | save_commits(git_rev_list("--all", "--full-history")) if defined $state_file; | ||
| 191 | return $revlist; | ||
| 192 | } | ||
| 193 | |||
| 194 | open STATE, $state_file or die "Cannot open $state_file: $!"; | ||
| 195 | flock STATE, LOCK_SH or die "Cannot lock $state_file"; | ||
| 196 | while (<STATE>) | ||
| 197 | { | ||
| 198 | chomp; | ||
| 199 | die "Invalid commit: $_" if not /^[0-9a-f]{40}$/; | ||
| 200 | $seen->{$_} = 1; | ||
| 201 | } | ||
| 202 | flock STATE, LOCK_UN or die "Cannot unlock $state_file"; | ||
| 203 | close STATE or die "Cannot close $state_file: $!"; | ||
| 204 | |||
| 205 | # FIXME: if another git-notify process reads the $state_file at *this* | ||
| 206 | # point, that process might generate duplicates of our notifications. | ||
| 207 | |||
| 208 | save_commits($revlist); | ||
| 209 | |||
| 210 | foreach my $commit (@$revlist) | ||
| 211 | { | ||
| 212 | push @$newrevs, $commit unless $seen->{$commit}; | ||
| 213 | } | ||
| 214 | return $newrevs; | ||
| 215 | } | ||
| 216 | |||
| 217 | # truncate the given string if it exceeds the specified number of characters | ||
| 218 | sub truncate_str($$) | ||
| 219 | { | ||
| 220 | my ($str, $max) = @_; | ||
| 221 | |||
| 222 | if (length($str) > $max) | ||
| 223 | { | ||
| 224 | $str = substr($str, 0, $max); | ||
| 225 | $str =~ s/\s+\S+$//; | ||
| 226 | $str .= " ..."; | ||
| 227 | } | ||
| 228 | return $str; | ||
| 229 | } | ||
| 230 | |||
| 231 | # right-justify the left column of "left: right" elements, omit undefined elements | ||
| 232 | sub format_table(@) | ||
| 233 | { | ||
| 234 | my @lines = @_; | ||
| 235 | my @table; | ||
| 236 | my $max = 0; | ||
| 237 | |||
| 238 | foreach my $line (@lines) | ||
| 239 | { | ||
| 240 | next if not defined $line; | ||
| 241 | my $pos = index($line, ":"); | ||
| 242 | |||
| 243 | $max = $pos if $pos > $max; | ||
| 244 | } | ||
| 245 | |||
| 246 | foreach my $line (@lines) | ||
| 247 | { | ||
| 248 | next if not defined $line; | ||
| 249 | my ($left, $right) = split(/: */, $line, 2); | ||
| 250 | |||
| 251 | push @table, (defined $left and defined $right) | ||
| 252 | ? sprintf("%*s: %s", $max + 1, $left, $right) | ||
| 253 | : $line; | ||
| 254 | } | ||
| 255 | return @table; | ||
| 256 | } | ||
| 257 | |||
| 258 | # format an integer date + timezone as string | ||
| 259 | # algorithm taken from git's date.c | ||
| 260 | sub format_date($$) | ||
| 261 | { | ||
| 262 | my ($time,$tz) = @_; | ||
| 263 | |||
| 264 | if ($tz < 0) | ||
| 265 | { | ||
| 266 | my $minutes = (-$tz / 100) * 60 + (-$tz % 100); | ||
| 267 | $time -= $minutes * 60; | ||
| 268 | } | ||
| 269 | else | ||
| 270 | { | ||
| 271 | my $minutes = ($tz / 100) * 60 + ($tz % 100); | ||
| 272 | $time += $minutes * 60; | ||
| 273 | } | ||
| 274 | return gmtime($time) . sprintf " %+05d", $tz; | ||
| 275 | } | ||
| 276 | |||
| 277 | # fetch a parameter from the git config file | ||
| 278 | sub git_config($) | ||
| 279 | { | ||
| 280 | my ($param) = @_; | ||
| 281 | |||
| 282 | open CONFIG, "-|" or exec "git", "config", $param; | ||
| 283 | my $ret = <CONFIG>; | ||
| 284 | chomp $ret if $ret; | ||
| 285 | close CONFIG or $ret = undef; | ||
| 286 | return $ret; | ||
| 287 | } | ||
| 288 | |||
| 289 | # parse command line options | ||
| 290 | sub parse_options() | ||
| 291 | { | ||
| 292 | while (@ARGV && $ARGV[0] =~ /^-/) | ||
| 293 | { | ||
| 294 | my $arg = shift @ARGV; | ||
| 295 | |||
| 296 | if ($arg eq '--') { last; } | ||
| 297 | elsif ($arg eq '-A') { $omit_author = 1; } | ||
| 298 | elsif ($arg eq '-C') { $show_committer = 1; } | ||
| 299 | elsif ($arg eq '-c') { $cia_project_name = shift @ARGV; } | ||
| 300 | elsif ($arg eq '-m') { $commitlist_address = shift @ARGV; } | ||
| 301 | elsif ($arg eq '-n') { $max_individual_notices = shift @ARGV; } | ||
| 302 | elsif ($arg eq '-r') { $repos_name = shift @ARGV; } | ||
| 303 | elsif ($arg eq '-S') { $sourceforge = 1; } | ||
| 304 | elsif ($arg eq '-s') { $max_diff_size = shift @ARGV; } | ||
| 305 | elsif ($arg eq '-T') { $emit_repo = 1; } | ||
| 306 | elsif ($arg eq '-t') { $state_file = shift @ARGV; } | ||
| 307 | elsif ($arg eq '-U') { $mode_mask = shift @ARGV; } | ||
| 308 | elsif ($arg eq '-u') { $gitweb_url = shift @ARGV; } | ||
| 309 | elsif ($arg eq '-i') { push @include_list, shift @ARGV; } | ||
| 310 | elsif ($arg eq '-X') { $ignore_merges = 1; } | ||
| 311 | elsif ($arg eq '-x') { push @exclude_list, shift @ARGV; } | ||
| 312 | elsif ($arg eq '-z') { $abbreviate_url = 1; } | ||
| 313 | elsif ($arg eq '-d') { $debug++; } | ||
| 314 | else { usage(); } | ||
| 315 | } | ||
| 316 | if (@ARGV && $#ARGV != 2) { usage(); } | ||
| 317 | @exclude_list = map { "^$_"; } @exclude_list; | ||
| 318 | } | ||
| 319 | |||
| 320 | # send an email notification | ||
| 321 | sub mail_notification($$$@) | ||
| 322 | { | ||
| 323 | my ($name, $subject, $content_type, @text) = @_; | ||
| 324 | |||
| 325 | $subject = "[$repos_name] $subject" if ($emit_repo and $name ne $cia_address); | ||
| 326 | $subject = encode("MIME-Q",$subject); | ||
| 327 | |||
| 328 | my @header = ("To: $name", "Subject: $subject", "Content-Type: $content_type"); | ||
| 329 | |||
| 330 | if ($debug) | ||
| 331 | { | ||
| 332 | binmode STDOUT, ":utf8"; | ||
| 333 | print "---------------------\n"; | ||
| 334 | print join("\n", @header), "\n\n", join("\n", @text), "\n"; | ||
| 335 | } | ||
| 336 | else | ||
| 337 | { | ||
| 338 | my $pid = open MAIL, "|-"; | ||
| 339 | return unless defined $pid; | ||
| 340 | if (!$pid) | ||
| 341 | { | ||
| 342 | exec $sendmail, "-t", "-oi", "-oem" or die "Cannot exec $sendmail"; | ||
| 343 | } | ||
| 344 | binmode MAIL, ":utf8"; | ||
| 345 | print MAIL join("\n", @header), "\n\n", join("\n", @text), "\n"; | ||
| 346 | close MAIL or warn $! ? "Cannot execute $sendmail: $!" : "$sendmail exited with status: $?"; | ||
| 347 | } | ||
| 348 | } | ||
| 349 | |||
| 350 | # get the default repository name | ||
| 351 | sub get_repos_name() | ||
| 352 | { | ||
| 353 | my $dir = `git rev-parse --git-dir`; | ||
| 354 | chomp $dir; | ||
| 355 | my $repos = realpath($dir); | ||
| 356 | $repos =~ s/(.*?)((\.git\/)?\.git)$/$1/; | ||
| 357 | $repos =~ s/(.*)\/([^\/]+)\/?$/$2/; | ||
| 358 | return $repos; | ||
| 359 | } | ||
| 360 | |||
| 361 | # return the type of the given object | ||
| 362 | sub get_object_type($) | ||
| 363 | { | ||
| 364 | my $obj = shift; | ||
| 365 | |||
| 366 | open TYPE, "-|" or exec "git", "cat-file", "-t", $obj or die "cannot run git-cat-file"; | ||
| 367 | my $type = <TYPE>; | ||
| 368 | chomp $type; | ||
| 369 | close TYPE or die $! ? "Cannot execute cat-file: $!" : "cat-file exited with status: $?"; | ||
| 370 | return $type; | ||
| 371 | } | ||
| 372 | |||
| 373 | # extract the information from a commit or tag object and return a hash containing the various fields | ||
| 374 | sub get_object_info($) | ||
| 375 | { | ||
| 376 | my $obj = shift; | ||
| 377 | my %info = (); | ||
| 378 | my @log = (); | ||
| 379 | my $do_log = 0; | ||
| 380 | |||
| 381 | $info{"encoding"} = "utf-8"; | ||
| 382 | |||
| 383 | my $type = get_object_type($obj); | ||
| 384 | |||
| 385 | open OBJ, "-|" or exec "git", "cat-file", $type, $obj or die "cannot run git-cat-file"; | ||
| 386 | while (<OBJ>) | ||
| 387 | { | ||
| 388 | chomp; | ||
| 389 | if ($do_log) | ||
| 390 | { | ||
| 391 | last if /^-----BEGIN PGP SIGNATURE-----/; | ||
| 392 | push @log, $_; | ||
| 393 | } | ||
| 394 | elsif (/^(author|committer|tagger) ((.*) (<.*>)) (\d+) ([+-]\d+)$/) | ||
| 395 | { | ||
| 396 | $info{$1} = $2; | ||
| 397 | $info{$1 . "_name"} = $3; | ||
| 398 | $info{$1 . "_email"} = $4; | ||
| 399 | $info{$1 . "_date"} = $5; | ||
| 400 | $info{$1 . "_tz"} = $6; | ||
| 401 | } | ||
| 402 | elsif (/^tag (.+)/) | ||
| 403 | { | ||
| 404 | $info{"tag"} = $1; | ||
| 405 | } | ||
| 406 | elsif (/^encoding (.+)/) | ||
| 407 | { | ||
| 408 | $info{"encoding"} = $1; | ||
| 409 | } | ||
| 410 | elsif (/^$/) { $do_log = 1; } | ||
| 411 | } | ||
| 412 | close OBJ or die $! ? "Cannot execute cat-file: $!" : "cat-file exited with status: $?"; | ||
| 413 | |||
| 414 | $info{"type"} = $type; | ||
| 415 | $info{"log"} = \@log; | ||
| 416 | return %info; | ||
| 417 | } | ||
| 418 | |||
| 419 | # send a ref change notice to a mailing list | ||
| 420 | sub send_ref_notice($$@) | ||
| 421 | { | ||
| 422 | my ($ref, $action, @notice) = @_; | ||
| 423 | my ($reftype, $refname) = ($ref =~ /^refs\/(head|tag)s\/(.+)/); | ||
| 424 | |||
| 425 | $reftype =~ s/^head$/branch/; | ||
| 426 | |||
| 427 | @notice = (format_table( | ||
| 428 | "Module: $repos_name", | ||
| 429 | ($reftype eq "tag" ? "Tag:" : "Branch:") . $refname, | ||
| 430 | @notice, | ||
| 431 | ($action ne "removed" and $gitweb_url) | ||
| 432 | ? "URL: ${gitweb_url}a=shortlog;h=$ref" : undef), | ||
| 433 | "", | ||
| 434 | "The $refname $reftype has been $action."); | ||
| 435 | |||
| 436 | mail_notification($commitlist_address, "$refname $reftype $action", | ||
| 437 | "text/plain; charset=us-ascii", @notice); | ||
| 438 | } | ||
| 439 | |||
| 440 | # send a commit notice to a mailing list | ||
| 441 | sub send_commit_notice($$) | ||
| 442 | { | ||
| 443 | my ($ref,$obj) = @_; | ||
| 444 | my %info = get_object_info($obj); | ||
| 445 | my @notice = (); | ||
| 446 | my ($url,$subject,$obj_string); | ||
| 447 | |||
| 448 | if ($gitweb_url) | ||
| 449 | { | ||
| 450 | if ($abbreviate_url) | ||
| 451 | { | ||
| 452 | open REVPARSE, "-|" or exec "git", "rev-parse", "--short", $obj or die "cannot exec git-rev-parse"; | ||
| 453 | $obj_string = <REVPARSE>; | ||
| 454 | chomp $obj_string if defined $obj_string; | ||
| 455 | close REVPARSE or die $! ? "Cannot execute rev-parse: $!" : "rev-parse exited with status: $?"; | ||
| 456 | } | ||
| 457 | $obj_string = $obj if not defined $obj_string; | ||
| 458 | $url = "${gitweb_url}a=$info{type};h=$obj_string"; | ||
| 459 | } | ||
| 460 | |||
| 461 | if ($info{"type"} eq "tag") | ||
| 462 | { | ||
| 463 | push @notice, format_table( | ||
| 464 | "Module: $repos_name", | ||
| 465 | "Tag: $ref", | ||
| 466 | "SHA1: $obj", | ||
| 467 | "Tagger:" . $info{"tagger"}, | ||
| 468 | "Date:" . format_date($info{"tagger_date"},$info{"tagger_tz"}), | ||
| 469 | $url ? "URL: $url" : undef), | ||
| 470 | "", | ||
| 471 | join "\n", @{$info{"log"}}; | ||
| 472 | |||
| 473 | $subject = "Tag " . $info{"tag"} . ": "; | ||
| 474 | $subject .= $info{"tagger_name"} . ": " unless $omit_author; | ||
| 475 | } | ||
| 476 | else | ||
| 477 | { | ||
| 478 | push @notice, format_table( | ||
| 479 | "Module: $repos_name", | ||
| 480 | "Branch: $ref", | ||
| 481 | "Commit: $obj", | ||
| 482 | "Author:" . $info{"author"}, | ||
| 483 | $show_committer && $info{"committer"} ne $info{"author"} ? "Committer:" . $info{"committer"} : undef, | ||
| 484 | "Date:" . format_date($info{"author_date"},$info{"author_tz"}), | ||
| 485 | $url ? "URL: $url" : undef), | ||
| 486 | "", | ||
| 487 | @{$info{"log"}}, | ||
| 488 | "", | ||
| 489 | "---", | ||
| 490 | ""; | ||
| 491 | |||
| 492 | open STAT, "-|" or exec "git", "diff-tree", "--stat", "-M", "--no-commit-id", $obj or die "cannot exec git-diff-tree"; | ||
| 493 | push @notice, join("", <STAT>); | ||
| 494 | close STAT or die $! ? "Cannot execute diff-tree: $!" : "diff-tree exited with status: $?"; | ||
| 495 | |||
| 496 | open DIFF, "-|" or exec "git", "diff-tree", "-p", "-M", "--no-commit-id", $obj or die "cannot exec git-diff-tree"; | ||
| 497 | my $diff = join("", <DIFF>); | ||
| 498 | close DIFF or die $! ? "Cannot execute diff-tree: $!" : "diff-tree exited with status: $?"; | ||
| 499 | |||
| 500 | if (($max_diff_size == -1) || (length($diff) < $max_diff_size)) | ||
| 501 | { | ||
| 502 | push @notice, $diff; | ||
| 503 | } | ||
| 504 | else | ||
| 505 | { | ||
| 506 | push @notice, "Diff: ${gitweb_url}a=commitdiff;h=$obj_string" if $gitweb_url; | ||
| 507 | } | ||
| 508 | $subject = $info{"author_name"} . ": " unless $omit_author; | ||
| 509 | } | ||
| 510 | |||
| 511 | $subject .= truncate_str(${$info{"log"}}[0],50); | ||
| 512 | $_ = decode($info{"encoding"}, $_) for @notice; | ||
| 513 | mail_notification($commitlist_address, $subject, "text/plain; charset=UTF-8", @notice); | ||
| 514 | } | ||
| 515 | |||
| 516 | # send a commit notice to the CIA server | ||
| 517 | sub send_cia_notice($$) | ||
| 518 | { | ||
| 519 | my ($ref,$commit) = @_; | ||
| 520 | my %info = get_object_info($commit); | ||
| 521 | my @cia_text = (); | ||
| 522 | |||
| 523 | return if $info{"type"} ne "commit"; | ||
| 524 | |||
| 525 | push @cia_text, | ||
| 526 | "<message>", | ||
| 527 | " <generator>", | ||
| 528 | " <name>git-notify script for CIA</name>", | ||
| 529 | " </generator>", | ||
| 530 | " <source>", | ||
| 531 | " <project>" . xml_escape($cia_project_name) . "</project>", | ||
| 532 | " <module>" . xml_escape($repos_name) . "</module>", | ||
| 533 | " <branch>" . xml_escape($ref). "</branch>", | ||
| 534 | " </source>", | ||
| 535 | " <body>", | ||
| 536 | " <commit>", | ||
| 537 | " <revision>" . substr($commit,0,10) . "</revision>", | ||
| 538 | " <author>" . xml_escape($info{"author"}) . "</author>", | ||
| 539 | " <log>" . xml_escape(join "\n", @{$info{"log"}}) . "</log>", | ||
| 540 | " <files>"; | ||
| 541 | |||
| 542 | open COMMIT, "-|" or exec "git", "diff-tree", "--name-status", "-r", "-M", $commit or die "cannot run git-diff-tree"; | ||
| 543 | while (<COMMIT>) | ||
| 544 | { | ||
| 545 | chomp; | ||
| 546 | if (/^([AMD])\t(.*)$/) | ||
| 547 | { | ||
| 548 | my ($action, $file) = ($1, $2); | ||
| 549 | my %actions = ( "A" => "add", "M" => "modify", "D" => "remove" ); | ||
| 550 | next unless defined $actions{$action}; | ||
| 551 | push @cia_text, " <file action=\"$actions{$action}\">" . xml_escape($file) . "</file>"; | ||
| 552 | } | ||
| 553 | elsif (/^R\d+\t(.*)\t(.*)$/) | ||
| 554 | { | ||
| 555 | my ($old, $new) = ($1, $2); | ||
| 556 | push @cia_text, " <file action=\"rename\" to=\"" . xml_escape($new) . "\">" . xml_escape($old) . "</file>"; | ||
| 557 | } | ||
| 558 | } | ||
| 559 | close COMMIT or die $! ? "Cannot execute diff-tree: $!" : "diff-tree exited with status: $?"; | ||
| 560 | |||
| 561 | push @cia_text, | ||
| 562 | " </files>", | ||
| 563 | $gitweb_url ? " <url>" . xml_escape("${gitweb_url}a=commit;h=$commit") . "</url>" : "", | ||
| 564 | " </commit>", | ||
| 565 | " </body>", | ||
| 566 | " <timestamp>" . $info{"author_date"} . "</timestamp>", | ||
| 567 | "</message>"; | ||
| 568 | |||
| 569 | mail_notification($cia_address, "DeliverXML", "text/xml", @cia_text); | ||
| 570 | } | ||
| 571 | |||
| 572 | # send a global commit notice when there are too many commits for individual mails | ||
| 573 | sub send_global_notice($$$) | ||
| 574 | { | ||
| 575 | my ($ref, $old_sha1, $new_sha1) = @_; | ||
| 576 | my $notice = git_rev_list("--pretty", "^$old_sha1", "$new_sha1", @exclude_list); | ||
| 577 | |||
| 578 | foreach my $rev (@$notice) | ||
| 579 | { | ||
| 580 | $rev =~ s/^commit /URL: ${gitweb_url}a=commit;h=/ if $gitweb_url; | ||
| 581 | } | ||
| 582 | |||
| 583 | mail_notification($commitlist_address, "New commits on branch $ref", "text/plain; charset=UTF-8", @$notice); | ||
| 584 | } | ||
| 585 | |||
| 586 | # send all the notices | ||
| 587 | sub send_all_notices($$$) | ||
| 588 | { | ||
| 589 | my ($old_sha1, $new_sha1, $ref) = @_; | ||
| 590 | my ($reftype, $refname, $tagtype, $action, @notice); | ||
| 591 | |||
| 592 | return if ($ref =~ /^refs\/remotes\// | ||
| 593 | or (@include_list && !grep {$_ eq $ref} @include_list)); | ||
| 594 | die "The name \"$ref\" doesn't sound like a local branch or tag" | ||
| 595 | if not (($reftype, $refname) = ($ref =~ /^refs\/(head|tag)s\/(.+)/)); | ||
| 596 | |||
| 597 | if ($reftype eq "tag") | ||
| 598 | { | ||
| 599 | $tagtype = get_object_type($ref) eq "tag" ? "annotated" : "lightweight"; | ||
| 600 | } | ||
| 601 | |||
| 602 | if ($new_sha1 eq '0' x 40) | ||
| 603 | { | ||
| 604 | $action = "removed"; | ||
| 605 | @notice = ( "Old SHA1: $old_sha1" ); | ||
| 606 | } | ||
| 607 | elsif ($old_sha1 eq '0' x 40) | ||
| 608 | { | ||
| 609 | if ($reftype eq "tag" and $tagtype eq "annotated") | ||
| 610 | { | ||
| 611 | send_commit_notice( $refname, $new_sha1 ) if $commitlist_address; | ||
| 612 | return; | ||
| 613 | } | ||
| 614 | $action = "created"; | ||
| 615 | @notice = ( "SHA1: $new_sha1" ); | ||
| 616 | } | ||
| 617 | elsif ($reftype eq "tag") | ||
| 618 | { | ||
| 619 | $action = "updated"; | ||
| 620 | @notice = ( "Old SHA1: $old_sha1", "New SHA1: $new_sha1" ); | ||
| 621 | } | ||
| 622 | elsif (not grep( $_ eq $old_sha1, @{ git_rev_list( $new_sha1, "--full-history" ) } )) | ||
| 623 | { | ||
| 624 | $action = "rewritten"; | ||
| 625 | @notice = ( "Old SHA1: $old_sha1", "New SHA1: $new_sha1" ); | ||
| 626 | } | ||
| 627 | |||
| 628 | send_ref_notice( $ref, $action, @notice ) if ($commitlist_address and $action); | ||
| 629 | |||
| 630 | unless ($reftype eq "tag" or $new_sha1 eq '0' x 40) | ||
| 631 | { | ||
| 632 | my $commits = get_new_commits ( $old_sha1, $new_sha1 ); | ||
| 633 | |||
| 634 | if (@$commits > $max_individual_notices) | ||
| 635 | { | ||
| 636 | send_global_notice( $refname, $old_sha1, $new_sha1 ) if $commitlist_address; | ||
| 637 | } | ||
| 638 | elsif (@$commits > 0) | ||
| 639 | { | ||
| 640 | foreach my $commit (@$commits) | ||
| 641 | { | ||
| 642 | send_commit_notice( $refname, $commit ) if $commitlist_address; | ||
| 643 | send_cia_notice( $refname, $commit ) if $cia_project_name; | ||
| 644 | } | ||
| 645 | } | ||
| 646 | elsif ($commitlist_address) | ||
| 647 | { | ||
| 648 | @notice = ( "Old SHA1: $old_sha1", "New SHA1: $new_sha1" ); | ||
| 649 | send_ref_notice( $ref, "modified", @notice ); | ||
| 650 | } | ||
| 651 | } | ||
| 652 | } | ||
| 653 | |||
| 654 | parse_options(); | ||
| 655 | |||
| 656 | umask( $mode_mask ); | ||
| 657 | |||
| 658 | # append repository path to URL | ||
| 659 | if ($gitweb_url) { | ||
| 660 | $gitweb_url .= $sourceforge ? "/$repos_name;" : "/$repos_name.git/?"; | ||
| 661 | } | ||
| 662 | |||
| 663 | if (@ARGV) | ||
| 664 | { | ||
| 665 | send_all_notices( $ARGV[0], $ARGV[1], $ARGV[2] ); | ||
| 666 | } | ||
| 667 | else # read them from stdin | ||
| 668 | { | ||
| 669 | while (<>) | ||
| 670 | { | ||
| 671 | chomp; | ||
| 672 | if (/^([0-9a-f]{40}) ([0-9a-f]{40}) (.*)$/) { send_all_notices( $1, $2, $3 ); } | ||
| 673 | } | ||
| 674 | } | ||
| 675 | |||
| 676 | exit 0; | ||
