diff options
author | Sebastian Thiel <byronimo@gmail.com> | 2011-07-07 23:37:19 +0200 |
---|---|---|
committer | Sebastian Thiel <byronimo@gmail.com> | 2011-07-07 23:37:19 +0200 |
commit | 023dc1244c02d415bb964eeb0b51b257523897df (patch) | |
tree | 2ebffe75f484d48a8fdc2c282a457cf77a86c3cf | |
parent | 2baf8a493618463d2bb41b8e96c8304bf48e2c8a (diff) | |
parent | f4f330f8588dacd43af6513e1e1e1a50237da1e7 (diff) | |
download | gitpython-023dc1244c02d415bb964eeb0b51b257523897df.tar.gz |
Merge branch 'dulwich'
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | git/db/compat.py | 24 | ||||
-rw-r--r-- | git/db/dulwich/__init__.py | 13 | ||||
-rw-r--r-- | git/db/dulwich/complex.py | 90 | ||||
-rw-r--r-- | git/db/interface.py | 12 | ||||
-rw-r--r-- | git/db/py/base.py | 65 | ||||
-rw-r--r-- | git/db/py/complex.py | 16 | ||||
-rw-r--r-- | git/db/py/resolve.py | 4 | ||||
-rw-r--r-- | git/test/db/base.py | 14 | ||||
-rw-r--r-- | git/test/db/dulwich/__init__.py | 4 | ||||
-rw-r--r-- | git/test/db/dulwich/lib.py | 23 | ||||
-rw-r--r-- | git/test/db/dulwich/test_base.py | 33 | ||||
-rw-r--r-- | git/test/db/lib.py | 3 | ||||
-rw-r--r-- | git/test/lib/helper.py | 76 | ||||
-rw-r--r-- | git/test/objects/test_submodule.py | 4 | ||||
-rw-r--r-- | git/test/performance/db/test_looseodb_dulwich.py | 6 | ||||
-rw-r--r-- | git/test/performance/db/test_odb_dulwich.py | 6 | ||||
-rw-r--r-- | git/test/test_remote.py | 8 |
18 files changed, 327 insertions, 75 deletions
@@ -6,3 +6,4 @@ /dist /doc/_build nbproject +.nosebazinga diff --git a/git/db/compat.py b/git/db/compat.py index 767ab5e0..771a1e77 100644 --- a/git/db/compat.py +++ b/git/db/compat.py @@ -4,14 +4,10 @@ # the New BSD License: http://www.opensource.org/licenses/bsd-license.php """Module providing adaptors to maintain backwards compatability""" -class RepoCompatibilityInterface(object): +class RepoCompatibilityInterfaceNoBare(object): """Interface to install backwards compatability of the new complex repository types with the previous, all in one, repository.""" - @property - def bare(self): - return self.is_bare - def rev_parse(self, *args, **kwargs): return self.resolve_object(*args, **kwargs) @@ -28,4 +24,22 @@ class RepoCompatibilityInterface(object): return self.head.reference def __repr__(self): + """Return the representation of the repository, the way it used to be""" return '<git.Repo "%s">' % self.git_dir + + @property + def branches(self): + return self.heads + + +class RepoCompatibilityInterface(RepoCompatibilityInterfaceNoBare): + """Interface to install backwards compatability of the new complex repository + types with the previous, all in one, repository.""" + + @property + def bare(self): + return self.is_bare + + @property + def refs(self): + return self.references diff --git a/git/db/dulwich/__init__.py b/git/db/dulwich/__init__.py new file mode 100644 index 00000000..92d30941 --- /dev/null +++ b/git/db/dulwich/__init__.py @@ -0,0 +1,13 @@ +"""Dulwich module initialization""" + +def init_dulwich(): + """:raise ImportError: if dulwich is not present""" + try: + import dulwich + except ImportError: + raise ImportError("Could not find 'dulwich' in the PYTHONPATH - dulwich functionality is not available") + #END handle dulwich import + + + +init_dulwich() diff --git a/git/db/dulwich/complex.py b/git/db/dulwich/complex.py new file mode 100644 index 00000000..ad5b97a4 --- /dev/null +++ b/git/db/dulwich/complex.py @@ -0,0 +1,90 @@ + +__all__ = ['DulwichGitODB', 'DulwichGitDB', 'DulwichCompatibilityGitDB'] + +from git.db.py.complex import PureGitODB +from git.db.py.base import ( + PureRepositoryPathsMixin, + PureConfigurationMixin, + PureIndexDB, + ) +from git.db.py.resolve import PureReferencesMixin +from git.db.py.transport import PureTransportDB +from git.db.py.submodule import PureSubmoduleDB + +from git.db.cmd.complex import CmdHighLevelRepository, GitCommandMixin +from git.db.compat import RepoCompatibilityInterfaceNoBare + +#from git.db.interface import ObjectDBW, ObjectDBR +from dulwich.repo import Repo as DulwichRepo +from dulwich.objects import ShaFile + +from git.base import OInfo, OStream +from git.fun import type_id_to_type_map, type_to_type_id_map + +from cStringIO import StringIO +import os + + +class DulwichGitODB(PureGitODB): + """A full fledged database to read and write object files from all kinds of sources.""" + + def __init__(self, objects_root): + """Initalize this instance""" + PureGitODB.__init__(self, objects_root) + if hasattr(self, 'working_dir'): + wd = self.working_dir + else: + wd = os.path.dirname(os.path.dirname(objects_root)) + #END try to figure out good entry for dulwich, which doesn't do an extensive search + self._dw_repo = DulwichRepo(wd) + + def __getattr__(self, attr): + try: + # supply LazyMixin with this call first + return super(DulwichGitODB, self).__getattr__(attr) + except AttributeError: + # now assume its on the dulwich repository ... for now + return getattr(self._dw_repo, attr) + #END handle attr + + #{ Object DBR + + def info(self, binsha): + type_id, uncomp_data = self._dw_repo.object_store.get_raw(binsha) + return OInfo(binsha, type_id_to_type_map[type_id], len(uncomp_data)) + + def stream(self, binsha): + type_id, uncomp_data = self._dw_repo.object_store.get_raw(binsha) + return OStream(binsha, type_id_to_type_map[type_id], len(uncomp_data), StringIO(uncomp_data)) + + #}END object dbr + + #{ Object DBW + + def store(self, istream): + obj = ShaFile.from_raw_string(type_to_type_id_map[istream.type], istream.read()) + self._dw_repo.object_store.add_object(obj) + istream.binsha = obj.sha().digest() + return istream + + #}END object dbw + +class DulwichGitDB( PureRepositoryPathsMixin, PureConfigurationMixin, + PureReferencesMixin, PureSubmoduleDB, + PureIndexDB, + PureTransportDB, # not fully implemented + GitCommandMixin, + CmdHighLevelRepository, + DulwichGitODB): # must come last, as it doesn't pass on __init__ with super + + + def __init__(self, root_path): + """Initialize ourselves on the .git directory, or the .git/objects directory.""" + PureRepositoryPathsMixin._initialize(self, root_path) + super(DulwichGitDB, self).__init__(self.objects_dir) + + +class DulwichCompatibilityGitDB(RepoCompatibilityInterfaceNoBare, DulwichGitDB): + """Basic dulwich compatibility database""" + pass + diff --git a/git/db/interface.py b/git/db/interface.py index 803f7769..9ad74cc1 100644 --- a/git/db/interface.py +++ b/git/db/interface.py @@ -561,16 +561,8 @@ class ReferencesMixin(object): raise NotImplementedError() #}END edit methods - - #{ Backward Compatability - # These aliases need to be provided by the implementing interface as well - refs = references - branches = heads - #} END backward compatability - - - - + + class RepositoryPathsMixin(object): """Represents basic functionality of a full git repository. This involves an optional working tree, a git directory with references and an object directory. diff --git a/git/db/py/base.py b/git/db/py/base.py index 2c21c136..49f28a8d 100644 --- a/git/db/py/base.py +++ b/git/db/py/base.py @@ -104,7 +104,6 @@ class PureRootPathDB(RootPathDB): super(PureRootPathDB, self).__init__(root_path) - #{ Interface def root_path(self): return self._root_path @@ -132,44 +131,33 @@ class PureCompoundDB(CompoundDB, PureObjectDBR, LazyMixin, CachingDB): def _set_cache_(self, attr): if attr == '_dbs': self._dbs = list() - elif attr == '_obj_cache': - self._obj_cache = dict() else: super(PureCompoundDB, self)._set_cache_(attr) - def _db_query(self, sha): - """:return: database containing the given 20 byte sha - :raise BadObject:""" - # most databases use binary representations, prevent converting - # it everytime a database is being queried - try: - return self._obj_cache[sha] - except KeyError: - pass - # END first level cache - - for db in self._dbs: - if db.has_object(sha): - self._obj_cache[sha] = db - return db - # END for each database - raise BadObject(sha) - #{ PureObjectDBR interface def has_object(self, sha): - try: - self._db_query(sha) - return True - except BadObject: - return False - # END handle exceptions + for db in self._dbs: + if db.has_object(sha): + return True + #END for each db + return False def info(self, sha): - return self._db_query(sha).info(sha) + for db in self._dbs: + try: + return db.info(sha) + except BadObject: + pass + #END for each db def stream(self, sha): - return self._db_query(sha).stream(sha) + for db in self._dbs: + try: + return db.stream(sha) + except BadObject: + pass + #END for each db def size(self): return reduce(lambda x,y: x+y, (db.size() for db in self._dbs), 0) @@ -186,7 +174,6 @@ class PureCompoundDB(CompoundDB, PureObjectDBR, LazyMixin, CachingDB): def update_cache(self, force=False): # something might have changed, clear everything - self._obj_cache.clear() stat = False for db in self._dbs: if isinstance(db, CachingDB): @@ -233,7 +220,7 @@ class PureCompoundDB(CompoundDB, PureObjectDBR, LazyMixin, CachingDB): class PureRepositoryPathsMixin(RepositoryPathsMixin): # slots has no effect here, its just to keep track of used attrs - __slots__ = ("_git_path", '_bare') + __slots__ = ("_git_path", '_bare', '_working_tree_dir') #{ Configuration repo_dir = '.git' @@ -272,14 +259,16 @@ class PureRepositoryPathsMixin(RepositoryPathsMixin): raise InvalidGitRepositoryError(epath) # END path not found - self._bare = self._git_path.endswith(self.repo_dir) + self._bare = self._working_tree_dir is None if hasattr(self, 'config_reader'): try: self._bare = self.config_reader("repository").getboolean('core','bare') except Exception: # lets not assume the option exists, although it should pass + #END handle exception #END check bare flag + self._working_tree_dir = self._bare and None or self._working_tree_dir #} end subclass interface @@ -313,7 +302,7 @@ class PureRepositoryPathsMixin(RepositoryPathsMixin): @property def working_tree_dir(self): - if self.is_bare: + if self._working_tree_dir is None: raise AssertionError("Repository at %s is bare and does not have a working tree directory" % self.git_dir) #END assertion return dirname(self.git_dir) @@ -354,6 +343,10 @@ class PureConfigurationMixin(ConfigurationMixin): repo_config_file_name = "config" #} END + def __new__(cls, *args, **kwargs): + """This is just a stupid workaround for the evil py2.6 change which makes mixins quite impossible""" + return super(PureConfigurationMixin, cls).__new__(cls, *args, **kwargs) + def __init__(self, *args, **kwargs): """Verify prereqs""" try: @@ -421,7 +414,11 @@ class PureAlternatesFileMixin(object): #} END configuration def __init__(self, *args, **kwargs): - super(PureAlternatesFileMixin, self).__init__(*args, **kwargs) + try: + super(PureAlternatesFileMixin, self).__init__(*args, **kwargs) + except TypeError: + pass + #END handle py2.6 code breaking changes self._alternates_path() # throws on incompatible type #{ Interface diff --git a/git/db/py/complex.py b/git/db/py/complex.py index d5c185f3..5f4e81e0 100644 --- a/git/db/py/complex.py +++ b/git/db/py/complex.py @@ -22,17 +22,7 @@ from submodule import PureSubmoduleDB from git.db.compat import RepoCompatibilityInterface -from git.util import ( - LazyMixin, - normpath, - join, - dirname - ) -from git.exc import ( - InvalidDBRoot, - BadObject, - AmbiguousObjectName - ) +from git.exc import InvalidDBRoot import os __all__ = ('PureGitODB', 'PurePartialGitDB', 'PureCompatibilityGitDB') @@ -106,7 +96,8 @@ class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB, PureAlternatesFi class PurePartialGitDB(PureGitODB, PureRepositoryPathsMixin, PureConfigurationMixin, PureReferencesMixin, PureSubmoduleDB, - PureIndexDB, PureTransportDB + PureIndexDB, + PureTransportDB # not fully implemented # HighLevelRepository Currently not implemented ! ): """Git like database with support for object lookup as well as reference resolution. @@ -122,7 +113,6 @@ class PurePartialGitDB(PureGitODB, super(PurePartialGitDB, self).__init__(self.objects_dir) - class PureCompatibilityGitDB(PurePartialGitDB, RepoCompatibilityInterface): """Pure git database with a compatability layer required by 0.3x code""" diff --git a/git/db/py/resolve.py b/git/db/py/resolve.py index 7bea779e..9a31fbd8 100644 --- a/git/db/py/resolve.py +++ b/git/db/py/resolve.py @@ -361,7 +361,3 @@ class PureReferencesMixin(ReferencesMixin): def delete_tag(self, *tags): return self.TagReferenceCls.delete(self, *tags) - - # compat - branches = heads - refs = references diff --git a/git/test/db/base.py b/git/test/db/base.py index 5291ba03..2f8f50e2 100644 --- a/git/test/db/base.py +++ b/git/test/db/base.py @@ -601,20 +601,26 @@ class RepoBase(TestDBBase): self.failUnlessRaises(NotImplementedError, rev_parse, "@{1 week ago}") def test_submodules(self): - assert len(self.rorepo.submodules) == 1 # non-recursive + assert len(self.rorepo.submodules) == 2 # non-recursive # in previous configurations, we had recursive repositories so this would compare to 2 - # now there is only one left, as gitdb was merged - assert len(list(self.rorepo.iter_submodules())) == 1 + # now there is only one left, as gitdb was merged, but we have smmap instead + assert len(list(self.rorepo.iter_submodules())) == 2 - assert isinstance(self.rorepo.submodule("git/ext/async"), Submodule) + assert isinstance(self.rorepo.submodule("async"), Submodule) self.failUnlessRaises(ValueError, self.rorepo.submodule, "doesn't exist") @with_rw_repo('HEAD', bare=False) def test_submodule_update(self, rwrepo): # fails in bare mode rwrepo._bare = True + # special handling: there are repo implementations which have a bare attribute. IN that case, set it directly + if not rwrepo.bare: + rwrepo.bare = True self.failUnlessRaises(InvalidGitRepositoryError, rwrepo.submodule_update) rwrepo._bare = False + if rwrepo.bare: + rwrepo.bare = False + #END special repo handling # test create submodule sm = rwrepo.submodules[0] diff --git a/git/test/db/dulwich/__init__.py b/git/test/db/dulwich/__init__.py new file mode 100644 index 00000000..8a681e42 --- /dev/null +++ b/git/test/db/dulwich/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2010, 2011 Sebastian Thiel (byronimo@gmail.com) and contributors +# +# This module is part of GitDB and is released under +# the New BSD License: http://www.opensource.org/licenses/bsd-license.php diff --git a/git/test/db/dulwich/lib.py b/git/test/db/dulwich/lib.py new file mode 100644 index 00000000..56734064 --- /dev/null +++ b/git/test/db/dulwich/lib.py @@ -0,0 +1,23 @@ +"""dulwich specific utilities, as well as all the default ones""" + +from git.test.lib import ( + InheritedTestMethodsOverrideWrapperMetaClsAutoMixin, + needs_module_or_skip + ) + +__all__ = ['needs_dulwich_or_skip', 'DulwichRequiredMetaMixin'] + +#{ Decoorators + +def needs_dulwich_or_skip(func): + """Skip this test if we have no dulwich - print warning""" + return needs_module_or_skip('dulwich')(func) + +#}END decorators + +#{ MetaClasses + +class DulwichRequiredMetaMixin(InheritedTestMethodsOverrideWrapperMetaClsAutoMixin): + decorator = [needs_dulwich_or_skip] + +#} END metaclasses diff --git a/git/test/db/dulwich/test_base.py b/git/test/db/dulwich/test_base.py new file mode 100644 index 00000000..50e64131 --- /dev/null +++ b/git/test/db/dulwich/test_base.py @@ -0,0 +1,33 @@ +# Copyright (C) 2010, 2011 Sebastian Thiel (byronimo@gmail.com) and contributors +# +# This module is part of GitDB and is released under +# the New BSD License: http://www.opensource.org/licenses/bsd-license.php +from lib import * +from git.test.lib import TestBase, with_rw_repo +from git.test.db.base import RepoBase + + + +try: + import dulwich +except ImportError: + # om this case, all other dulwich tests will be skipped + # Need to properly initialize the class though, otherwise it would fail + from git.db.complex import PureCompatibilityGitDB as DulwichDB +else: + # now we know dulwich is available, to do futher imports + from git.db.dulwich.complex import DulwichCompatibilityGitDB as DulwichDB + +#END handle imports + +class TestPyDBBase(RepoBase): + __metaclass__ = DulwichRequiredMetaMixin + RepoCls = DulwichDB + + @needs_dulwich_or_skip + @with_rw_repo('HEAD', bare=False) + def test_basics(self, rw_repo): + db = DulwichDB(rw_repo.working_tree_dir) + print db.git_dir + + diff --git a/git/test/db/lib.py b/git/test/db/lib.py index 499ca252..2b3ddde5 100644 --- a/git/test/db/lib.py +++ b/git/test/db/lib.py @@ -70,7 +70,8 @@ class TestDBBase(TestBase): each test type has its own repository """ if cls.needs_ro_repo: - assert cls.RepoCls is not None, "RepoCls class member must be set" + if cls is not TestDBBase: + assert cls.RepoCls is not None, "RepoCls class member must be set in %s" % cls cls.rorepo = cls.RepoCls(rorepo_dir()) #END handle rorepo diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index d9a92a52..2045f9d3 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -12,6 +12,9 @@ import tempfile import shutil import cStringIO +import warnings +from nose import SkipTest + from base import ( maketemp, rorepo_dir @@ -19,8 +22,8 @@ from base import ( __all__ = ( - 'StringProcessAdapter', 'GlobalsItemDeletorMetaCls', - 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', + 'StringProcessAdapter', 'GlobalsItemDeletorMetaCls', 'InheritedTestMethodsOverrideWrapperMetaClsAutoMixin', + 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', 'needs_module_or_skip' ) @@ -191,6 +194,27 @@ def with_rw_and_rw_remote_repo(working_tree_ref): return argument_passer +def needs_module_or_skip(module): + """Decorator to be used for test cases only. + Print a warning if the given module could not be imported, and skip the test. + Otherwise run the test as usual + :param module: the name of the module to skip""" + def argpasser(func): + def wrapper(self, *args, **kwargs): + try: + __import__(module) + except ImportError: + msg = "Module %r is required to run this test - skipping" % module + warnings.warn(msg) + raise SkipTest(msg) + #END check import + return func(self, *args, **kwargs) + #END wrapper + wrapper.__name__ = func.__name__ + return wrapper + #END argpasser + return argpasser + #} END decorators #{ Meta Classes @@ -207,10 +231,56 @@ class GlobalsItemDeletorMetaCls(type): new_type = super(GlobalsItemDeletorMetaCls, metacls).__new__(metacls, name, bases, clsdict) if name != metacls.ModuleToDelete: mod = __import__(new_type.__module__, globals(), locals(), new_type.__module__) - delattr(mod, metacls.ModuleToDelete) + try: + delattr(mod, metacls.ModuleToDelete) + except AttributeError: + pass + #END skip case that people import our base without actually using it #END handle deletion return new_type + +class InheritedTestMethodsOverrideWrapperMetaClsAutoMixin(object): + """Automatically picks up the actual metaclass of the the type to be created, + that is the one inherited by one of the bases, and patch up its __new__ to use + the InheritedTestMethodsOverrideWrapperInstanceDecorator with our configured decorator""" + + #{ Configuration + # decorator function to use when wrapping the inherited methods. Put it into a list as first member + # to hide it from being created as class method + decorator = [] + #}END configuration + + @classmethod + def _find_metacls(metacls, bases): + """emulate pythons lookup""" + mcls_attr = '__metaclass__' + for base in bases: + if hasattr(base, mcls_attr): + return getattr(base, mcls_attr) + return metacls._find_metacls(base.__bases__) + #END for each base + raise AssertionError("base class had not metaclass attached") + + @classmethod + def _patch_methods_recursive(metacls, bases, clsdict): + """depth-first patching of methods""" + for base in bases: + metacls._patch_methods_recursive(base.__bases__, clsdict) + for name, item in base.__dict__.iteritems(): + if not name.startswith('test_'): + continue + #END skip non-tests + clsdict[name] = metacls.decorator[0](item) + #END for each item + #END for each base + + def __new__(metacls, name, bases, clsdict): + assert metacls.decorator, "'decorator' member needs to be set in subclass" + base_metacls = metacls._find_metacls(bases) + metacls._patch_methods_recursive(bases, clsdict) + return base_metacls.__new__(base_metacls, name, bases, clsdict) + #} END meta classes class TestBase(TestCase): diff --git a/git/test/objects/test_submodule.py b/git/test/objects/test_submodule.py index e5adedbc..2b7c7f40 100644 --- a/git/test/objects/test_submodule.py +++ b/git/test/objects/test_submodule.py @@ -284,7 +284,9 @@ class TestSubmodule(TestObjectBase): # make sure sub-submodule is not modified by forcing it to update # to the revision it is supposed to point to. - csm.update() + for subitem in sm.traverse(): + subitem.update() + #END checkout to right commit # this would work assert sm.remove(dry_run=True) is sm diff --git a/git/test/performance/db/test_looseodb_dulwich.py b/git/test/performance/db/test_looseodb_dulwich.py new file mode 100644 index 00000000..cf27a528 --- /dev/null +++ b/git/test/performance/db/test_looseodb_dulwich.py @@ -0,0 +1,6 @@ +from git.db.dulwich.complex import DulwichGitODB +from looseodb_impl import TestLooseDBWPerformanceBase + +class TestPureLooseDB(TestLooseDBWPerformanceBase): + LooseODBCls = DulwichGitODB + diff --git a/git/test/performance/db/test_odb_dulwich.py b/git/test/performance/db/test_odb_dulwich.py new file mode 100644 index 00000000..069c5b43 --- /dev/null +++ b/git/test/performance/db/test_odb_dulwich.py @@ -0,0 +1,6 @@ +from git.db.dulwich.complex import DulwichCompatibilityGitDB +from odb_impl import TestObjDBPerformanceBase + +class TestPureDB(TestObjDBPerformanceBase): + RepoCls = DulwichCompatibilityGitDB + diff --git a/git/test/test_remote.py b/git/test/test_remote.py index cef8687b..30bd1232 100644 --- a/git/test/test_remote.py +++ b/git/test/test_remote.py @@ -21,6 +21,8 @@ from git.refs import ( SymbolicReference ) +from nose import SkipTest + import tempfile import shutil import os @@ -352,7 +354,13 @@ class TestRemote(TestBase): # the same repository TagReference.delete(rw_repo, new_tag, other_tag) remote.push(":%s" % other_tag.path) + + def test_todo(self): + # If you see this, plesase remind yourself, that all this needs to be run + # per repository type ! + raise SkipTest("todo") + @with_rw_and_rw_remote_repo('0.1.6') def test_base(self, rw_repo, remote_repo): num_remotes = 0 |