Skip to content

Commit 54ba91d

Browse files
feat: check for required git hooks (#854)
* feat: renku doctor checks for git hooks and their content
1 parent 0b60ec8 commit 54ba91d

File tree

4 files changed

+163
-24
lines changed

4 files changed

+163
-24
lines changed

renku/core/commands/checks/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
# limitations under the License.
1818
"""Define repository checks for :program:`renku doctor`."""
1919

20+
from .githooks import check_git_hooks_installed
2021
from .migration import check_dataset_metadata, check_missing_files
2122
from .references import check_missing_references
2223
from .validate_shacl import check_project_structure, check_datasets_structure
2324

2425
# Checks will be executed in the order as they are listed in __all__.
2526
# They are mostly used in ``doctor`` command to inspect broken things.
2627
__all__ = (
28+
'check_git_hooks_installed',
2729
'check_dataset_metadata',
2830
'check_missing_files',
2931
'check_missing_references',
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2019 - Swiss Data Science Center (SDSC)
4+
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
5+
# Eidgenössische Technische Hochschule Zürich (ETHZ).
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
"""Check for required Git hooks."""
19+
from io import StringIO
20+
from pathlib import Path
21+
22+
import pkg_resources
23+
from git.index.fun import hook_path as get_hook_path
24+
25+
from renku.core.management.githooks import HOOKS
26+
27+
from ..echo import WARNING
28+
29+
30+
def check_git_hooks_installed(client):
31+
"""Checks if all necessary hooks are installed."""
32+
for hook in HOOKS:
33+
hook_path = Path(get_hook_path(hook, client.repo.git_dir))
34+
if not hook_path.exists():
35+
message = WARNING + 'Git hooks are not installed. ' \
36+
'Use "renku githooks install" to install them. \n'
37+
return False, message
38+
39+
with hook_path.open() as file_:
40+
actual_hook = _extract_renku_hook(file_)
41+
with StringIO(_read_resource(hook)) as file_:
42+
expected_hook = _extract_renku_hook(file_)
43+
44+
if not expected_hook:
45+
message = WARNING + 'Cannot check for existence of Git hooks.\n'
46+
return False, message
47+
48+
if actual_hook != expected_hook:
49+
message = WARNING + 'Git hooks are outdated or not installed. ' \
50+
'Use "renku githooks install --force" to update them. \n'
51+
return False, message
52+
53+
return True, None
54+
55+
56+
def _extract_renku_hook(file_):
57+
lines = [line.strip() for line in file_ if line.strip()]
58+
start = end = -1
59+
for index, line in enumerate(lines):
60+
if line.startswith('# RENKU HOOK.'):
61+
start = index
62+
elif line.endswith('# END RENKU HOOK.'):
63+
end = index
64+
break
65+
66+
return lines[start:end] if 0 <= start <= end else []
67+
68+
69+
def _read_resource(hook):
70+
return pkg_resources.resource_string(
71+
'renku.data', '{hook}.sh'.format(hook=hook)
72+
).decode('utf-8')

renku/data/pre-commit.sh

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,35 @@
1717
# See the License for the specific language governing permissions and
1818
# limitations under the License.
1919

20-
# Find all modified files, and exit early if there aren't any.
20+
######################################
21+
# RENKU HOOK. DO NOT REMOVE OR MODIFY.
22+
######################################
23+
24+
# Find all modified files, and do nothing if there aren't any.
2125
MODIFIED_FILES=$(git diff --name-only --cached --diff-filter=M)
22-
if [ ! "$MODIFIED_FILES" ]; then
23-
exit 0
24-
fi
26+
if [ "$MODIFIED_FILES" ]; then
27+
# Verify that renku is installed; if not, warn and exit.
28+
if [ -z "$(command -v renku)" ]; then
29+
echo 'renku not on path; can not format. Please install renku:'
30+
# TODO add system detection and recommend brew for macOS.
31+
echo ' pip install renku'
32+
exit 2
33+
fi
2534

26-
# Verify that renku is installed; if not, warn and exit.
27-
if [ -z "$(command -v renku)" ]; then
28-
echo 'renku not on path; can not format. Please install renku:'
29-
# TODO add system detection and recommend brew for macOS.
30-
echo ' pip install renku'
31-
exit 2
35+
MODIFIED_OUTPUTS=$(renku show outputs "${MODIFIED_FILES[@]}")
36+
if [ "$MODIFIED_OUTPUTS" ]; then
37+
echo 'You are trying to update generated files.'
38+
echo
39+
echo 'Modified files:'
40+
for file in "${MODIFIED_OUTPUTS[@]}"; do
41+
echo " $file"
42+
done
43+
echo
44+
echo 'To commit anyway, use "git commit --no-verify".'
45+
exit 1
46+
fi
3247
fi
3348

34-
MODIFIED_OUTPUTS=$(renku show outputs "${MODIFIED_FILES[@]}")
35-
if [ "$MODIFIED_OUTPUTS" ]; then
36-
echo 'You are trying to update generated files.'
37-
echo
38-
echo 'Modified files:'
39-
for file in "${MODIFIED_OUTPUTS[@]}"; do
40-
echo " $file"
41-
done
42-
echo
43-
echo 'To commit anyway, use "git commit --no-verify".'
44-
exit 1
45-
else
46-
exit 0
47-
fi
49+
######################################
50+
# END RENKU HOOK.
51+
######################################

tests/core/commands/test_doctor.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2017-2019 - Swiss Data Science Center (SDSC)
4+
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
5+
# Eidgenössische Technische Hochschule Zürich (ETHZ).
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
"""Renku doctor tests."""
19+
from pathlib import Path
20+
21+
from renku.cli import cli
22+
23+
24+
def test_git_hooks(runner, project):
25+
"""Test detection of not-installed git hooks."""
26+
# Initially, every thing is OK
27+
result = runner.invoke(cli, ['doctor'])
28+
assert 0 == result.exit_code
29+
assert 'Everything seems to be ok.' in result.output
30+
31+
result = runner.invoke(cli, ['githooks', 'uninstall'])
32+
assert 0 == result.exit_code
33+
34+
result = runner.invoke(cli, ['doctor'])
35+
assert 1 == result.exit_code
36+
assert 'Git hooks are not installed.' in result.output
37+
38+
39+
def test_git_hooks_modified(runner, project):
40+
"""Test detection of modified git hooks."""
41+
result = runner.invoke(cli, ['githooks', 'install', '--force'])
42+
assert 0 == result.exit_code
43+
44+
hook_path = Path(project) / '.git' / 'hooks' / 'pre-commit'
45+
lines = hook_path.read_text().split('/n')
46+
47+
# Append some more commands
48+
appended = lines + ['# Some more commands', 'ls']
49+
hook_path.write_text('\n'.join(appended))
50+
51+
# Check passes as long as Renku hook is not modified
52+
result = runner.invoke(cli, ['doctor'])
53+
assert 0 == result.exit_code
54+
assert 'Everything seems to be ok.' in result.output
55+
56+
# Modify Renku hook
57+
hook_path.write_text('\n'.join(lines[:-5]))
58+
59+
result = runner.invoke(cli, ['doctor'])
60+
assert 1 == result.exit_code
61+
assert 'Git hooks are outdated or not installed.' in result.output

0 commit comments

Comments
 (0)