mirror of
https://github.com/AdaCore/git-hooks.git
synced 2026-02-12 12:43:11 -08:00
This commit is preparation work for the transition to Python 3.x, where the output obtained by running Git commands will become bytes as opposed to a string. In the vast majority of cases, we'll want to decode that output into a string. Ideally, we would want to do this in a way that is both compatible with Python 2.x and Python 3.x, but we have found that this requires a lot of work with many changes spread all over the code. So, instead, what this commit does is introduce the concept of decoding the output, but with the decoding only occurring when running under Python 3.x. That way, we can make progress towards Python 3.x while preserving the behavior under Python 2.x intact. Change-Id: I189577798ee96cba1fa55c7356babf102575642f TN: U530-006
171 lines
6.2 KiB
Python
171 lines
6.2 KiB
Python
"""Handling of Git Notes updates."""
|
|
|
|
from config import git_config
|
|
from errors import InvalidUpdate
|
|
from git import git, is_null_rev, is_valid_commit
|
|
from updates import AbstractUpdate, RefKind
|
|
from updates.commits import commit_info_list
|
|
from updates.emails import Email
|
|
from updates.notes import GitNotes
|
|
from utils import indent
|
|
|
|
# The template to be used as the body of the email to be sent
|
|
# for a notes commit which either adds, or modifies a git notes.
|
|
UPDATED_NOTES_COMMIT_EMAIL_BODY_TEMPLATE = """\
|
|
A Git note has been updated; it now contains:
|
|
|
|
%(notes_contents)s
|
|
|
|
This note annotates the following commit:
|
|
|
|
%(annotated_rev_log)s
|
|
"""
|
|
|
|
|
|
# The template to be used as the body of the email to be sent
|
|
# for a notes commit which deletes a git notes.
|
|
DELETED_NOTES_COMMIT_EMAIL_BODY_TEMPLATE = """\
|
|
Git notes annotating the following commit have been deleted.
|
|
|
|
%(annotated_rev_log)s
|
|
"""
|
|
|
|
|
|
class NotesUpdate(AbstractUpdate):
|
|
"""Update object for Git Notes creation or update.
|
|
|
|
The difference between Notes creation and Notes update is very
|
|
small, so this class has been implemented in a way to support
|
|
both (in other words, self.old_rev may be null).
|
|
"""
|
|
|
|
def self_sanity_check(self):
|
|
"""See AbstractUpdate.self_sanity_check."""
|
|
assert self.ref_kind == RefKind.notes_ref and self.object_type == "commit"
|
|
assert self.ref_name.startswith("refs/notes/")
|
|
|
|
def validate_ref_update(self):
|
|
"""See AbstractUpdate.validate_ref_update."""
|
|
# Only fast-forward changes are allowed.
|
|
self.__ensure_fast_forward()
|
|
|
|
# Also iterate over all new notes, and verify that
|
|
# the associated commit is available. We need these
|
|
# associated commits in order to create the emails
|
|
# to be sent for those notes.
|
|
for notes_commit in self.new_commits_for_ref:
|
|
notes = GitNotes(notes_commit.rev)
|
|
if not is_valid_commit(notes.annotated_rev):
|
|
error_message = [
|
|
"The commit associated to the following notes update",
|
|
"cannot be found. Please push your branch commits first",
|
|
"and then push your notes commits.",
|
|
"",
|
|
"Notes commit: %s" % notes.rev,
|
|
"Annotated commit: %s" % notes.annotated_rev,
|
|
"",
|
|
"Notes contents:",
|
|
] + notes.contents.splitlines()
|
|
raise InvalidUpdate(*error_message)
|
|
|
|
def pre_commit_checks(self):
|
|
"""See AbstractUpdate.pre_commit_checks."""
|
|
# Notes are a bit special, and mostly handled automatically
|
|
# by Git, so most of the pre-commit checks don't apply.
|
|
self.call_project_specific_commit_checker()
|
|
|
|
def get_update_email_contents(self):
|
|
"""See AbstractUpdate.get_update_email_contents."""
|
|
# No update email needed for notes (this is always
|
|
# a fast-forward commit)...
|
|
return None
|
|
|
|
def get_standard_commit_email(self, commit):
|
|
"""See AbstractUpdate.get_standard_commit_email."""
|
|
notes = GitNotes(commit.rev)
|
|
|
|
# Get commit info for the annotated commit
|
|
annotated_commit = commit_info_list("-1", notes.annotated_rev)[0]
|
|
|
|
# Get a description of the annotated commit (a la "git show"),
|
|
# except that we do not want the diff.
|
|
#
|
|
# Also, we have to handle the notes manually, as the commands
|
|
# get the notes from the HEAD of the notes/commits branch,
|
|
# whereas what we needs is the contents at the commit.rev.
|
|
# This makes a difference when a single push updates the notes
|
|
# of the same commit multiple times.
|
|
annotated_rev_log = git.log(
|
|
annotated_commit.rev, no_notes=True, max_count="1", _decode=True
|
|
)
|
|
notes_contents = (
|
|
None if notes.contents is None else indent(notes.contents, " " * 4)
|
|
)
|
|
|
|
# Determine subject tag based on ref name:
|
|
# * remove "refs/notes" prefix
|
|
# * remove entire tag if remaining component is "commits"
|
|
# (case of the default refs/notes/commits ref)
|
|
notes_ref = self.ref_name.split("/", 2)[2]
|
|
if notes_ref == "commits":
|
|
subject_tag = ""
|
|
else:
|
|
subject_tag = "(%s)" % notes_ref
|
|
|
|
subject = "[notes%s][%s] %s" % (
|
|
subject_tag,
|
|
self.email_info.project_name,
|
|
annotated_commit.subject,
|
|
)
|
|
|
|
body_template = (
|
|
DELETED_NOTES_COMMIT_EMAIL_BODY_TEMPLATE
|
|
if notes_contents is None
|
|
else UPDATED_NOTES_COMMIT_EMAIL_BODY_TEMPLATE
|
|
)
|
|
body = body_template % {
|
|
"annotated_rev_log": annotated_rev_log,
|
|
"notes_contents": notes_contents,
|
|
}
|
|
|
|
# Git commands calls strip on the output, which is usually
|
|
# a good thing, but not in the case of the diff output.
|
|
# Prevent this from happening by putting an artificial
|
|
# character at the start of the format string, and then
|
|
# by stripping it from the output.
|
|
diff = git.show(commit.rev, pretty="format:|", p=True, _decode=True)[1:]
|
|
|
|
email_bcc = git_config("hooks.filer-email")
|
|
|
|
return Email(
|
|
self.email_info,
|
|
annotated_commit.email_to(self.ref_name),
|
|
email_bcc,
|
|
subject,
|
|
body,
|
|
commit.full_author_email,
|
|
self.ref_name,
|
|
commit.base_rev_for_display(),
|
|
commit.rev,
|
|
diff,
|
|
)
|
|
|
|
def __ensure_fast_forward(self):
|
|
"""Raise InvalidUpdate if the update is not a fast-forward update."""
|
|
if is_null_rev(self.old_rev):
|
|
# Git Notes creation, and thus necessarily a fast-forward.
|
|
return
|
|
|
|
# Non-fast-foward updates are 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" % (self.new_rev, self.old_rev), _decode=True) == "":
|
|
return
|
|
|
|
raise InvalidUpdate(
|
|
"Your Git Notes are not up to date.",
|
|
"",
|
|
"Please update your Git Notes and push again.",
|
|
)
|