Files
git-hooks/hooks/fast_forward.py
Joel Brobecker d1a76a87a8 Change the hooks.non-fast-forward config to match the entire ref name
This lifts a limitation where non-fast-forward updates could only be
allowed for references whose name start with 'refs/heads/' (the
standard namespace for branches). As it happens, GCC is using branch
names in a different namespace. So this commit changes this config
option to match the entire reference name, rather than just the branch
name. There is a break in compatibility for the repositories already
using this configuration, but this change takes care of detecting
this situation and provide an informative message explaining the likely
situation.

Change-Id: I330d15ccef448e815c94a620f008275f1e1914db
TN: T209-002
2020-06-15 11:32:45 -07:00

124 lines
5.1 KiB
Python
Executable File

#! /usr/bin/env python
"""A module to handle the fast-forward policies...
This module can also be called as a script, and returns non-zero if
it detects that a non-fast-forward update being attempted on a branch
where it is not allowed. When such an error is detected, an informative
error message should be printed on stderr.
Usage: fast_forward.py REF_NAME OLD_REV NEW_REV
The arguments REF_NAME, OLD_REV and NEW_REV are identical
to the arguments used when git calls the "update" hook.
"""
import re
import sys
from config import git_config
from errors import InvalidUpdate
from git import git
from utils import warn
# A list of regular expressions that match the references where
# it will always be OK to do a non-fast-forward update (aka
# a "forced update").
FORCED_UPDATE_OK_REFS = ("refs/heads/topic/.*",)
# The error message shown to the user when rejecting a non-fast-forward
# update.
NON_FAST_FORWARD_ERROR_MESSAGE = """\
Non-fast-forward updates are not allowed for this reference.
Please rebase your changes on top of the latest HEAD,
and then try pushing again."""
# A warning added at the end of the NON_FAST_FORWARD_ERROR_MESSAGE
# when we believe the user is trying to push a non-fast-forward update
# to a branch that looks like it should be allowed, and yet isn't,
# because this repository's hooks.non-fast-forward configuration
# appears to be following the previous (now defunct) semantics.
OLD_STYLE_CONFIG_WARNING = """\
Note: It looks like the hooks.non-fast-forward configuration
for your repository is set to only match the name of the branch
being updated (e.g. "master"), which is how this configuration
option was originally interpreted. However, the semantics of
this option has since been changed and its values must now match
the reference name (e.g. "refs/heads/master"). If you believe
this non-fast-forward update should be allowed on this branch,
contact your repository adminstrator to review the repository's
hooks.non-fast-forward option configuration."""
def check_fast_forward(ref_name, old_rev, new_rev):
"""Raise InvalidUpdate if the update violates the fast-forward policy.
PARAMETERS
ref_name: The name of the reference being update (Eg:
refs/heads/master).
old_rev: The commit SHA1 of the reference before the update.
new_rev: The new commit SHA1 that the reference will point to
if the update is accepted.
"""
# Non-fast-foward updates can be characterized by the fact that
# there is at least one commit that is accessible from the old
# revision which would no longer be accessible from the new revision.
if git.rev_list("%s..%s" % (new_rev, old_rev)) == "":
# This is a fast-forward update.
return
# Non-fast-forward update. See if this is one of the references
# where such an update is allowed.
ok_refs = git_config('hooks.allow-non-fast-forward')
for ok_ref_re in ok_refs + FORCED_UPDATE_OK_REFS:
if re.match(ok_ref_re, ref_name) is not None:
# This is one of the branches where a non-fast-forward update
# is allowed. Allow the update, but print a warning for
# the user, just to make sure he is completely aware of
# the changes that just took place.
warn("!!! WARNING: This is *NOT* a fast-forward update.")
warn("!!! WARNING: You may have removed some important commits.")
return
# This non-fast-forward update is not allowed.
err_msg = NON_FAST_FORWARD_ERROR_MESSAGE
# In the previous version of these hooks, the allow-non-fast-forward
# config was assuming that all such updates would be on references
# whose name starts with 'refs/heads/'. This is no longer the case.
# For repositories using this configuration option with the old
# semantics, non-fast-forward updates will now start getting rejected.
#
# To help users facing this situation understand what's going on,
# see if this non-fast-forward update would have been accepted
# when interpreting the config option the old way; if yes, then
# we are probably in a situation where it's the config rather than
# the update that's a problem. Add some additional information
# to the error message in order to help him understand what's
# is likely happening.
if ref_name.startswith('refs/heads/'):
for ok_ref_re in ['refs/heads/' + branch.strip()
for branch in ok_refs]:
if re.match(ok_ref_re, ref_name) is not None:
err_msg += '\n\n' + OLD_STYLE_CONFIG_WARNING
break
raise InvalidUpdate(*err_msg.splitlines())
if __name__ == '__main__':
# First, retrieve the command-line arguments.
if len(sys.argv) != 4:
warn("Error(%s): Invalid usage, wrong number of arguments (%d)"
% (sys.argv[0], len(sys.argv)))
sys.exit(1)
try:
check_fast_forward(sys.argv[1], sys.argv[2], sys.argv[3])
except InvalidUpdate, E:
# The update was rejected. Print the rejection reason, and
# exit with a nonzero status.
warn(*E)
sys.exit(1)