Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Fix commit hooks to respect core.hooksPath configuration#2090

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
MirrorDNA-Reflection-Protocol wants to merge4 commits intogitpython-developers:main
base:main
Choose a base branch
Loading
fromMirrorDNA-Reflection-Protocol:fix-core-hookspath-support
Draft
Show file tree
Hide file tree
Changes fromall 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
41 changes: 39 additions & 2 deletionsgit/index/fun.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@
if TYPE_CHECKING:
from git.db import GitCmdObjectDB
from git.objects.tree import TreeCacheTup
from git.repo import Repo

from .base import IndexFile

Expand All@@ -60,10 +61,45 @@


def hook_path(name: str, git_dir: PathLike) -> str:
""":return: path to the given named hook in the given git repository directory"""
""":return: path to the given named hook in the given git repository directory

Note: This function does not respect the core.hooksPath configuration.
For commit hooks that should respect this config, use run_commit_hook() instead.
"""
return osp.join(git_dir, "hooks", name)


def _get_hooks_dir(repo: "Repo") -> str:
"""Get the hooks directory, respecting core.hooksPath configuration.

:param repo: The repository to get the hooks directory for.
:return: Path to the hooks directory.

Per git-config documentation, core.hooksPath can be:
- An absolute path: used as-is
- A relative path: relative to the directory where hooks are run from
(typically the working tree root for non-bare repos)
- If not set: defaults to $GIT_DIR/hooks
"""
try:
hooks_path = repo.config_reader().get_value("core", "hooksPath")
except Exception:
# Config key not found or other error - use default
hooks_path = None

if hooks_path:
hooks_path = str(hooks_path)
if osp.isabs(hooks_path):
return hooks_path
else:
# Relative paths are relative to the working tree (or git_dir for bare repos)
base_dir = repo.working_tree_dir if repo.working_tree_dir else repo.git_dir
return osp.normpath(osp.join(base_dir, hooks_path))
else:
# Default: $GIT_DIR/hooks
return osp.join(repo.git_dir, "hooks")


def _has_file_extension(path: str) -> str:
return osp.splitext(path)[1]

Expand All@@ -82,7 +118,8 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:

:raise git.exc.HookExecutionError:
"""
hp = hook_path(name, index.repo.git_dir)
hooks_dir = _get_hooks_dir(index.repo)
hp = osp.join(hooks_dir, name)
if not os.access(hp, os.X_OK):
return

Expand Down
122 changes: 122 additions & 0 deletionstest/test_index.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1055,6 +1055,128 @@ def test_run_commit_hook(self, rw_repo):
output = Path(rw_repo.git_dir, "output.txt").read_text(encoding="utf-8")
self.assertEqual(output, "ran fake hook\n")

@pytest.mark.xfail(
type(_win_bash_status) is WinBashStatus.Absent,
reason="Can't run a hook on Windows without bash.exe.",
raises=HookExecutionError,
)
@pytest.mark.xfail(
type(_win_bash_status) is WinBashStatus.WslNoDistro,
reason="Currently uses the bash.exe of WSL, even with no WSL distro installed",
raises=HookExecutionError,
)
@with_rw_repo("HEAD", bare=False)
def test_run_commit_hook_respects_core_hookspath(self, rw_repo):
"""Test that run_commit_hook() respects core.hooksPath configuration.

This addresses issue #2083 where commit hooks were always looked for in
$GIT_DIR/hooks instead of respecting the core.hooksPath config setting.
"""
index = rw_repo.index

# Create a custom hooks directory outside of .git
custom_hooks_dir = Path(rw_repo.working_tree_dir) / "custom-hooks"
custom_hooks_dir.mkdir()

# Create a hook in the custom location
custom_hook = custom_hooks_dir / "fake-hook"
custom_hook.write_text(HOOKS_SHEBANG + "echo 'ran from custom hooks path' >output.txt")
custom_hook.chmod(0o744)

# Set core.hooksPath in the repo config
with rw_repo.config_writer() as config:
config.set_value("core", "hooksPath", str(custom_hooks_dir))

# Run the hook - it should use the custom path
run_commit_hook("fake-hook", index)

output_file = Path(rw_repo.working_tree_dir) / "output.txt"
self.assertTrue(output_file.exists(), "Hook should have created output.txt")
output = output_file.read_text(encoding="utf-8")
self.assertEqual(output, "ran from custom hooks path\n")
Comment on lines +1069 to +1096
Copy link

CopilotAIDec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The test only covers non-bare repositories (bare=False). Consider adding test coverage for bare repositories withcore.hooksPath set, since the existingtest_run_commit_hook test on line 1050 uses a bare repository. This is important because the logic in_get_hooks_dir() falls back togit_dir for bare repos when resolving relative paths (line 98 ofgit/index/fun.py).

Copilot uses AI. Check for mistakes.

@pytest.mark.xfail(
type(_win_bash_status) is WinBashStatus.Absent,
reason="Can't run a hook on Windows without bash.exe.",
raises=HookExecutionError,
)
@pytest.mark.xfail(
type(_win_bash_status) is WinBashStatus.WslNoDistro,
reason="Currently uses the bash.exe of WSL, even with no WSL distro installed",
raises=HookExecutionError,
)
@with_rw_repo("HEAD", bare=False)
def test_run_commit_hook_respects_relative_core_hookspath(self, rw_repo):
"""Test that run_commit_hook() handles relative core.hooksPath correctly.

Per git-config docs, relative paths for core.hooksPath are relative to
the directory where hooks are run (typically the working tree root).
"""
index = rw_repo.index

# Create a custom hooks directory with a relative path
custom_hooks_dir = Path(rw_repo.working_tree_dir) / "relative-hooks"
custom_hooks_dir.mkdir()

# Create a hook in the custom location
custom_hook = custom_hooks_dir / "fake-hook"
custom_hook.write_text(HOOKS_SHEBANG + "echo 'ran from relative hooks path' >output.txt")
custom_hook.chmod(0o744)

# Set core.hooksPath to a relative path
with rw_repo.config_writer() as config:
config.set_value("core", "hooksPath", "relative-hooks")

# Run the hook - it should resolve the relative path correctly
run_commit_hook("fake-hook", index)

output_file = Path(rw_repo.working_tree_dir) / "output.txt"
self.assertTrue(output_file.exists(), "Hook should have created output.txt")
output = output_file.read_text(encoding="utf-8")
self.assertEqual(output, "ran from relative hooks path\n")
Comment on lines +1109 to +1136
Copy link

CopilotAIDec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The test only covers non-bare repositories (bare=False). Consider adding test coverage for bare repositories with relativecore.hooksPath, since the logic in_get_hooks_dir() falls back togit_dir for bare repos when resolving relative paths (line 98 ofgit/index/fun.py).

Copilot uses AI. Check for mistakes.

@pytest.mark.xfail(
type(_win_bash_status) is WinBashStatus.Absent,
reason="Can't run a hook on Windows without bash.exe.",
raises=HookExecutionError,
)
@pytest.mark.xfail(
type(_win_bash_status) is WinBashStatus.WslNoDistro,
reason="Currently uses the bash.exe of WSL, even with no WSL distro installed",
raises=HookExecutionError,
)
@with_rw_repo("HEAD", bare=True)
def test_run_commit_hook_respects_core_hookspath_bare_repo(self, rw_repo):
"""Test that run_commit_hook() respects core.hooksPath in bare repositories.

For bare repos, relative paths should be resolved relative to git_dir since
there is no working tree.
"""
index = rw_repo.index

# Create a custom hooks directory inside the git_dir for bare repo
# This ensures the path is relative to working_dir (which equals git_dir for bare repos)
custom_hooks_dir = Path(rw_repo.git_dir) / "custom-hooks"
custom_hooks_dir.mkdir(exist_ok=True)

# Create a hook in the custom location
custom_hook = custom_hooks_dir / "fake-hook"
custom_hook.write_text(HOOKS_SHEBANG + "echo 'ran from custom hooks path in bare repo' >output.txt")
custom_hook.chmod(0o744)

# Set core.hooksPath in the repo config (absolute path)
with rw_repo.config_writer() as config:
config.set_value("core", "hooksPath", str(custom_hooks_dir))

# Run the hook - it should use the custom path
run_commit_hook("fake-hook", index)

# Output goes to cwd, which for bare repos during hook execution is working_dir (same as git_dir)
output_file = Path(rw_repo.git_dir) / "output.txt"
self.assertTrue(output_file.exists(), "Hook should have created output.txt")
output = output_file.read_text(encoding="utf-8")
self.assertEqual(output, "ran from custom hooks path in bare repo\n")

@ddt.data((False,), (True,))
@with_rw_directory
def test_hook_uses_shell_not_from_cwd(self, rw_dir, case):
Expand Down
Loading

[8]ページ先頭

©2009-2026 Movatter.jp