From 068c18d38af8a741a083e28c4ebf86e0ac920df0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 24 Jan 2017 13:30:18 -0800 Subject: [PATCH] Add first class support for golang hooks --- pre_commit/git.py | 5 ++ pre_commit/languages/all.py | 2 + pre_commit/languages/docker.py | 5 +- pre_commit/languages/golang.py | 72 +++++++++++++++++++ pre_commit/languages/helpers.py | 15 ++++ pre_commit/languages/swift.py | 5 +- .../golang_hooks_repo/.pre-commit-hooks.yaml | 5 ++ .../golang-hello-world/main.go | 17 +++++ tests/languages/golang_test.py | 22 ++++++ tests/repository_test.py | 8 +++ tox.ini | 2 +- 11 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 pre_commit/languages/golang.py create mode 100644 testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml create mode 100644 testing/resources/golang_hooks_repo/golang-hello-world/main.go create mode 100644 tests/languages/golang_test.py diff --git a/pre_commit/git.py b/pre_commit/git.py index d3946c5b..d4277e79 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -32,6 +32,11 @@ def get_git_dir(git_root): )) +def get_remote_url(git_root): + ret = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)[1] + return ret.strip() + + def is_in_merge_conflict(): git_dir = get_git_dir('.') return ( diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index cc7af88c..f441ddd2 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from pre_commit.languages import docker +from pre_commit.languages import golang from pre_commit.languages import node from pre_commit.languages import pcre from pre_commit.languages import python @@ -43,6 +44,7 @@ from pre_commit.languages import system languages = { 'docker': docker, + 'golang': golang, 'node': node, 'pcre': pcre, 'python': python, diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 2e9129a7..fd249958 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -60,9 +60,8 @@ def install_environment( assert repo_cmd_runner.exists('Dockerfile'), ( 'No Dockerfile was found in the hook repository' ) - assert version == 'default', ( - 'Pre-commit does not support language_version for docker ' - ) + helpers.assert_version_default('docker', version) + helpers.assert_no_additional_deps('docker', additional_dependencies) assert_docker_available() directory = repo_cmd_runner.path( diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py new file mode 100644 index 00000000..ff4775d1 --- /dev/null +++ b/pre_commit/languages/golang.py @@ -0,0 +1,72 @@ +from __future__ import unicode_literals + +import contextlib +import os.path + +from pre_commit import git +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import Var +from pre_commit.languages import helpers +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output +from pre_commit.xargs import xargs + + +ENVIRONMENT_DIR = 'golangenv' + + +def get_env_patch(venv): # pragma: windows no cover + return ( + ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))), + ) + + +@contextlib.contextmanager +def in_env(repo_cmd_runner): # pragma: windows no cover + envdir = repo_cmd_runner.path( + helpers.environment_dir(ENVIRONMENT_DIR, 'default'), + ) + with envcontext(get_env_patch(envdir)): + yield + + +def guess_go_dir(remote_url): + if remote_url.endswith('.git'): + remote_url = remote_url[:-1 * len('.git')] + remote_url = remote_url.replace(':', '/') + looks_like_url = '//' in remote_url or '@' in remote_url + if looks_like_url: + _, _, remote_url = remote_url.rpartition('//') + _, _, remote_url = remote_url.rpartition('@') + return remote_url + else: + return 'unknown_src_dir' + + +def install_environment( + repo_cmd_runner, + version='default', + additional_dependencies=(), +): # pragma: windows no cover + helpers.assert_version_default('golang', version) + helpers.assert_no_additional_deps('golang', additional_dependencies) + directory = repo_cmd_runner.path( + helpers.environment_dir(ENVIRONMENT_DIR, 'default'), + ) + + with clean_path_on_failure(directory): + remote = git.get_remote_url(repo_cmd_runner.path()) + repo_src_dir = os.path.join(directory, 'src', guess_go_dir(remote)) + + # Clone into the goenv we'll create + helpers.run_setup_cmd( + repo_cmd_runner, ('git', 'clone', '.', repo_src_dir), + ) + + env = dict(os.environ, GOPATH=directory) + cmd_output('go', 'get', './...', cwd=repo_src_dir, env=env) + + +def run_hook(repo_cmd_runner, hook, file_args): # pragma: windows no cover + with in_env(repo_cmd_runner): + return xargs(helpers.to_cmd(hook), file_args) diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index a035c470..a6c93de1 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -18,3 +18,18 @@ def environment_dir(ENVIRONMENT_DIR, language_version): def to_cmd(hook): return tuple(shlex.split(hook['entry'])) + tuple(hook['args']) + + +def assert_version_default(binary, version): + if version != 'default': + raise AssertionError( + 'For now, pre-commit requires system-installed {}'.format(binary), + ) + + +def assert_no_additional_deps(lang, additional_deps): + if additional_deps: + raise AssertionError( + 'For now, pre-commit does not support ' + 'additional_dependencies for {}'.format(lang), + ) diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index d362e01e..4d171c5b 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -33,9 +33,8 @@ def install_environment( version='default', additional_dependencies=(), ): # pragma: windows no cover - assert version == 'default', ( - 'Pre-commit does not support language_version for docker ' - ) + helpers.assert_version_default('swift', version) + helpers.assert_no_additional_deps('swift', additional_dependencies) directory = repo_cmd_runner.path( helpers.environment_dir(ENVIRONMENT_DIR, 'default'), ) diff --git a/testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml new file mode 100644 index 00000000..206733bb --- /dev/null +++ b/testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml @@ -0,0 +1,5 @@ +- id: golang-hook + name: golang example hook + entry: golang-hello-world + language: golang + files: '' diff --git a/testing/resources/golang_hooks_repo/golang-hello-world/main.go b/testing/resources/golang_hooks_repo/golang-hello-world/main.go new file mode 100644 index 00000000..1e3c591a --- /dev/null +++ b/testing/resources/golang_hooks_repo/golang-hello-world/main.go @@ -0,0 +1,17 @@ +package main + + +import ( + "fmt" + "github.com/BurntSushi/toml" +) + +type Config struct { + What string +} + +func main() { + var conf Config + toml.Decode("What = 'world'\n", &conf) + fmt.Printf("hello %v\n", conf.What) +} diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py new file mode 100644 index 00000000..e0c9ab42 --- /dev/null +++ b/tests/languages/golang_test.py @@ -0,0 +1,22 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +import pytest + +from pre_commit.languages.golang import guess_go_dir + + +@pytest.mark.parametrize( + ('url', 'expected'), + ( + ('/im/a/path/on/disk', 'unknown_src_dir'), + ('git@github.com:golang/lint', 'github.com/golang/lint'), + ('git://github.com/golang/lint', 'github.com/golang/lint'), + ('http://github.com/golang/lint', 'github.com/golang/lint'), + ('https://github.com/golang/lint', 'github.com/golang/lint'), + ('ssh://git@github.com/golang/lint', 'github.com/golang/lint'), + ('git@github.com:golang/lint.git', 'github.com/golang/lint'), + ), +) +def test_guess_go_dir(url, expected): + assert guess_go_dir(url) == expected diff --git a/tests/repository_test.py b/tests/repository_test.py index 203852ce..9830c58b 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -257,6 +257,14 @@ def test_swift_hook(tempdir_factory, store): ) +@pytest.mark.integration +def test_golang_hook(tempdir_factory, store): + _test_hook_repo( + tempdir_factory, store, 'golang_hooks_repo', + 'golang-hook', [], b'hello world\n', + ) + + @pytest.mark.integration def test_missing_executable(tempdir_factory, store): _test_hook_repo( diff --git a/tox.ini b/tox.ini index 4063b93b..805e293b 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = py27,py34,py35,pypy [testenv] deps = -rrequirements-dev.txt -passenv = HOME HOMEPATH PROGRAMDATA TERM +passenv = GOROOT HOME HOMEPATH PROGRAMDATA TERM setenv = VIRTUALENV_NO_DOWNLOAD = 1 GIT_AUTHOR_NAME = "test"