Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Post-Commit Hook for GIT - Used with Fixx Issue Tracker. Allows for automatic resolving, closing and commenting issues when commiting.
# Created By: Radosław Szalski <>
# Tested with Python 3.1, git v. 1.7.1 (using msysgit) and Fixx 1.9
# Rewriting to Python 2.X should be straightforward
# Read more -
# This hook script is called after a commit is made
# It's purpose is to update FIXX's (bug tracker) issues when they are fixed/resolved/commented
# Depending on HOW you format your commit message it does different things
# you can:
# post only comment to issue
# resolve issue & post comment
# resolve issue & post comment & close issue
# I use Fixx not only for bug tacking, but for general issues/task tracking
# thus I often supply 'Fixed' as a Resolution but also 'Done' etc.
# you probably won't resolve issues as 'Won't fix' or 'Not a bug' directly from git,
# nonetheless you are free to use whatever resolution names you have defined
# For the script to work your commit message needs to contain (at least) [#{num}] at the beginning
# This will only post a comment to an issue
# example: git commit -m '[#666] some message here'
# To resolve an issue you supply the resolution name before #{num}
# example: git commit -m '[Fixed #666] some message here'
# To resolve & close an issue you add 'c' right after #{num}
# example: git commit -m '[Fixed #666c] some msg here'
# Note that comments are always posted
# You can supply any valid resolution name that is set for your project
# they can be lowercase or upper case, you can put spaces before and after a resolution
# but note, that resolution itself has to be intact
# example: '[ Won't Fix #666] msg'
# Recommendation: use only 1 space to separate Resolution Name from issue ID
import re
import subprocess
# I find httplib2 easier to use than Python's urllib
# You can get it here -
import httplib2
import json
# CONFIGURATION - change those
# you can get project's ID by going to Projects->{Project} in Fixx and checking the URI
# example: http://localhost:9000/projects/2
FIXX_URI = 'http://localhost:9000' # (no trailing '/') Address of the Fixx installation, this one is an example
USERNAME = 'username'
PASSWORD = 'password'
git = subprocess.Popen(['git', 'log', '-1', '--pretty=format:"%h %s |author|%cn|date|%ci"'], stdout = subprocess.PIPE)
# The response is: "{short-hash} {commit_msg} |author|{author}|date|{date}"
resp =[1:-2] # Removing unnecessary characters
# TODO verbose
pID = re.compile(r'(?P<hash>\w+)\s\[(?P<resolution>.*)\s?#(?P<id>\d+)(?P<close>c|C)?.*\](?P<msg>.*)\|author\|(?P<author>\w+)\|date\|(?P<date>.*)')
matches ='utf-8'))
# Don't bother if it doesn't match
if matches is not None:
gitLog = {}
gitLog['issueResolution'] ='resolution').lower().strip()
gitLog['issueID'] ='id').strip()
gitLog['commitHash'] ='hash').strip()
gitLog['commitMsg'] ='msg').strip()
gitLog['commitAuthor'] ='author').strip()
gitLog['commitDate'] ='date').strip()
if'close') and'close').lower() == 'c':
gitLog['close'] = True
gitLog['close'] = False
h = httplib2.Http()
h.follow_all_redirects = True
h.add_credentials(USERNAME, PASSWORD)
# For some weird reason, even though we supply credentials with add_credentials
# we still need to send an Authorization header, even if it's a dummy/fake one
# This probably is a bug in python 3.1 (because it happened when I used urllib as well)
# This is sent when we retrieve project resolutions
# because JSON is easier to parse than XML
headersJSON = {
'Accept': 'application/json',
'Content-type': 'application/json',
"Authorization": "Basic fake"
# Used for every other request
headersXML = {
'Accept': 'application/xml',
'Content-type': 'application/xml',
"Authorization": "Basic fake"
# The commentary that is attached to issues in Fixx
comment = """
Resolution set to: {resolution}
Commit: {hash}
Commiter: {author}
Date: {date}
Message: {msg}
""".format(resolution = gitLog['issueResolution'],
hash = gitLog['commitHash'],
author = gitLog['commitAuthor'],
date = gitLog['commitDate'],
msg = gitLog['commitMsg'])
# We're getting an issue with EVERY setting specified
# This is VERY important, since later we use PUT request to update the issue,
# And if we do not supply every possible Issue setting - those missing get reverted to defaults
# See API documentation for Fixx
resp, issue = h.request("{uri}/api/issues/{id}".format(uri = FIXX_URI, id = gitLog['issueID']), "GET", headers = headersXML)
issue = issue.decode('utf-8')
# We retrieve resolutions for current project, and parse them into a dictionary
def getResolutions(projectID):
resp, resolutions = h.request("{uri}/api/projects/{id}/resolutions".format(uri= FIXX_URI, id = projectID), "GET", headers = headersJSON)
decoder = json.JSONDecoder()
resolutionsJSON = decoder.decode(resolutions.decode("utf-8"))
resolutionsDict = {}
for res in resolutionsJSON["resolution"]:
resolutionsDict[res['name'].lower()] = res['id']
return resolutionsDict
# We're replacing only certain settings in Issue, others are not touched and preserved!
def resolveIssue(issue, issueID, resolutionID, close):
# This one probably might use some improvements
pattern = re.compile(r'</issue>')
pResolution = re.compile(r'<resolution>(\d+)</resolution>')
closePattern = re.compile(r'<closed>(\w+)</closed>')
if pResolution.match(issue):
issue = pResolution.sub('<resolution>{id}</resolution>'.format(id = resolutionID), issue)
issue = pattern.sub('<resolution>{id}</resolution></issue>'.format(id = resolutionID), issue)
if close:
issue = closePattern.sub('<closed>true</closed>', issue)
# Updating Issue
resp, content = h.request("{uri}/api/issues/{id}".format(uri = FIXX_URI, id = issueID), "PUT", body = issue, headers = headersXML)
# Posting a commentary
def postComment(gitLog, comment):
resp, content = h.request("{uri}/api/issues/{id}/comments".format(uri = FIXX_URI, id = gitLog['issueID']), "POST", body = comment, headers = headersXML )
# We get resolutions in aa dictionary format
resolutions = getResolutions(PROJECT_ID)
# If there's a resolution specified, we update the issue and psot comment
# if not, we just post a comment (useful if you just want to make some updates)
if gitLog['issueResolution'] in resolutions:
resolveIssue(issue, gitLog['issueID'], resolutions[gitLog['issueResolution']], gitLog['close'])
postComment(gitLog, comment)
postComment(gitLog, comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment