#!/usr/bin/python
"""
Script to verify errors on autotest code contributions (patches).
The workflow is as follows:

 * Patch will be applied and eventual problems will be notified.
 * If there are new files created, remember user to add them to VCS.
 * If any added file looks like a executable file, remember user to make them
   executable.
 * If any of the files added or modified introduces trailing whitespaces, tabs
   or incorrect indentation, report problems.
 * If any of the files have problems during pylint validation, report failures.
 * If any of the files changed have a unittest suite, run the unittest suite
   and report any failures.

Usage: check_patch.py -p [/path/to/patch]
       check_patch.py -i [patchwork id]
       check_patch.py -g [github pull request id]
       check_patch.py --full --yes [check all autotest tree]

@copyright: Red Hat Inc, 2009.
@author: Lucas Meneghel Rodrigues <lmr@redhat.com>
"""

import os, stat, logging, sys, optparse, re, urllib, shutil, unittest
try:
    import autotest.common as common
except ImportError:
    import common

from autotest.client.shared import utils, error, logging_config
from autotest.client.shared import logging_manager
from autotest.utils import run_pylint, reindent

# Hostname of patchwork server to use
PWHOST = "patchwork.virt.bos.redhat.com"

class CheckPatchLoggingConfig(logging_config.LoggingConfig):
    def configure_logging(self, results_dir=None, verbose=True):
        super(CheckPatchLoggingConfig, self).configure_logging(use_console=True,
                                                               verbose=verbose)
        p = os.path.join(os.getcwd(), 'check-patch.log')
        self.add_file_handler(file_path=p)

class VCS(object):
    """
    Abstraction layer to the version control system.
    """
    def __init__(self):
        """
        Class constructor. Guesses the version control name and instantiates it
        as a backend.
        """
        backend_name = self.guess_vcs_name()
        if backend_name == "SVN":
            self.backend = SubVersionBackend()
            self.type = "subversion"
        elif backend_name == "git":
            self.backend = GitBackend()
            self.type = "git"
        else:
            self.backend = None


    def guess_vcs_name(self):
        if os.path.isdir(".svn"):
            return "SVN"
        elif os.path.isdir(".git"):
            return "git"
        else:
            logging.error("Could not figure version control system. Are you "
                          "on a working directory? Aborting.")
            sys.exit(1)


    def get_unknown_files(self):
        """
        Return a list of files unknown to the VCS.
        """
        return self.backend.get_unknown_files()


    def get_modified_files(self):
        """
        Return a list of files that were modified, according to the VCS.
        """
        return self.backend.get_modified_files()


    def is_file_tracked(self, fl):
        """
        Return whether a file is tracked by the VCS.
        """
        return self.backend.is_file_tracked(fl)


    def add_untracked_file(self, fl):
        """
        Add an untracked file to version control.
        """
        return self.backend.add_untracked_file(file)


    def revert_file(self, fl):
        """
        Restore file according to the latest state on the reference repo.
        """
        return self.backend.revert_file(file)


    def apply_patch(self, patch):
        """
        Applies a patch using the most appropriate method to the particular VCS.
        """
        return self.backend.apply_patch(patch)


    def update(self):
        """
        Updates the tree according to the latest state of the public tree
        """
        return self.backend.update()


class SubVersionBackend(object):
    """
    Implementation of a subversion backend for use with the VCS abstraction
    layer.
    """
    def __init__(self):
        self.ignored_extension_list = ['.orig', '.bak']


    def get_unknown_files(self):
        status = utils.system_output("svn status --ignore-externals")
        unknown_files = []
        for line in status.split("\n"):
            status_flag = line[0]
            if line and status_flag == "?":
                for extension in self.ignored_extension_list:
                    if not line.endswith(extension):
                        unknown_files.append(line[1:].strip())
        return unknown_files


    def get_modified_files(self):
        status = utils.system_output("svn status --ignore-externals")
        modified_files = []
        for line in status.split("\n"):
            status_flag = line[0]
            if line and status_flag == "M" or status_flag == "A":
      
          modified_files.append(line[1:].strip())
        return modified_files


    def is_file_tracked(self, fl):
        stdout = None
        try:
            cmdresult = utils.run("svn status --ignore-externals %s" % fl,
                                  verbose=False
)
            stdout = cmdresult.stdout
        except error.CmdError:
            return False

        if stdout is not None:
            if stdout:
                if stdout.startswith("?"):
                    return False
                else:
                    return True
            else:
                return True
        else:
            return False


    def add_untracked_file(self, fl):
        """
        Add an untracked file under revision control.

        @param file: Path to untracked file.
        """
        try:
            utils.run('svn add %s' % fl)
        except error.CmdError, e:
            logging.error("Problem adding file %s to svn: %s", fl, e)
            sys.exit(1)


    def revert_file(self, fl):
        """
        Revert file against last revision.

        @param file: Path to file to be reverted.
        """
        try:
            utils.run('svn revert %s' % fl)
        except error.CmdError, e:
            logging.error("Problem reverting file %s: %s", fl, e)
            sys.exit(1)


    def apply_patch(self, patch):
        """
        Apply a patch to the code base. Patches are expected to be made using
        level -p1, and taken according to the code base top level.

        @param patch: Path to the patch file.
        """
        try:
            utils.system_output("patch -p1 < %s" % patch)
        except:
            logging.error("Patch applied incorrectly. Possible causes: ")
            logging.error("1 - Patch might not be -p1")
            logging.error("2 - You are not at the top of the autotest tree")
            logging.error("3 - Patch was made using an older tree")
            logging.error("4 - Mailer might have messed the patch")
            sys.exit(1)

    def update(self):
        try:
            utils.system("svn update")
        except error.CmdError, e:
            logging.error("SVN tree update failed: %s" % e)


class GitBackend(object):
    """
    Implementation of a git backend for use with the VCS abstraction layer.
    """
    def __init__(self):
        self.ignored_extension_list = ['.orig', '.bak']


    def get_unknown_files(self):
        status = utils.system_output("git status --porcelain")
        unknown_files = []
        for line in status.split("\n"):
            status_flag = line[0]
            if line and status_flag == "??":
                for extension in self.ignored_extension_list:
                    if not line.endswith(extension):
                        unknown_files.append(line[2:].strip())
        return unknown_files


    def get_modified_files(self):
        status = utils.system_output("git status --porcelain")
        modified_files = []
        for line in status.split("\n"):
            status_flag = line[0]
            if line and status_flag == "M" or status_flag == "A":
                modified_files.append(line[1:].strip())
        return modified_files


    def is_file_tracked(self, fl):
        try:
            utils.run("git ls-files %s --error-unmatch" % fl,
                      verbose=False)
            return True
        except error.CmdError:
            return False


 