Blame progit/hooks/files/progit_hook.py

Pierre-Yves Chibon 3935a2
#! /usr/bin/env python2
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
"""Progit specific hook to add comment on issues if the commits fixes or
Pierre-Yves Chibon 3935a2
relates to an issue.
Pierre-Yves Chibon 3935a2
"""
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon b9137f
import os
Pierre-Yves Chibon 3935a2
import re
Pierre-Yves Chibon 3935a2
import sys
Pierre-Yves Chibon 3935a2
import subprocess
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 4cc3ef
if 'PROGIT_CONFIG' not in os.environ \
Pierre-Yves Chibon 4cc3ef
        and os.path.exists('/etc/progit/progit.cfg'):
Pierre-Yves Chibon 4cc3ef
    print 'Using configuration file `/etc/progit/progit.cfg`'
Pierre-Yves Chibon 4cc3ef
    os.environ['PROGIT_CONFIG'] = '/etc/progit/progit.cfg'
Pierre-Yves Chibon 4cc3ef
Pierre-Yves Chibon 4cc3ef
Pierre-Yves Chibon 2d550c
import progit
Pierre-Yves Chibon 2d550c
import progit.exceptions
Pierre-Yves Chibon 2d550c
Pierre-Yves Chibon 2d550c
Pierre-Yves Chibon 3935a2
FIXES = [
Pierre-Yves Chibon 9822e3
    re.compile('fixe?[sd]?:?\s?#(\d+)', re.I),
Pierre-Yves Chibon 9822e3
    re.compile('.*\s+fixe?[sd]?:?\s?#(\d+)', re.I),
Pierre-Yves Chibon 9822e3
    re.compile('fixe?[sd]?:?\s?https?://.*/(\w+)/issue/(\d+)', re.I),
Pierre-Yves Chibon 9822e3
    re.compile('.*\s+fixe?[sd]?:?\s?https?://.*/(\w+)/issue/(\d+)', re.I),
Pierre-Yves Chibon 3935a2
]
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
RELATES = [
Pierre-Yves Chibon 2ac0f8
    re.compile('relate[sd]?:?\s?(?:to)?\s?#(\d+)', re.I),
Pierre-Yves Chibon 430b75
    re.compile('.*\s+relate[sd]?:?\s?#(\d+)', re.I),
Pierre-Yves Chibon 129a3d
    re.compile(
Pierre-Yves Chibon 129a3d
        'relate[sd]?:?\s?(?:to)?\s?https?://.*/(\w+)/issue/(\d+)', re.I),
Pierre-Yves Chibon 430b75
    re.compile('.*\s+relate[sd]?:?\s?https?://.*/(\w+)/issue/(\d+)', re.I),
Pierre-Yves Chibon 3935a2
]
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 1ea98c
Pierre-Yves Chibon 3935a2
def read_git_output(args, input=None, keepends=False, **kw):
Pierre-Yves Chibon 3935a2
    """Read the output of a Git command."""
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
    return read_output(['git'] + args, input=input, keepends=keepends, **kw)
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
def read_git_lines(args, keepends=False, **kw):
Pierre-Yves Chibon 3935a2
    """Return the lines output by Git command.
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
    Return as single lines, with newlines stripped off."""
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
    return read_git_output(args, keepends=True, **kw).splitlines(keepends)
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
def read_output(cmd, input=None, keepends=False, **kw):
Pierre-Yves Chibon 3935a2
    if input:
Pierre-Yves Chibon 3935a2
        stdin = subprocess.PIPE
Pierre-Yves Chibon 3935a2
    else:
Pierre-Yves Chibon 3935a2
        stdin = None
Pierre-Yves Chibon 3935a2
    p = subprocess.Popen(
Pierre-Yves Chibon 3935a2
        cmd, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw
Pierre-Yves Chibon 3935a2
        )
Pierre-Yves Chibon 3935a2
    (out, err) = p.communicate(input)
Pierre-Yves Chibon 3935a2
    retcode = p.wait()
Pierre-Yves Chibon 3935a2
    if retcode:
Pierre-Yves Chibon 3935a2
        print 'ERROR: %s =-- %s' % (cmd, retcode)
Pierre-Yves Chibon 3935a2
    if not keepends:
Pierre-Yves Chibon 3935a2
        out = out.rstrip('\n\r')
Pierre-Yves Chibon 3935a2
    return out
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
def generate_revision_change_log(new_commits_list):
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
    print 'Detailed log of new commits:\n\n'
Pierre-Yves Chibon e103a7
    commitid = None
Pierre-Yves Chibon 3935a2
    for line in read_git_lines(
Pierre-Yves Chibon 3935a2
            ['log', '--no-walk']
Pierre-Yves Chibon 3935a2
            + new_commits_list
Pierre-Yves Chibon 3935a2
            + ['--'],
Pierre-Yves Chibon 129a3d
            keepends=False,):
Pierre-Yves Chibon e103a7
        if line.startswith('commit'):
Pierre-Yves Chibon e103a7
            commitid = line.split('commit ')[-1]
Pierre-Yves Chibon e103a7
Pierre-Yves Chibon 907577
        line = line.strip()
Pierre-Yves Chibon 907577
Pierre-Yves Chibon 3935a2
        print '*', line
Pierre-Yves Chibon 3935a2
        for motif in FIXES:
Pierre-Yves Chibon 3935a2
            if motif.match(line):
Pierre-Yves Chibon 089151
                print 'fixes', motif.match(line).groups()
Pierre-Yves Chibon b37a8e
                project = None
Pierre-Yves Chibon c79d5a
                if len(motif.match(line).groups()) >= 2:
Pierre-Yves Chibon 151505
                    issueid = motif.match(line).group(2)
Pierre-Yves Chibon 151505
                    project = motif.match(line).group(1)
Pierre-Yves Chibon 151505
                else:
Pierre-Yves Chibon 151505
                    issueid = motif.match(line).group(1)
Pierre-Yves Chibon b37a8e
                fixes_commit(
Pierre-Yves Chibon 151505
                    commitid, issueid, project
Pierre-Yves Chibon b37a8e
                )
Pierre-Yves Chibon 3935a2
        for motif in RELATES:
Pierre-Yves Chibon 3935a2
            if motif.match(line):
Pierre-Yves Chibon 089151
                print 'relates to', motif.match(line).groups()
Pierre-Yves Chibon bb1c6f
                project = None
Pierre-Yves Chibon c79d5a
                if len(motif.match(line).groups()) >= 2:
Pierre-Yves Chibon bb1c6f
                    project = motif.match(line).group(2)
Pierre-Yves Chibon bb1c6f
                relates_commit(
Pierre-Yves Chibon bb1c6f
                    commitid, motif.match(line).group(1), project
Pierre-Yves Chibon bb1c6f
                )
Pierre-Yves Chibon bb1c6f
Pierre-Yves Chibon bb1c6f
Pierre-Yves Chibon bb1c6f
def relates_commit(commitid, issueid, project=None):
Pierre-Yves Chibon bb1c6f
    ''' Add a comment to an issue that this commit relates to it. '''
Pierre-Yves Chibon 36573f
    repo = project or get_repo_name()
Pierre-Yves Chibon 0392d2
    username = get_username()
Pierre-Yves Chibon bb1c6f
Pierre-Yves Chibon ac5603
    repo = progit.lib.get_project(progit.SESSION, repo, user=username)
Pierre-Yves Chibon c04b2e
    if not repo:
Pierre-Yves Chibon c04b2e
        repo = progit.lib.get_project(
Pierre-Yves Chibon c04b2e
            progit.SESSION, get_repo_name(), user=username)
Pierre-Yves Chibon 3b7b2b
    issue = progit.lib.search_issues(
Pierre-Yves Chibon d054da
        progit.SESSION, repo, issueid=issueid)
Pierre-Yves Chibon 42703d
Pierre-Yves Chibon 3b9fc1
    if issue is None or issue.project != repo:
Pierre-Yves Chibon bb1c6f
        return
Pierre-Yves Chibon bb1c6f
Pierre-Yves Chibon 58544c
    comment = ''' Commit [%s](../%s) relates to this ticket''' % (
Pierre-Yves Chibon 275f52
        commitid[:8], commitid[:8])
Pierre-Yves Chibon 36573f
Pierre-Yves Chibon 36573f
    try:
Pierre-Yves Chibon 36573f
        message = progit.lib.add_issue_comment(
Pierre-Yves Chibon 36573f
            progit.SESSION,
Pierre-Yves Chibon 36573f
            issue=issue,
Pierre-Yves Chibon 36573f
            comment=comment,
Pierre-Yves Chibon 275f52
            user=get_pusher(commitid),
Pierre-Yves Chibon 0a3375
            ticketfolder=progit.APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon 36573f
        )
Pierre-Yves Chibon 36573f
        progit.SESSION.commit()
Pierre-Yves Chibon 36573f
    except progit.exceptions.ProgitException as err:
Pierre-Yves Chibon 36573f
        print err
Pierre-Yves Chibon b1e3c1
    except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon b1e3c1
        progit.SESSION.rollback()
Pierre-Yves Chibon b1e3c1
        progit.APP.logger.exception(err)
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon b37a8e
def fixes_commit(commitid, issueid, project=None):
Pierre-Yves Chibon b37a8e
    ''' Add a comment to an issue that this commit fixes it and update
Pierre-Yves Chibon b37a8e
    the status if the commit is in the master branch. '''
Pierre-Yves Chibon b37a8e
    repo = project or get_repo_name()
Pierre-Yves Chibon 1ab53d
    username = get_username()
Pierre-Yves Chibon 1ab53d
Pierre-Yves Chibon 1ab53d
    repo = progit.lib.get_project(progit.SESSION, repo, user=username)
Pierre-Yves Chibon c04b2e
    if not repo:
Pierre-Yves Chibon c04b2e
        repo = progit.lib.get_project(
Pierre-Yves Chibon c04b2e
            progit.SESSION, get_repo_name(), user=username)
Pierre-Yves Chibon 3b7b2b
    issue = progit.lib.search_issues(
Pierre-Yves Chibon d054da
        progit.SESSION, repo, issueid=issueid)
Pierre-Yves Chibon b37a8e
Pierre-Yves Chibon 3b9fc1
    if issue is None or issue.project != repo:
Pierre-Yves Chibon b37a8e
        return
Pierre-Yves Chibon b37a8e
Pierre-Yves Chibon 58544c
    comment = ''' Commit [%s](../%s) fixes this ticket''' % (
Pierre-Yves Chibon b37a8e
        commitid[:8], commitid[:8])
Pierre-Yves Chibon b37a8e
Pierre-Yves Chibon b37a8e
    try:
Pierre-Yves Chibon b37a8e
        message = progit.lib.add_issue_comment(
Pierre-Yves Chibon b37a8e
            progit.SESSION,
Pierre-Yves Chibon b37a8e
            issue=issue,
Pierre-Yves Chibon b37a8e
            comment=comment,
Pierre-Yves Chibon b37a8e
            user=get_pusher(commitid),
Pierre-Yves Chibon 0a3375
            ticketfolder=progit.APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon b37a8e
        )
Pierre-Yves Chibon b37a8e
        progit.SESSION.commit()
Pierre-Yves Chibon b37a8e
    except progit.exceptions.ProgitException as err:
Pierre-Yves Chibon b37a8e
        print err
Pierre-Yves Chibon b1e3c1
    except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon b1e3c1
        progit.SESSION.rollback()
Pierre-Yves Chibon b1e3c1
        progit.APP.logger.exception(err)
Pierre-Yves Chibon b37a8e
Pierre-Yves Chibon e2db6b
    branches = [
Pierre-Yves Chibon e2db6b
        item.replace('* ', '') for item in read_git_lines(
Pierre-Yves Chibon e2db6b
            ['branch', '--contains', commitid],
Pierre-Yves Chibon e2db6b
            keepends=False)
Pierre-Yves Chibon e2db6b
    ]
Pierre-Yves Chibon b37a8e
Pierre-Yves Chibon b37a8e
    if 'master' in branches:
Pierre-Yves Chibon b37a8e
        try:
Pierre-Yves Chibon 885f0b
            progit.lib.edit_issue(
Pierre-Yves Chibon 885f0b
                progit.SESSION,
Pierre-Yves Chibon 885f0b
                issue,
Pierre-Yves Chibon 885f0b
                ticketfolder=progit.APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon 885f0b
                status='Fixed')
Pierre-Yves Chibon b37a8e
            progit.SESSION.commit()
Pierre-Yves Chibon b37a8e
        except progit.exceptions.ProgitException as err:
Pierre-Yves Chibon b37a8e
            print err
Pierre-Yves Chibon b1e3c1
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon b1e3c1
            progit.SESSION.rollback()
Pierre-Yves Chibon b1e3c1
            progit.APP.logger.exception(err)
Pierre-Yves Chibon b37a8e
Pierre-Yves Chibon b37a8e
Pierre-Yves Chibon 3935a2
def get_commits_id(fromrev, torev):
Pierre-Yves Chibon 3935a2
    ''' Retrieve the list commit between two revisions and return the list
Pierre-Yves Chibon 3935a2
    of their identifier.
Pierre-Yves Chibon 3935a2
    '''
Pierre-Yves Chibon 129a3d
    cmd = ['rev-list', '%s...%s' % (torev, fromrev)]
Pierre-Yves Chibon 3935a2
    return read_git_lines(cmd)
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon b9137f
def get_repo_name():
Pierre-Yves Chibon b9137f
    ''' Return the name of the git repo based on its path.
Pierre-Yves Chibon b9137f
    '''
Pierre-Yves Chibon b9137f
    repo = os.path.basename(os.getcwd()).split('.git')[0]
Pierre-Yves Chibon b9137f
    return repo
Pierre-Yves Chibon b9137f
Pierre-Yves Chibon b9137f
Pierre-Yves Chibon 0392d2
def get_username():
Pierre-Yves Chibon 0392d2
    ''' Return the username of the git repo based on its path.
Pierre-Yves Chibon 0392d2
    '''
Pierre-Yves Chibon 0392d2
    username = None
Pierre-Yves Chibon 0392d2
    repo = os.path.abspath(os.path.join(os.getcwd(), '..'))
Pierre-Yves Chibon 0392d2
    if progit.APP.config['FORK_FOLDER'] in repo:
Pierre-Yves Chibon 0392d2
        username = repo.split(progit.APP.config['FORK_FOLDER'])[1]
Pierre-Yves Chibon 0392d2
    return username
Pierre-Yves Chibon 0392d2
Pierre-Yves Chibon 0392d2
Pierre-Yves Chibon 8736e9
def get_pusher(commit):
Pierre-Yves Chibon 5ff2ba
    ''' Return the name of the person that pushed the commit. '''
Pierre-Yves Chibon 8736e9
    user = None
Pierre-Yves Chibon 8736e9
    output = read_git_lines(
Pierre-Yves Chibon 8736e9
        ['show', '--pretty=format:"%ae"', commit], keepends=False)
Pierre-Yves Chibon 8736e9
    if output:
Pierre-Yves Chibon 8736e9
        user = output[0].replace('"', '')
Pierre-Yves Chibon 8736e9
    if not user:
Pierre-Yves Chibon 8736e9
        user = os.environ.get('GL_USER', os.environ.get('USER', None))
Pierre-Yves Chibon 8736e9
    return user
Pierre-Yves Chibon 5ff2ba
Pierre-Yves Chibon 5ff2ba
Pierre-Yves Chibon 3935a2
def run_as_post_receive_hook():
Pierre-Yves Chibon 5ff2ba
Pierre-Yves Chibon 3935a2
    changes = []
Pierre-Yves Chibon 3935a2
    for line in sys.stdin:
Pierre-Yves Chibon 3935a2
        print line
Pierre-Yves Chibon 3935a2
        (oldrev, newrev, refname) = line.strip().split(' ', 2)
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
        print '  -- Old rev'
Pierre-Yves Chibon 3935a2
        print oldrev
Pierre-Yves Chibon 3935a2
        print '  -- New rev'
Pierre-Yves Chibon 3935a2
        print newrev
Pierre-Yves Chibon 3935a2
        print '  -- Ref name'
Pierre-Yves Chibon 3935a2
        print refname
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
        generate_revision_change_log(get_commits_id(oldrev, newrev))
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon b9137f
    print 'repo:', get_repo_name()
Pierre-Yves Chibon b9137f
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
def main(args):
Pierre-Yves Chibon 3935a2
        run_as_post_receive_hook()
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
Pierre-Yves Chibon 3935a2
if __name__ == '__main__':
Pierre-Yves Chibon 3935a2
    main(sys.argv[1:])