Support the Git "core.hooksPath" option when dealing with hook scripts
Previously, git-review would assume that the Git repository's hook directory is .git/hooks, relative to the root of the checkout. This assumption breaks if the user has set the core.hooksPath option on the repository (or, for that matter, in ~/.gitconfig or /etc/gitconfig). core.hooksPath can either be set to an absolute path, in which case it is to be interpreted as-is, or to a relative path, in which case it should be interpreted as relative to the root of the checkout. Introduce a new convenience function to suss out the correct path, and use it in places where the reference to .git/hooks was previously hard-coded. Reference: https: //git-scm.com/docs/git-config#Documentation/git-config.txt-corehooksPath Depends-on: I0f0f44e57a100420d8e6d2eaec7dbb5d77b654af Change-Id: Id8a3ac464ff75e6d8207f198089f018cc790eca5
This commit is contained in:
parent
d0d3da82f5
commit
c40eb491e6
@ -247,7 +247,7 @@ def run_custom_script(action):
|
|||||||
script_file = "%s-review" % (action)
|
script_file = "%s-review" % (action)
|
||||||
(top_dir, git_dir) = git_directories()
|
(top_dir, git_dir) = git_directories()
|
||||||
paths = [os.path.join(CONFIGDIR, "hooks", script_file),
|
paths = [os.path.join(CONFIGDIR, "hooks", script_file),
|
||||||
os.path.join(git_dir, "hooks", script_file)]
|
os.path.join(git_get_hooks_path(top_dir, git_dir), script_file)]
|
||||||
for fpath in paths:
|
for fpath in paths:
|
||||||
if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
|
if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
|
||||||
status, output = run_command_status(fpath)
|
status, output = run_command_status(fpath)
|
||||||
@ -285,7 +285,26 @@ def git_config_get_value(section, option, default=None, as_bool=False):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def git_get_hooks_path(top_dir, git_dir):
|
||||||
|
"""Get the path where we need to store and retrieve Git hooks.
|
||||||
|
|
||||||
|
Normally hooks go into .git/hooks, but users can override with the
|
||||||
|
core.hooksPath option. This can either be an absolute path, in
|
||||||
|
which case we use it as-is, or a relative path, in which case we
|
||||||
|
must interpret it as relative to top_dir.
|
||||||
|
"""
|
||||||
|
hook_dir = os.path.join(git_dir, "hooks")
|
||||||
|
hooks_path_option = git_config_get_value('core', 'hooksPath')
|
||||||
|
if hooks_path_option:
|
||||||
|
if os.path.isabs(hooks_path_option):
|
||||||
|
hook_dir = hooks_path_option
|
||||||
|
else:
|
||||||
|
hook_dir = os.path.join(top_dir, hooks_path_option)
|
||||||
|
return hook_dir
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
|
|
||||||
"""Expose as dictionary configuration options."""
|
"""Expose as dictionary configuration options."""
|
||||||
|
|
||||||
def __init__(self, config_file=None):
|
def __init__(self, config_file=None):
|
||||||
@ -365,6 +384,9 @@ def set_hooks_commit_msg(remote, target_file):
|
|||||||
run_command_exc(CannotInstallHook, *cmd)
|
run_command_exc(CannotInstallHook, *cmd)
|
||||||
# If there are submodules, the hook needs to be installed into
|
# If there are submodules, the hook needs to be installed into
|
||||||
# each of them.
|
# each of them.
|
||||||
|
# Here, we don't check for any nonstandard hooks path, because
|
||||||
|
# it should be safe to assume that very few users are inclined
|
||||||
|
# to set the core.hooksPath option in a submodule checkout.
|
||||||
run_command_exc(
|
run_command_exc(
|
||||||
CannotInstallHook,
|
CannotInstallHook,
|
||||||
"git", "submodule", "foreach",
|
"git", "submodule", "foreach",
|
||||||
@ -1686,7 +1708,8 @@ additional information:
|
|||||||
if options.custom_script:
|
if options.custom_script:
|
||||||
run_custom_script("pre")
|
run_custom_script("pre")
|
||||||
|
|
||||||
hook_file = os.path.join(git_dir, "hooks", "commit-msg")
|
hook_dir = git_get_hooks_path(top_dir, git_dir)
|
||||||
|
hook_file = os.path.join(hook_dir, "commit-msg")
|
||||||
have_hook = os.path.exists(hook_file) and os.access(hook_file, os.X_OK)
|
have_hook = os.path.exists(hook_file) and os.access(hook_file, os.X_OK)
|
||||||
|
|
||||||
if not have_hook:
|
if not have_hook:
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import tempfile
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
from git_review import tests
|
from git_review import tests
|
||||||
@ -102,6 +103,53 @@ class GitReviewTestCase(tests.BaseGitReviewTestCase):
|
|||||||
self._run_git_review(
|
self._run_git_review(
|
||||||
'-s', chdir=os.path.join(self.test_dir, 'subdirectory'))
|
'-s', chdir=os.path.join(self.test_dir, 'subdirectory'))
|
||||||
|
|
||||||
|
def test_git_review_s_without_core_hooks_path_option(self):
|
||||||
|
"""Test whether git-review -s correctly creates the commit-msg hook,
|
||||||
|
with the Git core.hooksPath option unset.
|
||||||
|
"""
|
||||||
|
hooks_subdir = ".git/hooks"
|
||||||
|
self.reset_remote()
|
||||||
|
|
||||||
|
# There really isn't a good way to ensure that
|
||||||
|
# "core.hooksPath" option is presently unset. "git config
|
||||||
|
# --unset" is not idempotent; if you try to unset a config
|
||||||
|
# option that isn't defined it fails with an exit code of
|
||||||
|
# 5. Running "git config core.hooksPath" to retrieve the value
|
||||||
|
# returns 1 if unset, but we can't use self.assertRaises here
|
||||||
|
# either because run_cmd raises a generic Exception and that's
|
||||||
|
# too broad. So instead, we don't check core.hooksPath at all
|
||||||
|
# here, and instead rely on the next two tests to unset the
|
||||||
|
# option after they've set it.
|
||||||
|
self._run_git_review('-s')
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(self.test_dir,
|
||||||
|
hooks_subdir,
|
||||||
|
'commit-msg')))
|
||||||
|
|
||||||
|
def test_git_review_s_with_core_hooks_path_option_relative(self):
|
||||||
|
"""Test whether git-review -s correctly creates the commit-msg hook,
|
||||||
|
with the Git core.hooksPath option set to a relative path.
|
||||||
|
"""
|
||||||
|
hooks_subdir = "foo"
|
||||||
|
self.reset_remote()
|
||||||
|
self._run_git("config", "core.hooksPath", hooks_subdir)
|
||||||
|
self._run_git_review('-s')
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(self.test_dir,
|
||||||
|
hooks_subdir,
|
||||||
|
'commit-msg')))
|
||||||
|
self._run_git("config", "--unset", "core.hooksPath")
|
||||||
|
|
||||||
|
def test_git_review_s_with_core_hooks_path_option_absolute(self):
|
||||||
|
"""Test whether git-review -s correctly creates the commit-msg hook,
|
||||||
|
with the Git core.hooksPath option set to an absolute path.
|
||||||
|
"""
|
||||||
|
self.reset_remote()
|
||||||
|
with tempfile.TemporaryDirectory() as hooks_dir:
|
||||||
|
self._run_git("config", "core.hooksPath", hooks_dir)
|
||||||
|
self._run_git_review('-s')
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(hooks_dir,
|
||||||
|
'commit-msg')))
|
||||||
|
self._run_git("config", "--unset", "core.hooksPath")
|
||||||
|
|
||||||
def test_git_review_d(self):
|
def test_git_review_d(self):
|
||||||
"""Test git-review -d."""
|
"""Test git-review -d."""
|
||||||
self._run_git_review('-s')
|
self._run_git_review('-s')
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
git-review now handles the Git "core.hooksPath" configuration
|
||||||
|
option correctly. Thus, it installs the commit-msg hook into the
|
||||||
|
core.hooksPath directory, if that option is set. Otherwise, it
|
||||||
|
continues to install the hook into .git/hooks, relative to the
|
||||||
|
root of the checkout.
|
Loading…
Reference in New Issue
Block a user