aboutsummaryrefslogtreecommitdiff
path: root/lib/git/objects
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2009-10-15 10:04:17 +0200
committerSebastian Thiel <byronimo@gmail.com>2009-10-15 10:04:17 +0200
commit6226720b0e6a5f7cb9223fc50363def487831315 (patch)
tree10f70f8e41c91f5bf57f04b616f3e5afdb9f8407 /lib/git/objects
parentb0e84a3401c84507dc017d6e4f57a9dfdb31de53 (diff)
parent4186a2dbbd48fd67ff88075c63bbd3e6c1d8a2df (diff)
downloadGitPython-6226720b0e6a5f7cb9223fc50363def487831315.tar.gz
GitPython-6226720b0e6a5f7cb9223fc50363def487831315.zip
Initial set of improvementes merged into master, including a class hierarchy redesign and performance improvements
Merge commit 'origin/improvements' * commit 'origin/improvements': (38 commits) test_performance: module containing benchmarks to get an idea of the achieved throughput Removed plenty of mocked tree tests as they cannot work anymore with persistent commands that require stdin AND binary data - not even an adapter would help here. These tests will have to be replaced. tree: now reads tress directly by parsing the binary data, allowing it to safe possibly hundreds of command calls Refs are now truly dynamic - this costs a little bit of (persistent command) work, but assures refs behave as expected persistent command signature changed to also return the hexsha from a possible input ref - the objects pointed to by refs are now baked on demand - perhaps it should change to always be re-retrieved using a property as it is relatively fast - this way refs can always be cached test_blob: removed many redundant tests that would fail now as the mock cannot handle the complexity of the command backend Implemented git command facility to keep persistent commands for fast object information retrieval test: Added time-consuming test which could also be a benchmark in fact - currently it cause hundreds of command invocations which is slow cmd: added option to return the process directly, allowing to read the output directly from the output stream added Iterable interface to Ref type renamed find_all to list_all, changed commit to use iterable interface in preparation for command changes Added base for all iteratable objects unified name of utils module, recently it was named util and utils in different packages tree: renamed content_from_string to _from_string to make it private. Removed tests that were testing that method tree: now behaves like a list with string indexing functionality - using a dict as cache is a problem as the tree is ordered, added blobs, trees and traverse method test_base: Improved basic object creation as well as set hash tests repo.active_branch now returns a Head object, not a string IndexObjects are now checking their slots to raise a proper error message in case someone tries to access an unset path or mode - this information cannot be retrieved afterwards as IndexObject information is kept in the object that pointed at them. To find this information, one would have to search all objects which is not feasible refs now take repo as first argument and derive from LazyMixin to allow them to dynamically retrieve their objects renamed from_string and list_from_string to _from_string and _list_from_string to indicate their new status as private method, adjusted all callers respectively ...
Diffstat (limited to 'lib/git/objects')
-rw-r--r--lib/git/objects/__init__.py11
-rw-r--r--lib/git/objects/base.py151
-rw-r--r--lib/git/objects/blob.py36
-rw-r--r--lib/git/objects/commit.py299
-rw-r--r--lib/git/objects/tag.py70
-rw-r--r--lib/git/objects/tree.py242
-rw-r--r--lib/git/objects/utils.py36
7 files changed, 845 insertions, 0 deletions
diff --git a/lib/git/objects/__init__.py b/lib/git/objects/__init__.py
new file mode 100644
index 00000000..39e650b7
--- /dev/null
+++ b/lib/git/objects/__init__.py
@@ -0,0 +1,11 @@
+"""
+Import all submodules main classes into the package space
+"""
+import inspect
+from tag import *
+from blob import *
+from tree import *
+from commit import *
+
+__all__ = [ name for name, obj in locals().items()
+ if not (name.startswith('_') or inspect.ismodule(obj)) ] \ No newline at end of file
diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py
new file mode 100644
index 00000000..07538ada
--- /dev/null
+++ b/lib/git/objects/base.py
@@ -0,0 +1,151 @@
+# base.py
+# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
+#
+# This module is part of GitPython and is released under
+# the BSD License: http://www.opensource.org/licenses/bsd-license.php
+import os
+from git.utils import LazyMixin
+
+_assertion_msg_format = "Created object %r whose python type %r disagrees with the acutal git object type %r"
+
+class Object(LazyMixin):
+ """
+ Implements an Object which may be Blobs, Trees, Commits and Tags
+ """
+ TYPES = ("blob", "tree", "commit", "tag")
+ __slots__ = ("repo", "id", "size", "data" )
+ type = None # to be set by subclass
+
+ def __init__(self, repo, id):
+ """
+ Initialize an object by identifying it by its id. All keyword arguments
+ will be set on demand if None.
+
+ ``repo``
+ repository this object is located in
+
+ ``id``
+ SHA1 or ref suitable for git-rev-parse
+ """
+ super(Object,self).__init__()
+ self.repo = repo
+ self.id = id
+
+ def _set_self_from_args_(self, args_dict):
+ """
+ Initialize attributes on self from the given dict that was retrieved
+ from locals() in the calling method.
+
+ Will only set an attribute on self if the corresponding value in args_dict
+ is not None
+ """
+ for attr, val in args_dict.items():
+ if attr != "self" and val is not None:
+ setattr( self, attr, val )
+ # END set all non-None attributes
+
+ def _set_cache_(self, attr):
+ """
+ Retrieve object information
+ """
+ if attr == "size":
+ hexsha, typename, self.size = self.repo.git.get_object_header(self.id)
+ assert typename == self.type, _assertion_msg_format % (self.id, typename, self.type)
+ elif attr == "data":
+ hexsha, typename, self.size, self.data = self.repo.git.get_object_data(self.id)
+ assert typename == self.type, _assertion_msg_format % (self.id, typename, self.type)
+ else:
+ super(Object,self)._set_cache_(attr)
+
+ def __eq__(self, other):
+ """
+ Returns
+ True if the objects have the same SHA1
+ """
+ return self.id == other.id
+
+ def __ne__(self, other):
+ """
+ Returns
+ True if the objects do not have the same SHA1
+ """
+ return self.id != other.id
+
+ def __hash__(self):
+ """
+ Returns
+ Hash of our id allowing objects to be used in dicts and sets
+ """
+ return hash(self.id)
+
+ def __str__(self):
+ """
+ Returns
+ string of our SHA1 as understood by all git commands
+ """
+ return self.id
+
+ def __repr__(self):
+ """
+ Returns
+ string with pythonic representation of our object
+ """
+ return '<git.%s "%s">' % (self.__class__.__name__, self.id)
+
+
+class IndexObject(Object):
+ """
+ Base for all objects that can be part of the index file , namely Tree, Blob and
+ SubModule objects
+ """
+ __slots__ = ("path", "mode")
+
+ def __init__(self, repo, id, mode=None, path=None):
+ """
+ Initialize a newly instanced IndexObject
+ ``repo``
+ is the Repo we are located in
+
+ ``id`` : string
+ is the git object id as hex sha
+
+ ``mode`` : int
+ is the file mode as int, use the stat module to evaluate the infomration
+
+ ``path`` : str
+ is the path to the file in the file system, relative to the git repository root, i.e.
+ file.ext or folder/other.ext
+
+ NOTE
+ Path may not be set of the index object has been created directly as it cannot
+ be retrieved without knowing the parent tree.
+ """
+ super(IndexObject, self).__init__(repo, id)
+ self._set_self_from_args_(locals())
+ if isinstance(mode, basestring):
+ self.mode = self._mode_str_to_int(mode)
+
+ def _set_cache_(self, attr):
+ if attr in IndexObject.__slots__:
+ # they cannot be retrieved lateron ( not without searching for them )
+ raise AttributeError( "path and mode attributes must have been set during %s object creation" % type(self).__name__ )
+ else:
+ super(IndexObject, self)._set_cache_(attr)
+
+ @classmethod
+ def _mode_str_to_int(cls, modestr):
+ """
+ ``modestr``
+ string like 755 or 644 or 100644 - only the last 3 chars will be used
+
+ Returns
+ String identifying a mode compatible to the mode methods ids of the
+ stat module regarding the rwx permissions for user, group and other
+ """
+ mode = 0
+ for iteration,char in enumerate(reversed(modestr[-3:])):
+ mode += int(char) << iteration*3
+ # END for each char
+ return mode
+
+
diff --git a/lib/git/objects/blob.py b/lib/git/objects/blob.py
new file mode 100644
index 00000000..88ca73d6
--- /dev/null
+++ b/lib/git/objects/blob.py
@@ -0,0 +1,36 @@
+# blob.py
+# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
+#
+# This module is part of GitPython and is released under
+# the BSD License: http://www.opensource.org/licenses/bsd-license.php
+
+import mimetypes
+import base
+
+class Blob(base.IndexObject):
+ """A Blob encapsulates a git blob object"""
+ DEFAULT_MIME_TYPE = "text/plain"
+ type = "blob"
+
+ __slots__ = tuple()
+
+
+ @property
+ def mime_type(self):
+ """
+ The mime type of this file (based on the filename)
+
+ Returns
+ str
+
+ NOTE
+ Defaults to 'text/plain' in case the actual file type is unknown.
+ """
+ guesses = None
+ if self.path:
+ guesses = mimetypes.guess_type(self.path)
+ return guesses and guesses[0] or self.DEFAULT_MIME_TYPE
+
+
+ def __repr__(self):
+ return '<git.Blob "%s">' % self.id
diff --git a/lib/git/objects/commit.py b/lib/git/objects/commit.py
new file mode 100644
index 00000000..101014ab
--- /dev/null
+++ b/lib/git/objects/commit.py
@@ -0,0 +1,299 @@
+# commit.py
+# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
+#
+# This module is part of GitPython and is released under
+# the BSD License: http://www.opensource.org/licenses/bsd-license.php
+
+import re
+import time
+from git.utils import Iterable
+from git.actor import Actor
+import git.diff as diff
+import git.stats as stats
+from tree import Tree
+import base
+
+class Commit(base.Object, Iterable):
+ """
+ Wraps a git Commit object.
+
+ This class will act lazily on some of its attributes and will query the
+ value on demand only if it involves calling the git binary.
+ """
+ # precompiled regex
+ re_actor_epoch = re.compile(r'^.+? (.*) (\d+) .*$')
+
+ # object configuration
+ type = "commit"
+ __slots__ = ("tree", "author", "authored_date", "committer", "committed_date",
+ "message", "parents")
+
+ def __init__(self, repo, id, tree=None, author=None, authored_date=None,
+ committer=None, committed_date=None, message=None, parents=None):
+ """
+ Instantiate a new Commit. All keyword arguments taking None as default will
+ be implicitly set if id names a valid sha.
+
+ The parameter documentation indicates the type of the argument after a colon ':'.
+
+ ``id``
+ is the sha id of the commit or a ref
+
+ ``parents`` : tuple( Commit, ... )
+ is a tuple of commit ids or actual Commits
+
+ ``tree`` : Tree
+ is the corresponding tree id or an actual Tree
+
+ ``author`` : Actor
+ is the author string ( will be implicitly converted into an Actor object )
+
+ ``authored_date`` : (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst )
+ is the authored DateTime
+
+ ``committer`` : Actor
+ is the committer string
+
+ ``committed_date`` : (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)
+ is the committed DateTime
+
+ ``message`` : string
+ is the commit message
+
+ Returns
+ git.Commit
+ """
+ super(Commit,self).__init__(repo, id)
+ self._set_self_from_args_(locals())
+
+ if parents is not None:
+ self.parents = tuple( self.__class__(repo, p) for p in parents )
+ # END for each parent to convert
+
+ if self.id and tree is not None:
+ self.tree = Tree(repo, id=tree, path='')
+ # END id to tree conversion
+
+ def _set_cache_(self, attr):
+ """
+ Called by LazyMixin superclass when the given uninitialized member needs
+ to be set.
+ We set all values at once.
+ """
+ if attr in Commit.__slots__:
+ # prepare our data lines to match rev-list
+ data_lines = self.data.splitlines()
+ data_lines.insert(0, "commit %s" % self.id)
+ temp = self._iter_from_process_or_stream(self.repo, iter(data_lines)).next()
+ self.parents = temp.parents
+ self.tree = temp.tree
+ self.author = temp.author
+ self.authored_date = temp.authored_date
+ self.committer = temp.committer
+ self.committed_date = temp.committed_date
+ self.message = temp.message
+ else:
+ super(Commit, self)._set_cache_(attr)
+
+ @property
+ def summary(self):
+ """
+ Returns
+ First line of the commit message.
+ """
+ return self.message.split('\n', 1)[0]
+
+ @classmethod
+ def count(cls, repo, ref, path=''):
+ """
+ Count the number of commits reachable from this ref
+
+ ``repo``
+ is the Repo
+
+ ``ref``
+ is the ref from which to begin (SHA1 or name)
+
+ ``path``
+ is an optinal path
+
+ Returns
+ int
+ """
+ return len(repo.git.rev_list(ref, '--', path).strip().splitlines())
+
+ @classmethod
+ def iter_items(cls, repo, ref, path='', **kwargs):
+ """
+ Find all commits matching the given criteria.
+
+ ``repo``
+ is the Repo
+
+ ``ref``
+ is the ref from which to begin (SHA1, Head or name)
+
+ ``path``
+ is an optinal path, if set only Commits that include the path
+ will be considered
+
+ ``kwargs``
+ optional keyword arguments to git where
+ ``max_count`` is the maximum number of commits to fetch
+ ``skip`` is the number of commits to skip
+
+ Returns
+ iterator yielding Commit items
+ """
+ options = {'pretty': 'raw', 'as_process' : True }
+ options.update(kwargs)
+
+ # the test system might confront us with string values -
+ proc = repo.git.rev_list(ref, '--', path, **options)
+ return cls._iter_from_process_or_stream(repo, proc)
+
+ @classmethod
+ def _iter_from_process_or_stream(cls, repo, proc_or_stream):
+ """
+ Parse out commit information into a list of Commit objects
+
+ ``repo``
+ is the Repo
+
+ ``proc``
+ git-rev-list process instance (raw format)
+
+ Returns
+ iterator returning Commit objects
+ """
+ stream = proc_or_stream
+ if not hasattr(stream,'next'):
+ stream = proc_or_stream.stdout
+
+ for line in stream:
+ id = line.split()[1]
+ assert line.split()[0] == "commit"
+ tree = stream.next().split()[1]
+
+ parents = []
+ next_line = None
+ for parent_line in stream:
+ if not parent_line.startswith('parent'):
+ next_line = parent_line
+ break
+ # END abort reading parents
+ parents.append(parent_line.split()[-1])
+ # END for each parent line
+
+ author, authored_date = cls._actor(next_line)
+ committer, committed_date = cls._actor(stream.next())
+
+ # empty line
+ stream.next()
+
+ message_lines = []
+ next_line = None
+ for msg_line in stream:
+ if not msg_line.startswith(' '):
+ break
+ # END abort message reading
+ message_lines.append(msg_line.strip())
+ # END while there are message lines
+ message = '\n'.join(message_lines)
+
+ yield Commit(repo, id=id, parents=parents, tree=tree, author=author, authored_date=authored_date,
+ committer=committer, committed_date=committed_date, message=message)
+ # END for each line in stream
+
+ @classmethod
+ def diff(cls, repo, a, b=None, paths=None):
+ """
+ Creates diffs between a tree and the index or between two trees:
+
+ ``repo``
+ is the Repo
+
+ ``a``
+ is a named commit
+
+ ``b``
+ is an optional named commit. Passing a list assumes you
+ wish to omit the second named commit and limit the diff to the
+ given paths.
+
+ ``paths``
+ is a list of paths to limit the diff to.
+
+ Returns
+ git.Diff[]::
+
+ between tree and the index if only a is given
+ between two trees if a and b are given and are commits
+ """
+ paths = paths or []
+
+ if isinstance(b, list):
+ paths = b
+ b = None
+
+ if paths:
+ paths.insert(0, "--")
+
+ if b:
+ paths.insert(0, b)
+ paths.insert(0, a)
+ text = repo.git.diff('-M', full_index=True, *paths)
+ return diff.Diff._list_from_string(repo, text)
+
+ @property
+ def diffs(self):
+ """
+ Returns
+ git.Diff[]
+ Diffs between this commit and its first parent or all changes if this
+ commit is the first commit and has no parent.
+ """
+ if not self.parents:
+ d = self.repo.git.show(self.id, '-M', full_index=True, pretty='raw')
+ return diff.Diff._list_from_string(self.repo, d)
+ else:
+ return self.diff(self.repo, self.parents[0].id, self.id)
+
+ @property
+ def stats(self):
+ """
+ Create a git stat from changes between this commit and its first parent
+ or from all changes done if this is the very first commit.
+
+ Return
+ git.Stats
+ """
+ if not self.parents:
+ text = self.repo.git.diff_tree(self.id, '--', numstat=True, root=True)
+ text2 = ""
+ for line in text.splitlines()[1:]:
+ (insertions, deletions, filename) = line.split("\t")
+ text2 += "%s\t%s\t%s\n" % (insertions, deletions, filename)
+ text = text2
+ else:
+ text = self.repo.git.diff(self.parents[0].id, self.id, '--', numstat=True)
+ return stats.Stats._list_from_string(self.repo, text)
+
+ def __str__(self):
+ """ Convert commit to string which is SHA1 """
+ return self.id
+
+ def __repr__(self):
+ return '<git.Commit "%s">' % self.id
+
+ @classmethod
+ def _actor(cls, line):
+ """
+ Parse out the actor (author or committer) info
+
+ Returns
+ [Actor, gmtime(acted at time)]
+ """
+ m = cls.re_actor_epoch.search(line)
+ actor, epoch = m.groups()
+ return (Actor._from_string(actor), time.gmtime(int(epoch)))
diff --git a/lib/git/objects/tag.py b/lib/git/objects/tag.py
new file mode 100644
index 00000000..ecf6349d
--- /dev/null
+++ b/lib/git/objects/tag.py
@@ -0,0 +1,70 @@
+# objects.py
+# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
+#
+# This module is part of GitPython and is released under
+# the BSD License: http://www.opensource.org/licenses/bsd-license.php
+"""
+Module containing all object based types.
+"""
+import base
+import commit
+from utils import get_object_type_by_name
+
+class TagObject(base.Object):
+ """
+ Non-Lightweight tag carrying additional information about an object we are pointing
+ to.
+ """
+ type = "tag"
+ __slots__ = ( "object", "tag", "tagger", "tagged_date", "message" )
+
+ def __init__(self, repo, id, object=None, tag=None,
+ tagger=None, tagged_date=None, message=None):
+ """
+ Initialize a tag object with additional data
+
+ ``repo``
+ repository this object is located in
+
+ ``id``
+ SHA1 or ref suitable for git-rev-parse
+
+ ``object``
+ Object instance of object we are pointing to
+
+ ``tag``
+ name of this tag
+
+ ``tagger``
+ Actor identifying the tagger
+
+ ``tagged_date`` : (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)
+ is the DateTime of the tag creation
+ """
+ super(TagObject, self).__init__(repo, id )
+ self._set_self_from_args_(locals())
+
+ def _set_cache_(self, attr):
+ """
+ Cache all our attributes at once
+ """
+ if attr in TagObject.__slots__:
+ lines = self.data.splitlines()
+
+ obj, hexsha = lines[0].split(" ") # object <hexsha>
+ type_token, type_name = lines[1].split(" ") # type <type_name>
+ self.object = get_object_type_by_name(type_name)(self.repo, hexsha)
+
+ self.tag = lines[2][4:] # tag <tag name>
+
+ tagger_info = lines[3][7:]# tagger <actor> <date>
+ self.tagger, self.tagged_date = commit.Commit._actor(tagger_info)
+
+ # line 4 empty - check git source to figure out purpose
+ self.message = "\n".join(lines[5:])
+ # END check our attributes
+ else:
+ super(TagObject, self)._set_cache_(attr)
+
+
+
diff --git a/lib/git/objects/tree.py b/lib/git/objects/tree.py
new file mode 100644
index 00000000..abfa9622
--- /dev/null
+++ b/lib/git/objects/tree.py
@@ -0,0 +1,242 @@
+# tree.py
+# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
+#
+# This module is part of GitPython and is released under
+# the BSD License: http://www.opensource.org/licenses/bsd-license.php
+
+import os
+import blob
+import base
+import binascii
+
+def sha_to_hex(sha):
+ """Takes a string and returns the hex of the sha within"""
+ hexsha = binascii.hexlify(sha)
+ assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % hexsha
+ return hexsha
+
+class Tree(base.IndexObject):
+ """
+ Tress represent a ordered list of Blobs and other Trees. Hence it can be
+ accessed like a list.
+
+ Tree's will cache their contents after first retrieval to improve efficiency.
+
+ ``Tree as a list``::
+
+ Access a specific blob using the
+ tree['filename'] notation.
+
+ You may as well access by index
+ blob = tree[0]
+
+
+ """
+
+ type = "tree"
+ __slots__ = "_cache"
+
+ # using ascii codes for comparison
+ ascii_commit_id = (0x31 << 4) + 0x36
+ ascii_blob_id = (0x31 << 4) + 0x30
+ ascii_tree_id = (0x34 << 4) + 0x30
+
+
+ def __init__(self, repo, id, mode=0, path=None):
+ super(Tree, self).__init__(repo, id, mode, path)
+
+ def _set_cache_(self, attr):
+ if attr == "_cache":
+ # Set the data when we need it
+ self._cache = self._get_tree_cache()
+ else:
+ super(Tree, self)._set_cache_(attr)
+
+ def _get_tree_cache(self):
+ """
+ Return
+ list(object_instance, ...)
+
+ ``treeish``
+ sha or ref identifying a tree
+ """
+ out = list()
+ for obj in self._iter_from_data():
+ if obj is not None:
+ out.append(obj)
+ # END if object was handled
+ # END for each line from ls-tree
+ return out
+
+
+ def _iter_from_data(self):
+ """
+ Reads the binary non-pretty printed representation of a tree and converts
+ it into Blob, Tree or Commit objects.
+
+ Note: This method was inspired by the parse_tree method in dulwich.
+
+ Returns
+ list(IndexObject, ...)
+ """
+ ord_zero = ord('0')
+ data = self.data
+ len_data = len(data)
+ i = 0
+ while i < len_data:
+ mode = 0
+ mode_boundary = i + 6
+
+ # keep it ascii - we compare against the respective values
+ type_id = (ord(data[i])<<4) + ord(data[i+1])
+ i += 2
+
+ while data[i] != ' ':
+ # move existing mode integer up one level being 3 bits
+ # and add the actual ordinal value of the character
+ mode = (mode << 3) + (ord(data[i]) - ord_zero)
+ i += 1
+ # END while reading mode
+
+ # byte is space now, skip it
+ i += 1
+
+ # parse name, it is NULL separated
+
+ ns = i
+ while data[i] != '\0':
+ i += 1
+ # END while not reached NULL
+ name = data[ns:i]
+
+ # byte is NULL, get next 20
+ i += 1
+ sha = data[i:i+20]
+ i = i + 20
+
+ hexsha = sha_to_hex(sha)
+ if type_id == self.ascii_blob_id:
+ yield blob.Blob(self.repo, hexsha, mode, name)
+ elif type_id == self.ascii_tree_id:
+ yield Tree(self.repo, hexsha, mode, name)
+ elif type_id == self.ascii_commit_id:
+ # todo
+ yield None
+ else:
+ raise TypeError( "Unknown type found in tree data: %i" % type_id )
+ # END for each byte in data stream
+
+
+ def __div__(self, file):
+ """
+ Find the named object in this tree's contents
+
+ Examples::
+
+ >>> Repo('/path/to/python-git').tree/'lib'
+ <git.Tree "6cc23ee138be09ff8c28b07162720018b244e95e">
+ >>> Repo('/path/to/python-git').tree/'README.txt'
+ <git.Blob "8b1e02c0fb554eed2ce2ef737a68bb369d7527df">
+
+ Returns
+ ``git.Blob`` or ``git.Tree``
+
+ Raise
+ KeyError if given file or tree does not exist in tree
+ """
+ return self[file]
+
+
+ def __repr__(self):
+ return '<git.Tree "%s">' % self.id
+
+ @classmethod
+ def _iter_recursive(cls, repo, tree, cur_depth, max_depth, predicate ):
+
+ for obj in tree:
+ # adjust path to be complete
+ obj.path = os.path.join(tree.path, obj.path)
+ if not predicate(obj):
+ continue
+ yield obj
+ if obj.type == "tree" and ( max_depth < 0 or cur_depth+1 <= max_depth ):
+ for recursive_obj in cls._iter_recursive( repo, obj, cur_depth+1, max_depth, predicate ):
+ yield recursive_obj
+ # END for each recursive object
+ # END if we may enter recursion
+ # END for each object
+
+ def traverse(self, max_depth=-1, predicate = lambda i: True):
+ """
+ Returns
+ Iterator to traverse the tree recursively up to the given level.
+ The iterator returns Blob and Tree objects
+
+ ``max_depth``
+
+ if -1, the whole tree will be traversed
+ if 0, only the first level will be traversed which is the same as
+ the default non-recursive iterator
+
+ ``predicate``
+
+ If predicate(item) returns True, item will be returned by iterator
+ """
+ return self._iter_recursive( self.repo, self, 0, max_depth, predicate )
+
+ @property
+ def trees(self):
+ """
+ Returns
+ list(Tree, ...) list of trees directly below this tree
+ """
+ return [ i for i in self if i.type == "tree" ]
+
+ @property
+ def blobs(self):
+ """
+ Returns
+ list(Blob, ...) list of blobs directly below this tree
+ """
+ return [ i for i in self if i.type == "blob" ]
+
+
+ # List protocol
+ def __getslice__(self,i,j):
+ return self._cache[i:j]
+
+ def __iter__(self):
+ return iter(self._cache)
+
+ def __len__(self):
+ return len(self._cache)
+
+ def __getitem__(self,item):
+ if isinstance(item, int):
+ return self._cache[item]
+
+ if isinstance(item, basestring):
+ # compatability
+ for obj in self._cache:
+ if obj.path == item:
+ return obj
+ # END for each obj
+ raise KeyError( "Blob or Tree named %s not found" % item )
+ # END index is basestring
+
+ raise TypeError( "Invalid index type: %r" % item )
+
+
+ def __contains__(self,item):
+ if isinstance(item, base.IndexObject):
+ return item in self._cache
+
+ # compatability
+ for obj in self._cache:
+ if item == obj.path:
+ return True
+ # END for each item
+ return False
+
+ def __reversed__(self):
+ return reversed(self._cache)
diff --git a/lib/git/objects/utils.py b/lib/git/objects/utils.py
new file mode 100644
index 00000000..15c1d114
--- /dev/null
+++ b/lib/git/objects/utils.py
@@ -0,0 +1,36 @@
+# util.py
+# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
+#
+# This module is part of GitPython and is released under
+# the BSD License: http://www.opensource.org/licenses/bsd-license.php
+"""
+Module for general utility functions
+"""
+import commit, tag, blob, tree
+
+def get_object_type_by_name(object_type_name):
+ """
+ Returns
+ type suitable to handle the given object type name.
+ Use the type to create new instances.
+
+ ``object_type_name``
+ Member of TYPES
+
+ Raises
+ ValueError: In case object_type_name is unknown
+ """
+ if object_type_name == "commit":
+ import commit
+ return commit.Commit
+ elif object_type_name == "tag":
+ import tag
+ return tag.TagObject
+ elif object_type_name == "blob":
+ import blob
+ return blob.Blob
+ elif object_type_name == "tree":
+ import tree
+ return tree.Tree
+ else:
+ raise ValueError("Cannot handle unknown object type: %s" % object_type_name)