Files
git-hooks/hooks/updates/notes/__init__.py
Joel Brobecker 9c82498e7b Introduce (the concept of) git command output decoding
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
2021-10-06 11:27:20 -07:00

101 lines
4.1 KiB
Python

"""Git Notes updates root module."""
from git import git, CalledProcessError, diff_tree
class GitNotes(object):
"""An object representing a Git Notes change.
ATTRIBUTES
rev: The revision of the notes change.
filename: The name of the file in the notes/commits branch
used to store the contents of the notes.
contents: The contents of the git notes. None if the notes
have been removed.
annotated_rev: The revision of the commit being annotated.
"""
def __init__(self, notes_rev):
"""The constructor.
PARAMETERS
notes_rev: The revision of the notes change.
"""
self.rev = notes_rev
self.filename = self.__get_notes_filename(notes_rev)
# Implement the contents "attribute" as a property.
# This allows us to lazy-initialize it. And use
# a private attribute name __contents to cache its value.
self.__contents = None
self.annotated_rev = self.filename.replace("/", "")
@property
def contents(self):
"""The contents "attribute", lazily initialized."""
if self.__contents is None:
self.__contents = self.__get_notes_contents(self.rev, self.filename)
return self.__contents
@classmethod
def __get_notes_filename(cls, notes_rev):
"""Return the filename used to store the git notes.
PARAMETERS
notes_rev: The revision of the notes change.
"""
# A git note for any given commit (called the "annotated commit"
# in this function) is maintained through a file in the special
# refs/notes/commits namespace. The name of that file seems
# to vary a little bit from case to case (sometimes it is just
# equal to "%s" % rev, while some other times it is equal to
# "%s/%s" % (rev[:2], rev[2:]) where rev is the revision of
# the annotated commit. The one constant is that it is easy
# to deduce the annotated commit ID, by just stripping the '/'
# characters.
# Look at the files modified by the git notes commit via
# diff-tree. There should be only one, pointing us towards
# the annotated commit.
all_changes = diff_tree("-r", notes_rev)
if not all_changes:
# notes_rev is probably the root commit. Just use the empty
# tree's sha1 as the reference.
empty_tree_rev = git.mktree(_input="")
all_changes = diff_tree("-r", empty_tree_rev, notes_rev)
# The output should be 2 lines...
# - The first line contains the hash of what is being compared,
# which should be notes_rev;
# - The second line contains the file change that interests us.
# ... except in the case where the notes_rev does not have
# a parent (first note). In that case, we diff-tree'ed against
# the empty tree rev, and the first line is omitted.
#
# Normally, there should only be one entry returned by diff_tree.
# However, there is a situation where the output is more than
# one entry: Newer version of git sometimes rename some of the
# files created by older versions of "git notes" during notes
# updates, and bunches those renamings together with a note
# update, thus creating commits that actually touch multiple
# files (N707-041). In that situation, it appears as though
# the first entry is always the one corresponding to the commit
# being annotated, so discard anything past the first line.
assert all_changes
(_, _, _, _, _, filename) = all_changes[0]
return filename
@classmethod
def __get_notes_contents(cls, notes_rev, notes_filename):
"""Return the contents of the notes at notes_rev.
PARAMETERS
notes_rev: The revision of the notes change.
notes_filename: The filename containing the notes.
"""
try:
return git.show("%s:%s" % (notes_rev, notes_filename), _decode=True)
except CalledProcessError:
# The note was probably deleted, so no more notes.
return None