Install and remove native git hooks.
The generated hook is a tiny POSIX shell script that delegates to grip hook
<stage>. It is marked with :data:MARKER so we never overwrite a hook we did not
write. Users of the pre-commit framework should use .pre-commit-hooks.yaml
instead; see the documentation.
HookStatus
dataclass
What is installed for one stage.
Source code in src/grip_hook/hooks.py
| @dataclass(frozen=True, slots=True)
class HookStatus:
"""What is installed for one stage."""
stage: Stage
path: Path
exists: bool
managed: bool
"""``True`` when the file was written entirely by grip."""
appended: bool
"""``True`` when grip's snippet was appended to a pre-existing hook."""
@property
def active(self) -> bool:
"""``True`` when grip runs at this stage."""
return self.managed or self.appended
|
managed
instance-attribute
True when the file was written entirely by grip.
appended
instance-attribute
True when grip's snippet was appended to a pre-existing hook.
active
property
True when grip runs at this stage.
status
status(git: Git, stage: Stage) -> HookStatus
Inspect the hook file for stage.
Source code in src/grip_hook/hooks.py
| def status(git: Git, stage: Stage) -> HookStatus:
"""Inspect the hook file for ``stage``."""
path = git.hooks_dir() / stage.value
if not path.exists():
return HookStatus(stage, path, exists=False, managed=False, appended=False)
content = path.read_text("utf-8", errors="replace")
managed = MARKER in content and content.startswith("#!/bin/sh\n" + MARKER)
appended = MARKER in content and not managed
return HookStatus(stage, path, exists=True, managed=managed, appended=appended)
|
install
install(git: Git, stage: Stage, *, force: bool = False, append: bool = False) -> HookStatus
Write the hook for stage.
Parameters:
| Name |
Type |
Description |
Default |
git
|
Git
|
|
required
|
stage
|
Stage
|
|
required
|
force
|
bool
|
Overwrite a hook that grip did not write.
|
False
|
append
|
bool
|
Append grip's snippet to an existing foreign hook instead of failing.
|
False
|
Raises:
| Type |
Description |
GripError
|
when a foreign hook exists and neither force nor append is set.
|
Source code in src/grip_hook/hooks.py
| def install(git: Git, stage: Stage, *, force: bool = False, append: bool = False) -> HookStatus:
"""Write the hook for ``stage``.
Args:
git: Repository handle.
stage: ``pre-commit`` or ``pre-push``.
force: Overwrite a hook that grip did not write.
append: Append grip's snippet to an existing foreign hook instead of failing.
Raises:
GripError: when a foreign hook exists and neither ``force`` nor ``append`` is set.
"""
if stage not in INSTALLABLE_STAGES:
raise GripError(f"cannot install a {stage.value} hook")
current = status(git, stage)
path = current.path
path.parent.mkdir(parents=True, exist_ok=True)
if current.exists and not current.managed and not current.appended:
if append:
with path.open("a", encoding="utf-8") as fh:
fh.write(_APPEND_SNIPPET.format(marker=MARKER, stage=stage.value))
_make_executable(path)
return status(git, stage)
if not force:
raise GripError(
f"{path} already exists and was not written by grip. Re-run with --append to "
"chain grip after it, or --force to replace it."
)
if current.appended and not force:
return current
path.write_text(_SCRIPT.format(marker=MARKER, stage=stage.value), "utf-8")
_make_executable(path)
if not _is_executable(path): # pragma: no cover - filesystem without exec bits
raise GripError(f"could not make {path} executable")
return status(git, stage)
|
uninstall
uninstall(git: Git, stage: Stage) -> HookStatus
Remove grip from the hook for stage (deleting the file if grip owns it).
Source code in src/grip_hook/hooks.py
| def uninstall(git: Git, stage: Stage) -> HookStatus:
"""Remove grip from the hook for ``stage`` (deleting the file if grip owns it)."""
current = status(git, stage)
if not current.exists:
return current
if current.managed:
current.path.unlink()
elif current.appended:
content = current.path.read_text("utf-8")
head, _, _tail = content.partition(f"\n\n{MARKER}\n")
# The appended block is always last, so dropping everything after the marker is safe.
current.path.write_text(head.rstrip("\n") + "\n", "utf-8")
return status(git, stage)
|