mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:27:15 +01:00
Put the hook in the last repo: local block, not at end of file
Most of these configs interleave local and remote hook repos -- diet-guard, screen-locker and wake-alarm carry nine `- repo:` blocks each. Appending at end of file lands the hook under whichever block happens to be last, and a local hook definition handed to a remote repo fails with an error naming an unknown id rather than the real problem. diet-guard's last block happens to be `local`, so this would have worked by luck there and broken elsewhere. A config with no local block now refuses rather than guessing. 308 tests, 100% branch coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK
This commit is contained in:
parent
71a43d9658
commit
49ce0b250c
@ -49,10 +49,32 @@ def _needs_hook(repo: Path) -> bool:
|
||||
return f"id: {HOOK_ID}" not in target.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _write_hook(repo: Path) -> None:
|
||||
"""Append the hook block to the repo's `local` hooks list.
|
||||
LOCAL_REPO = " - repo: local"
|
||||
|
||||
Appending rather than parsing-and-re-emitting is deliberate: every one of
|
||||
|
||||
def _local_block_end(lines: list[str]) -> int:
|
||||
"""Index just past the LAST `- repo: local` block, or -1 if there is none.
|
||||
|
||||
Appending at end-of-file is wrong whenever a config interleaves local and
|
||||
remote hook repos, which most of these do -- diet-guard, screen-locker and
|
||||
wake-alarm each carry nine `- repo:` blocks. A block appended under
|
||||
`- repo: https://github.com/...` would be handed to that remote repo's
|
||||
hook definitions and fail with an unhelpful error about an unknown id.
|
||||
"""
|
||||
starts = [n for n, line in enumerate(lines) if line.rstrip() == LOCAL_REPO]
|
||||
if not starts:
|
||||
return -1
|
||||
after = [
|
||||
n for n in range(starts[-1] + 1, len(lines))
|
||||
if lines[n].startswith(" - repo:")
|
||||
]
|
||||
return after[0] if after else len(lines)
|
||||
|
||||
|
||||
def _write_hook(repo: Path) -> None:
|
||||
"""Insert the hook into the repo's LAST `local` hooks list.
|
||||
|
||||
Inserting rather than parsing-and-re-emitting is deliberate: every one of
|
||||
these configs carries comments explaining why a hook exists, and a YAML
|
||||
round-trip through the standard library drops all of them.
|
||||
"""
|
||||
@ -60,8 +82,19 @@ def _write_hook(repo: Path) -> None:
|
||||
body = target.read_text(encoding="utf-8") if target.is_file() else EMPTY_PRECOMMIT
|
||||
if not body.endswith("\n"):
|
||||
body += "\n"
|
||||
# `body` was just made to end in a newline, so the split always leaves a
|
||||
# trailing empty element; dropping it keeps the join below symmetric.
|
||||
lines = body.split("\n")[:-1]
|
||||
cut = _local_block_end(lines)
|
||||
if cut < 0:
|
||||
raise ValueError(
|
||||
f"{target} has no `{LOCAL_REPO}` block to add the hook to; "
|
||||
"add one by hand, then re-run"
|
||||
)
|
||||
block = _template("precommit-hook.yaml").rstrip("\n").split("\n")
|
||||
lines[cut:cut] = ["", *block]
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(f"{body}\n{_template('precommit-hook.yaml')}", encoding="utf-8")
|
||||
target.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def plan(repo: Path) -> list[str]:
|
||||
|
||||
@ -180,3 +180,52 @@ def test_the_module_entrypoint_runs_the_cli(repo, monkeypatch):
|
||||
with pytest.raises(SystemExit) as exit_info:
|
||||
runpy.run_module("dep_freshness.install_main", run_name="__main__")
|
||||
assert exit_info.value.code == 0
|
||||
|
||||
|
||||
def test_the_hook_lands_in_the_last_local_block_not_at_end_of_file(repo):
|
||||
"""Most of these configs interleave local and remote hook repos.
|
||||
|
||||
diet-guard, screen-locker and wake-alarm carry nine `- repo:` blocks each,
|
||||
and a hook appended under a remote one is handed to that repo's hook
|
||||
definitions -- which fails with an error naming an unknown id rather than
|
||||
the real problem.
|
||||
"""
|
||||
write(
|
||||
repo,
|
||||
".pre-commit-config.yaml",
|
||||
"""\
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: file-length-cap
|
||||
entry: bash scripts/check_file_length.sh
|
||||
language: system
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.16.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
""",
|
||||
)
|
||||
installer.install(repo)
|
||||
lines = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8").split("\n")
|
||||
hook = lines.index(" - id: dependency-freshness")
|
||||
remote = lines.index(" - repo: https://github.com/astral-sh/ruff-pre-commit")
|
||||
assert hook < remote, "the hook must sit inside the local block"
|
||||
assert lines[-2] == " - id: ruff", "the remote block keeps its own hooks"
|
||||
|
||||
|
||||
def test_a_config_with_no_local_block_refuses_rather_than_guessing(repo):
|
||||
write(
|
||||
repo,
|
||||
".pre-commit-config.yaml",
|
||||
"""\
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.16.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
""",
|
||||
)
|
||||
with pytest.raises(ValueError, match="no ` - repo: local` block"):
|
||||
installer.install(repo)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user