Changeset - ecb2493d218a
[Not reviewed]
0 0 1
Branko Majic (branko) - 4 years ago 2020-06-29 04:18:01
branko@majic.rs
Implemented Mercurial hook for updating TBG issues with commit information. The hook works only with remote (HTTP) access. Resolves issue SCR-2.
1 file changed with 273 insertions and 0 deletions:
0 comments (0 inline, 0 general)
hooks/thebuggenie_hg_remote.py
Show inline comments
 
new file 100755
 
#
 
# 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.
 
#
 

	
 
import mercurial.node.bin
 
import mercurial.node.short
 
from urllib import quote
 
import subprocess
 

	
 
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("A %s" % f)
 
    for f in files_changed[1]:
 
        files_changed_status.append("D %s" % f)
 
    for f in files_changed[2]:
 
        files_changed_status.append("U %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"])
 

	
0 comments (0 inline, 0 general)