+class GitObject(Immutable, Repr):
+ """Base class for all git objects. One git object is represented by at
+ most one C{GitObject}, which makes it possible to compare them
+ using normal Python object comparison; it also ensures we don't
+ waste more memory than necessary."""
+
+class BlobData(Immutable, Repr):
+ """Represents the data contents of a git blob object."""
+ def __init__(self, string):
+ self.__string = str(string)
+ str = property(lambda self: self.__string)
+ def commit(self, repository):
+ """Commit the blob.
+ @return: The committed blob
+ @rtype: L{Blob}"""
+ sha1 = repository.run(['git', 'hash-object', '-w', '--stdin']
+ ).raw_input(self.str).output_one_line()
+ return repository.get_blob(sha1)
+
+class Blob(GitObject):
+ """Represents a git blob object. All the actual data contents of the
+ blob object is stored in the L{data} member, which is a
+ L{BlobData} object."""
+ typename = 'blob'
+ default_perm = '100644'
+ def __init__(self, repository, sha1):
+ self.__repository = repository
+ self.__sha1 = sha1
+ sha1 = property(lambda self: self.__sha1)
+ def __str__(self):
+ return 'Blob<%s>' % self.sha1
+ @property
+ def data(self):
+ return BlobData(self.__repository.cat_object(self.sha1))
+
+class ImmutableDict(dict):
+ """A dictionary that cannot be modified once it's been created."""
+ def error(*args, **kwargs):
+ raise TypeError('Cannot modify immutable dict')
+ __delitem__ = error
+ __setitem__ = error
+ clear = error
+ pop = error
+ popitem = error
+ setdefault = error
+ update = error
+
+class TreeData(Immutable, Repr):
+ """Represents the data contents of a git tree object."""
+ @staticmethod
+ def __x(po):
+ if isinstance(po, GitObject):
+ perm, object = po.default_perm, po
+ else:
+ perm, object = po
+ return perm, object
+ def __init__(self, entries):
+ """Create a new L{TreeData} object from the given mapping from names
+ (strings) to either (I{permission}, I{object}) tuples or just
+ objects."""
+ self.__entries = ImmutableDict((name, self.__x(po))
+ for (name, po) in entries.iteritems())
+ entries = property(lambda self: self.__entries)
+ """Map from name to (I{permission}, I{object}) tuple."""
+ def set_entry(self, name, po):
+ """Create a new L{TreeData} object identical to this one, except that
+ it maps C{name} to C{po}.
+
+ @param name: Name of the changed mapping
+ @type name: C{str}
+ @param po: Value of the changed mapping
+ @type po: L{Blob} or L{Tree} or (C{str}, L{Blob} or L{Tree})
+ @return: The new L{TreeData} object
+ @rtype: L{TreeData}"""
+ e = dict(self.entries)
+ e[name] = self.__x(po)
+ return type(self)(e)
+ def del_entry(self, name):
+ """Create a new L{TreeData} object identical to this one, except that
+ it doesn't map C{name} to anything.
+
+ @param name: Name of the deleted mapping
+ @type name: C{str}
+ @return: The new L{TreeData} object
+ @rtype: L{TreeData}"""
+ e = dict(self.entries)
+ del e[name]
+ return type(self)(e)
+ def commit(self, repository):
+ """Commit the tree.
+ @return: The committed tree
+ @rtype: L{Tree}"""
+ listing = ''.join(
+ '%s %s %s\t%s\0' % (mode, obj.typename, obj.sha1, name)
+ for (name, (mode, obj)) in self.entries.iteritems())
+ sha1 = repository.run(['git', 'mktree', '-z']
+ ).raw_input(listing).output_one_line()
+ return repository.get_tree(sha1)
+ @classmethod
+ def parse(cls, repository, s):
+ """Parse a raw git tree description.
+
+ @return: A new L{TreeData} object
+ @rtype: L{TreeData}"""
+ entries = {}
+ for line in s.split('\0')[:-1]:
+ m = re.match(r'^([0-7]{6}) ([a-z]+) ([0-9a-f]{40})\t(.*)$', line)
+ assert m
+ perm, type, sha1, name = m.groups()
+ entries[name] = (perm, repository.get_object(type, sha1))
+ return cls(entries)
+
+class Tree(GitObject):
+ """Represents a git tree object. All the actual data contents of the
+ tree object is stored in the L{data} member, which is a
+ L{TreeData} object."""
+ typename = 'tree'
+ default_perm = '040000'
+ def __init__(self, repository, sha1):