# This file is part of maggit.
#
# Copyright 2015 Matthieu Gautier <dev@mgautier.fr>
#
# Pit is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# Additional permission under the GNU Affero GPL version 3 section 7:
#
# If you modify this Program, or any covered work, by linking or
# combining it with other code, such other code is not for that reason
# alone subject to any of the requirements of the GNU Affero GPL
# version 3.
#
# You should have received a copy of the GNU Affero General Public License
# along with maggit. If not, see http://www.gnu.org/licenses
#
# In summary:
# - You can use this program for no cost.
# - You can use this program for both personal and commercial reasons.
# - You do not have to share your own program's code which uses this program.
# - You have to share modifications (e.g bug-fixes, improvements) you've made to this program.
import collections
from .gitObject import GitObject
from .tree import Tree
from .utils import parse_signature
_setattr = object.__setattr__
__all__ = ('Commit',)
class CommitPseudoList(collections.Sequence):
def __init__(self, repo, sequence):
self.repo = repo
self.sequence = sequence
self._refs = dict()
def __getitem__(self, index):
sha = self.sequence[index]
try:
return self._refs[sha]
except KeyError:
obj = Commit(self.repo, sha)
self._refs[sha] = obj
return obj
def __len__(self):
return len(self.sequence)
def get_sha(self, index):
return self.sequences[index]
[docs]class Commit(GitObject):
""" A commit object.
Attributes:
tree(:class:`~maggit.Tree`) : The tree object associated with the commit.
parents(tuple) : The parents of the commits.
Most of the time, there will only one parent.
In case of branch merge, there will be more than one parent.
author(:class:`~maggit.Person`) : The author of the commit.
author_date(timedate) : When the commit was created.
committer(:class:`~maggit.Person`) : The committer of the commit.
committer_date(timedate) : When the commit was committed.
first_line(str) : The first line of the commit message.
message(str) : The full commit message (including the first line).
"""
__slots__ = ('tree', '_tree',
'parents',
'author', 'author_date', '_author',
'committer', 'committer_date', '_committer',
'first_line', 'message', '_messageLines')
def _compute_tree(self):
return Tree(self.repo, self._tree)
def _compute_first_line(self):
return self._messageLines[0].decode('utf8')
def _compute_message(self):
return b'\n'.join(self._messageLines).decode('utf8')
def _compute_author(self):
author, date = parse_signature(self._author)
_setattr(self, 'author_date', date)
return author
def _compute_author_date(self):
author, date = parse_signature(self._author)
_setattr(self, 'author', author)
return date
def _compute_committer(self):
committer, date = parse_signature(self._committer)
_setattr(self, 'committer_date', date)
return committer
def _compute_committer_date(self):
committer, date = parse_signature(self._committer)
_setattr(self, 'committer', committer)
return date
def _read(self):
(treesha, parents, messageLines, author, committer) = self.repo.db.commit_content(self._sha)
_setattr(self, '_tree', treesha)
_setattr(self, 'parents', CommitPseudoList(self.repo, parents))
_setattr(self, '_messageLines', messageLines)
_setattr(self, '_author', author)
_setattr(self, '_committer', committer)
def get_first_appearances(self, root=None, depthLimit=None):
"""Return the first appearances for all entry in root.
This is mostly equivalent to `{path:Entry(commit, path).get_first_appearance() for path in commit.tree.entries}`
(if root is None).
But a way more performant as diving into history is made once.
Arguments:
root(bytes path): In wich subdirectory we must look.
depthLimit(int) : The commit limit number we go in history
If depthLimit is specified, and for a entry the first appearance is older than depthLimit,
the entry will be present in the dictionnary with a None value.
Returns:
A dict of (bytes, :class:`maggit.Commit`).
"""
entries_left = set(self.tree.entries.keys())
root = root.split(b'/') if root else ()
result = {}
current = self
c_trees = [current.tree]
for r in root:
c_trees.append(c_trees[-1][r])
depth = 0
while entries_left:
depth = depth + 1
if depthLimit and depth>depthLimit:
result.update((e,None) for e in entries_left)
break
try:
parent = current.parents[0]
except IndexError:
# No parent
result.update((e,current) for e in entries_left)
break
if parent._tree == c_trees[0]._sha:
# Tree is equal, advance in history
current = parent
# No need to change c_trees to p_trees, they are equals
continue
p_trees = [parent.tree]
need_continue = False
for i, r in enumerate(root, 1):
p_trees.append(p_trees[-1][r])
if p_trees[-1] is c_trees[i]:
current = parent
c_trees[:i] = p_trees
need_continue = True
break
if need_continue:
continue
# Ok, now we've got two trees where some entries differ
# Do the job
_, diff, in_current, _ = c_trees[-1].gen_entries_diff(p_trees[-1], entries_left)
diff |= in_current
result.update((e,current) for e in diff)
entries_left -= diff
current = parent
c_trees = p_trees
return result