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
101 lines
4.1 KiB
Python
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
|