From 4177eefd7bdaea96a529b00ba9cf751924ede202 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 5 May 2011 19:43:22 +0200 Subject: Added all code from gitdb to gitpython. Next is to make it generally work. Then the tests will need some work --- git/base.py | 311 ++++++ git/config.py | 412 +++++++- git/db.py | 437 --------- git/db/__init__.py | 6 + git/db/cmd/__init__.py | 1 + git/db/cmd/git.py | 437 +++++++++ git/db/interface.py | 469 +++++++++ git/db/py/__init__.py | 13 + git/db/py/base.py | 351 +++++++ git/db/py/git.py | 113 +++ git/db/py/loose.py | 262 +++++ git/db/py/mem.py | 113 +++ git/db/py/pack.py | 212 +++++ git/db/py/ref.py | 77 ++ git/db/py/resolve.py | 297 ++++++ git/db/py/transport.py | 89 ++ git/exc.py | 36 +- git/fun.py | 674 +++++++++++++ git/objects/base.py | 173 +++- git/objects/blob.py | 27 +- git/objects/commit.py | 259 ++++- git/objects/fun.py | 199 +++- git/objects/submodule/base.py | 3 + git/objects/tag.py | 73 +- git/objects/tree.py | 282 +++++- git/objects/util.py | 1 + git/pack.py | 1005 ++++++++++++++++++++ git/refs/__init__.py | 7 +- git/refs/head.py | 112 +-- git/refs/headref.py | 170 ++++ git/refs/log.py | 281 +++++- git/refs/reference.py | 81 +- git/refs/remote.py | 41 +- git/refs/symbolic.py | 655 ++++++++++++- git/refs/tag.py | 38 + git/stream.py | 694 ++++++++++++++ git/test/__init__.py | 9 + git/test/db/__init__.py | 4 + git/test/db/lib.py | 215 +++++ git/test/db/test_base.py | 18 + git/test/db/test_git.py | 47 + git/test/db/test_loose.py | 34 + git/test/db/test_mem.py | 30 + git/test/db/test_pack.py | 72 ++ git/test/db/test_ref.py | 60 ++ .../7b/b839852ed5e3a069966281bb08d50012fb309b | Bin 0 -> 446 bytes ...ck-11fdfa9e156ab73caae3b6da867192221f2089c2.idx | Bin 0 -> 1912 bytes ...k-11fdfa9e156ab73caae3b6da867192221f2089c2.pack | Bin 0 -> 51875 bytes ...ck-a2bf8e71d8c18879e499335762dd95119d93d9f1.idx | Bin 0 -> 2248 bytes ...k-a2bf8e71d8c18879e499335762dd95119d93d9f1.pack | Bin 0 -> 3732 bytes ...ck-c0438c19fb16422b6bbcce24387b3264416d485b.idx | Bin 0 -> 2672 bytes ...k-c0438c19fb16422b6bbcce24387b3264416d485b.pack | Bin 0 -> 49113 bytes git/test/lib/__init__.py | 1 + git/test/lib/base.py | 200 ++++ git/test/performance/test_pack.py | 90 ++ git/test/performance/test_pack_streaming.py | 80 ++ git/test/performance/test_streams.py | 165 ++++ git/test/test_base.py | 98 ++ git/test/test_example.py | 64 ++ git/test/test_pack.py | 247 +++++ git/test/test_refs.py | 73 +- git/test/test_stream.py | 155 +++ git/test/test_util.py | 125 ++- git/typ.py | 27 + git/util.py | 763 ++++++++++++++- 65 files changed, 10354 insertions(+), 634 deletions(-) create mode 100644 git/base.py delete mode 100644 git/db.py create mode 100644 git/db/__init__.py create mode 100644 git/db/cmd/__init__.py create mode 100644 git/db/cmd/git.py create mode 100644 git/db/interface.py create mode 100644 git/db/py/__init__.py create mode 100644 git/db/py/base.py create mode 100644 git/db/py/git.py create mode 100644 git/db/py/loose.py create mode 100644 git/db/py/mem.py create mode 100644 git/db/py/pack.py create mode 100644 git/db/py/ref.py create mode 100644 git/db/py/resolve.py create mode 100644 git/db/py/transport.py create mode 100644 git/fun.py create mode 100644 git/pack.py create mode 100644 git/refs/headref.py create mode 100644 git/stream.py create mode 100644 git/test/db/__init__.py create mode 100644 git/test/db/lib.py create mode 100644 git/test/db/test_base.py create mode 100644 git/test/db/test_git.py create mode 100644 git/test/db/test_loose.py create mode 100644 git/test/db/test_mem.py create mode 100644 git/test/db/test_pack.py create mode 100644 git/test/db/test_ref.py create mode 100644 git/test/fixtures/objects/7b/b839852ed5e3a069966281bb08d50012fb309b create mode 100644 git/test/fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx create mode 100644 git/test/fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack create mode 100644 git/test/fixtures/packs/pack-a2bf8e71d8c18879e499335762dd95119d93d9f1.idx create mode 100644 git/test/fixtures/packs/pack-a2bf8e71d8c18879e499335762dd95119d93d9f1.pack create mode 100644 git/test/fixtures/packs/pack-c0438c19fb16422b6bbcce24387b3264416d485b.idx create mode 100644 git/test/fixtures/packs/pack-c0438c19fb16422b6bbcce24387b3264416d485b.pack create mode 100644 git/test/lib/base.py create mode 100644 git/test/performance/test_pack.py create mode 100644 git/test/performance/test_pack_streaming.py create mode 100644 git/test/test_example.py create mode 100644 git/test/test_pack.py create mode 100644 git/test/test_stream.py create mode 100644 git/typ.py diff --git a/git/base.py b/git/base.py new file mode 100644 index 00000000..ff1062bf --- /dev/null +++ b/git/base.py @@ -0,0 +1,311 @@ +# 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 +"""Module with basic data structures - they are designed to be lightweight and fast""" +from util import ( + bin_to_hex, + zlib + ) + +from fun import ( + type_id_to_type_map, + type_to_type_id_map + ) + +__all__ = ('OInfo', 'OPackInfo', 'ODeltaPackInfo', + 'OStream', 'OPackStream', 'ODeltaPackStream', + 'IStream', 'InvalidOInfo', 'InvalidOStream' ) + +#{ ODB Bases + +class OInfo(tuple): + """Carries information about an object in an ODB, provding information + about the binary sha of the object, the type_string as well as the uncompressed size + in bytes. + + It can be accessed using tuple notation and using attribute access notation:: + + assert dbi[0] == dbi.binsha + assert dbi[1] == dbi.type + assert dbi[2] == dbi.size + + The type is designed to be as lighteight as possible.""" + __slots__ = tuple() + + def __new__(cls, sha, type, size): + return tuple.__new__(cls, (sha, type, size)) + + def __init__(self, *args): + tuple.__init__(self) + + #{ Interface + @property + def binsha(self): + """:return: our sha as binary, 20 bytes""" + return self[0] + + @property + def hexsha(self): + """:return: our sha, hex encoded, 40 bytes""" + return bin_to_hex(self[0]) + + @property + def type(self): + return self[1] + + @property + def type_id(self): + return type_to_type_id_map[self[1]] + + @property + def size(self): + return self[2] + #} END interface + + +class OPackInfo(tuple): + """As OInfo, but provides a type_id property to retrieve the numerical type id, and + does not include a sha. + + Additionally, the pack_offset is the absolute offset into the packfile at which + all object information is located. The data_offset property points to the abosolute + location in the pack at which that actual data stream can be found.""" + __slots__ = tuple() + + def __new__(cls, packoffset, type, size): + return tuple.__new__(cls, (packoffset,type, size)) + + def __init__(self, *args): + tuple.__init__(self) + + #{ Interface + + @property + def pack_offset(self): + return self[0] + + @property + def type(self): + return type_id_to_type_map[self[1]] + + @property + def type_id(self): + return self[1] + + @property + def size(self): + return self[2] + + #} END interface + + +class ODeltaPackInfo(OPackInfo): + """Adds delta specific information, + Either the 20 byte sha which points to some object in the database, + or the negative offset from the pack_offset, so that pack_offset - delta_info yields + the pack offset of the base object""" + __slots__ = tuple() + + def __new__(cls, packoffset, type, size, delta_info): + return tuple.__new__(cls, (packoffset, type, size, delta_info)) + + #{ Interface + @property + def delta_info(self): + return self[3] + #} END interface + + +class OStream(OInfo): + """Base for object streams retrieved from the database, providing additional + information about the stream. + Generally, ODB streams are read-only as objects are immutable""" + __slots__ = tuple() + + def __new__(cls, sha, type, size, stream, *args, **kwargs): + """Helps with the initialization of subclasses""" + return tuple.__new__(cls, (sha, type, size, stream)) + + + def __init__(self, *args, **kwargs): + tuple.__init__(self) + + #{ Stream Reader Interface + + def read(self, size=-1): + return self[3].read(size) + + @property + def stream(self): + return self[3] + + #} END stream reader interface + + +class ODeltaStream(OStream): + """Uses size info of its stream, delaying reads""" + + def __new__(cls, sha, type, size, stream, *args, **kwargs): + """Helps with the initialization of subclasses""" + return tuple.__new__(cls, (sha, type, size, stream)) + + #{ Stream Reader Interface + + @property + def size(self): + return self[3].size + + #} END stream reader interface + + +class OPackStream(OPackInfo): + """Next to pack object information, a stream outputting an undeltified base object + is provided""" + __slots__ = tuple() + + def __new__(cls, packoffset, type, size, stream, *args): + """Helps with the initialization of subclasses""" + return tuple.__new__(cls, (packoffset, type, size, stream)) + + #{ Stream Reader Interface + def read(self, size=-1): + return self[3].read(size) + + @property + def stream(self): + return self[3] + #} END stream reader interface + + +class ODeltaPackStream(ODeltaPackInfo): + """Provides a stream outputting the uncompressed offset delta information""" + __slots__ = tuple() + + def __new__(cls, packoffset, type, size, delta_info, stream): + return tuple.__new__(cls, (packoffset, type, size, delta_info, stream)) + + + #{ Stream Reader Interface + def read(self, size=-1): + return self[4].read(size) + + @property + def stream(self): + return self[4] + #} END stream reader interface + + +class IStream(list): + """Represents an input content stream to be fed into the ODB. It is mutable to allow + the ODB to record information about the operations outcome right in this instance. + + It provides interfaces for the OStream and a StreamReader to allow the instance + to blend in without prior conversion. + + The only method your content stream must support is 'read'""" + __slots__ = tuple() + + def __new__(cls, type, size, stream, sha=None): + return list.__new__(cls, (sha, type, size, stream, None)) + + def __init__(self, type, size, stream, sha=None): + list.__init__(self, (sha, type, size, stream, None)) + + #{ Interface + @property + def hexsha(self): + """:return: our sha, hex encoded, 40 bytes""" + return bin_to_hex(self[0]) + + def _error(self): + """:return: the error that occurred when processing the stream, or None""" + return self[4] + + def _set_error(self, exc): + """Set this input stream to the given exc, may be None to reset the error""" + self[4] = exc + + error = property(_error, _set_error) + + #} END interface + + #{ Stream Reader Interface + + def read(self, size=-1): + """Implements a simple stream reader interface, passing the read call on + to our internal stream""" + return self[3].read(size) + + #} END stream reader interface + + #{ interface + + def _set_binsha(self, binsha): + self[0] = binsha + + def _binsha(self): + return self[0] + + binsha = property(_binsha, _set_binsha) + + + def _type(self): + return self[1] + + def _set_type(self, type): + self[1] = type + + type = property(_type, _set_type) + + def _size(self): + return self[2] + + def _set_size(self, size): + self[2] = size + + size = property(_size, _set_size) + + def _stream(self): + return self[3] + + def _set_stream(self, stream): + self[3] = stream + + stream = property(_stream, _set_stream) + + #} END odb info interface + + +class InvalidOInfo(tuple): + """Carries information about a sha identifying an object which is invalid in + the queried database. The exception attribute provides more information about + the cause of the issue""" + __slots__ = tuple() + + def __new__(cls, sha, exc): + return tuple.__new__(cls, (sha, exc)) + + def __init__(self, sha, exc): + tuple.__init__(self, (sha, exc)) + + @property + def binsha(self): + return self[0] + + @property + def hexsha(self): + return bin_to_hex(self[0]) + + @property + def error(self): + """:return: exception instance explaining the failure""" + return self[1] + + +class InvalidOStream(InvalidOInfo): + """Carries information about an invalid ODB stream""" + __slots__ = tuple() + +#} END ODB Bases + diff --git a/git/config.py b/git/config.py index 40475ee4..f1a8832e 100644 --- a/git/config.py +++ b/git/config.py @@ -6,5 +6,415 @@ """Module containing module parser implementation able to properly read and write configuration files""" -from gitdb.config import GitConfigParser, SectionConstraint +import re +import os +import ConfigParser as cp +import inspect +import cStringIO + +from git.odict import OrderedDict +from git.util import LockFile + __all__ = ('GitConfigParser', 'SectionConstraint') + +class MetaParserBuilder(type): + """Utlity class wrapping base-class methods into decorators that assure read-only properties""" + def __new__(metacls, name, bases, clsdict): + """ + Equip all base-class methods with a needs_values decorator, and all non-const methods + with a set_dirty_and_flush_changes decorator in addition to that.""" + kmm = '_mutating_methods_' + if kmm in clsdict: + mutating_methods = clsdict[kmm] + for base in bases: + methods = ( t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_") ) + for name, method in methods: + if name in clsdict: + continue + method_with_values = needs_values(method) + if name in mutating_methods: + method_with_values = set_dirty_and_flush_changes(method_with_values) + # END mutating methods handling + + clsdict[name] = method_with_values + # END for each name/method pair + # END for each base + # END if mutating methods configuration is set + + new_type = super(MetaParserBuilder, metacls).__new__(metacls, name, bases, clsdict) + return new_type + + + +def needs_values(func): + """Returns method assuring we read values (on demand) before we try to access them""" + def assure_data_present(self, *args, **kwargs): + self.read() + return func(self, *args, **kwargs) + # END wrapper method + assure_data_present.__name__ = func.__name__ + return assure_data_present + +def set_dirty_and_flush_changes(non_const_func): + """Return method that checks whether given non constant function may be called. + If so, the instance will be set dirty. + Additionally, we flush the changes right to disk""" + def flush_changes(self, *args, **kwargs): + rval = non_const_func(self, *args, **kwargs) + self.write() + return rval + # END wrapper method + flush_changes.__name__ = non_const_func.__name__ + return flush_changes + + +class SectionConstraint(object): + """Constrains a ConfigParser to only option commands which are constrained to + always use the section we have been initialized with. + + It supports all ConfigParser methods that operate on an option""" + __slots__ = ("_config", "_section_name") + _valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option", + "remove_section", "remove_option", "options") + + def __init__(self, config, section): + self._config = config + self._section_name = section + + def __getattr__(self, attr): + if attr in self._valid_attrs_: + return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs) + return super(SectionConstraint,self).__getattribute__(attr) + + def _call_config(self, method, *args, **kwargs): + """Call the configuration at the given method which must take a section name + as first argument""" + return getattr(self._config, method)(self._section_name, *args, **kwargs) + + @property + def config(self): + """return: Configparser instance we constrain""" + return self._config + + +class GitConfigParser(cp.RawConfigParser, object): + """Implements specifics required to read git style configuration files. + + This variation behaves much like the git.config command such that the configuration + will be read on demand based on the filepath given during initialization. + + The changes will automatically be written once the instance goes out of scope, but + can be triggered manually as well. + + The configuration file will be locked if you intend to change values preventing other + instances to write concurrently. + + :note: + The config is case-sensitive even when queried, hence section and option names + must match perfectly.""" + __metaclass__ = MetaParserBuilder + + + #{ Configuration + # The lock type determines the type of lock to use in new configuration readers. + # They must be compatible to the LockFile interface. + # A suitable alternative would be the BlockingLockFile + t_lock = LockFile + + #} END configuration + + OPTCRE = re.compile( + r'\s?(?P