{"thread":{"id":"14590","subject":"post-receive-hook emailer","startedAt":"2008-07-22T09:34:35Z","lastAt":"2008-07-22T16:45:03Z","messageCount":3,"participants":["Ask Bjørn Hansen","Abhijit Menon-Sen","Vincent Kergonna"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"84315","messageId":"00AEED4D-BD34-4584-B303-32C5F587EF0F@develooper.com","threadId":"14590","inReplyTo":null,"subject":"post-receive-hook emailer","fromName":"Ask Bjørn Hansen","fromEmail":"ask@develooper.com","sentAt":"2008-07-22T09:34:35Z","receivedAt":"2008-07-22T09:34:35Z","isPatch":false,"sender":{"key":"ask@develooper.com","avatar":"https://gravatar.com/avatar/05ce68433216df7d04bb0d82b7d93b11957e2137e6ed23c4b5bc061d78635f2f?d=mp&s=160"},"body":"Hi everyone,\n\nAnyone know what is used to send the http://marc.info/?l=git-commits-head \n  mails?\n\nThe post-receive contrib hook in the repository isn't working for us -  \nwe want diffs and such in our mails.   I started working on patching  \nthe script in contrib; but it's not really clear to me if the current  \nstate of that script is as it should be and I just have a different  \nuse case.\n\nAnyway - anyone have a mailer that sends diffs and such?  :-)   Or  \nshould I just write a wrapper around format-patch and send-email?    \n(To handle branches well it gets slightly complicated, quick - or  \nmaybe I just haven't understood how the post-receive hook works).\n\n\n  - ask\n\n-- \nhttp://develooper.com/ - http://askask.com/\n"},{"id":"84319","messageId":"20080722104022.GA6621@toroid.org","threadId":"14590","inReplyTo":"00AEED4D-BD34-4584-B303-32C5F587EF0F@develooper.com","subject":"Re: post-receive-hook emailer","fromName":"Abhijit Menon-Sen","fromEmail":"ams@toroid.org","sentAt":"2008-07-22T10:40:22Z","receivedAt":"2008-07-22T10:40:22Z","isPatch":false,"sender":{"key":"ams@toroid.org","avatar":null},"body":"Hi Ask.\n\nAt 2008-07-22 02:34:35 -0700, ask@develooper.com wrote:\n>\n> Anyway - anyone have a mailer that sends diffs and such? :-)\n\nNot quite what you want, but I wrote the attached post-receive hook/hack\nto send change notifications by Jabber. \"hooks.notify.recipients\" should\ncontain a list of jabber IDs; and you can change the \"git log\" line, for\nexample by adding \"-p --stat\" or something, to change what gets sent.\n\nIt should be trivial to change the last twenty-odd lines to send email\ninstead.\n\n-- ams\n\n#!/usr/bin/perl\n# Abhijit Menon-Sen <ams@oryx.com>\n\nuse Net::Jabber;\n\nmy $dir;\nchomp( $dir = qx(git rev-parse --git-dir 2>/dev/null) );\ndie \"GIT_DIR not defined.\\n\" unless $dir;\n$ENV{GIT_DIR} = $dir;\n\nmy $r;\nchomp( $r = qx(git config hooks.notify.recipients) );\nmy @recipients = split /,\\s*/, $r;\n\nmy @changes;\nwhile ( <> ) {\n    chomp;\n    my ( $old, $new, $ref ) = split / /;\n    $ref =~ s!refs/heads/!!;\n    next unless $ref eq \"some/branch\";\n    my $s = qx(git log --no-merges --reverse --find-copies-harder --raw -r $old..$new);\n    $s =~ s/^:.*?\\t/\\t/gsm;\n    push @changes, $s;\n}\n\nexit unless @changes;\nexit unless @recipients;\n\nmy $client = new Net::Jabber::Client ();\n$client->Connect(\n    hostname => 'example.com',\n) or die \"Cannot connect to jabber server: $!.\\n\";\n\nmy @r = $client->AuthSend(\n    username => 'git',\n    password => 'some-password',\n    resource => 'git'\n);\ndie \"Cannot authenticate (@r).\\n\" if $r[0] ne \"ok\";\n\nforeach my $c (@changes) {\n    for my $r (@recipients) {\n        my $msg = new Net::Jabber::Message ();\n        $msg->SetMessage(\n            to => $r,\n            subject => 'Changes pushed to x:y.git/some/branch',\n            body => $c\n        );\n        $client->Send( $msg );\n    }\n}\n\n$client->Disconnect();\n"},{"id":"84332","messageId":"2754.78.153.224.34.1216745103.squirrel@mail.kergonna.fr","threadId":"14590","inReplyTo":"00AEED4D-BD34-4584-B303-32C5F587EF0F@develooper.com","subject":"Re: post-receive-hook emailer","fromName":"Vincent Kergonna","fromEmail":"git@kergonna.fr","sentAt":"2008-07-22T16:45:03Z","receivedAt":"2008-07-22T16:45:03Z","isPatch":false,"sender":{"key":"git@kergonna.fr","avatar":null},"body":"> Hi everyone,\n>\n\nHi,\n\n> Anyway - anyone have a mailer that sends diffs and such?  :-)   Or\n> should I just write a wrapper around format-patch and send-email?\n> (To handle branches well it gets slightly complicated, quick - or\n> maybe I just haven't understood how the post-receive hook works).\n\nBelow is a script I use to send diffs for each commit pushed to a\nrepository. This script should be called from the 'update' hook.\n\n-- \nVincent\n\n\n#!/usr/bin/perl\n#\n# Tool to send git commit notifications\n#\n# Copyright 2005 Alexandre Julliard\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n#\n# This script is meant to be called from .git/hooks/update.\n#\n# Usage: git-notify [options] [--] refname old-sha1 new-sha1\n#\n#   -c name   Send CIA notifications under specified project name\n#   -m addr   Send mail notifications to specified address\n#   -n max    Set max number of individual mails to send\n#   -r name   Set the git repository name\n#   -s bytes  Set the maximum diff size in bytes (-1 for no limit)\n#   -u url    Set the URL to the gitweb browser\n#   -x branch Exclude changes to the specified branch from reports\n#\n\nuse strict;\nuse open ':utf8';\nuse Encode 'encode';\nuse Cwd 'realpath';\n\nbinmode STDIN, ':utf8';\nbinmode STDOUT, ':utf8';\n\n# some parameters you may want to change\n\n# base URL of the gitweb repository browser (can be set with the -u option)\nmy $gitweb_url = \"\";\n\n# set this to something that takes \"-s\"\nmy $mailer = \"/usr/bin/mail\";\n\n# default repository name (can be changed with the -r option)\nmy $repos_name = \"\";\n\n# Repository owner\nmy $repos_owner = \"\";\n\n# max size of diffs in bytes (can be changed with the -s option)\nmy $max_diff_size = 2500000;\n\n# address for mail notices (can be set with -m option)\nmy $commitlist_address;\n\n# project name for CIA notices (can be set with -c option)\nmy $cia_project_name;\n\n# CIA notification address\nmy $cia_address = \"cia\\@cia.navi.cx\";\n\n# max number of individual notices before falling back to a single global\nnotice (can be set with -n option)\nmy $max_individual_notices = 100;\n\n# debug mode\nmy $debug = 0;\n\n# branches to exclude\nmy @exclude_list = ();\n\nsub usage()\n{\n    print \"Usage: $0 [options] [--] refname old-sha1 new-sha1\\n\";\n    print \"   -c name   Send CIA notifications under specified project\nname\\n\";\n    print \"   -m addr   Send mail notifications to specified address\\n\";\n    print \"   -n max    Set max number of individual mails to send\\n\";\n    print \"   -r name   Set the git repository name\\n\";\n    print \"   -s bytes  Set the maximum diff size in bytes (-1 for no\nlimit)\\n\";\n    print \"   -u url    Set the URL to the gitweb browser\\n\";\n    print \"   -x branch Exclude changes to the specified branch from\nreports\\n\";\n\t  print \"   -o owner  Set repository owner (used as a filter on commits)\\n\";\n    exit 1;\n}\n\nsub xml_escape($)\n{\n    my $str = shift;\n    $str =~ s/&/&amp;/g;\n    $str =~ s/</&lt;/g;\n    $str =~ s/>/&gt;/g;\n    my @chars = unpack \"U*\", $str;\n    $str = join \"\", map { ($_ > 127) ? sprintf \"&#%u;\", $_ : chr($_); }\n@chars;\n    return $str;\n}\n\n# format an integer date + timezone as string\n# algorithm taken from git's date.c\nsub format_date($$)\n{\n    my ($time,$tz) = @_;\n\n    if ($tz < 0)\n    {\n        my $minutes = (-$tz / 100) * 60 + (-$tz % 100);\n        $time -= $minutes * 60;\n    }\n    else\n    {\n        my $minutes = ($tz / 100) * 60 + ($tz % 100);\n        $time += $minutes * 60;\n    }\n    return gmtime($time) . sprintf \" %+05d\", $tz;\n}\n\n# parse command line options\nsub parse_options()\n{\n    while (@ARGV && $ARGV[0] =~ /^-/)\n    {\n        my $arg = shift @ARGV;\n\n        if ($arg eq '--') { last; }\n        elsif ($arg eq '-c') { $cia_project_name = shift @ARGV; }\n        elsif ($arg eq '-m') { $commitlist_address = shift @ARGV; }\n        elsif ($arg eq '-n') { $max_individual_notices = shift @ARGV; }\n        elsif ($arg eq '-r') { $repos_name = shift @ARGV; }\n        elsif ($arg eq '-s') { $max_diff_size = shift @ARGV; }\n        elsif ($arg eq '-u') { $gitweb_url = shift @ARGV; }\n        elsif ($arg eq '-x') { push @exclude_list, \"^\" . shift @ARGV; }\n\t\t    elsif ($arg eq '-o') { $repos_owner = shift @ARGV; }\n        elsif ($arg eq '-d') { $debug++; }\n        else { usage(); }\n    }\n    if (@ARGV && $#ARGV != 2) { usage(); }\n}\n\n# send an email notification\nsub mail_notification($$$@)\n{\n    my ($name, $subject, $content_type, @text) = @_;\n    $subject = encode(\"MIME-Q\",$subject);\n    if ($debug)\n    {\n        print \"---------------------\\n\";\n        print \"To: $name\\n\";\n        print \"Subject: $subject\\n\";\n        print \"Content-Type: $content_type\\n\";\n        print \"\\n\", join(\"\\n\", @text), \"\\n\";\n    }\n    else\n    {\n        my $pid = open MAIL, \"|-\";\n        return unless defined $pid;\n        if (!$pid)\n        {\n            exec $mailer, \"-s\", $subject, \"-a\", \"Content-Type:\n$content_type\", $name or die \"Cannot exec $mailer\";\n        }\n        print MAIL join(\"\\n\", @text), \"\\n\";\n        close MAIL;\n    }\n}\n\n# get the default repository name\nsub get_repos_name()\n{\n    my $dir = `git rev-parse --git-dir`;\n    chomp $dir;\n    my $repos = realpath($dir);\n    $repos =~ s/(.*?)((\\.git\\/)?\\.git)$/\\1/;\n    $repos =~ s/(.*)\\/([^\\/]+)\\/?$/\\2/;\n    return $repos;\n}\n\n# extract the information from a commit or tag object and return a hash\ncontaining the various fields\nsub get_object_info($)\n{\n    my $obj = shift;\n    my %info = ();\n    my @log = ();\n    my $do_log = 0;\n\n    open TYPE, \"-|\" or exec \"git\", \"cat-file\", \"-t\", $obj or die \"cannot\nrun git-cat-file\";\n    my $type = <TYPE>;\n    chomp $type;\n    close TYPE;\n\n    open OBJ, \"-|\" or exec \"git\", \"cat-file\", $type, $obj or die \"cannot\nrun git-cat-file\";\n    while (<OBJ>)\n    {\n        chomp;\n        if ($do_log)\n        {\n            last if /^-----BEGIN PGP SIGNATURE-----/;\n            push @log, $_;\n        }\n        elsif (/^(author|committer|tagger) ((.*)(<.*>)) (\\d+) ([+-]\\d+)$/)\n        {\n            $info{$1} = $2;\n            $info{$1 . \"_name\"} = $3;\n            $info{$1 . \"_email\"} = $4;\n            $info{$1 . \"_date\"} = $5;\n            $info{$1 . \"_tz\"} = $6;\n        }\n        elsif (/^tag (.*)$/)\n        {\n            $info{\"tag\"} = $1;\n        }\n        elsif (/^$/) { $do_log = 1; }\n    }\n    close OBJ;\n\n    $info{\"type\"} = $type;\n    $info{\"log\"} = \\@log;\n    return %info;\n}\n\n# send a commit notice to a mailing list\nsub send_commit_notice($$)\n{\n    my ($ref,$obj) = @_;\n    my %info = get_object_info($obj);\n    my @notice = ();\n    my $subject;\n\n\treturn if $info{\"log\"} eq \"^(m|M)erge.*\";\n\n\tif ($info{\"author\"} =~ \"$repos_owner\") {\n\n\t\tif ($info{\"type\"} eq \"tag\")\n\t\t{\n\t\t\tpush @notice,\n\t\t\t\"Module: $repos_name\",\n\t\t\t\"Branch: $ref\",\n\t\t\t\"Tag:    $obj\",\n\t\t\t#\"URL:    $gitweb_url/?a=tag;h=$obj\",\n\t\t\t\"\",\n\t\t\t\"Tagger: \" . $info{\"tagger\"},\n\t\t\t\"Date:   \" . format_date($info{\"tagger_date\"},$info{\"tagger_tz\"}),\n\t\t\t\"\",\n\t\t\tjoin \"\\n\", @{$info{\"log\"}};\n\t\t\t$subject = \"[CommitDiff] Tag \" . $info{\"tag\"} . \" : \" .\n$info{\"tagger_name\"} . \": \" . ${$info{\"log\"}}[0];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpush @notice,\n\t\t\t\"Module: $repos_name\",\n\t\t\t\"Branch: $ref\",\n\t\t\t\"Commit: $obj\",\n\t\t\t\"Parent: $info()\",\n\t\t\t\"URL:    $gitweb_url/?a=commit;h=$obj\",\n\t\t\t\"Author: \" . $info{\"author\"},\n\t\t\t\"AuthorDate:   \" . format_date($info{\"author_date\"},$info{\"author_tz\"}),\n\t\t\t\"\",\n\t\t\tjoin \"\\n\", @{$info{\"log\"}},\n\t\t\t\"\",\n\t\t\t\"---\",\n\t\t\t\"\";\n\n\t\t\topen STAT, \"-|\" or exec \"git\", \"diff-tree\", \"--stat\", \"-M\",\n\"--no-commit-id\", $obj or die \"cannot exec git-diff-tree\";\n\t\t\tpush @notice, join(\"\", <STAT>);\n\t\t\tclose STAT;\n\n\t\t\topen DIFF, \"-|\" or exec \"git\", \"diff-tree\", \"-p\", \"-M\",\n\"--no-commit-id\", $obj or die \"cannot exec git-diff-tree\";\n\t\t\tmy $diff = join( \"\", <DIFF> );\n\t\t\tclose DIFF;\n\n\t\t\tif (($max_diff_size == -1) || (length($diff) < $max_diff_size))\n\t\t\t{\n\t\t\t\tpush @notice, $diff;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpush @notice, \"Diff:   $gitweb_url/?a=commitdiff;h=$obj\",\n\t\t\t}\n\n\t\t\t$subject = \"[CommitDiff] \" . ${$info{\"log\"}}[0];\n\t\t}\n\n\t\tmail_notification($commitlist_address, $subject, \"text/plain;\ncharset=UTF-8\", @notice);\n\t}\n}\n\n# send a commit notice to the CIA server\nsub send_cia_notice($$)\n{\n    my ($ref,$commit) = @_;\n    my %info = get_object_info($commit);\n    my @cia_text = ();\n\n    return if $info{\"type\"} ne \"commit\";\n\n    push @cia_text,\n        \"<message>\",\n        \"  <generator>\",\n        \"    <name>git-notify script for CIA</name>\",\n        \"  </generator>\",\n        \"  <source>\",\n        \"    <project>\" . xml_escape($cia_project_name) . \"</project>\",\n        \"    <module>\" . xml_escape($repos_name) . \"</module>\",\n        \"    <branch>\" . xml_escape($ref). \"</branch>\",\n        \"  </source>\",\n        \"  <body>\",\n        \"    <commit>\",\n        \"      <revision>\" . substr($commit,0,10) . \"</revision>\",\n        \"      <author>\" . xml_escape($info{\"author\"}) . \"</author>\",\n        \"      <log>\" . xml_escape(join \"\\n\", @{$info{\"log\"}}) . \"</log>\",\n        \"      <files>\";\n\n    open COMMIT, \"-|\" or exec \"git\", \"diff-tree\", \"--name-status\", \"-r\",\n\"-M\", $commit or die \"cannot run git-diff-tree\";\n    while (<COMMIT>)\n    {\n        chomp;\n        if (/^([AMD])\\t(.*)$/)\n        {\n            my ($action, $file) = ($1, $2);\n            my %actions = ( \"A\" => \"add\", \"M\" => \"modify\", \"D\" => \"remove\" );\n            next unless defined $actions{$action};\n            push @cia_text, \"        <file action=\\\"$actions{$action}\\\">\"\n. xml_escape($file) . \"</file>\";\n        }\n        elsif (/^R\\d+\\t(.*)\\t(.*)$/)\n        {\n            my ($old, $new) = ($1, $2);\n            push @cia_text, \"        <file action=\\\"rename\\\" to=\\\"\" .\nxml_escape($new) . \"\\\">\" . xml_escape($old) . \"</file>\";\n        }\n    }\n    close COMMIT;\n\n    push @cia_text,\n        \"      </files>\",\n        \"      <url>\" . xml_escape(\"$gitweb_url/?a=commit;h=$commit\") .\n\"</url>\",\n        \"    </commit>\",\n        \"  </body>\",\n        \"  <timestamp>\" . $info{\"author_date\"} . \"</timestamp>\",\n        \"</message>\";\n\n    mail_notification($cia_address, \"DeliverXML\", \"text/xml\", @cia_text);\n}\n\n# send a global commit notice when there are too many commits for\nindividual mails\nsub send_global_notice($$$)\n{\n    my ($ref, $old_sha1, $new_sha1) = @_;\n    my @notice = ();\n\n    open LIST, \"-|\" or exec \"git\", \"rev-list\", \"--pretty\", \"^$old_sha1\",\n\"$new_sha1\", @exclude_list or die \"cannot exec git-rev-list\";\n    while (<LIST>)\n    {\n        chomp;\n        s/^commit /URL:    $gitweb_url\\/?a=commit;h=/;\n        push @notice, $_;\n    }\n    close LIST;\n\n    mail_notification($commitlist_address, \"New commits on branch $ref\",\n\"text/plain; charset=UTF-8\", @notice);\n}\n\n# send all the notices\nsub send_all_notices($$$)\n{\n    my ($ref, $old_sha1, $new_sha1) = @_;\n\n    $ref =~ s/^refs\\/heads\\///;\n\n    if ($old_sha1 eq '0' x 40)  # new ref\n    {\n        send_commit_notice( $ref, $new_sha1 ) if $commitlist_address;\n        return;\n    }\n\n    my @commits = ();\n\n    open LIST, \"-|\" or exec \"git\", \"rev-list\", \"^$old_sha1\", \"$new_sha1\",\n@exclude_list or die \"cannot exec git-rev-list\";\n    while (<LIST>)\n    {\n        chomp;\n        die \"invalid commit $_\" unless /^[0-9a-f]{40}$/;\n        unshift @commits, $_;\n    }\n    close LIST;\n\n    if (@commits > $max_individual_notices)\n    {\n        send_global_notice( $ref, $old_sha1, $new_sha1 ) if\n$commitlist_address;\n        return;\n    }\n\n    foreach my $commit (@commits)\n    {\n        send_commit_notice( $ref, $commit ) if $commitlist_address;\n        send_cia_notice( $ref, $commit ) if $cia_project_name;\n    }\n}\n\n$repos_name = get_repos_name();\nparse_options();\n\n# append repository path to URL\n$gitweb_url .= \"/$repos_name.git\";\n\nif (@ARGV)\n{\n    send_all_notices( $ARGV[0], $ARGV[1], $ARGV[2] );\n}\nelse  # read them from stdin\n{\n    while (<>)\n    {\n        chomp;\n        if (/^([0-9a-f]{40}) ([0-9a-f]{40}) (.*)$/) { send_all_notices(\n$3, $1, $2 ); }\n    }\n}\n\nexit 0;\n"}]}