Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow pre-commit to run in subpath #2007

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
- id: tmt-lint
name: tmt lint
entry: bash -c "git ls-files --error-unmatch $(python3 -c 'import tmt; print(tmt.Tree(logger=tmt.Logger.create(), path=\".\").root)')/.fmf/version && tmt lint --failed-only --source $@" PAD
files: '.*\.fmf$'
# Use a simple wrapper instead of simply `tmt lint` in order to reorder
# some arguments that need to be passed to `tmt`
entry: python -m tmt._pre_commit --pre-check lint --failed-only --source
files: '(?:.*\.fmf|.*/\.fmf/version)$'
verbose: false
pass_filenames: true
language: python
language_version: python3

- id: tmt-tests-lint
name: tmt tests lint
entry: bash -c "git ls-files --error-unmatch $(python3 -c 'import tmt; print(tmt.Tree(logger=tmt.Logger.create(), path=\".\").root)')/.fmf/version && tmt tests lint --failed-only --source $@" PAD
files: '.*\.fmf$'
entry: python -m tmt._pre_commit --pre-check tests lint --failed-only --source
files: '(?:.*\.fmf|.*/\.fmf/version)$'
verbose: false
pass_filenames: true
language: python
language_version: python3

- id: tmt-plans-lint
name: tmt plans lint
entry: bash -c "git ls-files --error-unmatch $(python3 -c 'import tmt; print(tmt.Tree(logger=tmt.Logger.create(), path=\".\").root)')/.fmf/version && tmt plans lint --failed-only --source $@" PAD
files: '.*\.fmf$'
entry: python -m tmt._pre_commit --pre-check plans lint --failed-only --source
files: '(?:.*\.fmf|.*/\.fmf/version)$'
verbose: false
pass_filenames: true
language: python
language_version: python3

- id: tmt-stories-lint
name: tmt stories lint
entry: bash -c "git ls-files --error-unmatch $(python3 -c 'import tmt; print(tmt.Tree(logger=tmt.Logger.create(), path=\".\").root)')/.fmf/version && tmt stories lint --failed-only --source $@" PAD
files: '.*\.fmf$'
entry: python -m tmt._pre_commit --pre-check stories lint --failed-only --source
files: '(?:.*\.fmf|.*/\.fmf/version)$'
verbose: false
pass_filenames: true
language: python
Expand Down
21 changes: 21 additions & 0 deletions tests/precommit/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,27 @@ EOF
rlAssertNotGrep '/good' $rlRun_LOG
rlAssertGrep '/wrong' $rlRun_LOG

# Check it works in different fmf_root
rlRun "mkdir tmt_root"
rlRun -s "git mv .fmf/ tmt_root/.fmf/"
# Should fail because fmf_root is in different location
# (Also tests that pre-commit runs when .fmf/version is changed)
rlRun -s "git commit -m 'wrong_fmf_root'" "1"
rlAssertGrep "$expected_command.*Failed" $rlRun_LOG
# Add the correct root
cat <<EOF > .pre-commit-config.yaml
repos:
- repo: $REPO
rev: "$REV"
hooks:
- id: "$hook"
args: [ --root, tmt_root ]
EOF
rlRun -s "git add .pre-commit-config.yaml"
rlRun -s "git mv main.fmf tmt_root"
rlRun -s "git commit -m 'pass different root'"
rlAssertGrep "$expected_command.*Passed" $rlRun_LOG

rlRun "popd"
rlRun "rm -rf $tmp" 0 "Removing tmp directory"
rlPhaseEnd
Expand Down
Empty file added tmt/_pre_commit/__init__.py
Empty file.
67 changes: 67 additions & 0 deletions tmt/_pre_commit/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import re
import sys

from tmt.__main__ import run_cli


def _run_precommit() -> None: # pyright: ignore[reportUnusedFunction] (used by project.scripts)
"""
A simple wrapper that re-orders cli arguments before :py:func:`run_cli`.

This utility is needed in order to move arguments like ``--root`` before
the ``lint`` keyword when run by ``pre-commit``.

Only a limited number of arguments are reordered.
"""

# Only re-ordering a few known/necessary cli arguments of `main` command
# Could be improved if the click commands can be introspected like with
# `attrs.fields`
# https://github.com/pallets/click/issues/2709

argv = sys.argv
# Get the last argument that starts with `-`. Other inputs after that are
# assumed to be filenames (which can be thousands of them)
for last_arg in reversed(range(len(argv))):
if argv[last_arg].startswith("-"):
break
else:
# If there were no args passed, then just `run_cli`
run_cli()
return

# At this point we need to check and re-order the arguments if needed
for pattern, nargs in [
(r"(--root$|-r)", 1),
(r"(--root=.*)", 0),
(r"(--verbose$|-v+)", 0),
(r"(--debug$|-d+)", 0),
(r"(--pre-check$)", 0),
]:
if any(match := re.match(pattern, arg) for arg in argv[1:last_arg + 1]):
assert match
# Actual argument matched
actual_arg = match.group(1)
indx = argv.index(actual_arg)
# Example of reorder args:
# ["tmt", "tests", "lint", "--fix", "--root", "/some/path", "some_file"]
# - argv[0]: name of the program is always first
# ["tmt"]
# - argv[indx:indx+1+nargs]: args to move (pass to `tmt.cli.main`)
# ["--root", "/some/path"]
# - argv[1:indx]: all other keywords (`lint` keyword is in here)
# ["tests", "lint", "--fix"]
# - argv[indx+1+nargs:]: All remaining keywords
# ["some_file"]
# After reorder:
# ["tmt", "--root", "/some/path", "tests", "lint", "--fix", "some_file"]
argv = (
argv[0:1] + argv[indx: indx + 1 + nargs] + argv[1:indx] + argv[indx + 1 + nargs:]
)
# Finally move the reordered args to sys.argv and run the cli
sys.argv = argv
run_cli()


if __name__ == "__main__":
_run_precommit()
17 changes: 17 additions & 0 deletions tmt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,18 @@ def write_dl(
'--force-color', is_flag=True, default=False,
help='Forces tmt to use colors in the output and logging.'
)
@option(
'--pre-check', is_flag=True, default=False, hidden=True,
help='Run pre-checks on the git root. (Used by pre-commit wrapper).'
)
LecrisUT marked this conversation as resolved.
Show resolved Hide resolved
def main(
click_contex: Context,
root: str,
context: list[str],
no_color: bool,
force_color: bool,
show_time: bool,
pre_check: bool,
**kwargs: Any) -> None:
""" Test Management Tool """

Expand All @@ -339,6 +344,18 @@ def main(
# Save click context and fmf context for future use
tmt.utils.Common.store_cli_invocation(click_contex)

# Run pre-checks
if pre_check:
git_command = tmt.utils.Command('git', 'rev-parse', '--show-toplevel')
git_root = git_command.run(cwd=None, logger=logger).stdout
if not git_root:
raise tmt.utils.RunError("git rev-parse did not produce a path", git_command, 0)
git_root = git_root.strip()
git_command = tmt.utils.Command(
'git', 'ls-files', '--error-unmatch', f"{git_root}/{root}/.fmf/version"
)
git_command.run(cwd=None, logger=logger)

# Initialize metadata tree (from given path or current directory)
tree = tmt.Tree(logger=logger, path=Path(root))

Expand Down
1 change: 1 addition & 0 deletions tmt/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def option(
metavar: Optional[str] = None,
prompt: Optional[str] = None,
envvar: Optional[str] = None,
hidden: bool = False,
# Following parameters are our additions.
choices: Optional[Sequence[str]] = None,
deprecated: Optional[Deprecated] = None) -> ClickOptionDecoratorType:
Expand Down
Loading