mirror of
https://github.com/safishamsi/graphify.git
synced 2026-08-29 01:36:33 +00:00
Reject Windows-style git hooks paths instead of creating a junk dir (#1385)
On WSL/POSIX, Path("C:\\...").is_absolute() is False, so a Windows absolute
core.hooksPath (or rev-parse --git-path output) was joined under the repo root
and mkdir'd as a literal backslash-named junk directory while install reported
success and the real .git/hooks got nothing. Both branches of _hooks_dir now
reject drive-letter / backslash paths with a clear error so the failure is loud.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -284,6 +284,25 @@ def _git_root(path: Path) -> Path | None:
|
||||
return None
|
||||
|
||||
|
||||
_WINDOWS_DRIVE_RE = re.compile(r"^[A-Za-z]:[\\/]")
|
||||
|
||||
|
||||
def _reject_windows_path(value: str, source: str) -> None:
|
||||
"""Raise if a hooks path looks like a Windows absolute path (#1385).
|
||||
|
||||
On POSIX/WSL ``Path("C:\\Users\\...").is_absolute()`` is False, so an absolute
|
||||
Windows hooks path gets joined under the repo root and mkdir'd as a literal
|
||||
junk directory (backslashes and all), while install reports success and the
|
||||
real ``.git/hooks`` gets nothing. Fail loudly instead so the user can fix it.
|
||||
"""
|
||||
if _WINDOWS_DRIVE_RE.match(value) or "\\" in value:
|
||||
raise RuntimeError(
|
||||
f"git hooks path from {source} looks like a Windows path: {value!r}. "
|
||||
f"On WSL/POSIX this can't resolve to a real directory. Unset it with "
|
||||
f"`git config --local --unset core.hooksPath`, or set a POSIX path."
|
||||
)
|
||||
|
||||
|
||||
def _hooks_dir(root: Path) -> Path:
|
||||
"""Return the git hooks directory, respecting core.hooksPath if set (e.g. Husky)."""
|
||||
try:
|
||||
@@ -292,6 +311,7 @@ def _hooks_dir(root: Path) -> Path:
|
||||
# configparser lowercases option names; git's hooksPath becomes hookspath
|
||||
custom = cfg.get("core", "hookspath", fallback="").strip()
|
||||
if custom:
|
||||
_reject_windows_path(custom, "core.hooksPath")
|
||||
p = Path(custom).expanduser()
|
||||
if not p.is_absolute():
|
||||
p = root / p
|
||||
@@ -331,6 +351,7 @@ def _hooks_dir(root: Path) -> Path:
|
||||
# A valid hooks path can never contain newlines or NUL. Their presence
|
||||
# means git echoed an unrecognised flag back (old git behaviour).
|
||||
if res.returncode == 0 and raw and not any(c in raw for c in ("\n", "\r", "\x00")):
|
||||
_reject_windows_path(raw, "git rev-parse --git-path hooks")
|
||||
d = (root / raw).resolve()
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
return d
|
||||
|
||||
@@ -333,3 +333,44 @@ def test_installed_hooks_contain_no_nohup(tmp_path):
|
||||
text = (repo / ".git" / "hooks" / name).read_text(encoding="utf-8")
|
||||
assert "nohup" not in text, f"installed {name} still references nohup"
|
||||
assert "start_new_session=True" in text
|
||||
|
||||
|
||||
# ── #1385: reject Windows-style hooks paths instead of creating a junk dir ───
|
||||
|
||||
def _set_hookspath(repo: Path, value: str) -> None:
|
||||
subprocess.run(["git", "-C", str(repo), "config", "--local", "core.hooksPath", value],
|
||||
check=True, capture_output=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("winpath", [
|
||||
r"C:\Users\u\repo\.git\hooks",
|
||||
r"c:/Users/u/.git/hooks",
|
||||
r"D:\hooks",
|
||||
r"some\back\slashed\path",
|
||||
])
|
||||
def test_windows_hookspath_rejected_no_junk_dir(tmp_path, winpath):
|
||||
"""A Windows-style core.hooksPath must raise (loud failure), not silently
|
||||
create a backslash-named junk directory and report success (#1385)."""
|
||||
repo = _make_git_repo(tmp_path)
|
||||
_set_hookspath(repo, winpath)
|
||||
with pytest.raises(RuntimeError, match="Windows path"):
|
||||
install(repo)
|
||||
# no junk directory got created anywhere under the repo
|
||||
junk = [p for p in repo.rglob("*") if "\\" in p.name or p.name.startswith(("C:", "c:", "D:"))]
|
||||
assert junk == [], f"junk dir created: {junk}"
|
||||
|
||||
|
||||
def test_posix_custom_hookspath_still_works(tmp_path):
|
||||
"""A legitimate POSIX core.hooksPath (Husky-style) must still install."""
|
||||
repo = _make_git_repo(tmp_path)
|
||||
_set_hookspath(repo, ".husky")
|
||||
msg = install(repo)
|
||||
assert "post-commit" in msg
|
||||
assert (repo / ".husky" / "post-commit").exists()
|
||||
|
||||
|
||||
def test_default_hooks_dir_unaffected(tmp_path):
|
||||
"""No core.hooksPath -> normal .git/hooks install, no rejection."""
|
||||
repo = _make_git_repo(tmp_path)
|
||||
install(repo)
|
||||
assert (repo / ".git" / "hooks" / "post-commit").exists()
|
||||
|
||||
Reference in New Issue
Block a user