Hooks & APIs

The Commit-msg Hook

Every developer must have this hook on his local machine in order to:

  • Commit codes locally several times then pushes it to the server.
  • Merge auto-commits with auto-messages without referencing to a JIRA ticket.

The commit-msg hook is a python script file and it must be placed in the developer's local repository as: .git/hooks/commit-msg.  There is no server-side hook required.

The following settings must be configured in the script file:

  • JIRA_XMLRPC -- path to JIRA.
  • (Example: https://jira.example.com/rpc/xmlrpc)
  • JIRA_USER, JIRA_PASSWORD -- developer's credentials.
  • JIRA_TICKET_PATTERN -- pattern that will search ticket references.
  • (Example: re.compile(r'\[(\w+7-\d+?)\]'))

In Linux and OSX, this file must have executable permissions in the file system; in Windows, setting this permission is not necessary.  To use the hook in Windows without python installed, see Python on Windows FAQ open in new tab.

See the commit-msg hook code on the right panel or download the sample commit-msg file, make the necessary changes, and place it in the required folder.

fyi
The commit-msg hook is a python script file that must be located in the developer's local repository.
Sample contents of the commit-msg file:
#!/usr/bin/python
#
# This script is intended to be run as a commit-msg script in a GIT
# repository and check the presence of JIRA ticket numbers in the log messages.
#
#    - NO_JIRA_TICKET_MESSAGE (an error message returned to the user when the
#      svn commit message doesn't contain a jira ticket);
#    - INVALID_JIRA_TICKET_MESSAGE (an error message returned to the user when
#      the svn commit message contains an invalid jira ticket);
#    - JIRA_XMLRPC (url of the JIRA XML-RPC server);
#    - JIRA_USER (name of the JIRA user who has permission to look up issues in
#      the JIRA server);
#    - JIRA_PASSWORD (password of the JIRA user described above);

import sys
import re
import xmlrpclib

NO_JIRA_TICKET_MESSAGE = \
'No JIRA ticket present in the commit message. \
Please include the JIRA ticket enclosed in brackets: [ABC-789].'
INVALID_JIRA_TICKET_MESSAGE = \
'Proper JIRA ticket syntax was found, but none were valid tickets. \
Please check the tickets and try again.'
TOO_MANY_JIRA_TICKETS_MESSAGE = \
'Only 1 JIRA ticket is allowed per commit. Please commit only 1 change at a time.'
INVALID_ISSUE_TYPE_MESSAGE = \
'You may not commit against subtasks or task-splits. \
Please commit against the parent ticket.'

JIRA_XMLRPC = 'https://jira.example.com/rpc/xmlrpc'
JIRA_USER = 'user'
JIRA_PASSWORD = 'password'
JIRA_TICKET_PATTERN = re.compile(r'\[(\w+?-\d+?)\]')

FAULT_MSG_ISSUE_NOT_FOUND = 'com.atlassian.jira.rpc.exception.RemotePermissionException'

def check_message(message):
    tickets = JIRA_TICKET_PATTERN.findall(message)

    if not tickets:
        return NO_JIRA_TICKET_MESSAGE

    if len(tickets) > 1:
        return TOO_MANY_JIRA_TICKETS_MESSAGE

    ticket = tickets[0]

    try:
        issue = proxy.jira1.getIssue(auth, ticket)
    except xmlrpclib.Fault, e:
        if e.faultString.find(FAULT_MSG_ISSUE_NOT_FOUND) >= 0:
            return INVALID_JIRA_TICKET_MESSAGE
        else:
            raise

    # Check if issue is subtask or task-split
    if issue['type'] == '8' or issue['type'] == '5':
        return INVALID_ISSUE_TYPE_MESSAGE

    return None

proxy = xmlrpclib.ServerProxy(JIRA_XMLRPC)

try:
    auth = proxy.jira1.login(JIRA_USER, JIRA_PASSWORD)
except:
    print >> sys.stderr, 'Cannot connect to JIRA: ' + str(sys.exc_info()[1])
    sys.exit(2)

msg_file = open(sys.argv[1], 'r')
msg = msg_file.read()

err_msg = check_message(msg)

if err_msg:
    print >> sys.stderr, 'Error: %s\nCommit message:\n%s' % (err_msg, msg)
    sys.exit(1)