mirror of
https://github.com/AdaCore/git-hooks.git
synced 2026-02-12 12:43:11 -08:00
This commit introduces a new class (ThirdPartyHook), which centralizes the handling of third-party hooks (scripts that project set up via the configuration file). Most of the code is simply a move of the code from maybe_call_thirdparty_hook, with a couple of enhancements: - Ability to specify the directory from which to call the hook; - Handling of the situation when calling the hook itself fails. We can already see the benefits of this in function style_check_files, where the code now necessary to call the style_checker is both clearer and more compact. In the future, the intent is to push this class one step further to allow users to specify hooks to be fetched from the repository, rather than requiring that the hooks be installed (usually by an admin) on the machine hosting the repository. The current class' API should allow us to implement this in a way that's transparent to all users. Change-Id: Ie8bf8accbbfd75a91b914628fad27d780532dac4 TN: T209-005
121 lines
4.7 KiB
Python
121 lines
4.7 KiB
Python
"""Implements git's pre-receive hook.
|
|
|
|
The arguments for this hook are passed via stdin: For each reference
|
|
being sent for update, stdin contains one line with that reference's
|
|
name (Eg: refs/heads/master).
|
|
"""
|
|
from collections import OrderedDict
|
|
import sys
|
|
|
|
from config import CONFIG_REF, ThirdPartyHook
|
|
from errors import InvalidUpdate
|
|
from git import is_null_rev
|
|
from init import init_all_globals
|
|
from utils import warn
|
|
|
|
|
|
def pre_receive(refs_data):
|
|
"""Implement the pre-receive hook.
|
|
|
|
PARAMETERS
|
|
refs_data: An OrderedDict, indexed by the name of the ref
|
|
being updated, and containing 2-elements tuple. This tuple
|
|
contains the previous revision, and the new revision of the
|
|
reference.
|
|
"""
|
|
# Enforce a rule that, if the CONFIG_REF reference is being updated,
|
|
# then it must be the only reference being updated.
|
|
#
|
|
# Rationale:
|
|
# ----------
|
|
#
|
|
# The CONFIG_REF reference has special significance for us, as
|
|
# this is the reference where we load the repository's configuration
|
|
# from. And when a user pushes an update to that reference, it really
|
|
# makes better sense to immediately take the changes it brings into
|
|
# account. For instance, if we realized that the hooks are configured
|
|
# with the wrong mailing-list address, and we push a change to fix
|
|
# that, we want the corresponding email to be sent to the correct
|
|
# address. Similarly, it allows us, during the initial repository
|
|
# setup phase, to enable the hooks prior to adding the hooks's config,
|
|
# followed by pushing the initial config as usual, so as to get
|
|
# the benefits of having the hooks perform the necessary validation
|
|
# checks and send emails when relevant.
|
|
#
|
|
# Now, the reason why we need to enforce that CONFIG_REF updates
|
|
# be done on their own is because the update hook gets called
|
|
# once per reference being updated. What this means is that
|
|
# the update script doesn't have enough context to determine
|
|
# with certainty where to get the correct configuration from.
|
|
# Enforcing CONFIG_REF updates to be done on their own push
|
|
# solves that problem.
|
|
if len(refs_data) > 1 and CONFIG_REF in refs_data:
|
|
err_msg = [
|
|
'You are trying to push multiple references at the same time:']
|
|
err_msg.extend(' - {}'.format(ref_name)
|
|
for ref_name in sorted(refs_data))
|
|
err_msg.extend([
|
|
'',
|
|
'Updates to the {CONFIG_REF} reference must be pushed'
|
|
.format(CONFIG_REF=CONFIG_REF),
|
|
'on their own. Please push this reference first, and then',
|
|
'retry pushing the remaining references.'])
|
|
raise InvalidUpdate(*err_msg)
|
|
|
|
# Verify that we are not trying to delete the CONFIG_REF reference.
|
|
# This is not allowed, because this would delete the repository's
|
|
# configuration. We do this extra early so as to provide the user
|
|
# with a clear message rather than hitting an error later on, when
|
|
# we may not have enough context to generate a clear error message.
|
|
if CONFIG_REF in refs_data:
|
|
_, new_rev = refs_data[CONFIG_REF]
|
|
if is_null_rev(new_rev):
|
|
raise InvalidUpdate(
|
|
'Deleting the reference {CONFIG_REF} is not allowed.'
|
|
.format(CONFIG_REF=CONFIG_REF),
|
|
'',
|
|
'This reference provides important configuration information',
|
|
'and thus must not be deleted.')
|
|
|
|
|
|
def maybe_pre_receive_hook(pre_receive_data):
|
|
"""Call the pre-receive-hook if defined.
|
|
|
|
This function implements supports for the hooks.pre-receive-hook
|
|
config variable, by calling this function if the config variable
|
|
is defined.
|
|
|
|
ARGUMENTS
|
|
pre_receive_data: The data received via stdin by the pre-receive hook.
|
|
"""
|
|
result = ThirdPartyHook('hooks.pre-receive-hook').call_if_defined(
|
|
hook_input=pre_receive_data)
|
|
if result is not None:
|
|
hook_exe, p, out = result
|
|
if p.returncode != 0:
|
|
raise InvalidUpdate(
|
|
"Update rejected by this repository's hooks.pre-receive-hook"
|
|
" script",
|
|
'({}):'.format(hook_exe),
|
|
*out.splitlines())
|
|
else:
|
|
sys.stdout.write(out)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
stdin = sys.stdin.read()
|
|
refs_data = OrderedDict()
|
|
for line in stdin.splitlines():
|
|
old_rev, new_rev, ref_name = line.strip().split()
|
|
refs_data[ref_name] = (old_rev, new_rev)
|
|
|
|
try:
|
|
init_all_globals(refs_data)
|
|
pre_receive(refs_data)
|
|
maybe_pre_receive_hook(stdin)
|
|
except InvalidUpdate as e:
|
|
# The update was rejected. Print the rejection reason, and
|
|
# exit with a nonzero status.
|
|
warn(*e)
|
|
sys.exit(1)
|