Thursday, April 12, 2012

Receiving git commit notifies by email

GitHub has the ability to check commits before they're merged, but what if you wanted to receive the commit diffs by email so that you knew when they actually made them into your main branch? The Ruby script git-commit-notifier helps fill this purpose, and you can take advantage of GitHub's post-receive hooks and use a Sinatra server to receive notifications such as the following via HTTP POST requests:

Example

So how would you integrate git-commit-notifier with GitHub? First you need to clone the branch on the host that can receive these POST requests. Because GitHub sends a post-receive hook for each merge, you can often receive multiple web requests being sent to your Sinatra server. The naive way would be to launch the script within the POST command (i.e. %x in Ruby), but using this approach requires any operation you perform to be completed before the HTTP connection is closed.

require 'rubygems'
require 'json'
require 'sinatra'

post '/' do
if params[:payload]
   push = JSON.parse(params[:payload])
 print "JSON response: #{push.inspect}"
end

   # Tried to run as a daemon, fork, but maybe just doing an exec will produce fewer dup msgs.
   system("mirror_repo.sh ")

end

The script that would perform the merge_repo.sh task would then be something of the following:
cd /home/myrepo
git fetch github
# Sends diffs between our branch and origin/master
CURRENT_HASH=`git log HEAD -1 --pretty=%H`
NEW_HASH=`git log origin/master -1 --pretty=%H`
# git merge will actually be a fast-forward
git merge origin
git log --no-merges --pretty=format:"%P %H" $CURRENT_HASH..$NEW_HASH | awk '{print "echo " $0 " refs/heads/master | git-commit-notifier /usr/local/config/git-commit-notifier.yml"}' | sh
git checkout master
exit 0

The basic approach fetches the branch (assuming it's located on 'github'), pipes the changes listed by the parent and git hash into using the git-commit-notifier script before merging the branch. Assuming the upstream branch is simply an ancestor of the current branch, the merging should be a fast-forward.

If you plan on mirroring the repository (i.e. via Gitosis), you could setup a post-receive hook that would be configured to send diffs each time they were merged.
while read oldrev newrev ref
do
if [ "$REFSPEC" == "refs/heads/master" ]; then
echo "$oldrev $newrev $ref" | git-commit-notifier  /usr/local/config/git-commit-notifier.yml
fi
done

3 comments:

  1. Thanks, this helped me to setup email notification for github repository. I even skipped the POST receive from GitHub - just polling it periodically via cron, works fine without messages duplication. I had one thing to fix from your article:
    "git log HEAD -1 --pretty=format:%H" instead of "git log HEAD -1 --pretty=%H"

    A snippet to notify about changes in selected branches:

    branches = %w(master rc some_other_branch)
    branches.each do |branch|
    exec("git checkout #{branch}")
    last_rev_cmd = "git log HEAD -1 --pretty=format:%H"
    old_rev = exec(last_rev_cmd)
    exec("git pull")
    new_rev = exec(last_rev_cmd)
    next if old_rev == new_rev
    exec("git-commit-notifier #{CONFIG_PATH} #{old_rev} #{new_rev} refs/heads/#{branch}")
    end

    Best regards,
    Artem

    ReplyDelete
  2. Both -pretty=format:%H and --pretty=%H seem to work with git 1.7.9.5. What version are you using?

    ReplyDelete
  3. Thanks, this is useful. I took this info and inserted some information in the README.md of git-commit-notifier.

    Why not use the JSON parameters available sent by GitHub to determine what has changed? See the example WebHook here:

    https://github.com/git-commit-notifier/git-commit-notifier

    The change-notify.sh script listed there assumes the repository and branches exist already. I've modified it to automatically clone a valid repository and create a remote tracking branch when necessary.

    ReplyDelete