From b377c07200392ac35a6ed668673451d3c9b1f5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Fr=C3=A9cinaux?= Date: Fri, 5 Sep 2008 21:51:14 +0200 Subject: Use a dictionnary for tree contents It seems more natural to use a dictionnary for directories, since we usually want to access them by name, and entry order is not relevant. Also, finding a particular blob given its name is O(1) instead of O(N). --- lib/git/tree.py | 11 +++++------ test/git/test_repo.py | 4 ++-- test/git/test_tree.py | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/git/tree.py b/lib/git/tree.py index f1aa0b3b..86e893ef 100644 --- a/lib/git/tree.py +++ b/lib/git/tree.py @@ -32,13 +32,13 @@ class Tree(LazyMixin): def construct_initialize(self, repo, id, text): self.repo = repo self.id = id - self.contents = [] + self.contents = {} self.__baked__ = False for line in text.splitlines(): - self.contents.append(self.content_from_string(self.repo, line)) - - self.contents = [c for c in self.contents if c is not None] + obj = self.content_from_string(self.repo, line) + if obj: + self.contents[obj.name] = obj self.__bake_it__() return self @@ -84,8 +84,7 @@ class Tree(LazyMixin): Returns ``GitPython.Blob`` or ``GitPython.Tree`` or ``None`` if not found """ - contents = [c for c in self.contents if c.name == file] - return contents and contents[0] or None + return self.contents.get(file) @property def basename(self): diff --git a/test/git/test_repo.py b/test/git/test_repo.py index 7550e1d6..c7a4c01b 100644 --- a/test/git/test_repo.py +++ b/test/git/test_repo.py @@ -96,8 +96,8 @@ class TestRepo(object): tree = self.repo.tree('master') - assert_equal(4, len([c for c in tree.contents if isinstance(c, Blob)])) - assert_equal(3, len([c for c in tree.contents if isinstance(c, Tree)])) + assert_equal(4, len([c for c in tree.contents.values() if isinstance(c, Blob)])) + assert_equal(3, len([c for c in tree.contents.values() if isinstance(c, Tree)])) assert_true(git.called) assert_equal(git.call_args, (('ls_tree', 'master'), {})) diff --git a/test/git/test_tree.py b/test/git/test_tree.py index 957b8962..d66764b3 100644 --- a/test/git/test_tree.py +++ b/test/git/test_tree.py @@ -18,7 +18,7 @@ class TestTree(object): tree = self.repo.tree('master') - child = tree.contents[-1] + child = tree.contents['grit'] child.contents child.contents -- cgit v1.2.1 From 43eb1edf93c381bf3f3809a809df33dae23b50d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Fr=C3=A9cinaux?= Date: Fri, 5 Sep 2008 23:04:58 +0200 Subject: Initialize trees completely in tree.__bake__(). This is a simplification of the tree baking code. As a matter of consequency, Tree.construct() and tree.construct_initialize() have been killed, and repo.tree() has lost the "paths" argument. This is not a problem since one can just have the same result with: dict(k, o for k, o in tree.items() if k in paths) --- lib/git/repo.py | 8 +++----- lib/git/tree.py | 22 ++++++---------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/lib/git/repo.py b/lib/git/repo.py index 0e52fab7..86057d8b 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -199,24 +199,22 @@ class Repo(object): diff_refs = list(set(other_repo_refs) - set(repo_refs)) return map(lambda ref: Commit.find_all(other_repo, ref, max_count=1)[0], diff_refs) - def tree(self, treeish = 'master', paths = []): + def tree(self, treeish = 'master'): """ The Tree object for the given treeish reference ``treeish`` is the reference (default 'master') - ``paths`` - is an optional Array of directory paths to restrict the tree (default []) Examples:: - repo.tree('master', ['lib/']) + repo.tree('master') Returns ``GitPython.Tree`` """ - return Tree.construct(self, treeish, paths) + return Tree(self, id=treeish) def blob(self, id): """ diff --git a/lib/git/tree.py b/lib/git/tree.py index 86e893ef..1ed3396d 100644 --- a/lib/git/tree.py +++ b/lib/git/tree.py @@ -21,28 +21,18 @@ class Tree(LazyMixin): setattr(self, k, v) def __bake__(self): - temp = Tree.construct(self.repo, self.id) - self.contents = temp.contents + # Ensure the treeish references directly a tree + treeish = self.id + if not treeish.endswith(':'): + treeish = treeish + ':' - @classmethod - def construct(cls, repo, treeish, paths = []): - output = repo.git.ls_tree(treeish, *paths) - return Tree(repo, id=treeish).construct_initialize(repo, treeish, output) - - def construct_initialize(self, repo, id, text): - self.repo = repo - self.id = id + # Read the tree contents. self.contents = {} - self.__baked__ = False - - for line in text.splitlines(): + for line in self.repo.git.ls_tree(self.id).splitlines(): obj = self.content_from_string(self.repo, line) if obj: self.contents[obj.name] = obj - self.__bake_it__() - return self - def content_from_string(self, repo, text): """ Parse a content item and create the appropriate object -- cgit v1.2.1 From 0425bc64384fe9a6a22edb7831d6e8c1756e2c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Fr=C3=A9cinaux?= Date: Sat, 6 Sep 2008 00:10:12 +0200 Subject: Implement dict protocol for trees. It is rather intuitive to consider trees as a dict of objects (like a directory could be seen as a dict of files). --- lib/git/tree.py | 36 ++++++++++++++++++++++++++++----- test/git/test_repo.py | 4 ++-- test/git/test_tree.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/lib/git/tree.py b/lib/git/tree.py index 1ed3396d..630fa1fc 100644 --- a/lib/git/tree.py +++ b/lib/git/tree.py @@ -15,7 +15,7 @@ class Tree(LazyMixin): self.id = None self.mode = None self.name = None - self.contents = None + self._contents = None for k, v in kwargs.items(): setattr(self, k, v) @@ -27,11 +27,11 @@ class Tree(LazyMixin): treeish = treeish + ':' # Read the tree contents. - self.contents = {} + self._contents = {} for line in self.repo.git.ls_tree(self.id).splitlines(): obj = self.content_from_string(self.repo, line) - if obj: - self.contents[obj.name] = obj + if obj is not None: + self._contents[obj.name] = obj def content_from_string(self, repo, text): """ @@ -74,7 +74,7 @@ class Tree(LazyMixin): Returns ``GitPython.Blob`` or ``GitPython.Tree`` or ``None`` if not found """ - return self.contents.get(file) + return self.get(file) @property def basename(self): @@ -82,3 +82,29 @@ class Tree(LazyMixin): def __repr__(self): return '' % 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, keys): + 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/test/git/test_repo.py b/test/git/test_repo.py index c7a4c01b..3c323878 100644 --- a/test/git/test_repo.py +++ b/test/git/test_repo.py @@ -96,8 +96,8 @@ class TestRepo(object): tree = self.repo.tree('master') - assert_equal(4, len([c for c in tree.contents.values() if isinstance(c, Blob)])) - assert_equal(3, len([c for c in tree.contents.values() if isinstance(c, Tree)])) + assert_equal(4, len([c for c in tree.values() if isinstance(c, Blob)])) + assert_equal(3, len([c for c in tree.values() if isinstance(c, Tree)])) assert_true(git.called) assert_equal(git.call_args, (('ls_tree', 'master'), {})) diff --git a/test/git/test_tree.py b/test/git/test_tree.py index d66764b3..6b62c958 100644 --- a/test/git/test_tree.py +++ b/test/git/test_tree.py @@ -18,9 +18,9 @@ class TestTree(object): tree = self.repo.tree('master') - child = tree.contents['grit'] - child.contents - child.contents + child = tree['grit'] + child.items() + child.items() assert_true(git.called) assert_equal(2, git.call_count) @@ -96,6 +96,55 @@ class TestTree(object): assert_true(git.called) assert_equal(git.call_args, (('ls_tree', 'master'), {})) + @patch(Blob, 'size') + @patch(Git, '_call_process') + def test_dict(self, blob, git): + git.return_value = fixture('ls_tree_a') + blob.return_value = 1 + + tree = self.repo.tree('master') + + assert_equal('aa06ba24b4e3f463b3c4a85469d0fb9e5b421cf8', tree['lib'].id) + assert_equal('8b1e02c0fb554eed2ce2ef737a68bb369d7527df', tree['README.txt'].id) + + assert_true(git.called) + assert_equal(git.call_args, (('ls_tree', 'master'), {})) + + @patch(Blob, 'size') + @patch(Git, '_call_process') + def test_dict_with_zero_length_file(self, blob, git): + git.return_value = fixture('ls_tree_a') + blob.return_value = 0 + + tree = self.repo.tree('master') + + assert_not_none(tree['README.txt']) + assert_equal('8b1e02c0fb554eed2ce2ef737a68bb369d7527df', tree['README.txt'].id) + + assert_true(git.called) + assert_equal(git.call_args, (('ls_tree', 'master'), {})) + + @patch(Git, '_call_process') + def test_dict_with_commits(self, git): + git.return_value = fixture('ls_tree_commit') + + tree = self.repo.tree('master') + + assert_none(tree.get('bar')) + assert_equal('2afb47bcedf21663580d5e6d2f406f08f3f65f19', tree['foo'].id) + assert_equal('f623ee576a09ca491c4a27e48c0dfe04be5f4a2e', tree['baz'].id) + + assert_true(git.called) + assert_equal(git.call_args, (('ls_tree', 'master'), {})) + + @patch(Git, '_call_process') + @raises(KeyError) + def test_dict_with_non_existant_file(self, git): + git.return_value = fixture('ls_tree_commit') + + tree = self.repo.tree('master') + tree['bar'] + def test_repr(self): self.tree = Tree(self.repo, **{'id': 'abc'}) assert_equal('', repr(self.tree)) -- cgit v1.2.1 From 2f6a6e35d003c243968cdb41b72fbbe609e56841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Fr=C3=A9cinaux?= Date: Sat, 6 Sep 2008 00:34:18 +0200 Subject: Make Tree.content_from_string a static method. It doesn't use an object's private contents, so let's go... --- lib/git/tree.py | 3 ++- test/git/test_tree.py | 13 ++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/git/tree.py b/lib/git/tree.py index 630fa1fc..59d3af1e 100644 --- a/lib/git/tree.py +++ b/lib/git/tree.py @@ -33,7 +33,8 @@ class Tree(LazyMixin): if obj is not None: self._contents[obj.name] = obj - def content_from_string(self, repo, text): + @staticmethod + def content_from_string(repo, text): """ Parse a content item and create the appropriate object diff --git a/test/git/test_tree.py b/test/git/test_tree.py index 6b62c958..34aa4d61 100644 --- a/test/git/test_tree.py +++ b/test/git/test_tree.py @@ -10,7 +10,6 @@ from git import * class TestTree(object): def setup(self): self.repo = Repo(GIT_REPO) - self.tree = Tree(self.repo) @patch(Git, '_call_process') def test_contents_should_cache(self, git): @@ -28,7 +27,7 @@ class TestTree(object): def test_content_from_string_tree_should_return_tree(self): text = fixture('ls_tree_a').splitlines()[-1] - tree = self.tree.content_from_string(None, text) + tree = Tree.content_from_string(None, text) assert_equal(Tree, tree.__class__) assert_equal("650fa3f0c17f1edb4ae53d8dcca4ac59d86e6c44", tree.id) @@ -38,7 +37,7 @@ class TestTree(object): def test_content_from_string_tree_should_return_blob(self): text = fixture('ls_tree_b').split("\n")[0] - tree = self.tree.content_from_string(None, text) + tree = Tree.content_from_string(None, text) assert_equal(Blob, tree.__class__) assert_equal("aa94e396335d2957ca92606f909e53e7beaf3fbb", tree.id) @@ -48,12 +47,12 @@ class TestTree(object): def test_content_from_string_tree_should_return_commit(self): text = fixture('ls_tree_commit').split("\n")[1] - tree = self.tree.content_from_string(None, text) + tree = Tree.content_from_string(None, text) assert_none(tree) @raises(TypeError) def test_content_from_string_invalid_type_should_raise(self): - self.tree.content_from_string(None, "040000 bogus 650fa3f0c17f1edb4ae53d8dcca4ac59d86e6c44 test") + Tree.content_from_string(None, "040000 bogus 650fa3f0c17f1edb4ae53d8dcca4ac59d86e6c44 test") @patch(Blob, 'size') @patch(Git, '_call_process') @@ -146,5 +145,5 @@ class TestTree(object): tree['bar'] def test_repr(self): - self.tree = Tree(self.repo, **{'id': 'abc'}) - assert_equal('', repr(self.tree)) + tree = Tree(self.repo, id='abc') + assert_equal('', repr(tree)) -- cgit v1.2.1 From c8c50d8be2dc5ae74e53e44a87f580bf25956af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Fr=C3=A9cinaux?= Date: Sat, 6 Sep 2008 00:35:04 +0200 Subject: Do not use **kwargs for constructors. It is better to have an explicit list of variables for the constructors, be it only to avoid mispelled arguments. --- lib/git/blob.py | 24 ++++++++++++++---------- lib/git/commit.py | 33 +++++++++++++++++---------------- lib/git/tree.py | 11 ++++------- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/lib/git/blob.py b/lib/git/blob.py index 0b4b19c2..80d237d7 100644 --- a/lib/git/blob.py +++ b/lib/git/blob.py @@ -14,29 +14,33 @@ from commit import Commit class Blob(object): DEFAULT_MIME_TYPE = "text/plain" - def __init__(self, repo, **kwargs): + def __init__(self, repo, id, mode=None, name=None): """ Create an unbaked Blob containing just the specified attributes ``repo`` is the Repo - ``atts`` - is a dict of instance variable data + ``id`` + is the git object id + + ``mode`` + is the file mode + + ``name`` + is the file name Returns GitPython.Blob """ - self.id = None - self.mode = None - self.name = None + self.repo = repo + self.id = id + self.mode = mode + self.name = name + self._size = None self.data_stored = None - self.repo = repo - for k, v in kwargs.items(): - setattr(self, k, v) - @property def size(self): """ diff --git a/lib/git/commit.py b/lib/git/commit.py index 13472deb..93a2b70f 100644 --- a/lib/git/commit.py +++ b/lib/git/commit.py @@ -9,12 +9,13 @@ import time from actor import Actor from lazy import LazyMixin -import tree +from tree import Tree import diff import stats class Commit(LazyMixin): - def __init__(self, repo, **kwargs): + 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 @@ -42,29 +43,29 @@ class Commit(LazyMixin): ``message`` is the first line of the commit message + ``parents`` + is the list of the parents of the commit + Returns GitPython.Commit """ LazyMixin.__init__(self) self.repo = repo - self.id = None - self.tree = None - self.author = None - self.authored_date = None - self.committer = None - self.committed_date = None - self.message = None + self.id = id self.parents = None - - for k, v in kwargs.items(): - setattr(self, k, v) + self.tree = None + self.author = author + self.authored_date = authored_date + self.committer = committer + self.committed_date = committed_date + self.message = message if self.id: - if 'parents' in kwargs: - self.parents = map(lambda p: Commit(repo, id=p), kwargs['parents']) - if 'tree' in kwargs: - self.tree = tree.Tree(repo, id=kwargs['tree']) + if parents is not None: + self.parents = [Commit(repo, p) for p in parents] + if tree is not None: + self.tree = Tree(repo, id=tree) def __bake__(self): temp = Commit.find_all(self.repo, self.id, max_count=1)[0] diff --git a/lib/git/tree.py b/lib/git/tree.py index 59d3af1e..dbd78ac4 100644 --- a/lib/git/tree.py +++ b/lib/git/tree.py @@ -9,17 +9,14 @@ from lazy import LazyMixin import blob class Tree(LazyMixin): - def __init__(self, repo, **kwargs): + def __init__(self, repo, id, mode=None, name=None): LazyMixin.__init__(self) self.repo = repo - self.id = None - self.mode = None - self.name = None + self.id = id + self.mode = mode + self.name = name self._contents = None - for k, v in kwargs.items(): - setattr(self, k, v) - def __bake__(self): # Ensure the treeish references directly a tree treeish = self.id -- cgit v1.2.1 From befb617d0c645a5fa3177a1b830a8c9871da4168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Fr=C3=A9cinaux?= Date: Sun, 7 Sep 2008 21:49:14 +0200 Subject: Make daemon export a property of git.Repo Now you can do this: >>> exported = repo.daemon_export >>> repo.daemon_export = True --- lib/git/repo.py | 35 ++++++++++++++++------------------- test/git/test_repo.py | 6 ++++-- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/git/repo.py b/lib/git/repo.py index 86057d8b..8cfc2cad 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -375,25 +375,22 @@ class Repo(object): kwargs['prefix'] = prefix self.git.archive(treeish, "| gzip", **kwargs) - def enable_daemon_serve(self): - """ - Enable git-daemon serving of this repository by writing the - git-daemon-export-ok file to its git directory - - Returns - None - """ - touch(os.path.join(self.path, DAEMON_EXPORT_FILE)) - - def disable_daemon_serve(self): - """ - Disable git-daemon serving of this repository by ensuring there is no - git-daemon-export-ok file in its git directory - - Returns - None - """ - return os.remove(os.path.join(self.path, DAEMON_EXPORT_FILE)) + def _get_daemon_export(self): + filename = os.path.join(self.path, self.DAEMON_EXPORT_FILE) + return os.path.exists(filename) + + def _set_daemon_export(self, value): + filename = os.path.join(self.path, self.DAEMON_EXPORT_FILE) + fileexists = os.path.exists(filename) + if value and not fileexists: + touch(filename) + elif not value and fileexists: + os.unlink(filename) + + daemon_export = property(_get_daemon_export, _set_daemon_export, + doc="git-daemon export of this repository") + del _get_daemon_export + del _set_daemon_export def _get_alternates(self): """ diff --git a/test/git/test_repo.py b/test/git/test_repo.py index 3c323878..036ae1e9 100644 --- a/test/git/test_repo.py +++ b/test/git/test_repo.py @@ -194,10 +194,12 @@ class TestRepo(object): @patch('git.utils', 'touch') def test_enable_daemon_serve(self, touch): - self.repo.enable_daemon_serve + self.repo.daemon_serve = False + assert_false(self.repo.daemon_serve) def test_disable_daemon_serve(self): - self.repo.disable_daemon_serve + self.repo.daemon_serve = True + assert_true(self.repo.daemon_serve) # @patch(os.path, 'exists') # @patch('__builtin__', 'open') -- cgit v1.2.1 From 5d3e2f7f57620398df896d9569c5d7c5365f823d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Fr=C3=A9cinaux?= Date: Sun, 7 Sep 2008 21:55:13 +0200 Subject: Allow modifying the project description Do this: >>> repo.description = "Foo Bar" >>> repo.description 'Foo Bar' --- lib/git/repo.py | 27 +++++++++++++-------------- test/git/test_repo.py | 4 +++- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/git/repo.py b/lib/git/repo.py index 8cfc2cad..aff595f1 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -62,20 +62,19 @@ class Repo(object): self.git = Git(self.wd) - @property - def description(self): - """ - The project's description. Taken verbatim from GIT_REPO/description - - Returns - str - """ - try: - f = open(os.path.join(self.path, 'description')) - result = f.read() - return result.rstrip() - finally: - f.close() + # Description property + def _get_description(self): + filename = os.path.join(self.path, 'description') + return file(filename).read().rstrip() + + def _set_description(self, descr): + filename = os.path.join(self.path, 'description') + file(filename, 'w').write(descr+'\n') + + description = property(_get_description, _set_description, + doc="the project's description") + del _get_description + del _set_description @property def heads(self): diff --git a/test/git/test_repo.py b/test/git/test_repo.py index 036ae1e9..29ba463b 100644 --- a/test/git/test_repo.py +++ b/test/git/test_repo.py @@ -25,7 +25,9 @@ class TestRepo(object): Repo("repos/foobar") def test_description(self): - assert_equal("Unnamed repository; edit this file to name it for gitweb.", self.repo.description) + txt = "Test repository" + self.repo.description = txt + assert_equal(self.repo.description, txt) def test_heads_should_return_array_of_head_objects(self): for head in self.repo.heads: -- cgit v1.2.1 From a2856af1d9289ee086b10768b53b65e0fd13a335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Fr=C3=A9cinaux?= Date: Mon, 8 Sep 2008 19:27:51 +0200 Subject: Update tutorial to demonstrate dict-like tree objects. --- doc/tutorial.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/tutorial.txt b/doc/tutorial.txt index 5ad56129..42e015b6 100644 --- a/doc/tutorial.txt +++ b/doc/tutorial.txt @@ -124,8 +124,8 @@ This tree contains three ``Blob`` objects and one ``Tree`` object. The trees are subdirectories and the blobs are files. Trees below the root have additional attributes. - >>> contents = tree.contents[-2] - + >>> contents = tree["lib"] + >>> contents.name 'test' @@ -134,7 +134,8 @@ additional attributes. '040000' There is a convenience method that allows you to get a named sub-object -from a tree. +from a tree with a syntax similar to how paths are written in an unix +system. >>> tree/"lib" -- cgit v1.2.1