summaryrefslogtreecommitdiff
path: root/lib/git/objects
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2009-10-12 11:50:14 +0200
committerSebastian Thiel <byronimo@gmail.com>2009-10-12 11:50:14 +0200
commitf2834177c0fdf6b1af659e460fd3348f468b8ab0 (patch)
tree2cb12187e664a026974383ed303cf307df2d4029 /lib/git/objects
parent3c0a65226f038c58fc6d6ed525f38fc00b3579b7 (diff)
downloadgitpython-f2834177c0fdf6b1af659e460fd3348f468b8ab0.tar.gz
Reorganized package structure and cleaned up imports
Diffstat (limited to 'lib/git/objects')
-rw-r--r--lib/git/objects/__init__.py11
-rw-r--r--lib/git/objects/base.py162
-rw-r--r--lib/git/objects/blob.py36
-rw-r--r--lib/git/objects/commit.py284
-rw-r--r--lib/git/objects/tag.py71
-rw-r--r--lib/git/objects/tree.py102
-rw-r--r--lib/git/objects/util.py36
7 files changed, 702 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..43aa8dd1
--- /dev/null
+++ b/lib/git/objects/base.py
@@ -0,0 +1,162 @@
+# 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
+
+class LazyMixin(object):
+ lazy_properties = []
+ __slots__ = tuple()
+
+ def __getattr__(self, attr):
+ """
+ Whenever an attribute is requested that we do not know, we allow it
+ to be created and set. Next time the same attribute is reqeusted, it is simply
+ returned from our dict/slots.
+ """
+ self._set_cache_(attr)
+ # will raise in case the cache was not created
+ return object.__getattribute__(self, attr)
+
+ def _set_cache_(self, attr):
+ """ This method should be overridden in the derived class.
+ It should check whether the attribute named by attr can be created
+ and cached. Do nothing if you do not know the attribute or call your subclass
+
+ The derived class may create as many additional attributes as it deems
+ necessary in case a git command returns more information than represented
+ in the single attribute."""
+ pass
+
+
+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":
+ self.size = int(self.repo.git.cat_file(self.id, s=True).rstrip())
+ elif attr == "data":
+ self.data = self.repo.git.cat_file(self.id, p=True, with_raw_output=True)
+
+ 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)
+
+ @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..c70b03e4
--- /dev/null
+++ b/lib/git/objects/commit.py
@@ -0,0 +1,284 @@
+# 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.actor import Actor
+from tree import Tree
+import git.diff as diff
+import git.stats as stats
+import base
+
+class Commit(base.Object):
+ """
+ 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
+
+ ``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)
+ # 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 self.__slots__:
+ temp = Commit.find_all(self.repo, self.id, max_count=1)[0]
+ 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 find_all(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 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
+ git.Commit[]
+ """
+ options = {'pretty': 'raw'}
+ options.update(kwargs)
+
+ output = repo.git.rev_list(ref, '--', path, **options)
+ return cls.list_from_string(repo, output)
+
+ @classmethod
+ def list_from_string(cls, repo, text):
+ """
+ Parse out commit information into a list of Commit objects
+
+ ``repo``
+ is the Repo
+
+ ``text``
+ is the text output from the git-rev-list command (raw format)
+
+ Returns
+ git.Commit[]
+ """
+ lines =text.splitlines(False)
+ commits = []
+
+ while lines:
+ id = lines.pop(0).split()[1]
+ tree = lines.pop(0).split()[1]
+
+ parents = []
+ while lines and lines[0].startswith('parent'):
+ parents.append(lines.pop(0).split()[-1])
+ # END while there are parent lines
+ author, authored_date = cls._actor(lines.pop(0))
+ committer, committed_date = cls._actor(lines.pop(0))
+
+ # free line
+ lines.pop(0)
+
+ message_lines = []
+ while lines and not lines[0].startswith('commit'):
+ message_lines.append(lines.pop(0).strip())
+ # END while there are message lines
+ message = '\n'.join(message_lines[:-1]) # last line is empty
+
+ commits.append(Commit(repo, id=id, parents=parents, tree=tree, author=author, authored_date=authored_date,
+ committer=committer, committed_date=committed_date, message=message))
+ # END while lines
+ return commits
+
+ @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..af1022f0
--- /dev/null
+++ b/lib/git/objects/tag.py
@@ -0,0 +1,71 @@
+# 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 util 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 self.__slots__:
+ output = self.repo.git.cat_file(self.type,self.id)
+ lines = output.split("\n")
+
+ 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..597668ae
--- /dev/null
+++ b/lib/git/objects/tree.py
@@ -0,0 +1,102 @@
+# 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
+
+class Tree(base.IndexObject):
+
+ type = "tree"
+ __slots__ = "_contents"
+
+ def __init__(self, repo, id, mode=None, path=None):
+ super(Tree, self).__init__(repo, id, mode, path)
+
+ def _set_cache_(self, attr):
+ if attr == "_contents":
+ # Read the tree contents.
+ self._contents = {}
+ for line in self.repo.git.ls_tree(self.id).splitlines():
+ obj = self.content_from_string(self.repo, line)
+ if obj is not None:
+ self._contents[obj.path] = obj
+ else:
+ super(Tree, self)._set_cache_(attr)
+
+ @staticmethod
+ def content_from_string(repo, text):
+ """
+ Parse a content item and create the appropriate object
+
+ ``repo``
+ is the Repo
+
+ ``text``
+ is the single line containing the items data in `git ls-tree` format
+
+ Returns
+ ``git.Blob`` or ``git.Tree``
+ """
+ try:
+ mode, typ, id, path = text.expandtabs(1).split(" ", 3)
+ except:
+ return None
+
+ if typ == "tree":
+ return Tree(repo, id, mode, path)
+ elif typ == "blob":
+ return blob.Blob(repo, id, mode, path)
+ elif typ == "commit":
+ return None
+ else:
+ raise(TypeError, "Invalid type: %s" % typ)
+
+ 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`` or ``None`` if not found
+ """
+ return self.get(file)
+
+
+ def __repr__(self):
+ return '<git.Tree "%s">' % self.id
+
+ # Implement the basics of the dict protocol:
+ # directories/trees can be seen as object dicts.
+ def __getitem__(self, key):
+ return self._contents[key]
+
+ def __iter__(self):
+ return iter(self._contents)
+
+ def __len__(self):
+ return len(self._contents)
+
+ def __contains__(self, key):
+ return key in self._contents
+
+ def get(self, key):
+ return self._contents.get(key)
+
+ def items(self):
+ return self._contents.items()
+
+ def keys(self):
+ return self._contents.keys()
+
+ def values(self):
+ return self._contents.values()
diff --git a/lib/git/objects/util.py b/lib/git/objects/util.py
new file mode 100644
index 00000000..15c1d114
--- /dev/null
+++ b/lib/git/objects/util.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)