diff --git a/flake8/exceptions.py b/flake8/exceptions.py index c5f3c5a..e709ff5 100644 --- a/flake8/exceptions.py +++ b/flake8/exceptions.py @@ -43,10 +43,52 @@ class InvalidSyntax(Flake8Exception): super(InvalidSyntax, self).__init__(*args, **kwargs) -class GitHookAlreadyExists(Flake8Exception): +class HookInstallationError(Flake8Exception): + """Parent exception for all hooks errors.""" + pass + + +class GitHookAlreadyExists(HookInstallationError): """Exception raised when the git pre-commit hook file already exists.""" def __init__(self, *args, **kwargs): """Initialize the path attribute.""" self.path = kwargs.pop('path') super(GitHookAlreadyExists, self).__init__(*args, **kwargs) + + def __str__(self): + """Provide a nice message regarding the exception.""" + msg = ('The Git pre-commit hook ({0}) already exists. To convince ' + 'Flake8 to install the hook, please remove the existing ' + 'hook.') + return msg.format(self.path) + + +class MercurialHookAlreadyExists(HookInstallationError): + """Exception raised when a mercurial hook is already configured.""" + + hook_name = None + + def __init__(self, *args, **kwargs): + """Initialize the relevant attributes.""" + self.path = kwargs.pop('path') + self.value = kwargs.pop('value') + super(MercurialHookAlreadyExists, self).__init__(*args, **kwargs) + + def __str__(self): + msg = ('The Mercurial {0} hook already exists with "{1}" in {2}. ' + 'To convince Flake8 to install the hook, please remove the ' + '{0} configuration from the [hooks] section of your hgrc.') + return msg.format(self.hook_name, self.value, self.path) + + +class MercurialCommitHookAlreadyExists(MercurialHookAlreadyExists): + """Exception raised when the hg commit hook is already configured.""" + + hook_name = 'commit' + + +class MercurialQRefreshHookAlreadyExists(MercurialHookAlreadyExists): + """Exception raised when the hg commit hook is already configured.""" + + hook_name = 'qrefresh' diff --git a/flake8/main/git.py b/flake8/main/git.py index 6e9aead..7e55321 100644 --- a/flake8/main/git.py +++ b/flake8/main/git.py @@ -71,7 +71,9 @@ def install(): if not os.path.exists(hooks_directory): os.mkdir(hooks_directory) - pre_commit_file = os.path.join(hooks_directory, 'hooks', 'pre-commit') + pre_commit_file = os.path.abspath( + os.path.join(hooks_directory, 'pre-commit') + ) if os.path.exists(pre_commit_file): raise exceptions.GitHookAlreadyExists( 'File already exists', diff --git a/flake8/main/mercurial.py b/flake8/main/mercurial.py index 9767f34..54725c6 100644 --- a/flake8/main/mercurial.py +++ b/flake8/main/mercurial.py @@ -4,13 +4,106 @@ .. autofunction:: install """ +import configparser +import os +import subprocess + +from flake8 import exceptions as exc __all__ = ('hook', 'install') -def hook(lazy=False, strict=False): - pass +def hook(ui, repo, **kwargs): + """Execute Flake8 on the repository provided by Mercurial. + + To understand the parameters read more of the Mercurial documentation + around Hooks: https://www.mercurial-scm.org/wiki/Hook. + + We avoid using the ``ui`` attribute because it can cause issues with + the GPL license tha Mercurial is under. We don't import it, but we + avoid using it all the same. + """ + from flake8.main import application + hgrc = find_hgrc(create_if_missing=False) + if hgrc is None: + print('Cannot locate your root mercurial repository.') + raise SystemExit(True) + + hgconfig = configparser_for(hgrc) + strict = hgconfig.get('flake8', 'strict', fallback=True) + + app = application.Application() + app.run() + + if strict: + return app.result_count + return 0 def install(): - pass + """Ensure that the mercurial hooks are installed.""" + hgrc = find_hgrc(create_if_missing=True) + if hgrc is None: + print('Could not locate your root mercurial repository.') + raise SystemExit(True) + + hgconfig = configparser_for(hgrc) + + if not hgconfig.has_section('hooks'): + hgconfig.add_section('hooks') + + if hgconfig.has_option('hooks', 'commit'): + raise exc.MercurialCommitHookAlreadyExists( + path=hgrc, + value=hgconfig.get('hooks', 'commit'), + ) + + if hgconfig.has_option('hooks', 'qrefresh'): + raise exc.MercurialQRefreshHookAlreadyExists( + path=hgrc, + value=hgconfig.get('hooks', 'qrefresh'), + ) + + hgconfig.set('hooks', 'commit', 'python:flake8.main.mercurial.hook') + hgconfig.set('hooks', 'qrefresh', 'python:flake8.main.mercurial.hook') + + if not hgconfig.has_section('flake8'): + hgconfig.add_section('flake8') + + if not hgconfig.has_option('flake8', 'strict'): + hgconfig.set('flake8', 'strict', False) + + with open(hgrc, 'w') as fd: + hgconfig.write(fd) + + +def find_hgrc(create_if_missing=False): + root = subprocess.Popen( + ['hg', 'root'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + (hg_directory, _) = root.communicate() + if callable(getattr(hg_directory, 'decode', None)): + hg_directory = hg_directory.decode('utf-8') + + if not os.path.isdir(hg_directory): + return None + + hgrc = os.path.abspath( + os.path.join(hg_directory, '.hg', 'hgrc') + ) + if not os.path.exists(hgrc): + if create_if_missing: + open(hgrc, 'w').close() + else: + return None + + return hgrc + + +def configparser_for(path): + parser = configparser.ConfigParser(interpolation=None) + parser.read(path) + return parser diff --git a/flake8/main/vcs.py b/flake8/main/vcs.py index 95cbfa1..10e6256 100644 --- a/flake8/main/vcs.py +++ b/flake8/main/vcs.py @@ -1,4 +1,5 @@ """Module containing some of the logic for our VCS installation logic.""" +from flake8 import exceptions as exc from flake8.main import git from flake8.main import mercurial @@ -20,8 +21,15 @@ def install(option, option_string, value, parser): https://docs.python.org/2/library/optparse.html#optparse-option-callbacks """ installer = _INSTALLERS.get(value) - installer() + errored = False + try: + installer() + except exc.HookInstallationError as hook_error: + print(str(hook_error)) + errored = True + raise SystemExit(errored) def choices(): - return _INSTALLERS.keys() + """Return the list of VCS choices.""" + return list(_INSTALLERS.keys())