Reintroduce Python2.6 support.
authorJelmer Vernooij <jelmer@samba.org>
Mon, 1 Sep 2014 19:11:14 +0000 (21:11 +0200)
committerJelmer Vernooij <jelmer@samba.org>
Sat, 13 Sep 2014 14:50:55 +0000 (16:50 +0200)
20 files changed:
.travis.yml
Makefile
dulwich/_compat.py [new file with mode: 0644]
dulwich/config.py
dulwich/contrib/test_swift.py
dulwich/protocol.py
dulwich/tests/__init__.py
dulwich/tests/compat/test_client.py
dulwich/tests/compat/test_pack.py
dulwich/tests/compat/test_utils.py
dulwich/tests/compat/test_web.py
dulwich/tests/compat/utils.py
dulwich/tests/test_client.py
dulwich/tests/test_fastexport.py
dulwich/tests/test_file.py
dulwich/tests/test_greenthreads.py
dulwich/tests/test_patch.py
dulwich/tests/utils.py
dulwich/web.py
setup.py

index cbb313dcb16d282b59606ae54b5d87e65d6bf474..b7499f087d928940254b7985931970da70e13fb5 100644 (file)
@@ -1,5 +1,6 @@
 language: python
 python:
+  - "2.6"
   - "2.7"
 virtualenv:
   system_site_packages: true
index 6851efe222c077d695ed2e3d8529d8673ae677ba..da33c8825a7f322422af10c4b0f44346f6041407 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,8 +3,12 @@ PYFLAKES = pyflakes
 PEP8 = pep8
 SETUP = $(PYTHON) setup.py
 PYDOCTOR ?= pydoctor
+ifeq ($(shell $(PYTHON) -c "import sys; print(sys.version_info >= (2, 7))"),True)
 TESTRUNNER ?= unittest
-RUNTEST = PYTHONHASHSEED=random PYTHONPATH=.:$(PYTHONPATH) $(PYTHON) -m $(TESTRUNNER)
+else
+TESTRUNNER ?= unittest2.__main__
+endif
+RUNTEST = PYTHONHASHSEED=random PYTHONPATH=.:$(PYTHONPATH) $(PYTHON) -m $(TESTRUNNER) $(TEST_OPTIONS)
 
 DESTDIR=/
 
diff --git a/dulwich/_compat.py b/dulwich/_compat.py
new file mode 100644 (file)
index 0000000..31e67bf
--- /dev/null
@@ -0,0 +1,962 @@
+# _compat.py -- For dealing with python2.6 oddness
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2
+# of the License or (at your option) a later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.
+
+"""Misc utilities to work with python <2.7.
+
+These utilities can all be deleted when dulwich decides it wants to stop
+support for python <2.7.
+"""
+
+# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and
+# pypy. Passes Python2.7's test suite and incorporates all the latest updates.
+# Copyright (C) Raymond Hettinger, MIT license
+
+try:
+    from thread import get_ident as _get_ident
+except ImportError:
+    from dummy_thread import get_ident as _get_ident
+
+try:
+    from _abcoll import KeysView, ValuesView, ItemsView
+except ImportError:
+    pass
+
+class OrderedDict(dict):
+    'Dictionary that remembers insertion order'
+    # An inherited dict maps keys to values.
+    # The inherited dict provides __getitem__, __len__, __contains__, and get.
+    # The remaining methods are order-aware.
+    # Big-O running times for all methods are the same as for regular
+    # dictionaries.
+
+    # The internal self.__map dictionary maps keys to links in a doubly linked
+    # list. The circular doubly linked list starts and ends with a sentinel
+    # element. The sentinel element never gets deleted (this simplifies the
+    # algorithm). Each link is stored as a list of length three:  [PREV, NEXT,
+    # KEY].
+
+    def __init__(self, *args, **kwds):
+        '''Initialize an ordered dictionary.  Signature is the same as for
+        regular dictionaries, but keyword arguments are not recommended
+        because their insertion order is arbitrary.
+
+        '''
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__root
+        except AttributeError:
+            self.__root = root = []                     # sentinel node
+            root[:] = [root, root, None]
+            self.__map = {}
+        self.__update(*args, **kwds)
+
+    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+        'od.__setitem__(i, y) <==> od[i]=y'
+        # Setting a new item creates a new link which goes at the end of the
+        # linked list, and the inherited dictionary is updated with the new
+        # key/value pair.
+        if key not in self:
+            root = self.__root
+            last = root[0]
+            last[1] = root[0] = self.__map[key] = [last, root, key]
+        dict_setitem(self, key, value)
+
+    def __delitem__(self, key, dict_delitem=dict.__delitem__):
+        'od.__delitem__(y) <==> del od[y]'
+        # Deleting an existing item uses self.__map to find the link which is
+        # then removed by updating the links in the predecessor and successor
+        # nodes.
+        dict_delitem(self, key)
+        link_prev, link_next, key = self.__map.pop(key)
+        link_prev[1] = link_next
+        link_next[0] = link_prev
+
+    def __iter__(self):
+        'od.__iter__() <==> iter(od)'
+        root = self.__root
+        curr = root[1]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[1]
+
+    def __reversed__(self):
+        'od.__reversed__() <==> reversed(od)'
+        root = self.__root
+        curr = root[0]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[0]
+
+    def clear(self):
+        'od.clear() -> None.  Remove all items from od.'
+        try:
+            for node in self.__map.itervalues():
+                del node[:]
+            root = self.__root
+            root[:] = [root, root, None]
+            self.__map.clear()
+        except AttributeError:
+            pass
+        dict.clear(self)
+
+    def popitem(self, last=True):
+        """od.popitem() -> (k, v), return and remove a (key, value) pair.
+        Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+        """
+        if not self:
+            raise KeyError('dictionary is empty')
+        root = self.__root
+        if last:
+            link = root[0]
+            link_prev = link[0]
+            link_prev[1] = root
+            root[0] = link_prev
+        else:
+            link = root[1]
+            link_next = link[1]
+            root[1] = link_next
+            link_next[0] = root
+        key = link[2]
+        del self.__map[key]
+        value = dict.pop(self, key)
+        return key, value
+
+    # -- the following methods do not depend on the internal structure --
+
+    def keys(self):
+        """'od.keys() -> list of keys in od"""
+        return list(self)
+
+    def values(self):
+        """od.values() -> list of values in od"""
+        return [self[key] for key in self]
+
+    def items(self):
+        """od.items() -> list of (key, value) pairs in od"""
+        return [(key, self[key]) for key in self]
+
+    def iterkeys(self):
+        """od.iterkeys() -> an iterator over the keys in od"""
+        return iter(self)
+
+    def itervalues(self):
+        """od.itervalues -> an iterator over the values in od"""
+        for k in self:
+            yield self[k]
+
+    def iteritems(self):
+        """od.iteritems -> an iterator over the (key, value) items in od"""
+        for k in self:
+            yield (k, self[k])
+
+    def update(*args, **kwds):
+        """od.update(E, F) -> None.  Update od from dict/iterable E and F.
+
+        If E is a dict instance, does:           for k in E: od[k] = E[k]
+        If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]
+        Or if E is an iterable of items, does:   for k, v in E: od[k] = v
+        In either case, this is followed by:     for k, v in F.items(): od[k] = v
+        """
+        if len(args) > 2:
+            raise TypeError('update() takes at most 2 positional '
+                            'arguments (%d given)' % (len(args),))
+        elif not args:
+            raise TypeError('update() takes at least 1 argument (0 given)')
+        self = args[0]
+        # Make progressively weaker assumptions about "other"
+        other = ()
+        if len(args) == 2:
+            other = args[1]
+        if isinstance(other, dict):
+            for key in other:
+                self[key] = other[key]
+        elif hasattr(other, 'keys'):
+            for key in other.keys():
+                self[key] = other[key]
+        else:
+            for key, value in other:
+                self[key] = value
+        for key, value in kwds.items():
+            self[key] = value
+
+    __update = update  # let subclasses override update without breaking
+                       # __init__
+
+    __marker = object()
+
+    def pop(self, key, default=__marker):
+        """od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+        If key is not found, d is returned if given, otherwise KeyError is raised.
+
+        """
+        if key in self:
+            result = self[key]
+            del self[key]
+            return result
+        if default is self.__marker:
+            raise KeyError(key)
+        return default
+
+    def setdefault(self, key, default=None):
+        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+        if key in self:
+            return self[key]
+        self[key] = default
+        return default
+
+    def __repr__(self, _repr_running={}):
+        'od.__repr__() <==> repr(od)'
+        call_key = id(self), _get_ident()
+        if call_key in _repr_running:
+            return '...'
+        _repr_running[call_key] = 1
+        try:
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+        finally:
+            del _repr_running[call_key]
+
+    def __reduce__(self):
+        'Return state information for pickling'
+        items = [[k, self[k]] for k in self]
+        inst_dict = vars(self).copy()
+        for k in vars(OrderedDict()):
+            inst_dict.pop(k, None)
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def copy(self):
+        'od.copy() -> a shallow copy of od'
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+        and values equal to v (which defaults to None).
+
+        '''
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        while comparison to a regular mapping is order-insensitive.
+
+        '''
+        if isinstance(other, OrderedDict):
+            return len(self)==len(other) and self.items() == other.items()
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
+
+    # -- the following methods are only used in Python 2.7 --
+
+    def viewkeys(self):
+        "od.viewkeys() -> a set-like object providing a view on od's keys"
+        return KeysView(self)
+
+    def viewvalues(self):
+        "od.viewvalues() -> an object providing a view on od's values"
+        return ValuesView(self)
+
+    def viewitems(self):
+        "od.viewitems() -> a set-like object providing a view on od's items"
+        return ItemsView(self)
+
+
+# Copyright 2007 Google, Inc. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+from abc import ABCMeta, abstractmethod
+import sys
+
+### ONE-TRICK PONIES ###
+
+def _hasattr(C, attr):
+    try:
+        return any(attr in B.__dict__ for B in C.__mro__)
+    except AttributeError:
+        # Old-style class
+        return hasattr(C, attr)
+
+
+class Hashable:
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def __hash__(self):
+        return 0
+
+    @classmethod
+    def __subclasshook__(cls, C):
+        if cls is Hashable:
+            try:
+                for B in C.__mro__:
+                    if "__hash__" in B.__dict__:
+                        if B.__dict__["__hash__"]:
+                            return True
+                        break
+            except AttributeError:
+                # Old-style class
+                if getattr(C, "__hash__", None):
+                    return True
+        return NotImplemented
+
+
+class Iterable:
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def __iter__(self):
+        while False:
+            yield None
+
+    @classmethod
+    def __subclasshook__(cls, C):
+        if cls is Iterable:
+            if _hasattr(C, "__iter__"):
+                return True
+        return NotImplemented
+
+Iterable.register(str)
+
+
+class Iterator(Iterable):
+
+    @abstractmethod
+    def next(self):
+        'Return the next item from the iterator. When exhausted, raise StopIteration'
+        raise StopIteration
+
+    def __iter__(self):
+        return self
+
+    @classmethod
+    def __subclasshook__(cls, C):
+        if cls is Iterator:
+            if _hasattr(C, "next") and _hasattr(C, "__iter__"):
+                return True
+        return NotImplemented
+
+
+class Sized:
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def __len__(self):
+        return 0
+
+    @classmethod
+    def __subclasshook__(cls, C):
+        if cls is Sized:
+            if _hasattr(C, "__len__"):
+                return True
+        return NotImplemented
+
+
+class Container:
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def __contains__(self, x):
+        return False
+
+    @classmethod
+    def __subclasshook__(cls, C):
+        if cls is Container:
+            if _hasattr(C, "__contains__"):
+                return True
+        return NotImplemented
+
+
+class Callable:
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def __call__(self, *args, **kwds):
+        return False
+
+    @classmethod
+    def __subclasshook__(cls, C):
+        if cls is Callable:
+            if _hasattr(C, "__call__"):
+                return True
+        return NotImplemented
+
+
+### SETS ###
+
+
+class Set(Sized, Iterable, Container):
+    """A set is a finite, iterable container.
+
+    This class provides concrete generic implementations of all
+    methods except for __contains__, __iter__ and __len__.
+
+    To override the comparisons (presumably for speed, as the
+    semantics are fixed), all you have to do is redefine __le__ and
+    then the other operations will automatically follow suit.
+    """
+
+    def __le__(self, other):
+        if not isinstance(other, Set):
+            return NotImplemented
+        if len(self) > len(other):
+            return False
+        for elem in self:
+            if elem not in other:
+                return False
+        return True
+
+    def __lt__(self, other):
+        if not isinstance(other, Set):
+            return NotImplemented
+        return len(self) < len(other) and self.__le__(other)
+
+    def __gt__(self, other):
+        if not isinstance(other, Set):
+            return NotImplemented
+        return len(self) > len(other) and self.__ge__(other)
+
+    def __ge__(self, other):
+        if not isinstance(other, Set):
+            return NotImplemented
+        if len(self) < len(other):
+            return False
+        for elem in other:
+            if elem not in self:
+                return False
+        return True
+
+    def __eq__(self, other):
+        if not isinstance(other, Set):
+            return NotImplemented
+        return len(self) == len(other) and self.__le__(other)
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    @classmethod
+    def _from_iterable(cls, it):
+        '''Construct an instance of the class from any iterable input.
+
+        Must override this method if the class constructor signature
+        does not accept an iterable for an input.
+        '''
+        return cls(it)
+
+    def __and__(self, other):
+        if not isinstance(other, Iterable):
+            return NotImplemented
+        return self._from_iterable(value for value in other if value in self)
+
+    __rand__ = __and__
+
+    def isdisjoint(self, other):
+        'Return True if two sets have a null intersection.'
+        for value in other:
+            if value in self:
+                return False
+        return True
+
+    def __or__(self, other):
+        if not isinstance(other, Iterable):
+            return NotImplemented
+        chain = (e for s in (self, other) for e in s)
+        return self._from_iterable(chain)
+
+    __ror__ = __or__
+
+    def __sub__(self, other):
+        if not isinstance(other, Set):
+            if not isinstance(other, Iterable):
+                return NotImplemented
+            other = self._from_iterable(other)
+        return self._from_iterable(value for value in self
+                                   if value not in other)
+
+    def __rsub__(self, other):
+        if not isinstance(other, Set):
+            if not isinstance(other, Iterable):
+                return NotImplemented
+            other = self._from_iterable(other)
+        return self._from_iterable(value for value in other
+                                   if value not in self)
+
+    def __xor__(self, other):
+        if not isinstance(other, Set):
+            if not isinstance(other, Iterable):
+                return NotImplemented
+            other = self._from_iterable(other)
+        return (self - other) | (other - self)
+
+    __rxor__ = __xor__
+
+    # Sets are not hashable by default, but subclasses can change this
+    __hash__ = None
+
+    def _hash(self):
+        """Compute the hash value of a set.
+
+        Note that we don't define __hash__: not all sets are hashable.
+        But if you define a hashable set type, its __hash__ should
+        call this function.
+
+        This must be compatible __eq__.
+
+        All sets ought to compare equal if they contain the same
+        elements, regardless of how they are implemented, and
+        regardless of the order of the elements; so there's not much
+        freedom for __eq__ or __hash__.  We match the algorithm used
+        by the built-in frozenset type.
+        """
+        MAX = sys.maxint
+        MASK = 2 * MAX + 1
+        n = len(self)
+        h = 1927868237 * (n + 1)
+        h &= MASK
+        for x in self:
+            hx = hash(x)
+            h ^= (hx ^ (hx << 16) ^ 89869747)  * 3644798167
+            h &= MASK
+        h = h * 69069 + 907133923
+        h &= MASK
+        if h > MAX:
+            h -= MASK + 1
+        if h == -1:
+            h = 590923713
+        return h
+
+Set.register(frozenset)
+
+
+class MutableSet(Set):
+    """A mutable set is a finite, iterable container.
+
+    This class provides concrete generic implementations of all
+    methods except for __contains__, __iter__, __len__,
+    add(), and discard().
+
+    To override the comparisons (presumably for speed, as the
+    semantics are fixed), all you have to do is redefine __le__ and
+    then the other operations will automatically follow suit.
+    """
+
+    @abstractmethod
+    def add(self, value):
+        """Add an element."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def discard(self, value):
+        """Remove an element.  Do not raise an exception if absent."""
+        raise NotImplementedError
+
+    def remove(self, value):
+        """Remove an element. If not a member, raise a KeyError."""
+        if value not in self:
+            raise KeyError(value)
+        self.discard(value)
+
+    def pop(self):
+        """Return the popped value.  Raise KeyError if empty."""
+        it = iter(self)
+        try:
+            value = next(it)
+        except StopIteration:
+            raise KeyError
+        self.discard(value)
+        return value
+
+    def clear(self):
+        """This is slow (creates N new iterators!) but effective."""
+        try:
+            while True:
+                self.pop()
+        except KeyError:
+            pass
+
+    def __ior__(self, it):
+        for value in it:
+            self.add(value)
+        return self
+
+    def __iand__(self, it):
+        for value in (self - it):
+            self.discard(value)
+        return self
+
+    def __ixor__(self, it):
+        if it is self:
+            self.clear()
+        else:
+            if not isinstance(it, Set):
+                it = self._from_iterable(it)
+            for value in it:
+                if value in self:
+                    self.discard(value)
+                else:
+                    self.add(value)
+        return self
+
+    def __isub__(self, it):
+        if it is self:
+            self.clear()
+        else:
+            for value in it:
+                self.discard(value)
+        return self
+
+MutableSet.register(set)
+
+
+### MAPPINGS ###
+
+
+class Mapping(Sized, Iterable, Container):
+
+    """A Mapping is a generic container for associating key/value
+    pairs.
+
+    This class provides concrete generic implementations of all
+    methods except for __getitem__, __iter__, and __len__.
+
+    """
+
+    @abstractmethod
+    def __getitem__(self, key):
+        raise KeyError
+
+    def get(self, key, default=None):
+        'D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.'
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+    def __contains__(self, key):
+        try:
+            self[key]
+        except KeyError:
+            return False
+        else:
+            return True
+
+    def iterkeys(self):
+        'D.iterkeys() -> an iterator over the keys of D'
+        return iter(self)
+
+    def itervalues(self):
+        'D.itervalues() -> an iterator over the values of D'
+        for key in self:
+            yield self[key]
+
+    def iteritems(self):
+        'D.iteritems() -> an iterator over the (key, value) items of D'
+        for key in self:
+            yield (key, self[key])
+
+    def keys(self):
+        "D.keys() -> list of D's keys"
+        return list(self)
+
+    def items(self):
+        "D.items() -> list of D's (key, value) pairs, as 2-tuples"
+        return [(key, self[key]) for key in self]
+
+    def values(self):
+        "D.values() -> list of D's values"
+        return [self[key] for key in self]
+
+    # Mappings are not hashable by default, but subclasses can change this
+    __hash__ = None
+
+    def __eq__(self, other):
+        if not isinstance(other, Mapping):
+            return NotImplemented
+        return dict(self.items()) == dict(other.items())
+
+    def __ne__(self, other):
+        return not (self == other)
+
+class MappingView(Sized):
+
+    def __init__(self, mapping):
+        self._mapping = mapping
+
+    def __len__(self):
+        return len(self._mapping)
+
+    def __repr__(self):
+        return '{0.__class__.__name__}({0._mapping!r})'.format(self)
+
+
+class KeysView(MappingView, Set):
+
+    @classmethod
+    def _from_iterable(self, it):
+        return set(it)
+
+    def __contains__(self, key):
+        return key in self._mapping
+
+    def __iter__(self):
+        for key in self._mapping:
+            yield key
+
+
+class ItemsView(MappingView, Set):
+
+    @classmethod
+    def _from_iterable(self, it):
+        return set(it)
+
+    def __contains__(self, item):
+        key, value = item
+        try:
+            v = self._mapping[key]
+        except KeyError:
+            return False
+        else:
+            return v == value
+
+    def __iter__(self):
+        for key in self._mapping:
+            yield (key, self._mapping[key])
+
+
+class ValuesView(MappingView):
+
+    def __contains__(self, value):
+        for key in self._mapping:
+            if value == self._mapping[key]:
+                return True
+        return False
+
+    def __iter__(self):
+        for key in self._mapping:
+            yield self._mapping[key]
+
+
+class MutableMapping(Mapping):
+
+    """A MutableMapping is a generic container for associating
+    key/value pairs.
+
+    This class provides concrete generic implementations of all
+    methods except for __getitem__, __setitem__, __delitem__,
+    __iter__, and __len__.
+
+    """
+
+    @abstractmethod
+    def __setitem__(self, key, value):
+        raise KeyError
+
+    @abstractmethod
+    def __delitem__(self, key):
+        raise KeyError
+
+    __marker = object()
+
+    def pop(self, key, default=__marker):
+        '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+          If key is not found, d is returned if given, otherwise KeyError is raised.
+        '''
+        try:
+            value = self[key]
+        except KeyError:
+            if default is self.__marker:
+                raise
+            return default
+        else:
+            del self[key]
+            return value
+
+    def popitem(self):
+        '''D.popitem() -> (k, v), remove and return some (key, value) pair
+           as a 2-tuple; but raise KeyError if D is empty.
+        '''
+        try:
+            key = next(iter(self))
+        except StopIteration:
+            raise KeyError
+        value = self[key]
+        del self[key]
+        return key, value
+
+    def clear(self):
+        'D.clear() -> None.  Remove all items from D.'
+        try:
+            while True:
+                self.popitem()
+        except KeyError:
+            pass
+
+    def update(*args, **kwds):
+        ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
+            If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
+            If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
+            In either case, this is followed by: for k, v in F.items(): D[k] = v
+        '''
+        if len(args) > 2:
+            raise TypeError("update() takes at most 2 positional "
+                            "arguments ({} given)".format(len(args)))
+        elif not args:
+            raise TypeError("update() takes at least 1 argument (0 given)")
+        self = args[0]
+        other = args[1] if len(args) >= 2 else ()
+
+        if isinstance(other, Mapping):
+            for key in other:
+                self[key] = other[key]
+        elif hasattr(other, "keys"):
+            for key in other.keys():
+                self[key] = other[key]
+        else:
+            for key, value in other:
+                self[key] = value
+        for key, value in kwds.items():
+            self[key] = value
+
+    def setdefault(self, key, default=None):
+        'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
+        try:
+            return self[key]
+        except KeyError:
+            self[key] = default
+        return default
+
+MutableMapping.register(dict)
+
+
+### SEQUENCES ###
+
+
+class Sequence(Sized, Iterable, Container):
+    """All the operations on a read-only sequence.
+
+    Concrete subclasses must override __new__ or __init__,
+    __getitem__, and __len__.
+    """
+
+    @abstractmethod
+    def __getitem__(self, index):
+        raise IndexError
+
+    def __iter__(self):
+        i = 0
+        try:
+            while True:
+                v = self[i]
+                yield v
+                i += 1
+        except IndexError:
+            return
+
+    def __contains__(self, value):
+        for v in self:
+            if v == value:
+                return True
+        return False
+
+    def __reversed__(self):
+        for i in reversed(range(len(self))):
+            yield self[i]
+
+    def index(self, value):
+        '''S.index(value) -> integer -- return first index of value.
+           Raises ValueError if the value is not present.
+        '''
+        for i, v in enumerate(self):
+            if v == value:
+                return i
+        raise ValueError
+
+    def count(self, value):
+        'S.count(value) -> integer -- return number of occurrences of value'
+        return sum(1 for v in self if v == value)
+
+Sequence.register(tuple)
+Sequence.register(basestring)
+Sequence.register(buffer)
+Sequence.register(xrange)
+
+
+class MutableSequence(Sequence):
+
+    """All the operations on a read-only sequence.
+
+    Concrete subclasses must provide __new__ or __init__,
+    __getitem__, __setitem__, __delitem__, __len__, and insert().
+
+    """
+
+    @abstractmethod
+    def __setitem__(self, index, value):
+        raise IndexError
+
+    @abstractmethod
+    def __delitem__(self, index):
+        raise IndexError
+
+    @abstractmethod
+    def insert(self, index, value):
+        'S.insert(index, object) -- insert object before index'
+        raise IndexError
+
+    def append(self, value):
+        'S.append(object) -- append object to the end of the sequence'
+        self.insert(len(self), value)
+
+    def reverse(self):
+        'S.reverse() -- reverse *IN PLACE*'
+        n = len(self)
+        for i in range(n//2):
+            self[i], self[n-i-1] = self[n-i-1], self[i]
+
+    def extend(self, values):
+        'S.extend(iterable) -- extend sequence by appending elements from the iterable'
+        for v in values:
+            self.append(v)
+
+    def pop(self, index=-1):
+        '''S.pop([index]) -> item -- remove and return item at index (default last).
+           Raise IndexError if list is empty or index is out of range.
+        '''
+        v = self[index]
+        del self[index]
+        return v
+
+    def remove(self, value):
+        '''S.remove(value) -- remove first occurrence of value.
+           Raise ValueError if the value is not present.
+        '''
+        del self[self.index(value)]
+
+    def __iadd__(self, values):
+        self.extend(values)
+        return self
+
+MutableSequence.register(list)
index 178636f2076d57c9dc5c0d2109cb956f27e6bf1f..5d303aad708f2e5e098a31fa1c454cf2816b92b3 100644 (file)
@@ -28,10 +28,16 @@ import errno
 import os
 import re
 
-from collections import (
-    OrderedDict,
-    MutableMapping,
-    )
+try:
+    from collections import (
+        OrderedDict,
+        MutableMapping,
+        )
+except ImportError:
+    from dulwich._compat import (
+        OrderedDict,
+        MutableMapping
+        )
 
 
 from dulwich.file import GitFile
index 30dd5d4da868277bcab81b35743bee5f9e81790c..f6f6bac03ac690ff7541460215b29def29ac07f1 100644 (file)
@@ -25,7 +25,10 @@ import posixpath
 
 from time import time
 from cStringIO import StringIO
-from unittest import skipIf
+try:
+    from unittest import skipIf
+except ImportError:
+    from unittest2 import skipIf
 
 from dulwich.tests import (
     TestCase,
@@ -553,18 +556,18 @@ class TestSwiftConnector(TestCase):
 
     def test_create_root(self):
         with patch('dulwich.contrib.swift.SwiftConnector.test_root_exists',
-                lambda *args: None), \
-             patch('geventhttpclient.HTTPClient.request',
+                lambda *args: None):
+            with patch('geventhttpclient.HTTPClient.request',
                 lambda *args: Response()):
-            self.assertEqual(self.conn.create_root(), None)
+                self.assertEqual(self.conn.create_root(), None)
 
     def test_create_root_fails(self):
         with patch('dulwich.contrib.swift.SwiftConnector.test_root_exists',
-                   lambda *args: None), \
-             patch('geventhttpclient.HTTPClient.request',
-                   lambda *args: Response(status=404)):
-            self.assertRaises(swift.SwiftException,
-                              lambda: self.conn.create_root())
+                   lambda *args: None):
+            with patch('geventhttpclient.HTTPClient.request',
+                       lambda *args: Response(status=404)):
+                self.assertRaises(swift.SwiftException,
+                                  lambda: self.conn.create_root())
 
     def test_get_container_objects(self):
         with patch('geventhttpclient.HTTPClient.request',
@@ -621,13 +624,13 @@ class TestSwiftConnector(TestCase):
 
     def test_del_root(self):
         with patch('dulwich.contrib.swift.SwiftConnector.del_object',
-                   lambda *args: None), \
-             patch('dulwich.contrib.swift.SwiftConnector.'
-                   'get_container_objects',
-                   lambda *args: ({'name': 'a'}, {'name': 'b'})), \
-             patch('geventhttpclient.HTTPClient.request',
-                    lambda *args: Response()):
-            self.assertEqual(self.conn.del_root(), None)
+                   lambda *args: None):
+            with patch('dulwich.contrib.swift.SwiftConnector.'
+                       'get_container_objects',
+                       lambda *args: ({'name': 'a'}, {'name': 'b'})):
+                with patch('geventhttpclient.HTTPClient.request',
+                           lambda *args: Response()):
+                    self.assertEqual(self.conn.del_root(), None)
 
 
 @skipIf(missing_libs, skipmsg)
index 1e35435a1fd266ddf6ed7e21dddaa5c97d469892..57d278715e9c7dae8216c93866ce0c6db7727cca 100644 (file)
@@ -121,7 +121,7 @@ class Protocol(object):
                 self.report_activity(size, 'read')
             pkt_contents = read(size-4)
             if len(pkt_contents) + 4 != size:
-                raise AssertionError('Length of pkt read {:04x} does not match length prefix {:04x}.'
+                raise AssertionError('Length of pkt read %04x does not match length prefix %04x.'
                                      .format(len(pkt_contents) + 4, size))
             return pkt_contents
         except socket.error as e:
index ad5fa294815a2794c50fd76da3b5229efcc1e4b0..38f1b270fbb0f407e2deb1f1a2cb2ed7ab2854e5 100644 (file)
@@ -29,7 +29,10 @@ import tempfile
 
 # If Python itself provides an exception, use that
 import unittest
-from unittest import TestCase as _TestCase
+if sys.version < (2, 7):
+    from unittest2 import SkipTest, TestCase as _TestCase
+else:
+    from unittest import SkipTest, TestCase as _TestCase
 
 
 def get_safe_env(env=None):
index 39c341d063ffd334d02659d089f2e5a3cd59ca38..1362ee76fe251f02467a1149d1e54983d1137f89 100644 (file)
@@ -33,7 +33,6 @@ import tarfile
 import tempfile
 import threading
 import urllib
-from unittest import SkipTest
 
 if sys.platform == 'win32':
     import ctypes
@@ -49,6 +48,7 @@ from dulwich import (
     )
 from dulwich.tests import (
     get_safe_env,
+    SkipTest,
     )
 
 from dulwich.tests.compat.utils import (
index 93dbe186e8c432087ad5a71b86d5d57e29aa15a9..9091ba3ce9d6beb2c6c8ed3b9b4c1363381ac5dc 100644 (file)
@@ -25,7 +25,6 @@ import os
 import re
 import shutil
 import tempfile
-from unittest import SkipTest
 
 from dulwich.pack import (
     write_pack,
@@ -33,6 +32,9 @@ from dulwich.pack import (
 from dulwich.objects import (
     Blob,
     )
+from dulwich.tests import (
+    SkipTest,
+    )
 from dulwich.tests.test_pack import (
     a_sha,
     pack1_sha,
index 0cec49b03ba726c6dc305c1ab92d68a05c38c692..53bb71e933160726fa22376bc75d87b4f9c619e2 100644 (file)
@@ -19,9 +19,8 @@
 
 """Tests for git compatibility utilities."""
 
-from unittest import SkipTest
-
 from dulwich.tests import (
+    SkipTest,
     TestCase,
     )
 from dulwich.tests.compat import utils
index 66b8d587d6650dacb36c9e1eab8f80a2b4d959ad..f6cced799413abc0f7358486cf737b8b5f70a7eb 100644 (file)
@@ -25,14 +25,14 @@ warning: these tests should be fairly stable, but when writing/debugging new
 """
 
 import threading
-from unittest import (
-    SkipTest,
-    )
 from wsgiref import simple_server
 
 from dulwich.server import (
     DictBackend,
     )
+from dulwich.tests import (
+    SkipTest,
+    )
 from dulwich.web import (
     make_wsgi_chain,
     HTTPGitApplication,
index 19ad09de476206038c526d0207c71a40dcd80577..b876fc9c55efa8eaeacdb310d5f1beee19743d78 100644 (file)
@@ -25,13 +25,13 @@ import socket
 import subprocess
 import tempfile
 import time
-from unittest import SkipTest
 
 from dulwich.repo import Repo
 from dulwich.protocol import TCP_GIT_PORT
 
 from dulwich.tests import (
     get_safe_env,
+    SkipTest,
     TestCase,
     )
 
index 1bd4c11a068007998fca77be6d33948be6b3be70..bdfcbe0bbe3c25e201e49a16e72783900a76e6bf 100644 (file)
 
 from io import BytesIO
 import sys
-from unittest import skipIf
+try:
+    from unittest import skipIf
+except ImportError:
+    from unittest2 import skipIf
 
 from dulwich import (
     client,
index a3c7018ebdf063c14c9dd68af431ddb2dd65651a..308768de3e980bfaed482f3b381ed21be5b95ec7 100644 (file)
@@ -19,7 +19,6 @@
 
 from io import BytesIO
 import stat
-from unittest import SkipTest
 
 
 from dulwich.object_store import (
@@ -34,6 +33,7 @@ from dulwich.repo import (
     MemoryRepo,
     )
 from dulwich.tests import (
+    SkipTest,
     TestCase,
     )
 from dulwich.tests.utils import (
index e2a12aec1cef5abd007c4ef72ea8225c28c2d65c..3393f028f15081ef6b21909ad0b827c78f583ff5 100644 (file)
@@ -22,10 +22,10 @@ import os
 import shutil
 import sys
 import tempfile
-from unittest import SkipTest
 
 from dulwich.file import GitFile, fancy_rename
 from dulwich.tests import (
+    SkipTest,
     TestCase,
     )
 
index 9bd46954c90c694c63ab07988cc8dfb1a85d9d46..25391b6923932a0bf1f9f06942ab25eff8374cbb 100644 (file)
@@ -35,7 +35,10 @@ from dulwich.objects import (
     parse_timezone,
     )
 
-from unittest import skipIf
+try:
+    from unittest import skipIf
+except ImportError:
+    from unittest2 import skipIf
 
 try:
     import gevent
index 46248a1ecfa939a030419833330b0bc683e610ce..87c7d55d7f9f58137ac85c199772c34df3a5610a 100644 (file)
@@ -19,7 +19,6 @@
 """Tests for patch.py."""
 
 from io import BytesIO
-from unittest import SkipTest
 
 from dulwich.objects import (
     Blob,
@@ -38,6 +37,7 @@ from dulwich.patch import (
     write_tree_diff,
     )
 from dulwich.tests import (
+    SkipTest,
     TestCase,
     )
 
index 8a84c1873890bfb444cb69667c7632b9c073bac2..8d9e3ad15817b8192fd8f3ced149a1e10a3c2071 100644 (file)
@@ -26,9 +26,7 @@ import shutil
 import tempfile
 import time
 import types
-from unittest import (
-    SkipTest,
-    )
+
 import warnings
 
 from dulwich.index import (
@@ -49,6 +47,8 @@ from dulwich.pack import (
     create_delta,
     )
 from dulwich.repo import Repo
+from dulwich.tests import SkipTest
+
 
 # Plain files are very frequently used in tests, so let the mode be very short.
 F = 0o100644  # Shorthand mode for Files.
index c184779b88672f5cb157bba26a09d2ecf9957e9f..239ef8f5d7c6902658cb60086753c5a3e418b07f 100644 (file)
@@ -418,8 +418,11 @@ class ServerHandlerLogger(ServerHandler):
     """ServerHandler that uses dulwich's logger for logging exceptions."""
 
     def log_exception(self, exc_info):
-        logger.exception('Exception happened during processing of request',
-                         exc_info=exc_info)
+        if sys.version < (2, 7):
+            logger.exception('Exception happened during processing of request')
+        else:
+            logger.exception('Exception happened during processing of request',
+                             exc_info=exc_info)
 
     def log_message(self, format, *args):
         logger.info(format, *args)
index 66d91737211801f216196dd76c657c9868801496..b1bd5b3bd2e6cd26ec29de7533a2b55719f26519 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -71,6 +71,7 @@ setup(name='dulwich',
       classifiers=[
           'Development Status :: 4 - Beta',
           'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)',
+          'Programming Language :: Python :: 2.6',
           'Programming Language :: Python :: 2.7',
           'Operating System :: POSIX',
           'Topic :: Software Development :: Version Control',