# 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 .blob import Blob
_setattr = object.__setattr__
__all__ = ('Tree',)
# Just a helper function to join path together
pjoin = b'/'.join
class TreeDict(collections.Mapping):
def __init__(self, repo, raw_mapping):
self.repo = repo
self.mapping = raw_mapping
def __getitem__(self, name):
mode, sha_or_obj = self.mapping[name]
if isinstance(sha_or_obj, GitObject):
return mode, sha_or_obj
obj = self._create_object(mode, sha_or_obj)
self.mapping[name] = (mode, obj)
return mode, obj
def __len__(self):
return len(self.mapping)
def __iter__(self):
return iter(self.mapping)
def get_sha(self, name):
mode, sha_or_obj = self.mapping[name]
try:
return sha_or_obj.sha
except AttributeError:
return sha_or_obj
def _create_object(self, mode, _sha):
if mode == b"40000":
return Tree(self.repo, _sha)
else:
return Blob(self.repo, _sha)
[docs]class Tree(GitObject):
""" A blob object.
Attributes:
entries(unmutable mapping) : This is the entries of the tree.
"""
__slots__ = ('entries',)
def _read(self):
_entries = self.repo.db.tree_content(self._sha)
_setattr(self, 'entries', TreeDict(self.repo, _entries))
def __getitem__(self, name):
"""Return the entry corresponding to the name"""
return self.entries[name]
def is_entry_diff(self, other, path):
"""Check if path is in both trees and if the two subfiles/subtrees differ
Arguments:
other(Tree): The other tree objet to compare from
path(bytes path) : The path to check
Returns:
- False if there is no diff
- True if the path is in both tree, but content differs
- The tree that content the path if the path is in only one tree
"""
sentries = self.entries
oentries = other.entries
for p in path.split(b'/'):
try:
sentries = sentries[p][1]
except KeyError:
# self doesn't contain the path
return other
try:
oentries = oentries[p][1]
except KeyError:
# other doesn't contain the path
return self
if sentries == oentries:
# Either entre or parent subtree is equal
# Thanks to git hash we know that the final path will
# be equal
return False
# We reach the end of the path and entries are differents
return True
def gen_entries_diff(self, other, subset=None):
"""Compare the twe tree and return four sets:
- equal : the entries that are in both self and other and are equal is both
- diff : the entries that are in both self and other but are not equal
- in_self : the entries that are only in self
- in_other : the entries that are only in other
"""
sentries = set(self.entries.mapping.keys())
oentries = set(other.entries.mapping.keys())
if subset is not None:
sentries &= subset
oentries &= subset
in_both = sentries & oentries
only_in_s = sentries - in_both
only_in_o = oentries - in_both
sget_sha = lambda k, m=self.entries.mapping: m[k][1]
oget_sha = lambda k, m=other.entries.mapping: m[k][1]
equal = set(k for k in in_both if sget_sha(k)==oget_sha(k))
diff = in_both - equal
return equal, diff, only_in_s, only_in_o
def gen_diff_map(self, other):
equal, diff, in_self, in_other = self.gen_entries_diff(other)
result = { k:'=' for k in equal}
result.update((k,'!') for k in diff)
result.update((k,'<') for k in in_self)
result.update((k,'>') for k in in_other)
return result
def gen_full_diff_map(self, other):
_, diff, in_self, in_other = self.gen_entries_diff(other)
result = {}
for d in diff:
sentry = self.entries[d][1]
oentry = other.entries[d][1]
if type(sentry) == Tree and type(oentry) == Tree:
sub_diff = sentry.gen_full_diff_map(oentry)
result.update((pjoin(d, e),sd) for e, sd in sub_diff.items())
elif type(sentry) == Blob and type(oentry) == Blob:
result[d] = '!'
for n in in_self:
sentry = self.entries[n][1]
if type(sentry) == Tree:
sub_new = sentry.list_all_files()
result.update((pjoin(n, e),'<') for e in sub_new)
else:
result[n] = '<'
for o in in_other:
oentry = other.entries[d][1]
if type(oentry) == Tree:
sub_old = oentry.list_all_files()
result.update((pjoin(o, e),'>') for e in sub_old)
else:
result[o] = '>'
return result
def list_all_files(self):
result = []
for e in self.entries:
if type(self.entries[e]) == Tree:
result.extend(pjoin(e, i) for i in self.entries[e].list_all_files())
else:
result.append(e)
return result