From c449478231b6364875a896dfdaea941f4fba0d47 Mon Sep 17 00:00:00 2001 From: Mohammad Alisafaee Date: Mon, 9 Dec 2019 13:28:12 +0100 Subject: [PATCH 1/3] feat: renku doctor checks for git hooks --- renku/core/commands/checks/__init__.py | 2 ++ renku/core/commands/checks/githooks.py | 37 ++++++++++++++++++++++++++ tests/core/commands/test_doctor.py | 34 +++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 renku/core/commands/checks/githooks.py create mode 100644 tests/core/commands/test_doctor.py diff --git a/renku/core/commands/checks/__init__.py b/renku/core/commands/checks/__init__.py index 48ab9788b8..c1c81baeb9 100644 --- a/renku/core/commands/checks/__init__.py +++ b/renku/core/commands/checks/__init__.py @@ -17,6 +17,7 @@ # limitations under the License. """Define repository checks for :program:`renku doctor`.""" +from .githooks import check_git_hooks_installed from .migration import check_dataset_metadata, check_missing_files from .references import check_missing_references from .validate_shacl import check_project_structure, check_datasets_structure @@ -24,6 +25,7 @@ # Checks will be executed in the order as they are listed in __all__. # They are mostly used in ``doctor`` command to inspect broken things. __all__ = ( + 'check_git_hooks_installed', 'check_dataset_metadata', 'check_missing_files', 'check_missing_references', diff --git a/renku/core/commands/checks/githooks.py b/renku/core/commands/checks/githooks.py new file mode 100644 index 0000000000..58b5a592b1 --- /dev/null +++ b/renku/core/commands/checks/githooks.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2019 - Swiss Data Science Center (SDSC) +# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and +# Eidgenössische Technische Hochschule Zürich (ETHZ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Check for required Git hooks.""" +from pathlib import Path + +from git.index.fun import hook_path as get_hook_path + +from renku.core.management.githooks import HOOKS + +from ..echo import WARNING + + +def check_git_hooks_installed(client): + """Checks if all necessary hooks are installed.""" + for hook in HOOKS: + hook_path = Path(get_hook_path(hook, client.repo.git_dir)) + if not hook_path.exists(): + message = WARNING + 'Git hooks are not installed. ' \ + 'Use "renku githooks install" to install them. \n' + return False, message + + return True, None diff --git a/tests/core/commands/test_doctor.py b/tests/core/commands/test_doctor.py new file mode 100644 index 0000000000..b38b47fa0a --- /dev/null +++ b/tests/core/commands/test_doctor.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2017-2019 - Swiss Data Science Center (SDSC) +# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and +# Eidgenössische Technische Hochschule Zürich (ETHZ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Renku doctor tests.""" +from renku.cli import cli + + +def test_git_hooks(runner, project): + """Test detection of not-installed git hooks.""" + # Initially, every thing is OK + result = runner.invoke(cli, ['doctor']) + assert 0 == result.exit_code + assert 'Everything seems to be ok.' in result.output + + result = runner.invoke(cli, ['githooks', 'uninstall']) + assert 0 == result.exit_code + + result = runner.invoke(cli, ['doctor']) + assert 1 == result.exit_code + assert 'Git hooks are not installed.' in result.output From 52ec2b924e8b4c576d49f433555852fce06d6f8e Mon Sep 17 00:00:00 2001 From: Mohammad Alisafaee Date: Mon, 9 Dec 2019 15:47:54 +0100 Subject: [PATCH 2/3] refactor: allow other pre-commit hooks --- renku/data/pre-commit.sh | 52 +++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/renku/data/pre-commit.sh b/renku/data/pre-commit.sh index ae46409fbb..dfa2c9be0c 100755 --- a/renku/data/pre-commit.sh +++ b/renku/data/pre-commit.sh @@ -17,31 +17,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Find all modified files, and exit early if there aren't any. +###################################### +# RENKU HOOK. DO NOT REMOVE OR MODIFY. +###################################### + +# Find all modified files, and do nothing if there aren't any. MODIFIED_FILES=$(git diff --name-only --cached --diff-filter=M) -if [ ! "$MODIFIED_FILES" ]; then - exit 0 -fi +if [ "$MODIFIED_FILES" ]; then + # Verify that renku is installed; if not, warn and exit. + if [ -z "$(command -v renku)" ]; then + echo 'renku not on path; can not format. Please install renku:' + # TODO add system detection and recommend brew for macOS. + echo ' pip install renku' + exit 2 + fi -# Verify that renku is installed; if not, warn and exit. -if [ -z "$(command -v renku)" ]; then - echo 'renku not on path; can not format. Please install renku:' - # TODO add system detection and recommend brew for macOS. - echo ' pip install renku' - exit 2 + MODIFIED_OUTPUTS=$(renku show outputs "${MODIFIED_FILES[@]}") + if [ "$MODIFIED_OUTPUTS" ]; then + echo 'You are trying to update generated files.' + echo + echo 'Modified files:' + for file in "${MODIFIED_OUTPUTS[@]}"; do + echo " $file" + done + echo + echo 'To commit anyway, use "git commit --no-verify".' + exit 1 + fi fi -MODIFIED_OUTPUTS=$(renku show outputs "${MODIFIED_FILES[@]}") -if [ "$MODIFIED_OUTPUTS" ]; then - echo 'You are trying to update generated files.' - echo - echo 'Modified files:' - for file in "${MODIFIED_OUTPUTS[@]}"; do - echo " $file" - done - echo - echo 'To commit anyway, use "git commit --no-verify".' - exit 1 -else - exit 0 -fi +###################################### +# END RENKU HOOK. +###################################### From 8274f484926db2f5eda80b04bbe62ac7c31823c5 Mon Sep 17 00:00:00 2001 From: Mohammad Alisafaee Date: Tue, 10 Dec 2019 15:07:45 +0100 Subject: [PATCH 3/3] feat: compare git hook content --- renku/core/commands/checks/githooks.py | 35 ++++++++++++++++++++++++++ tests/core/commands/test_doctor.py | 27 ++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/renku/core/commands/checks/githooks.py b/renku/core/commands/checks/githooks.py index 58b5a592b1..f101d4d2ca 100644 --- a/renku/core/commands/checks/githooks.py +++ b/renku/core/commands/checks/githooks.py @@ -16,8 +16,10 @@ # See the License for the specific language governing permissions and # limitations under the License. """Check for required Git hooks.""" +from io import StringIO from pathlib import Path +import pkg_resources from git.index.fun import hook_path as get_hook_path from renku.core.management.githooks import HOOKS @@ -34,4 +36,37 @@ def check_git_hooks_installed(client): 'Use "renku githooks install" to install them. \n' return False, message + with hook_path.open() as file_: + actual_hook = _extract_renku_hook(file_) + with StringIO(_read_resource(hook)) as file_: + expected_hook = _extract_renku_hook(file_) + + if not expected_hook: + message = WARNING + 'Cannot check for existence of Git hooks.\n' + return False, message + + if actual_hook != expected_hook: + message = WARNING + 'Git hooks are outdated or not installed. ' \ + 'Use "renku githooks install --force" to update them. \n' + return False, message + return True, None + + +def _extract_renku_hook(file_): + lines = [line.strip() for line in file_ if line.strip()] + start = end = -1 + for index, line in enumerate(lines): + if line.startswith('# RENKU HOOK.'): + start = index + elif line.endswith('# END RENKU HOOK.'): + end = index + break + + return lines[start:end] if 0 <= start <= end else [] + + +def _read_resource(hook): + return pkg_resources.resource_string( + 'renku.data', '{hook}.sh'.format(hook=hook) + ).decode('utf-8') diff --git a/tests/core/commands/test_doctor.py b/tests/core/commands/test_doctor.py index b38b47fa0a..f5879563d9 100644 --- a/tests/core/commands/test_doctor.py +++ b/tests/core/commands/test_doctor.py @@ -16,6 +16,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """Renku doctor tests.""" +from pathlib import Path + from renku.cli import cli @@ -32,3 +34,28 @@ def test_git_hooks(runner, project): result = runner.invoke(cli, ['doctor']) assert 1 == result.exit_code assert 'Git hooks are not installed.' in result.output + + +def test_git_hooks_modified(runner, project): + """Test detection of modified git hooks.""" + result = runner.invoke(cli, ['githooks', 'install', '--force']) + assert 0 == result.exit_code + + hook_path = Path(project) / '.git' / 'hooks' / 'pre-commit' + lines = hook_path.read_text().split('/n') + + # Append some more commands + appended = lines + ['# Some more commands', 'ls'] + hook_path.write_text('\n'.join(appended)) + + # Check passes as long as Renku hook is not modified + result = runner.invoke(cli, ['doctor']) + assert 0 == result.exit_code + assert 'Everything seems to be ok.' in result.output + + # Modify Renku hook + hook_path.write_text('\n'.join(lines[:-5])) + + result = runner.invoke(cli, ['doctor']) + assert 1 == result.exit_code + assert 'Git hooks are outdated or not installed.' in result.output