diff --git a/bots/humbug_git_config.py b/bots/humbug_git_config.py new file mode 100644 index 0000000..1ab0c6c --- /dev/null +++ b/bots/humbug_git_config.py @@ -0,0 +1,34 @@ +# Humbug Inc's internal git plugin configuration. +# The plugin and example config are under api/integrations/ + +# Leaving all the instructions out of this file to avoid having to +# sync them as we update the comments. + +HUMBUG_USER = "humbug+commits@humbughq.com" +HUMBUG_API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +# commit_notice_destination() lets you customize where commit notices +# are sent to. +# +# It takes the following arguments: +# * repo = the name of the git repository +# * branch = the name of the branch that was pushed to +# * commit = the commit id +# +# Returns a dictionary encoding the stream and subject to send the +# notification to (or None to send no notification, e.g. for ). +# +# The default code below will send every commit pushed to "master" to +# * stream "commits" +# * subject "deploy => master" (using a pretty unicode right arrow) +# And similarly for branch "test-post-receive" (for use when testing). +def commit_notice_destination(repo, branch, commit): + if branch in ["master", "post-receive-test"]: + return dict(stream = "commits", + subject = u"deploy \u21D2 %s" % (branch,)) + + # Return None for cases where you don't want a notice sent + return None + +HUMBUG_API_PATH = "/home/humbug/humbug/api" +HUMBUG_SITE = "https://staging.humbughq.com" diff --git a/integrations/git/humbug_git_config.py b/integrations/git/humbug_git_config.py new file mode 100644 index 0000000..f505d5b --- /dev/null +++ b/integrations/git/humbug_git_config.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright © 2013 Humbug, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + +# Change these values to configure authentication for the plugin +HUMBUG_USER = "git@example.com" +HUMBUG_API_KEY = "0123456789abcdef0123456789abcdef" + +# commit_notice_destination() lets you customize where commit notices +# are sent to with the full power of a Python function. +# +# It takes the following arguments: +# * repo = the name of the git repository +# * branch = the name of the branch that was pushed to +# * commit = the commit id +# +# Returns a dictionary encoding the stream and subject to send the +# notification to (or None to send no notification). +# +# The default code below will send every commit pushed to "master" to +# * stream "commits" +# * subject "deploy => master" (using a pretty unicode right arrow) +# And similarly for branch "test-post-receive" (for use when testing). +def commit_notice_destination(repo, branch, commit): + if branch in ["master", "test-post-receive"]: + return dict(stream = "commits", + subject = u"deploy \u21D2 %s" % (branch,)) + + # Return None for cases where you don't want a notice sent + return None + +## If properly installed, the Humbug API should be in your import +## path, but if not, set a custom path below +HUMBUG_API_PATH = None + +# This should not need to change unless you have a custom Humbug subdomain. +HUMBUG_SITE = "https://humbughq.com" diff --git a/integrations/git/post-receive b/integrations/git/post-receive new file mode 100755 index 0000000..6d8ecb6 --- /dev/null +++ b/integrations/git/post-receive @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Humbug notification post-receive hook. +# Copyright © 2012-2013 Humbug, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# The "post-receive" script is run after receive-pack has accepted a pack +# and the repository has been updated. It is passed arguments in through +# stdin in the form +# +# For example: +# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master + +import os +import sys +import subprocess +import time +import os.path + +sys.path.insert(0, os.path.dirname(__file__)) +import humbug_git_config as config + +if config.HUMBUG_API_PATH is not None: + sys.path.append(config.HUMBUG_API_PATH) + +import humbug +client = humbug.Client( + email=config.HUMBUG_USER, + site=config.HUMBUG_SITE, + api_key=config.HUMBUG_API_KEY) + +# check_output is backported from subprocess.py in Python 2.7 +def check_output(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise subprocess.CalledProcessError(retcode, cmd, output=output) + return output +subprocess.check_output = check_output + +def git_repository_name(): + output = subprocess.check_output(["git", "rev-parse", "--is-bare-repository"]) + if output.strip() == "true": + return os.path.basename(os.getcwd())[:-len(".git")] + else: + return os.path.basename(os.path.dirname(os.getcwd())) + +def git_commit_range(oldrev, newrev): + log_cmd = ["git", "log", "--reverse", + "--pretty=%aE %s", "%s..%s" % (oldrev, newrev)] + commits = '' + for ln in subprocess.check_output(log_cmd).splitlines(): + email, subject = ln.split(None, 1) + if len(subject) > 60: + subject = subject[:58].rstrip() + '...' + commits += '!gravatar(%s) %s\n' % (email, subject) + return commits + +def send_bot_message(oldrev, newrev, refname): + repo_name = git_repository_name() + branch = refname.replace('refs/heads/', '') + destination = config.commit_notice_destination(repo_name, branch, newrev) + if destination is None: + # Don't forward the notice anywhere + return + + added = git_commit_range(oldrev, newrev) + removed = git_commit_range(newrev, oldrev) + + new_head = newrev[:12] + + if removed: + message = '`%s` was pushed to `%s`, **REMOVING**:\n\n%s' % (new_head, branch, removed) + if added: + message += '\n**and adding**:\n\n' + added + message += '\n**A HISTORY REWRITE HAS OCCURRED!**' + message += '\nPlease check your local branches to deal with this.' + elif added: + message = '`%s` was deployed to `%s` with:\n\n%s' % (new_head, branch, added) + else: + message = '`%s` was pushed to `%s`... but nothing changed?' % (new_head, branch) + + message_data = { + "type": "stream", + "to": destination["stream"], + "subject": destination["subject"], + "content": message, + } + client.send_message(message_data) + +for ln in sys.stdin: + oldrev, newrev, refname = ln.strip().split() + send_bot_message(oldrev, newrev, refname)