summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES10
-rw-r--r--lib/git/__init__.py8
-rw-r--r--lib/git/blob.py126
-rw-r--r--lib/git/diff.py2
-rw-r--r--lib/git/head.py49
-rw-r--r--lib/git/objects/__init__.py11
-rw-r--r--lib/git/objects/base.py (renamed from lib/git/base.py)159
-rw-r--r--lib/git/objects/blob.py36
-rw-r--r--lib/git/objects/commit.py (renamed from lib/git/commit.py)6
-rw-r--r--lib/git/objects/tag.py (renamed from lib/git/tag.py)71
-rw-r--r--lib/git/objects/tree.py (renamed from lib/git/tree.py)0
-rw-r--r--lib/git/objects/util.py36
-rw-r--r--lib/git/refs.py241
-rw-r--r--lib/git/repo.py98
-rw-r--r--test/git/test_base.py12
-rw-r--r--test/git/test_blob.py28
-rw-r--r--test/git/test_repo.py27
-rw-r--r--test/git/test_tag.py2
18 files changed, 479 insertions, 443 deletions
diff --git a/CHANGES b/CHANGES
index 5efcfbf2..69eddacc 100644
--- a/CHANGES
+++ b/CHANGES
@@ -17,6 +17,16 @@ General
where unique
* removed basename method from Objects with path's as it replicated features of os.path
+objects Package
+----------------
+* blob, tree, tag and commit module have been moved to new objects package. This should
+ not affect you though unless you explicitly imported individual objects. If you just
+ used the git package, names did not change.
+
+Repo
+----
+* Moved blame method from Blob to repo as it appeared to belong there much more.
+
Diff
----
* Members a a_commit and b_commit renamed to a_blob and b_blob - they are populated
diff --git a/lib/git/__init__.py b/lib/git/__init__.py
index 5ce3c122..6f482128 100644
--- a/lib/git/__init__.py
+++ b/lib/git/__init__.py
@@ -9,19 +9,17 @@ import inspect
__version__ = 'git'
+from git.objects import *
+from git.refs import *
from git.actor import Actor
-from git.blob import Blob
-from git.commit import Commit
from git.diff import Diff
from git.errors import InvalidGitRepositoryError, NoSuchPathError, GitCommandError
from git.cmd import Git
-from git.head import Head
from git.repo import Repo
from git.stats import Stats
-from git.tag import Tag,TagRef,TagObject
-from git.tree import Tree
from git.utils import dashify
from git.utils import touch
+
__all__ = [ name for name, obj in locals().items()
if not (name.startswith('_') or inspect.ismodule(obj)) ]
diff --git a/lib/git/blob.py b/lib/git/blob.py
deleted file mode 100644
index 1fafb128..00000000
--- a/lib/git/blob.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# 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 os
-import re
-import time
-from actor import Actor
-from commit import Commit
-import base
-
-class Blob(base.IndexObject):
- """A Blob encapsulates a git blob object"""
- DEFAULT_MIME_TYPE = "text/plain"
- type = "blob"
-
- __slots__ = tuple()
-
- # precompiled regex
- re_whitespace = re.compile(r'\s+')
- re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
- re_author_committer_start = re.compile(r'^(author|committer)')
- re_tab_full_line = re.compile(r'^\t(.*)$')
-
- @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
-
- @classmethod
- def blame(cls, repo, commit, file):
- """
- The blame information for the given file at the given commit
-
- Returns
- list: [git.Commit, list: [<line>]]
- A list of tuples associating a Commit object with a list of lines that
- changed within the given commit. The Commit objects will be given in order
- of appearance.
- """
- data = repo.git.blame(commit, '--', file, p=True)
- commits = {}
- blames = []
- info = None
-
- for line in data.splitlines(False):
- parts = cls.re_whitespace.split(line, 1)
- firstpart = parts[0]
- if cls.re_hexsha_only.search(firstpart):
- # handles
- # 634396b2f541a9f2d58b00be1a07f0c358b999b3 1 1 7 - indicates blame-data start
- # 634396b2f541a9f2d58b00be1a07f0c358b999b3 2 2
- digits = parts[-1].split(" ")
- if len(digits) == 3:
- info = {'id': firstpart}
- blames.append([None, []])
- # END blame data initialization
- else:
- m = cls.re_author_committer_start.search(firstpart)
- if m:
- # handles:
- # author Tom Preston-Werner
- # author-mail <tom@mojombo.com>
- # author-time 1192271832
- # author-tz -0700
- # committer Tom Preston-Werner
- # committer-mail <tom@mojombo.com>
- # committer-time 1192271832
- # committer-tz -0700 - IGNORED BY US
- role = m.group(0)
- if firstpart.endswith('-mail'):
- info["%s_email" % role] = parts[-1]
- elif firstpart.endswith('-time'):
- info["%s_date" % role] = time.gmtime(int(parts[-1]))
- elif role == firstpart:
- info[role] = parts[-1]
- # END distinguish mail,time,name
- else:
- # handle
- # filename lib/grit.rb
- # summary add Blob
- # <and rest>
- if firstpart.startswith('filename'):
- info['filename'] = parts[-1]
- elif firstpart.startswith('summary'):
- info['summary'] = parts[-1]
- elif firstpart == '':
- if info:
- sha = info['id']
- c = commits.get(sha)
- if c is None:
- c = Commit( repo, id=sha,
- author=Actor.from_string(info['author'] + ' ' + info['author_email']),
- authored_date=info['author_date'],
- committer=Actor.from_string(info['committer'] + ' ' + info['committer_email']),
- committed_date=info['committer_date'],
- message=info['summary'])
- commits[sha] = c
- # END if commit objects needs initial creation
- m = cls.re_tab_full_line.search(line)
- text, = m.groups()
- blames[-1][0] = c
- blames[-1][1].append( text )
- info = None
- # END if we collected commit info
- # END distinguish filename,summary,rest
- # END distinguish author|committer vs filename,summary,rest
- # END distinguish hexsha vs other information
- return blames
-
- def __repr__(self):
- return '<git.Blob "%s">' % self.id
diff --git a/lib/git/diff.py b/lib/git/diff.py
index 943fb08a..4bc88bf4 100644
--- a/lib/git/diff.py
+++ b/lib/git/diff.py
@@ -5,7 +5,7 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import re
-import blob
+import objects.blob as blob
class Diff(object):
"""
diff --git a/lib/git/head.py b/lib/git/head.py
deleted file mode 100644
index 42dfd735..00000000
--- a/lib/git/head.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# head.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 commit
-import base
-
-class Head(base.Ref):
- """
- A Head is a named reference to a Commit. Every Head instance contains a name
- and a Commit object.
-
- Examples::
-
- >>> repo = Repo("/path/to/repo")
- >>> head = repo.heads[0]
-
- >>> head.name
- 'master'
-
- >>> head.commit
- <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
-
- >>> head.commit.id
- '1c09f116cbc2cb4100fb6935bb162daa4723f455'
- """
-
- @property
- def commit(self):
- """
- Returns
- Commit object the head points to
- """
- return self.object
-
- @classmethod
- def find_all(cls, repo, common_path = "refs/heads", **kwargs):
- """
- Returns
- git.Head[]
-
- For more documentation, please refer to git.base.Ref.find_all
- """
- return super(Head,cls).find_all(repo, common_path, **kwargs)
-
- def __repr__(self):
- return '<git.Head "%s">' % self.name
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/base.py b/lib/git/objects/base.py
index 252ebe4b..43aa8dd1 100644
--- a/lib/git/base.py
+++ b/lib/git/objects/base.py
@@ -109,36 +109,8 @@ class Object(LazyMixin):
string with pythonic representation of our object
"""
return '<git.%s "%s">' % (self.__class__.__name__, self.id)
-
- @classmethod
- def get_type_by_name(cls, 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)
-
-
+
+
class IndexObject(Object):
"""
Base for all objects that can be part of the index file , namely Tree, Blob and
@@ -188,130 +160,3 @@ class IndexObject(Object):
return mode
-class Ref(object):
- """
- Represents a named reference to any object
- """
- __slots__ = ("path", "object")
-
- def __init__(self, path, object = None):
- """
- Initialize this instance
-
- ``path``
- Path relative to the .git/ directory pointing to the ref in question, i.e.
- refs/heads/master
-
- ``object``
- Object instance, will be retrieved on demand if None
- """
- self.path = path
- self.object = object
-
- def __str__(self):
- return self.name
-
- def __repr__(self):
- return '<git.%s "%s">' % (self.__class__.__name__, self.path)
-
- def __eq__(self, other):
- return self.path == other.path and self.object == other.object
-
- def __ne__(self, other):
- return not ( self == other )
-
- def __hash__(self):
- return hash(self.path)
-
- @property
- def name(self):
- """
- Returns
- (shortest) Name of this reference - it may contain path components
- """
- # first two path tokens are can be removed as they are
- # refs/heads or refs/tags or refs/remotes
- tokens = self.path.split('/')
- if len(tokens) < 3:
- return self.path # could be refs/HEAD
-
- return '/'.join(tokens[2:])
-
- @classmethod
- def find_all(cls, repo, common_path = "refs", **kwargs):
- """
- Find all refs in the repository
-
- ``repo``
- is the Repo
-
- ``common_path``
- Optional keyword argument to the path which is to be shared by all
- returned Ref objects
-
- ``kwargs``
- Additional options given as keyword arguments, will be passed
- to git-for-each-ref
-
- Returns
- git.Ref[]
-
- List is sorted by committerdate
- The returned objects are compatible to the Ref base, but represent the
- actual type, such as Head or Tag
- """
-
- options = {'sort': "committerdate",
- 'format': "%(refname)%00%(objectname)%00%(objecttype)%00%(objectsize)"}
-
- options.update(kwargs)
-
- output = repo.git.for_each_ref(common_path, **options)
- return cls.list_from_string(repo, output)
-
- @classmethod
- def list_from_string(cls, repo, text):
- """
- Parse out ref information into a list of Ref compatible objects
-
- ``repo``
- is the Repo
- ``text``
- is the text output from the git-for-each-ref command
-
- Returns
- git.Ref[]
-
- list of Ref objects
- """
- heads = []
-
- for line in text.splitlines():
- heads.append(cls.from_string(repo, line))
-
- return heads
-
- @classmethod
- def from_string(cls, repo, line):
- """
- Create a new Ref instance from the given string.
-
- ``repo``
- is the Repo
-
- ``line``
- is the formatted ref information
-
- Format::
-
- name: [a-zA-Z_/]+
- <null byte>
- id: [0-9A-Fa-f]{40}
-
- Returns
- git.Head
- """
- full_path, hexsha, type_name, object_size = line.split("\x00")
- obj = Object.get_type_by_name(type_name)(repo, hexsha)
- obj.size = object_size
- return cls(full_path, obj)
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/commit.py b/lib/git/objects/commit.py
index 68415be5..c70b03e4 100644
--- a/lib/git/commit.py
+++ b/lib/git/objects/commit.py
@@ -7,10 +7,10 @@
import re
import time
-from actor import Actor
+from git.actor import Actor
from tree import Tree
-import diff
-import stats
+import git.diff as diff
+import git.stats as stats
import base
class Commit(base.Object):
diff --git a/lib/git/tag.py b/lib/git/objects/tag.py
index 89060ee0..af1022f0 100644
--- a/lib/git/tag.py
+++ b/lib/git/objects/tag.py
@@ -1,71 +1,15 @@
-# tag.py
+# 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
-
-import commit
+"""
+Module containing all object based types.
+"""
import base
+import commit
+from util import get_object_type_by_name
-class TagRef(base.Ref):
- """
- Class representing a lightweight tag reference which either points to a commit
- or to a tag object. In the latter case additional information, like the signature
- or the tag-creator, is available.
-
- This tag object will always point to a commit object, but may carray additional
- information in a tag object::
-
- tagref = TagRef.find_all(repo)[0]
- print tagref.commit.message
- if tagref.tag is not None:
- print tagref.tag.message
- """
-
- __slots__ = "tag"
-
- def __init__(self, path, commit_or_tag):
- """
- Initialize a newly instantiated Tag
-
- ``path``
- is the full path to the tag
-
- ``commit_or_tag``
- is the Commit or TagObject that this tag ref points to
- """
- super(TagRef, self).__init__(path, commit_or_tag)
- self.tag = None
-
- if commit_or_tag.type == "tag":
- self.tag = commit_or_tag
- # END tag object handling
-
- @property
- def commit(self):
- """
- Returns
- Commit object the tag ref points to
- """
- if self.object.type == "commit":
- return self.object
- # it is a tag object
- return self.object.object
-
- @classmethod
- def find_all(cls, repo, common_path = "refs/tags", **kwargs):
- """
- Returns
- git.Tag[]
-
- For more documentation, please refer to git.base.Ref.find_all
- """
- return super(TagRef,cls).find_all(repo, common_path, **kwargs)
-
-
-# provide an alias
-Tag = TagRef
-
class TagObject(base.Object):
"""
Non-Lightweight tag carrying additional information about an object we are pointing
@@ -110,7 +54,7 @@ class TagObject(base.Object):
obj, hexsha = lines[0].split(" ") # object <hexsha>
type_token, type_name = lines[1].split(" ") # type <type_name>
- self.object = base.Object.get_type_by_name(type_name)(self.repo, hexsha)
+ self.object = get_object_type_by_name(type_name)(self.repo, hexsha)
self.tag = lines[2][4:] # tag <tag name>
@@ -124,3 +68,4 @@ class TagObject(base.Object):
super(TagObject, self)._set_cache_(attr)
+
diff --git a/lib/git/tree.py b/lib/git/objects/tree.py
index 597668ae..597668ae 100644
--- a/lib/git/tree.py
+++ b/lib/git/objects/tree.py
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)
diff --git a/lib/git/refs.py b/lib/git/refs.py
new file mode 100644
index 00000000..cb730edb
--- /dev/null
+++ b/lib/git/refs.py
@@ -0,0 +1,241 @@
+# refs.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 ref based objects
+"""
+from objects.base import Object
+from objects.util import get_object_type_by_name
+
+class Ref(object):
+ """
+ Represents a named reference to any object
+ """
+ __slots__ = ("path", "object")
+
+ def __init__(self, path, object = None):
+ """
+ Initialize this instance
+
+ ``path``
+ Path relative to the .git/ directory pointing to the ref in question, i.e.
+ refs/heads/master
+
+ ``object``
+ Object instance, will be retrieved on demand if None
+ """
+ self.path = path
+ self.object = object
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return '<git.%s "%s">' % (self.__class__.__name__, self.path)
+
+ def __eq__(self, other):
+ return self.path == other.path and self.object == other.object
+
+ def __ne__(self, other):
+ return not ( self == other )
+
+ def __hash__(self):
+ return hash(self.path)
+
+ @property
+ def name(self):
+ """
+ Returns
+ (shortest) Name of this reference - it may contain path components
+ """
+ # first two path tokens are can be removed as they are
+ # refs/heads or refs/tags or refs/remotes
+ tokens = self.path.split('/')
+ if len(tokens) < 3:
+ return self.path # could be refs/HEAD
+
+ return '/'.join(tokens[2:])
+
+ @classmethod
+ def find_all(cls, repo, common_path = "refs", **kwargs):
+ """
+ Find all refs in the repository
+
+ ``repo``
+ is the Repo
+
+ ``common_path``
+ Optional keyword argument to the path which is to be shared by all
+ returned Ref objects
+
+ ``kwargs``
+ Additional options given as keyword arguments, will be passed
+ to git-for-each-ref
+
+ Returns
+ git.Ref[]
+
+ List is sorted by committerdate
+ The returned objects are compatible to the Ref base, but represent the
+ actual type, such as Head or Tag
+ """
+
+ options = {'sort': "committerdate",
+ 'format': "%(refname)%00%(objectname)%00%(objecttype)%00%(objectsize)"}
+
+ options.update(kwargs)
+
+ output = repo.git.for_each_ref(common_path, **options)
+ return cls.list_from_string(repo, output)
+
+ @classmethod
+ def list_from_string(cls, repo, text):
+ """
+ Parse out ref information into a list of Ref compatible objects
+
+ ``repo``
+ is the Repo
+ ``text``
+ is the text output from the git-for-each-ref command
+
+ Returns
+ git.Ref[]
+
+ list of Ref objects
+ """
+ heads = []
+
+ for line in text.splitlines():
+ heads.append(cls.from_string(repo, line))
+
+ return heads
+
+ @classmethod
+ def from_string(cls, repo, line):
+ """
+ Create a new Ref instance from the given string.
+
+ ``repo``
+ is the Repo
+
+ ``line``
+ is the formatted ref information
+
+ Format::
+
+ name: [a-zA-Z_/]+
+ <null byte>
+ id: [0-9A-Fa-f]{40}
+
+ Returns
+ git.Head
+ """
+ full_path, hexsha, type_name, object_size = line.split("\x00")
+ obj = get_object_type_by_name(type_name)(repo, hexsha)
+ obj.size = object_size
+ return cls(full_path, obj)
+
+
+class Head(Ref):
+ """
+ A Head is a named reference to a Commit. Every Head instance contains a name
+ and a Commit object.
+
+ Examples::
+
+ >>> repo = Repo("/path/to/repo")
+ >>> head = repo.heads[0]
+
+ >>> head.name
+ 'master'
+
+ >>> head.commit
+ <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
+
+ >>> head.commit.id
+ '1c09f116cbc2cb4100fb6935bb162daa4723f455'
+ """
+
+ @property
+ def commit(self):
+ """
+ Returns
+ Commit object the head points to
+ """
+ return self.object
+
+ @classmethod
+ def find_all(cls, repo, common_path = "refs/heads", **kwargs):
+ """
+ Returns
+ git.Head[]
+
+ For more documentation, please refer to git.base.Ref.find_all
+ """
+ return super(Head,cls).find_all(repo, common_path, **kwargs)
+
+ def __repr__(self):
+ return '<git.Head "%s">' % self.name
+
+
+
+class TagRef(Ref):
+ """
+ Class representing a lightweight tag reference which either points to a commit
+ or to a tag object. In the latter case additional information, like the signature
+ or the tag-creator, is available.
+
+ This tag object will always point to a commit object, but may carray additional
+ information in a tag object::
+
+ tagref = TagRef.find_all(repo)[0]
+ print tagref.commit.message
+ if tagref.tag is not None:
+ print tagref.tag.message
+ """
+
+ __slots__ = "tag"
+
+ def __init__(self, path, commit_or_tag):
+ """
+ Initialize a newly instantiated Tag
+
+ ``path``
+ is the full path to the tag
+
+ ``commit_or_tag``
+ is the Commit or TagObject that this tag ref points to
+ """
+ super(TagRef, self).__init__(path, commit_or_tag)
+ self.tag = None
+
+ if commit_or_tag.type == "tag":
+ self.tag = commit_or_tag
+ # END tag object handling
+
+ @property
+ def commit(self):
+ """
+ Returns
+ Commit object the tag ref points to
+ """
+ if self.object.type == "commit":
+ return self.object
+ # it is a tag object
+ return self.object.object
+
+ @classmethod
+ def find_all(cls, repo, common_path = "refs/tags", **kwargs):
+ """
+ Returns
+ git.Tag[]
+
+ For more documentation, please refer to git.base.Ref.find_all
+ """
+ return super(TagRef,cls).find_all(repo, common_path, **kwargs)
+
+
+# provide an alias
+Tag = TagRef
diff --git a/lib/git/repo.py b/lib/git/repo.py
index 3c872218..6e23dbc6 100644
--- a/lib/git/repo.py
+++ b/lib/git/repo.py
@@ -5,16 +5,18 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import os
+import re
import gzip
import StringIO
+import time
+
from errors import InvalidGitRepositoryError, NoSuchPathError
from utils import touch, is_git_dir
from cmd import Git
-from head import Head
-from blob import Blob
-from tag import Tag
-from commit import Commit
-from tree import Tree
+from actor import Actor
+from refs import *
+from objects import *
+
class Repo(object):
"""
@@ -23,6 +25,12 @@ class Repo(object):
the log.
"""
DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
+
+ # precompiled regex
+ re_whitespace = re.compile(r'\s+')
+ re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
+ re_author_committer_start = re.compile(r'^(author|committer)')
+ re_tab_full_line = re.compile(r'^\t(.*)$')
def __init__(self, path=None):
"""
@@ -108,6 +116,86 @@ class Repo(object):
``git.Tag[]``
"""
return Tag.find_all(self)
+
+ def blame(self, commit, file):
+ """
+ The blame information for the given file at the given commit
+
+ Returns
+ list: [git.Commit, list: [<line>]]
+ A list of tuples associating a Commit object with a list of lines that
+ changed within the given commit. The Commit objects will be given in order
+ of appearance.
+ """
+ data = self.git.blame(commit, '--', file, p=True)
+ commits = {}
+ blames = []
+ info = None
+
+ for line in data.splitlines(False):
+ parts = self.re_whitespace.split(line, 1)
+ firstpart = parts[0]
+ if self.re_hexsha_only.search(firstpart):
+ # handles
+ # 634396b2f541a9f2d58b00be1a07f0c358b999b3 1 1 7 - indicates blame-data start
+ # 634396b2f541a9f2d58b00be1a07f0c358b999b3 2 2
+ digits = parts[-1].split(" ")
+ if len(digits) == 3:
+ info = {'id': firstpart}
+ blames.append([None, []])
+ # END blame data initialization
+ else:
+ m = self.re_author_committer_start.search(firstpart)
+ if m:
+ # handles:
+ # author Tom Preston-Werner
+ # author-mail <tom@mojombo.com>
+ # author-time 1192271832
+ # author-tz -0700
+ # committer Tom Preston-Werner
+ # committer-mail <tom@mojombo.com>
+ # committer-time 1192271832
+ # committer-tz -0700 - IGNORED BY US
+ role = m.group(0)
+ if firstpart.endswith('-mail'):
+ info["%s_email" % role] = parts[-1]
+ elif firstpart.endswith('-time'):
+ info["%s_date" % role] = time.gmtime(int(parts[-1]))
+ elif role == firstpart:
+ info[role] = parts[-1]
+ # END distinguish mail,time,name
+ else:
+ # handle
+ # filename lib/grit.rb
+ # summary add Blob
+ # <and rest>
+ if firstpart.startswith('filename'):
+ info['filename'] = parts[-1]
+ elif firstpart.startswith('summary'):
+ info['summary'] = parts[-1]
+ elif firstpart == '':
+ if info:
+ sha = info['id']
+ c = commits.get(sha)
+ if c is None:
+ c = Commit( self, id=sha,
+ author=Actor.from_string(info['author'] + ' ' + info['author_email']),
+ authored_date=info['author_date'],
+ committer=Actor.from_string(info['committer'] + ' ' + info['committer_email']),
+ committed_date=info['committer_date'],
+ message=info['summary'])
+ commits[sha] = c
+ # END if commit objects needs initial creation
+ m = self.re_tab_full_line.search(line)
+ text, = m.groups()
+ blames[-1][0] = c
+ blames[-1][1].append( text )
+ info = None
+ # END if we collected commit info
+ # END distinguish filename,summary,rest
+ # END distinguish author|committer vs filename,summary,rest
+ # END distinguish hexsha vs other information
+ return blames
def commits(self, start='master', path='', max_count=10, skip=0):
"""
diff --git a/test/git/test_base.py b/test/git/test_base.py
index 8f522cec..a153eb83 100644
--- a/test/git/test_base.py
+++ b/test/git/test_base.py
@@ -7,8 +7,10 @@
import time
from test.testlib import *
from git import *
-import git.base as base
+import git.objects.base as base
+import git.refs as refs
from itertools import chain
+from git.objects.util import get_object_type_by_name
class TestBase(object):
@@ -60,7 +62,7 @@ class TestBase(object):
ref_count = 0
for ref in chain(self.repo.tags, self.repo.heads):
ref_count += 1
- assert isinstance(ref, base.Ref)
+ assert isinstance(ref, refs.Ref)
assert str(ref) == ref.name
assert repr(ref)
assert ref == ref
@@ -69,10 +71,10 @@ class TestBase(object):
# END for each ref
assert len(s) == ref_count
- def test_get_type_by_name(self):
+ def test_get_object_type_by_name(self):
for tname in base.Object.TYPES:
- assert base.Object in base.Object.get_type_by_name(tname).mro()
+ assert base.Object in get_object_type_by_name(tname).mro()
# END for each known type
- assert_raises( ValueError, base.Object.get_type_by_name, "doesntexist" )
+ assert_raises( ValueError, get_object_type_by_name, "doesntexist" )
diff --git a/test/git/test_blob.py b/test/git/test_blob.py
index 94d3a33b..ebb53d0c 100644
--- a/test/git/test_blob.py
+++ b/test/git/test_blob.py
@@ -65,34 +65,6 @@ class TestBlob(object):
blob = Blob(self.repo, **{'id': 'abc','path': 'something'})
assert_equal("text/plain", blob.mime_type)
- @patch_object(Git, '_call_process')
- def test_should_display_blame_information(self, git):
- git.return_value = fixture('blame')
- b = Blob.blame(self.repo, 'master', 'lib/git.py')
- assert_equal(13, len(b))
- assert_equal( 2, len(b[0]) )
- # assert_equal(25, reduce(lambda acc, x: acc + len(x[-1]), b))
- assert_equal(hash(b[0][0]), hash(b[9][0]))
- c = b[0][0]
- assert_true(git.called)
- assert_equal(git.call_args, (('blame', 'master', '--', 'lib/git.py'), {'p': True}))
-
- assert_equal('634396b2f541a9f2d58b00be1a07f0c358b999b3', c.id)
- assert_equal('Tom Preston-Werner', c.author.name)
- assert_equal('tom@mojombo.com', c.author.email)
- assert_equal(time.gmtime(1191997100), c.authored_date)
- assert_equal('Tom Preston-Werner', c.committer.name)
- assert_equal('tom@mojombo.com', c.committer.email)
- assert_equal(time.gmtime(1191997100), c.committed_date)
- assert_equal('initial grit setup', c.message)
-
- # test the 'lines per commit' entries
- tlist = b[0][1]
- assert_true( tlist )
- assert_true( isinstance( tlist[0], basestring ) )
- assert_true( len( tlist ) < sum( len(t) for t in tlist ) ) # test for single-char bug
-
-
def test_should_return_appropriate_representation(self):
blob = Blob(self.repo, **{'id': 'abc'})
assert_equal('<git.Blob "abc">', repr(blob))
diff --git a/test/git/test_repo.py b/test/git/test_repo.py
index 421b8256..3e2fb3dc 100644
--- a/test/git/test_repo.py
+++ b/test/git/test_repo.py
@@ -257,3 +257,30 @@ class TestRepo(object):
git.return_value = 'refs/heads/major-refactoring'
assert_equal(self.repo.active_branch, 'major-refactoring')
assert_equal(git.call_args, (('symbolic_ref', 'HEAD'), {}))
+
+ @patch_object(Git, '_call_process')
+ def test_should_display_blame_information(self, git):
+ git.return_value = fixture('blame')
+ b = self.repo.blame( 'master', 'lib/git.py')
+ assert_equal(13, len(b))
+ assert_equal( 2, len(b[0]) )
+ # assert_equal(25, reduce(lambda acc, x: acc + len(x[-1]), b))
+ assert_equal(hash(b[0][0]), hash(b[9][0]))
+ c = b[0][0]
+ assert_true(git.called)
+ assert_equal(git.call_args, (('blame', 'master', '--', 'lib/git.py'), {'p': True}))
+
+ assert_equal('634396b2f541a9f2d58b00be1a07f0c358b999b3', c.id)
+ assert_equal('Tom Preston-Werner', c.author.name)
+ assert_equal('tom@mojombo.com', c.author.email)
+ assert_equal(time.gmtime(1191997100), c.authored_date)
+ assert_equal('Tom Preston-Werner', c.committer.name)
+ assert_equal('tom@mojombo.com', c.committer.email)
+ assert_equal(time.gmtime(1191997100), c.committed_date)
+ assert_equal('initial grit setup', c.message)
+
+ # test the 'lines per commit' entries
+ tlist = b[0][1]
+ assert_true( tlist )
+ assert_true( isinstance( tlist[0], basestring ) )
+ assert_true( len( tlist ) < sum( len(t) for t in tlist ) ) # test for single-char bug
diff --git a/test/git/test_tag.py b/test/git/test_tag.py
index fe3f78cc..2ebb860a 100644
--- a/test/git/test_tag.py
+++ b/test/git/test_tag.py
@@ -7,7 +7,7 @@
from mock import *
from test.testlib import *
from git import *
-from git.tag import TagObject
+from git.objects.tag import TagObject
import time
class TestTag(object):