Files @ 890e7e5fa47c
Branch filter:

Location: majic-scripts/hooks/thebuggenie_hg_remote.py

branko
Updated the hg ignore file to ignore the Python compiled files (pyc) as well.
#
# Hook for submitting Mercurial pushed commits to The Bug Genie using the HTTP
# Access mode.
#
# The hook can be used either as a 'changegroup' or 'incoming' hook. When
# executed as a changegroup hook, all pushed changes will be processed.
#
# In order to use same hook configuration for all of user's repositories, it is
# required to modify the the user's ~/.hgrc Mercurial configuration file. In
# order to have per-repository configuration for the hook, use induvidual
# repository's Mercurial configuration file (.hg/hgrc file, relative to
# repository's root directory). The recommended way is to use the per-repository
# settings.
#
# The following parameters have to be defined (under the specified sections in
# the configuration file):
#
# [extensions]
#
# buggenie = /path/to/thebuggenie_hg_remote.py
#
# [buggenie]
# program = [curl|wget]
# url = tbg_url
# passkey = project_passkey
# project = project_id
# nosslverify = [true|false]
# 
# [hooks]
# changegroup.buggenie = python:buggenie.hook
#
# The following parameters are mandatory:
#
# program
#    Specifies the program that should be used for submitting the report to
#    TBG. Supported programs are 'wget' and 'curl'.
# url
#    Specifies the base URL where The Bug Genie installation can be reached
#    at.
# passkey
#    Specifies the passkey that should be used for authenticating with The Bug
#    Genie instance for the specified project. The passkey can be obtained from
#    the project's 'VCS Integration' page.
# project
#    Specifies the project identifier that should be used when submitting a
#    report to The Bug Genie. The project ID can be obtained from the project's
#    'VCS Integration' page.
#
# The following parameters are optional:
#
# nosslverify
#     Specifies that the calling program should _not_ verify the certificate (if
#     the URL specified begins with https). This can be used in conjunction with
#     the self-signed certificates.
#

from urllib import quote
import subprocess

import mercurial.node

def call_program(program, arguments, url):
    """
    Calls the specified program, passing it the arguments. The URL is passed
    through stdin to the program.


    Arguments:

    program - Binary that should be called.

    arguments - Arguments that should be passed to the program.

    url - TBG report submit URL that should be passed for processing to the
    program.

    Returns:

    Tuple consisting out of error_code, stdout, and stderr generated by call.
    """

    # Prefix the arguments with program.
    arguments.insert(0, program)

    # Set-up the subprocess for execution.
    process = subprocess.Popen(arguments, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    # curl expects slightly different format when reading URL from stdin.
    if program == "curl":
        url = "url=%s" % url

    # Obtain the contents of stdout and stderr.
    stdout, stderr = process.communicate(url)

    # Return the exit code of process, and message that should be shown to user.
    return process.returncode, stdout, stderr

def extract_report(repo, context):
    """
    Extract the commit report from provided context.


    Arguments:

    repo - Mercurial repository object.

    context - Mercurial commit context for which the report should be extracted.


    Returns:

    Dictionary containing the key/value pairs that should be used for
    constructing the GET arguments of a TBG update URL.
    """

    # Extract some basic information first.
    report = {}
    report['author'] = context.user()
    report['rev'] = "%d:%s" % (context.rev(), mercurial.node.short(context.node()))
    report['commit_msg'] = context.description()
    report['date'] = str(context.date()[0]).split(".0")[0]

    # Extract parent revision identifier.
    parent = context.parents()[0]
    report['oldrev'] = "%d:%s" % (parent.rev(), mercurial.node.short(parent.node()))

    # Get the list of changed files compared to previous commit.
    files_changed = repo.status(parent.node(), context.node())

    # Create a string containing information about all changes, one file per
    # line, first column being type of change, second column being filename.
    files_changed_status = []
    for f in files_changed[0]:
        files_changed_status.append("U %s" % f)
    for f in files_changed[1]:
        files_changed_status.append("A %s" % f)
    for f in files_changed[2]:
        files_changed_status.append("D %s" % f)
    report['changed'] = "\n".join(files_changed_status)

    # Finally return the report.
    return report

def submit_report(commit, repo, ui, program, program_params, base_url, project_id, passkey):
    """
    Submits report to TBG for a single commit.

    Arguments:
    
    commit - Mercurial commit identifier.

    repo - Mercurial repository object.

    ui - Mercurial user interface object.

    program - Name of the program that should be used.

    program_params - Parameters that should be passed to the program.

    base_url - Base URL at which TBG can be found.

    project_id - TBG project identifier.

    passkey - Passkey that should be used for authentication.
    """

    # Get the commit hash and context.
    commit_hash = repo.changelog.node(commit)
    commit_context = repo.changectx(commit_hash)
    # Extract information for the commit.
    report = extract_report(repo, commit_context)
    # Add the passkey to report, it will be used for constructing the URL.
    report["passkey"] = passkey

    # Generate the URL.
    # Strip the trailing slash.
    base_url = base_url.rstrip("/")
    # Set-up the base link for project.
    url = "%s/vcs_integration/report/%s/?" % (base_url, project_id)
    # Pass the GET arguments.
    args = [ "%s=%s" % (quote(key), quote(value)) for key, value in report.iteritems()]
    url += "&".join(args)

    # Call the program.
    exit_code, stdout, stderr = call_program(program, program_params, url)

    # Since wget has a bit poor way of outputting error, filter out the URL used
    # (in order to avoid passkey leakage).
    if program == "wget":
        stderr = "\n".join(stderr.split("\n")[1:])

    # Output the stderr content if program returned with an error. Otherwise
    # output the information retrieved from TBG.
    if exit_code != 0:
        ui.warn("TBG Hook: An error happened while trying to submit the data.\n")
        ui.warn("TBG Hook: Program returned error message:\n")
        # Strip out the whitespaces and newlines from the end, and make sure we
        # have a single final newline.
        stderr.rstrip()
        stderr += "\n"
        ui.warn(stderr)
    else:
        # Strip out the whitespaces and newlines from the end, and make sure we
        # have a single final newline.
        stdout.rstrip()
        stdout += "\n"
        ui.warn(stdout)

def hook(ui, repo, hooktype, node = None, url = None, **kwargs):
    """
    Commit and changegroup hook for Mercurial.


    Arguments:

    ui - Mercurial user interface object.

    repo - Mercurial repository object.

    node - Mercurial revision ID.

    url - Mercurial path/URL.

    kwargs - Additional keyword arguments.
    """

    # Read the configuration options.
    config = dict((param, value) for param, value in ui.configitems('buggenie'))

    # Make sure the required settings were specified.
    if not "program" in config:
        ui.warn("TBG HG Hook: No program was specified. Check your settings.\n")
        return
    if not "url" in config:
        ui.warn("TBG HG Hook: No base URL was specified. Check your settings.\n")
        return
    if not "project" in config:
        ui.warn("TBG HG Hook: No project ID was specified. Check your settings.\n")
        return
    if not "passkey" in config:
        ui.warn("TBG HG Hook: No passkey was specified. Check your settings.\n")
        return

    # Set-up parameters to be passed onto program.
    if config["program"] == "wget":
        # Use dots for progress, output to stdout, read link from stdin.
        program_params = [ "--progress=dot", "-O", "-", "-i", "-" ]
        if config.get("nosslverify") == "true":
            program_params.append("--no-check-certificate")
    elif config["program"] == "curl":
        # Quiet, but show errors, fail on server error, and read link from stdin.
        program_params = [ "-s", "-S", "-f", "-K", "-" ]
        if config.get("nosslverify") == "true":
            program_params.append("--insecure")
    else:
        ui.warn("TBG HG Hook: Specified program '%s' is not supported. Check your settings.\n")
        return

    # Get the node hash.
    node_hash = mercurial.node.bin(node)

    # If the hook is called as a changegroup hook, process all related commits.
    if hooktype == "changegroup":
        start = repo.changelog.rev(node_hash)
        end = len(repo.changelog)

        for commit in xrange(start, end):
            submit_report(commit, repo, ui, config["program"], program_params, config["url"], config["project"], config["passkey"])

    # If the hook is called as a commit, hook, process just the single commit.
    elif hooktype == "commit":
        submit_report(commit, repo, ui, config["program"], program_params, config["url"], config["project"], config["passkey"])