Files @ 890e7e5fa47c
Branch filter:

Location: majic-scripts/hooks/thebuggenie_hg_remote.py - annotation

branko
Updated the hg ignore file to ignore the Python compiled files (pyc) as well.
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
4feffceb040e
4feffceb040e
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
1e328a842d05
ecb2493d218a
1e328a842d05
ecb2493d218a
1e328a842d05
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
ecb2493d218a
#
# 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"])