<commit_before>#!/usr/bin/env python

"""
git-pw: TODO
"""

from __future__ import print_function

import argparse
import os
import subprocess
import sys
import xml

if sys.version < '3':
    import xmlrpclib
else:
    import xmlrpc.client as xmlrpclib

import pkg_resources


def get_version():
    try:
        return pkg_resources.get_provider(
            pkg_resources.Requirement.parse('git-pw'))
    except pkg_resources.DistributionNotFound:
        return 0


class GitPWException(Exception):
    msg_fmt = 'An unknown exception occured'

    def __init__(self, message=None, **kwargs):
        if not message:
            try:
                message = self.msg_fmt % kwargs
            except KeyError:
                # someone did something silly, but print the message anyway
                message = self.msg_fmt

        self.message = message
        super(GitPWException, self).__init__(message)


class ConnectionFailed(GitPWException):
    msg_fmt = 'Unable to connect to \'%(host)s\': %(reason)s'


class UnsupportedOperation(GitPWException):
    msg_fmt = ('Your patchwork version is too old. '
               'Expected: %(expected)s, Actual: %(actual)s')


class InvalidPatchID(GitPWException):
    msg_fmt = 'The patch \'%(patch_id)s\' was not found'


class MissingConfig(GitPWException):
    msg_fmt = 'The key \'%(name)s\' was not found. Did you set it?'


def _get_connection(host=None):
    """Creates an connection to the XML-RPC API."""
    if not host:
        host = get_config('pw.server')

    try:
        api = xmlrpclib.ServerProxy(host)
        api.pw_rpc_version()  # ensure the connection is actually valid
    except xmlrpclib.ProtocolError:
        raise ConnectionFailed(host=host, reason='protocol error')
    except xml.parsers.expat.ExpatError:
        raise ConnectionFailed(host=host, reason='invalid response')
    except Exception:
        raise ConnectionFailed(host=host, reason='unknown error')

    return api


def _run_command(args, stdin=None):
    p = subprocess.Popen(
        args,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        stdin=subprocess.PIPE if stdin else None)
    (out, _) = p.communicate(stdin)
    out = out.decode('utf-8', 'replace').strip()

    return (p.returncode, out)


def require_api_version(version):
    """Ensure PW API is correct version for command.

    This allows the application to gracefully degrade, based on the features
    of the API.
    """
    def _require_api_version_inner(func):
        def _validation_wrapper(*args, **kwargs):
            api = _get_connection()
            pw_version = api.pw_rpc_version()

            # NOTE(stephenfin): Older versions of the API return an int, hence
            # need to cast
            if pw_version == 1:
                pw_version = (1, 0, 0)

            if pw_version < version:
                raise UnsupportedOperation(expected=version, actual=pw_version)

            func(*args, **kwargs)

        return _validation_wrapper

    return _require_api_version_inner


def get_config(name, default=None):
    cmd = ['git', 'config', '--get', name]
    code, output = _run_command(cmd)

    # TODO(stephenfin): Handle non-'1' return codes:
    #   https://www.kernel.org/pub/software/scm/git/docs/git-config.html
    if code:
        if default:
            return default
        raise MissingConfig(name=name)

    return output


def cherrypick_patch(patch_id):
    api = _get_connection()
    patch = api.patch_get_mbox(patch_id)
    if not patch:
        raise InvalidPatchID(patch_id=patch_id)

    # TODO(stephenfin): We should probably make sure the patch applies
    # cleanly before doing so. Maybe '--dry-run'?
    cmd = ['git', 'am']
    code, output = _run_command(cmd, patch)
    # TODO(stephenfin): Use an exception here
    if code:
        print(output)
        sys.exit(1)


# TODO(stephenfin): Series support has still not merged so this version should
# be updated when appropriate
@require_api_version((1, 1, 0))
def download_patch(patch_id):
    raise NotImplementedError('Series support is still in limbo')


# TODO(stephenfin): Series support has still not merged so this version should
# be updated when appropriate
@require_api_version((1, 1, 0))
def download_series(series_id):
    raise NotImplementedError('Series support is still in limbo')


def list_patches():
    api = _get_connection()

    # TODO(stephenfin): Limit patches to open status
    patches = api.patch_list({'project_id': get_config('pw.projectid')})
    patches = [(patch['id'], patch['project'], patch['name']) for patch in patches]

    for patch in patches:
        # TODO(stephenfin): It would be good to calculate the length of these
        # fields dynamically
        # TODO(stephenfin): Colour is good. We should use some.
        print('%6s  %12s  %s' % patch)


def main():
    usage = 'git pw [OPTIONS...] [BRANCH]'

    class DownloadAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            setattr(namespace, self.dest, values)
            setattr(namespace, self.const, True)

    parser = argparse.ArgumentParser(usage=usage, description=__doc__)<commit_msg>pep8: Resolve 'E501 line too long' error<commit_after>#!/usr/bin/env python

"""
git-pw: TODO
"""

from __future__ import print_function

import argparse
import os
import subprocess
import sys
import xml

if sys.version < '3':
    import xmlrpclib
else:
    import xmlrpc.client as xmlrpclib

import pkg_resources


def get_version():
    try:
        return pkg_resources.get_provider(
            pkg_resources.Requirement.parse('git-pw'))
    except pkg_resources.DistributionNotFound:
        return 0


class GitPWException(Exception):
    msg_fmt = 'An unknown exception occured'

    def __init__(self, message=None, **kwargs):
        if not message:
            try:
                message = self.msg_fmt % kwargs
            except KeyError:
                # someone did something silly, but print the message anyway
                message = self.msg_fmt

        self.message = message
        super(GitPWException, self).__init__(message)


class ConnectionFailed(GitPWException):
    msg_fmt = 'Unable to connect to \'%(host)s\': %(reason)s'


class UnsupportedOperation(GitPWException):
    msg_fmt = ('Your patchwork version is too old. '
               'Expected: %(expected)s, Actual: %(actual)s')


class InvalidPatchID(GitPWException):
    msg_fmt = 'The patch \'%(patch_id)s\' was not found'


class MissingConfig(GitPWException):
    msg_fmt = 'The key \'%(name)s\' was not found. Did you set it?'


def _get_connection(host=None):
    """Creates an connection to the XML-RPC API."""
    if not host:
        host = get_config('pw.server')

    try:
        api = xmlrpclib.ServerProxy(host)
        api.pw_rpc_version()  # ensure the connection is actually valid
    except xmlrpclib.ProtocolError:
        raise ConnectionFailed(host=host, reason='protocol error')
    except xml.parsers.expat.ExpatError:
        raise ConnectionFailed(host=host, reason='invalid response')
    except Exception:
        raise ConnectionFailed(host=host, reason='unknown error')

    return api


def _run_command(args, stdin=None):
    p = subprocess.Popen(
        args,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        stdin=subprocess.PIPE if stdin else None)
    (out, _) = p.communicate(stdin)
    out = out.decode('utf-8', 'replace').strip()

    return (p.returncode, out)


def require_api_version(version):
    """Ensure PW API is correct version for command.

    This allows the application to gracefully degrade, based on the features
    of the API.
    """
    def _require_api_version_inner(func):
        def _validation_wrapper(*args, **kwargs):
            api = _get_connection()
            pw_version = api.pw_rpc_version()

            # NOTE(stephenfin): Older versions of the API return an int, hence
            # need to cast
            if pw_version == 1:
                pw_version = (1, 0, 0)

            if pw_version < version:
                raise UnsupportedOperation(expected=version, actual=pw_version)

            func(*args, **kwargs)

        return _validation_wrapper

    return _require_api_version_inner


def get_config(name, default=None):
    cmd = ['git', 'config', '--get', name]
    code, output = _run_command(cmd)

    # TODO(stephenfin): Handle non-'1' return codes:
    #   https://www.kernel.org/pub/software/scm/git/docs/git-config.html
    if code:
        if default:
            return default
        raise MissingConfig(name=name)

    return output


def cherrypick_patch(patch_id):
    api = _get_connection()
    patch = api.patch_get_mbox(patch_id)
    if not patch:
        raise InvalidPatchID(patch_id=patch_id)

    # TODO(stephenfin): We should probably make sure the patch applies
    # cleanly before doing so. Maybe '--dry-run'?
    cmd = ['git', 'am']
    code, output = _run_command(cmd, patch)
    # TODO(stephenfin): Use an exception here
    if code:
        print(output)
        sys.exit(1)


# TODO(stephenfin): Series support has still not merged so this version should
# be updated when appropriate
@require_api_version((1, 1, 0))
def download_patch(patch_id):
    raise NotImplementedError('Series support is still in limbo')


# TODO(stephenfin): Series support has still not merged so this version should
# be updated when appropriate
@require_api_version((1, 1, 0))
def download_series(series_id):
    raise NotImplementedError('Series support is still in limbo')


def list_patches():
    api = _get_connection()

    # TODO(stephenfin): Limit patches to open status
    patches = api.patch_list({'project_id': get_config('pw.projectid')})
    patches = [
        (patch['id'], patch['project'], patch['name']) for patch in patches]

    for patch in patches:
        # TODO(stephenfin): It would be good to calculate the length of these
        # fields dynamically
        # TODO(stephenfin): Colour is good. We should use some.
        print('%6s  %12s  %s' % patch)


def main():
    usage = 'git pw [OPTIONS...] [BRANCH]'

    class DownloadAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            setattr(namespace, self.dest, values)
            setattr(namespace, self.const, True)

    parser = argparse.ArgumentParser(usage=usage, description=__doc__)
{'fraction_non_alphanumeric': 0.10210526315789474, 'fraction_numerical': 0.003827751196172249, 'mean_word_length': 2.970744680851064, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 14}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '173724', 'n_tokens_mistral': 3208, 'n_tokens_neox': 3036, 'n_words': 897}