mirror of
https://github.com/AdaCore/git-hooks.git
synced 2026-02-12 12:43:11 -08:00
101 lines
4.2 KiB
Python
101 lines
4.2 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))
|
|
except CalledProcessError:
|
|
# The note was probably deleted, so no more notes.
|
|
return None
|