blob: 456e99737612278277467cfbde06c3eb6e7b569b [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
from infra.libs.decorators import cached_property
from infra.libs.git2.util import CalledProcessError
from infra.libs.git2.util import INVALID
from infra.libs.git2.data import CommitData
LOGGER = logging.getLogger(__name__)
class Commit(object):
"""Represents the identity of a commit in a git repo."""
def __init__(self, repo, hsh):
"""
@type repo: Repo
"""
assert CommitData.HASH_RE.match(hsh)
self._repo = repo
self._hsh = hsh
# Comparison & Representation
def __eq__(self, other):
return (self is other) or (
isinstance(other, Commit) and (
self.hsh == other.hsh
)
)
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return 'Commit({_repo!r}, {_hsh!r})'.format(**self.__dict__)
# Accessors
# pylint: disable=W0212
repo = property(lambda self: self._repo)
hsh = property(lambda self: self._hsh)
# Properties
@cached_property
def data(self):
"""Get a structured data representation of this commit."""
try:
raw_data = self.repo.run('cat-file', 'commit', self.hsh)
except CalledProcessError:
return INVALID
return CommitData.from_raw(raw_data)
@cached_property
def parent(self):
"""Get the corresponding parent Commit() for this Commit(), or None.
If self has more than one parent, returns INVALID.
"""
parents = self.data.parents
if len(parents) > 1:
LOGGER.error('Commit %r has more than one parent!', self.hsh)
return INVALID
return self.repo.get_commit(parents[0]) if parents else None
# Methods
def alter(self, **kwargs):
"""Get a new Commit which is the same as this one, except for alterations
specified by kwargs.
This will intern the new Commit object into the Repo.
"""
return self.repo.get_commit(
self.repo.intern(self.data.alter(**kwargs), 'commit'))
def notes(self, ref='refs/notes/commits'):
"""Get git-notes content for this commit"""
return self.repo.notes(self.hsh, ref)
def extra_footers(self):
"""Get any extra footers for this commit as an OrderedDict of
{footer: [values]}.
Extra footers are stored as git-notes on the refs/notes/extra_footers ref.
"""
footer_lines = []
raw = self.notes('refs/notes/extra_footers')
if raw is not None:
_, footer_lines = CommitData.parse_raw_message(
[''] + raw.rstrip('\n').splitlines()
)
return CommitData.frozen_dict_from_kv_pairs(footer_lines)