#!/usr/bin/python
#
# pylintcodediff --- Locate pylint errors corresponding to changed lines
#
# Copyright (C) 2014  Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author: Anne Mulhern <amulhern@redhat.com>

from __future__ import print_function
import argparse
import os
import subprocess
import sys

def make_parser(parser):
    parser.add_argument('commit',
       help='most recent ignored commit')
    parser.add_argument('--branch',
       default='pylint-code-diff-test',
       help='name of test branch')
    parser.add_argument('--force',
       action='store_true',
       help='run anyway')
    parser.add_argument('--pylint-log',
       default='pylint-log',
       help='path to pylint log')

def run_command(command):
    """ Run a git command.

        :return: (stdout, stderr, returncode) tuple
    """
    proc = subprocess.Popen(command,
       stdout=subprocess.PIPE,
       stderr=subprocess.PIPE)
    (stdoutdata, stderrdata) = proc.communicate()
    while True:
        if proc.returncode is not None:
            return (stdoutdata, stderrdata, proc.returncode)

def current_branch_name():
    """ Reads current git branch from stdout.

        :return: name of current git branch
        :rtype: str
    """
    command = [ 'git', 'rev-parse', '--abbrev-ref', 'HEAD']
    (stdoutdata, _stderrdata, ret) = run_command(command)
    if ret == 0:
        return stdoutdata.strip()
    else:
        raise RuntimeError("failed to get current branch name")

def checkout_new_branch(branch):
    """ Checkout a git branch.

        :param str branch: Name of branch, should not currently exist.
    """
    command = [ 'git', 'checkout', '-b', branch ]
    (_stdoutdata, _stderrdata, ret) = run_command(command)
    if ret != 0:
        raise RuntimeError("failed to check out new branch %s" % branch)

def checkout_branch(branch, force=False):
    """ Checkout a git branch.

        :param str branch: Name of branch, should not currently exist.
    """
    command = [ 'git', 'checkout', branch ]
    if force:
        command.append('-f')
    (_stdoutdata, _stderrdata, ret) = run_command(command)
    if ret != 0:
        raise RuntimeError("failed to check out branch %s" % branch)

def remove_branch(branch):
    """ Remove a git branch.

        :param str branch: Name of branch, must exist.
    """
    command = [ 'git', 'branch', '-D', branch ]
    (_stdoutdata, _stderrdata, ret) = run_command(command)
    if ret != 0:
        raise RuntimeError("failed to delete branch %s" % branch)

def reset(commit):
    """ Reset current branch to commit.

        :param str commit: commit id
    """
    command = [ 'git', 'reset', commit ]
    (_stdoutdata, _stderrdata, ret) = run_command(command)
    if ret != 0:
        raise RuntimeError("failed to reset commit %s" % commit)

def diff_quality(pylint_file):
    command = [ 'diff-quality', '--violations=pylint', pylint_file ]
    (stdoutdata, _stderrdata, ret) = run_command(command)
    if ret != 0:
        raise RuntimeError("diff-quality did not complete succesfully")

    print(stdoutdata)

def clean():
    """ Checks whether git detects some changes in the current branch.

        :return: True if git finds no changes, otherwise False
        :rtype: bool
    """
    command = [ 'git', 'diff-index', '--exit-code', '--cached', 'HEAD' ]
    (_stdoutdata, _stderrdata, ret) = run_command(command)
    if ret != 0:
        return False

    command = [ 'git', 'diff-files', '--exit-code' ]
    (_stdoutdata, _stderrdata, ret) = run_command(command)
    if ret != 0:
        return False

    return True

def main():
    parser = argparse.ArgumentParser(description="Find pylint messages related to changes subsequent to given commit.")
    make_parser(parser)

    args = parser.parse_args()

    branch = args.branch
    commit = args.commit
    force = args.force

    if not force and not clean():
        sys.exit(
           "Your working branch has some changes that may be lost while "
           "running this test. Use the --force flag to proceed anyway."
        )

    pylint_log = os.path.abspath(args.pylint_log)

    if not os.path.isfile(pylint_log):
        sys.exit("Pylint log file %s does not exist." % pylint_log)

    try:
        current_branch = current_branch_name()
        checkout_new_branch(branch)
    except RuntimeError as e:
        sys.exit("Error setting up test branch: %s" % e)

    try:
        reset(commit)
        diff_quality(pylint_log)
    except RuntimeError as e:
        print("Couldn't complete testing, trying to clean up: %s." % e, file=sys.stderr)

    try:
        checkout_branch(current_branch, force=True)
        remove_branch(branch)
    except RuntimeError as e:
        sys.exit("Error cleaning up branch %s after testing: %s." % (branch, e))

if __name__ == "__main__":
    main()
