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.

     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/&/&amp;/g;
    98     $str =~ s/</&lt;/g;
    99     $str =~ s/>/&gt;/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;

mercurial