Jelmer Vernooij преди 13 години
родител
ревизия
5076a847ad

+ 33 - 0
NEWS

@@ -1,3 +1,36 @@
+0.8.4	2012-03-28
+
+ BUG FIXES
+
+  * Options on the same line as sections in config files are now supported.
+    (Jelmer Vernooij, #920553)
+
+  * Only negotiate capabilities that are also supported by the server.
+    (Rod Cloutier, Risto Kankkunen)
+
+  * Fix parsing of invalid timezone offsets with two minus signs.
+    (Jason R. Coombs, #697828)
+
+  * Reset environment variables during tests, to avoid
+    test isolation leaks reading ~/.gitconfig. (Risto Kankkunen)
+
+ TESTS
+
+  * $HOME is now explicitly specified for tests that use it to read
+    ``~/.gitconfig``, to prevent test isolation issues.
+    (Jelmer Vernooij, #920330)
+
+ FEATURES
+
+  * Additional arguments to get_transport_and_path are now passed
+    on to the constructor of the transport. (Sam Vilain)
+
+  * The WSGI server now transparently handles when a git client submits data
+    using Content-Encoding: gzip.
+    (David Blewett, Jelmer Vernooij)
+
+  * Add dulwich.index.build_index_from_tree(). (milki)
+
 0.8.3	2012-01-21
 
  FEATURES

+ 6 - 0
debian/changelog

@@ -1,3 +1,9 @@
+dulwich (0.8.4-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Jelmer Vernooij <jelmer@debian.org>  Wed, 28 Mar 2012 14:22:12 +0200
+
 dulwich (0.8.3-1) unstable; urgency=low
 
   * New upstream release.

+ 1 - 1
dulwich/__init__.py

@@ -23,4 +23,4 @@
 
 from dulwich import (client, protocol, repo, server)
 
-__version__ = (0, 8, 3)
+__version__ = (0, 8, 4)

+ 23 - 21
dulwich/client.py

@@ -153,10 +153,10 @@ class GitClient(object):
             activity.
         """
         self._report_activity = report_activity
-        self._fetch_capabilities = list(FETCH_CAPABILITIES)
-        self._send_capabilities = list(SEND_CAPABILITIES)
+        self._fetch_capabilities = set(FETCH_CAPABILITIES)
+        self._send_capabilities = set(SEND_CAPABILITIES)
         if thin_packs:
-            self._fetch_capabilities.append('thin-pack')
+            self._fetch_capabilities.add('thin-pack')
 
     def _read_refs(self, proto):
         server_capabilities = None
@@ -169,7 +169,7 @@ class GitClient(object):
             if server_capabilities is None:
                 (ref, server_capabilities) = extract_capabilities(ref)
             refs[ref] = sha
-        return refs, server_capabilities
+        return refs, set(server_capabilities)
 
     def send_pack(self, path, determine_wants, generate_pack_contents,
                   progress=None):
@@ -323,7 +323,7 @@ class GitClient(object):
                     report_status_parser.handle_packet).parse
             self._read_side_band64k_data(proto, channel_callbacks)
         else:
-            if 'report-status':
+            if 'report-status' in capabilities:
                 for pkt in proto.read_pkt_seq():
                     report_status_parser.handle_packet(pkt)
         if report_status_parser is not None:
@@ -438,9 +438,7 @@ class TraditionalGitClient(GitClient):
         """
         proto, unused_can_read = self._connect('receive-pack', path)
         old_refs, server_capabilities = self._read_refs(proto)
-        negotiated_capabilities = list(self._send_capabilities)
-        if 'report-status' not in server_capabilities:
-            negotiated_capabilities.remove('report-status')
+        negotiated_capabilities = self._send_capabilities & server_capabilities
         try:
             new_refs = determine_wants(old_refs)
         except:
@@ -470,8 +468,8 @@ class TraditionalGitClient(GitClient):
         :param progress: Callback for progress reports (strings)
         """
         proto, can_read = self._connect('upload-pack', path)
-        (refs, server_capabilities) = self._read_refs(proto)
-        negotiated_capabilities = list(self._fetch_capabilities)
+        refs, server_capabilities = self._read_refs(proto)
+        negotiated_capabilities = self._fetch_capabilities & server_capabilities
         try:
             wants = determine_wants(refs)
         except:
@@ -515,7 +513,7 @@ class TCPGitClient(TraditionalGitClient):
             port = TCP_GIT_PORT
         self._host = host
         self._port = port
-        GitClient.__init__(self, *args, **kwargs)
+        TraditionalGitClient.__init__(self, *args, **kwargs)
 
     def _connect(self, cmd, path):
         sockaddrs = socket.getaddrinfo(self._host, self._port,
@@ -578,7 +576,7 @@ class SubprocessGitClient(TraditionalGitClient):
         self._stderr = kwargs.get('stderr')
         if 'stderr' in kwargs:
             del kwargs['stderr']
-        GitClient.__init__(self, *args, **kwargs)
+        TraditionalGitClient.__init__(self, *args, **kwargs)
 
     def _connect(self, service, path):
         import subprocess
@@ -617,7 +615,7 @@ class SSHGitClient(TraditionalGitClient):
         self.host = host
         self.port = port
         self.username = username
-        GitClient.__init__(self, *args, **kwargs)
+        TraditionalGitClient.__init__(self, *args, **kwargs)
         self.alternative_paths = {}
 
     def _get_cmd_path(self, cmd):
@@ -708,7 +706,7 @@ class HttpGitClient(GitClient):
         url = self._get_url(path)
         old_refs, server_capabilities = self._discover_references(
             "git-receive-pack", url)
-        negotiated_capabilities = list(self._send_capabilities)
+        negotiated_capabilities = self._send_capabilities & server_capabilities
         new_refs = determine_wants(old_refs)
         if new_refs is None:
             return old_refs
@@ -743,7 +741,7 @@ class HttpGitClient(GitClient):
         url = self._get_url(path)
         refs, server_capabilities = self._discover_references(
             "git-upload-pack", url)
-        negotiated_capabilities = list(server_capabilities)
+        negotiated_capabilities = server_capabilities
         wants = determine_wants(refs)
         if wants is not None:
             wants = [cid for cid in wants if cid != ZERO_SHA]
@@ -764,31 +762,35 @@ class HttpGitClient(GitClient):
         return refs
 
 
-def get_transport_and_path(uri):
+def get_transport_and_path(uri, **kwargs):
     """Obtain a git client from a URI or path.
 
     :param uri: URI or path
+    :param thin_packs: Whether or not thin packs should be retrieved
+    :param report_activity: Optional callback for reporting transport
+        activity.
     :return: Tuple with client instance and relative path.
     """
     parsed = urlparse.urlparse(uri)
     if parsed.scheme == 'git':
-        return TCPGitClient(parsed.hostname, port=parsed.port), parsed.path
+        return (TCPGitClient(parsed.hostname, port=parsed.port, **kwargs),
+                parsed.path)
     elif parsed.scheme == 'git+ssh':
         return SSHGitClient(parsed.hostname, port=parsed.port,
-                            username=parsed.username), parsed.path
+                            username=parsed.username, **kwargs), parsed.path
     elif parsed.scheme in ('http', 'https'):
         return HttpGitClient(urlparse.urlunparse(parsed)), parsed.path
 
     if parsed.scheme and not parsed.netloc:
         # SSH with no user@, zero or one leading slash.
-        return SSHGitClient(parsed.scheme), parsed.path
+        return SSHGitClient(parsed.scheme, **kwargs), parsed.path
     elif parsed.scheme:
         raise ValueError('Unknown git protocol scheme: %s' % parsed.scheme)
     elif '@' in parsed.path and ':' in parsed.path:
         # SSH with user@host:foo.
         user_host, path = parsed.path.split(':')
         user, host = user_host.rsplit('@')
-        return SSHGitClient(host, username=user), path
+        return SSHGitClient(host, username=user, **kwargs), path
 
     # Otherwise, assume it's a local path.
-    return SubprocessGitClient(), uri
+    return SubprocessGitClient(**kwargs), uri

+ 25 - 25
dulwich/config.py

@@ -198,14 +198,13 @@ class ConfigFile(ConfigDict):
         for lineno, line in enumerate(f.readlines()):
             line = line.lstrip()
             if setting is None:
-                if _strip_comments(line).strip() == "":
-                    continue
-                if line[0] == "[":
+                if len(line) > 0 and line[0] == "[":
                     line = _strip_comments(line).rstrip()
-                    if line[-1] != "]":
+                    last = line.index("]")
+                    if last == -1:
                         raise ValueError("expected trailing ]")
-                    key = line.strip()
-                    pts = key[1:-1].split(" ", 1)
+                    pts = line[1:last].split(" ", 1)
+                    line = line[last+1:]
                     pts[0] = pts[0].lower()
                     if len(pts) == 2:
                         if pts[1][0] != "\"" or pts[1][-1] != "\"":
@@ -227,26 +226,27 @@ class ConfigFile(ConfigDict):
                         else:
                             section = (pts[0], )
                     ret._values[section] = {}
+                if _strip_comments(line).strip() == "":
+                    continue
+                if section is None:
+                    raise ValueError("setting %r without section" % line)
+                try:
+                    setting, value = line.split("=", 1)
+                except ValueError:
+                    setting = line
+                    value = "true"
+                setting = setting.strip().lower()
+                if not _check_variable_name(setting):
+                    raise ValueError("invalid variable name %s" % setting)
+                if value.endswith("\\\n"):
+                    value = value[:-2]
+                    continuation = True
                 else:
-                    if section is None:
-                        raise ValueError("setting %r without section" % line)
-                    try:
-                        setting, value = line.split("=", 1)
-                    except ValueError:
-                        setting = line
-                        value = "true"
-                    setting = setting.strip().lower()
-                    if not _check_variable_name(setting):
-                        raise ValueError("invalid variable name %s" % setting)
-                    if value.endswith("\\\n"):
-                        value = value[:-2]
-                        continuation = True
-                    else:
-                        continuation = False
-                    value = _parse_string(value)
-                    ret._values[section][setting] = value
-                    if not continuation:
-                        setting = None
+                    continuation = False
+                value = _parse_string(value)
+                ret._values[section][setting] = value
+                if not continuation:
+                    setting = None
             else: # continuation line
                 if line.endswith("\\\n"):
                     line = line[:-2]

+ 48 - 9
dulwich/index.py

@@ -59,7 +59,7 @@ def pathjoin(*args):
 
 def read_cache_time(f):
     """Read a cache time.
-    
+
     :param f: File-like object to read from
     :return: Tuple with seconds and nanoseconds
     """
@@ -68,7 +68,7 @@ def read_cache_time(f):
 
 def write_cache_time(f, t):
     """Write a cache time.
-    
+
     :param f: File-like object to write to
     :param t: Time to write (as int, float or tuple with secs and nsecs)
     """
@@ -97,7 +97,7 @@ def read_cache_entry(f):
     # Padding:
     real_size = ((f.tell() - beginoffset + 8) & ~7)
     data = f.read((beginoffset + real_size) - f.tell())
-    return (name, ctime, mtime, dev, ino, mode, uid, gid, size, 
+    return (name, ctime, mtime, dev, ino, mode, uid, gid, size,
             sha_to_hex(sha), flags & ~0x0fff)
 
 
@@ -105,7 +105,7 @@ def write_cache_entry(f, entry):
     """Write an index entry to a file.
 
     :param f: File object
-    :param entry: Entry to write, tuple with: 
+    :param entry: Entry to write, tuple with:
         (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
     """
     beginoffset = f.tell()
@@ -132,7 +132,7 @@ def read_index(f):
 
 def read_index_dict(f):
     """Read an index file and return it as a dictionary.
-    
+
     :param f: File object to read from
     """
     ret = {}
@@ -143,7 +143,7 @@ def read_index_dict(f):
 
 def write_index(f, entries):
     """Write an index file.
-    
+
     :param f: File-like object to write to
     :param entries: Iterable over the entries to write
     """
@@ -167,7 +167,7 @@ def cleanup_mode(mode):
     """Cleanup a mode value.
 
     This will return a mode that can be stored in a tree object.
-    
+
     :param mode: Mode to clean up.
     """
     if stat.S_ISLNK(mode):
@@ -186,7 +186,7 @@ class Index(object):
 
     def __init__(self, filename):
         """Open an index file.
-        
+
         :param filename: Path to the index file
         """
         self._filename = filename
@@ -226,7 +226,7 @@ class Index(object):
 
     def __getitem__(self, name):
         """Retrieve entry by relative path.
-        
+
         :return: tuple with (ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
         """
         return self._byname[name]
@@ -302,7 +302,9 @@ def commit_tree(object_store, blobs):
     :param blobs: Iterable over blob path, sha, mode entries
     :return: SHA1 of the created tree.
     """
+
     trees = {"": {}}
+
     def add_tree(path):
         if path in trees:
             return trees[path]
@@ -387,3 +389,40 @@ def index_entry_from_stat(stat_val, hex_sha, flags, mode=None):
     return (stat_val.st_ctime, stat_val.st_mtime, stat_val.st_dev,
             stat_val.st_ino, mode, stat_val.st_uid,
             stat_val.st_gid, stat_val.st_size, hex_sha, flags)
+
+
+def build_index_from_tree(prefix, index_path, object_store, tree_id):
+    """Generate and materialize index from a tree
+
+    :param tree_id: Tree to materialize
+    :param prefix: Target dir for materialized index files
+    :param index_path: Target path for generated index
+    :param object_store: Non-empty object store holding tree contents
+
+    :note:: existing index is wiped and contents are not merged
+        in a working dir. Suiteable only for fresh clones.
+    """
+
+    index = Index(index_path)
+
+    for entry in object_store.iter_tree_contents(tree_id):
+        full_path = os.path.join(prefix, entry.path)
+
+        if not os.path.exists(os.path.dirname(full_path)):
+            os.makedirs(os.path.dirname(full_path))
+
+        # FIXME: Merge new index into working tree
+        if stat.S_ISLNK(entry.mode):
+            os.symlink(object_store[entry.sha].as_raw_string(), full_path)
+        else:
+            with open(full_path, 'wb') as file:
+                # Write out file
+                file.write(object_store[entry.sha].as_raw_string())
+
+            os.chmod(full_path, entry.mode)
+
+        # Add file to index
+        st = os.lstat(full_path)
+        index[entry.path] = index_entry_from_stat(st, entry.sha, 0)
+
+    index.write()

+ 1 - 1
dulwich/object_store.py

@@ -270,7 +270,7 @@ class PackBasedObjectStore(BaseObjectStore):
 
     def pack_loose_objects(self):
         """Pack loose objects.
-        
+
         :return: Number of objects packed
         """
         objects = set()

+ 20 - 11
dulwich/objects.py

@@ -66,7 +66,7 @@ def S_ISGITLINK(m):
     """Check if a mode indicates a submodule.
 
     :param m: Mode to check
-    :return: a `boolean`
+    :return: a ``boolean``
     """
     return (stat.S_IFMT(m) == S_IFGITLINK)
 
@@ -865,7 +865,7 @@ class Tree(ShaFile):
     def add(self, name, mode, hexsha):
         """Add an entry to the tree.
 
-        :param mode: The mode of the entry as an integral type. Not all 
+        :param mode: The mode of the entry as an integral type. Not all
             possible modes are supported by git; see check() for details.
         :param name: The name of the entry, as a string.
         :param hexsha: The hex SHA of the entry as a string.
@@ -989,29 +989,38 @@ def parse_timezone(text):
         and a boolean indicating whether this was a UTC timezone
         prefixed with a negative sign (-0000).
     """
-    offset = int(text)
-    negative_utc = (offset == 0 and text[0] == '-')
+    # cgit parses the first character as the sign, and the rest
+    #  as an integer (using strtol), which could also be negative.
+    #  We do the same for compatibility. See #697828.
+    if not text[0] in '+-':
+        raise ValueError("Timezone must start with + or - (%(text)s)" % vars())
+    sign = text[0]
+    offset = int(text[1:])
+    if sign == '-':
+        offset = -offset
+    unnecessary_negative_timezone = (offset >= 0 and sign == '-')
     signum = (offset < 0) and -1 or 1
     offset = abs(offset)
     hours = int(offset / 100)
     minutes = (offset % 100)
-    return signum * (hours * 3600 + minutes * 60), negative_utc
+    return (signum * (hours * 3600 + minutes * 60),
+            unnecessary_negative_timezone)
 
 
-def format_timezone(offset, negative_utc=False):
+def format_timezone(offset, unnecessary_negative_timezone=False):
     """Format a timezone for Git serialization.
 
     :param offset: Timezone offset as seconds difference to UTC
-    :param negative_utc: Whether to use a minus sign for UTC
-        (-0000 rather than +0000).
+    :param unnecessary_negative_timezone: Whether to use a minus sign for
+        UTC or positive timezones (-0000 and --700 rather than +0000 / +0700).
     """
     if offset % 60 != 0:
         raise ValueError("Unable to handle non-minute offset.")
-    if offset < 0 or (offset == 0 and negative_utc):
+    if offset < 0 or unnecessary_negative_timezone:
         sign = '-'
+        offset = -offset
     else:
         sign = '+'
-    offset = abs(offset)
     return '%c%02d%02d' % (sign, offset / 3600, (offset / 60) % 60)
 
 
@@ -1035,7 +1044,7 @@ class Commit(ShaFile):
         super(Commit, self).__init__()
         self._parents = []
         self._encoding = None
-        self._extra = {}
+        self._extra = []
         self._author_timezone_neg_utc = False
         self._commit_timezone_neg_utc = False
 

+ 46 - 14
dulwich/repo.py

@@ -22,7 +22,7 @@
 """Repository access.
 
 This module contains the base class for git repositories
-(BaseRepo) and an implementation which uses a repository on 
+(BaseRepo) and an implementation which uses a repository on
 local disk (Repo).
 
 """
@@ -201,7 +201,7 @@ class RefsContainer(object):
             try:
                 ret[key] = self[("%s/%s" % (base, key)).strip("/")]
             except KeyError:
-                continue # Unable to resolve
+                continue  # Unable to resolve
 
         return ret
 
@@ -553,7 +553,7 @@ class DiskRefsContainer(RefsContainer):
                     return header + iter(f).next().rstrip("\r\n")
                 else:
                     # Read only the first 40 bytes
-                    return header + f.read(40-len(SYMREF))
+                    return header + f.read(40 - len(SYMREF))
             finally:
                 f.close()
         except IOError, e:
@@ -635,7 +635,7 @@ class DiskRefsContainer(RefsContainer):
                     f.abort()
                     raise
             try:
-                f.write(new_ref+"\n")
+                f.write(new_ref + "\n")
             except (OSError, IOError):
                 f.abort()
                 raise
@@ -668,7 +668,7 @@ class DiskRefsContainer(RefsContainer):
                 f.abort()
                 return False
             try:
-                f.write(ref+"\n")
+                f.write(ref + "\n")
             except (OSError, IOError):
                 f.abort()
                 raise
@@ -850,8 +850,8 @@ class BaseRepo(object):
     def open_index(self):
         """Open the index for this repository.
 
-        :raises NoIndexPresent: If no index is present
-        :return: Index instance
+        :raise NoIndexPresent: If no index is present
+        :return: The matching `Index`
         """
         raise NotImplementedError(self.open_index)
 
@@ -909,11 +909,19 @@ class BaseRepo(object):
         return self.object_store.get_graph_walker(heads)
 
     def ref(self, name):
-        """Return the SHA1 a ref is pointing to."""
+        """Return the SHA1 a ref is pointing to.
+
+        :param name: Name of the ref to look up
+        :raise KeyError: when the ref (or the one it points to) does not exist
+        :return: SHA1 it is pointing at
+        """
         return self.refs[name]
 
     def get_refs(self):
-        """Get dictionary with all refs."""
+        """Get dictionary with all refs.
+
+        :return: A ``dict`` mapping ref names to SHA1s
+        """
         return self.refs.as_dict()
 
     def head(self):
@@ -1067,6 +1075,7 @@ class BaseRepo(object):
         :param queue_cls: A class to use for a queue of commits, supporting the
             iterator protocol. The constructor takes a single argument, the
             Walker.
+        :return: A `Walker` object
         """
         from dulwich.walk import Walker
         if include is None:
@@ -1141,6 +1150,8 @@ class BaseRepo(object):
             raise ValueError(name)
 
     def _get_user_identity(self):
+        """Determine the identity to use for new commits.
+        """
         config = self.get_config_stack()
         return "%s <%s>" % (
             config.get(("user", ), "name"),
@@ -1289,7 +1300,11 @@ class Repo(BaseRepo):
         return os.path.join(self.controldir(), INDEX_FILENAME)
 
     def open_index(self):
-        """Open the index for this repository."""
+        """Open the index for this repository.
+
+        :raise NoIndexPresent: If no index is present
+        :return: The matching `Index`
+        """
         from dulwich.index import Index
         if not self.has_index():
             raise NoIndexPresent()
@@ -1319,7 +1334,7 @@ class Repo(BaseRepo):
                 try:
                     del index[path]
                 except KeyError:
-                    pass # already removed
+                    pass  # already removed
             else:
                 blob = Blob()
                 f = open(full_path, 'rb')
@@ -1338,7 +1353,9 @@ class Repo(BaseRepo):
         :param target_path: Target path
         :param mkdir: Create the target directory
         :param bare: Whether to create a bare repository
-        :return: Created repository
+        :param origin: Base name for refs in target repository
+            cloned from this repository
+        :return: Created repository as `Repo`
         """
         if not bare:
             target = self.init(target_path, mkdir=mkdir)
@@ -1346,7 +1363,7 @@ class Repo(BaseRepo):
             target = self.init_bare(target_path)
         self.fetch(target)
         target.refs.import_refs(
-            'refs/remotes/'+origin, self.refs.as_dict('refs/heads'))
+            'refs/remotes/' + origin, self.refs.as_dict('refs/heads'))
         target.refs.import_refs(
             'refs/tags', self.refs.as_dict('refs/tags'))
         try:
@@ -1355,6 +1372,18 @@ class Repo(BaseRepo):
                 self.refs['refs/heads/master'])
         except KeyError:
             pass
+
+        # Update target head
+        head, head_sha = self.refs._follow('HEAD')
+        target.refs.set_symbolic_ref('HEAD', head)
+        target['HEAD'] = head_sha
+
+        if not bare:
+            # Checkout HEAD to target dir
+            from dulwich.index import build_index_from_tree
+            build_index_from_tree(target.path, target.index_path(),
+                    target.object_store, target['HEAD'].tree)
+
         return target
 
     def __repr__(self):
@@ -1435,7 +1464,10 @@ class MemoryRepo(BaseRepo):
         return StringIO(contents)
 
     def open_index(self):
-        """Fail to open index for this repo, since it is bare."""
+        """Fail to open index for this repo, since it is bare.
+
+        :raise NoIndexPresent: Raised when no index is present
+        """
         raise NoIndexPresent()
 
     @classmethod

+ 39 - 2
dulwich/tests/__init__.py

@@ -30,10 +30,47 @@ import tempfile
 if sys.version_info >= (2, 7):
     # If Python itself provides an exception, use that
     import unittest
-    from unittest import SkipTest, TestCase
+    from unittest import SkipTest, TestCase as _TestCase
 else:
     import unittest2 as unittest
-    from unittest2 import SkipTest, TestCase
+    from unittest2 import SkipTest, TestCase as _TestCase
+
+
+def get_safe_env(env=None):
+    """Returns the environment "env" (or a copy of "os.environ" by default)
+    modified to avoid side-effects caused by user's ~/.gitconfig"""
+
+    if env is None:
+        env = os.environ.copy()
+    # On Windows it's not enough to set "HOME" to a non-existing
+    # directory. Git.cmd takes the first existing directory out of
+    # "%HOME%", "%HOMEDRIVE%%HOMEPATH%" and "%USERPROFILE%".
+    for e in 'HOME', 'HOMEPATH', 'USERPROFILE':
+        env[e] = '/nosuchdir'
+    return env
+
+
+class TestCase(_TestCase):
+
+    def makeSafeEnv(self):
+        """Create environment with homedirectory-related variables stripped.
+
+        Modifies os.environ for the duration of a test case to avoid
+        side-effects caused by the user's ~/.gitconfig and other
+        files in their home directory.
+        """
+        old_env = os.environ
+        def restore():
+            os.environ = old_env
+        self.addCleanup(restore)
+        new_env = dict(os.environ)
+        for e in ['HOME', 'HOMEPATH', 'USERPROFILE']:
+            new_env[e] = '/nosuchdir'
+        os.environ = new_env
+
+    def setUp(self):
+        super(TestCase, self).setUp()
+        self.makeSafeEnv()
 
 
 class BlackboxTestCase(TestCase):

+ 4 - 3
dulwich/tests/compat/test_client.py

@@ -43,6 +43,7 @@ from dulwich import (
     repo,
     )
 from dulwich.tests import (
+    get_safe_env,
     SkipTest,
     )
 
@@ -172,7 +173,7 @@ class DulwichClientTestBase(object):
         c.archive(self._build_path('/server_new.export'), 'HEAD', f.write)
         f.seek(0)
         tf = tarfile.open(fileobj=f)
-        self.assertEquals(['baz', 'foo'], tf.getnames())
+        self.assertEqual(['baz', 'foo'], tf.getnames())
 
     def test_fetch_pack(self):
         c = self._client()
@@ -210,7 +211,7 @@ class DulwichClientTestBase(object):
         del sendrefs['HEAD']
         gen_pack = lambda have, want: []
         c = self._client()
-        self.assertEquals(dest.refs["refs/heads/abranch"], dummy_commit)
+        self.assertEqual(dest.refs["refs/heads/abranch"], dummy_commit)
         c.send_pack(self._build_path('/dest'), lambda _: sendrefs, gen_pack)
         self.assertFalse("refs/heads/abranch" in dest.refs)
 
@@ -255,7 +256,7 @@ class TestSSHVendor(object):
     def connect_ssh(host, command, username=None, port=None):
         cmd, path = command[0].replace("'", '').split(' ')
         cmd = cmd.split('-', 1)
-        p = subprocess.Popen(cmd + [path], stdin=subprocess.PIPE,
+        p = subprocess.Popen(cmd + [path], env=get_safe_env(), stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
         return client.SubprocessWrapper(p)
 

+ 1 - 1
dulwich/tests/compat/test_pack.py

@@ -63,4 +63,4 @@ class TestPack(PackTests):
                 continue  # non-sha line
             pack_shas.add(sha)
         orig_shas = set(o.id for o in origpack.iterobjects())
-        self.assertEquals(orig_shas, pack_shas)
+        self.assertEqual(orig_shas, pack_shas)

+ 8 - 3
dulwich/tests/compat/test_web.py

@@ -34,6 +34,7 @@ from dulwich.tests import (
     SkipTest,
     )
 from dulwich.web import (
+    make_wsgi_chain,
     HTTPGitApplication,
     HTTPGitRequestHandler,
     )
@@ -101,8 +102,12 @@ class SmartWebTestCase(WebTests, CompatTestCase):
         self.assertFalse('side-band-64k' in caps)
 
     def _make_app(self, backend):
-        app = HTTPGitApplication(backend, handlers=self._handlers())
-        self._check_app(app)
+        app = make_wsgi_chain(backend, handlers=self._handlers())
+        to_check = app
+        # peel back layers until we're at the base application
+        while not issubclass(to_check.__class__, HTTPGitApplication):
+            to_check = to_check.app
+        self._check_app(to_check)
         return app
 
 
@@ -125,7 +130,7 @@ class DumbWebTestCase(WebTests, CompatTestCase):
     """Test cases for dumb HTTP server."""
 
     def _make_app(self, backend):
-        return HTTPGitApplication(backend, dumb=True)
+        return make_wsgi_chain(backend, dumb=True)
 
     def test_push_to_dulwich(self):
         # Note: remove this if dumb pushing is supported

+ 5 - 1
dulwich/tests/compat/utils.py

@@ -30,6 +30,7 @@ from dulwich.repo import Repo
 from dulwich.protocol import TCP_GIT_PORT
 
 from dulwich.tests import (
+    get_safe_env,
     SkipTest,
     TestCase,
     )
@@ -117,13 +118,16 @@ def run_git(args, git_path=_DEFAULT_GIT, input=None, capture_stdout=False,
         False, None will be returned as stdout contents.
     :raise OSError: if the git executable was not found.
     """
+
+    env = get_safe_env(popen_kwargs.pop('env', None))
+
     args = [git_path] + args
     popen_kwargs['stdin'] = subprocess.PIPE
     if capture_stdout:
         popen_kwargs['stdout'] = subprocess.PIPE
     else:
         popen_kwargs.pop('stdout', None)
-    p = subprocess.Popen(args, **popen_kwargs)
+    p = subprocess.Popen(args, env=env, **popen_kwargs)
     stdout, stderr = p.communicate(input=input)
     return (p.returncode, stdout)
 

+ 9 - 9
dulwich/tests/test_blackbox.py

@@ -39,16 +39,16 @@ class GitReceivePackTests(BlackboxTestCase):
     def test_basic(self):
         process = self.run_command("dul-receive-pack", [self.path])
         (stdout, stderr) = process.communicate("0000")
-        self.assertEquals('', stderr)
-        self.assertEquals('0000', stdout[-4:])
-        self.assertEquals(0, process.returncode)
+        self.assertEqual('', stderr)
+        self.assertEqual('0000', stdout[-4:])
+        self.assertEqual(0, process.returncode)
 
     def test_missing_arg(self):
         process = self.run_command("dul-receive-pack", [])
         (stdout, stderr) = process.communicate()
-        self.assertEquals('usage: dul-receive-pack <git-dir>\n', stderr)
-        self.assertEquals('', stdout)
-        self.assertEquals(1, process.returncode)
+        self.assertEqual('usage: dul-receive-pack <git-dir>\n', stderr)
+        self.assertEqual('', stdout)
+        self.assertEqual(1, process.returncode)
 
 
 class GitUploadPackTests(BlackboxTestCase):
@@ -62,6 +62,6 @@ class GitUploadPackTests(BlackboxTestCase):
     def test_missing_arg(self):
         process = self.run_command("dul-upload-pack", [])
         (stdout, stderr) = process.communicate()
-        self.assertEquals('usage: dul-upload-pack <git-dir>\n', stderr)
-        self.assertEquals('', stdout)
-        self.assertEquals(1, process.returncode)
+        self.assertEqual('usage: dul-upload-pack <git-dir>\n', stderr)
+        self.assertEqual('', stdout)
+        self.assertEqual(1, process.returncode)

+ 43 - 26
dulwich/tests/test_client.py

@@ -61,10 +61,10 @@ class GitClientTests(TestCase):
                                   self.rout.write)
 
     def test_caps(self):
-        self.assertEquals(set(['multi_ack', 'side-band-64k', 'ofs-delta',
+        self.assertEqual(set(['multi_ack', 'side-band-64k', 'ofs-delta',
                                'thin-pack', 'multi_ack_detailed']),
                           set(self.client._fetch_capabilities))
-        self.assertEquals(set(['ofs-delta', 'report-status', 'side-band-64k']),
+        self.assertEqual(set(['ofs-delta', 'report-status', 'side-band-64k']),
                           set(self.client._send_capabilities))
 
     def test_archive_ack(self):
@@ -73,7 +73,7 @@ class GitClientTests(TestCase):
             '0000')
         self.rin.seek(0)
         self.client.archive('bla', 'HEAD', None, None)
-        self.assertEquals(self.rout.getvalue(), '0011argument HEAD0000')
+        self.assertEqual(self.rout.getvalue(), '0011argument HEAD0000')
 
     def test_fetch_pack_none(self):
         self.rin.write(
@@ -83,62 +83,62 @@ class GitClientTests(TestCase):
             '0000')
         self.rin.seek(0)
         self.client.fetch_pack('bla', lambda heads: [], None, None, None)
-        self.assertEquals(self.rout.getvalue(), '0000')
+        self.assertEqual(self.rout.getvalue(), '0000')
 
     def test_get_transport_and_path_tcp(self):
         client, path = get_transport_and_path('git://foo.com/bar/baz')
         self.assertTrue(isinstance(client, TCPGitClient))
-        self.assertEquals('foo.com', client._host)
-        self.assertEquals(TCP_GIT_PORT, client._port)
+        self.assertEqual('foo.com', client._host)
+        self.assertEqual(TCP_GIT_PORT, client._port)
         self.assertEqual('/bar/baz', path)
 
         client, path = get_transport_and_path('git://foo.com:1234/bar/baz')
         self.assertTrue(isinstance(client, TCPGitClient))
-        self.assertEquals('foo.com', client._host)
-        self.assertEquals(1234, client._port)
+        self.assertEqual('foo.com', client._host)
+        self.assertEqual(1234, client._port)
         self.assertEqual('/bar/baz', path)
 
     def test_get_transport_and_path_ssh_explicit(self):
         client, path = get_transport_and_path('git+ssh://foo.com/bar/baz')
         self.assertTrue(isinstance(client, SSHGitClient))
-        self.assertEquals('foo.com', client.host)
-        self.assertEquals(None, client.port)
-        self.assertEquals(None, client.username)
+        self.assertEqual('foo.com', client.host)
+        self.assertEqual(None, client.port)
+        self.assertEqual(None, client.username)
         self.assertEqual('/bar/baz', path)
 
         client, path = get_transport_and_path(
             'git+ssh://foo.com:1234/bar/baz')
         self.assertTrue(isinstance(client, SSHGitClient))
-        self.assertEquals('foo.com', client.host)
-        self.assertEquals(1234, client.port)
+        self.assertEqual('foo.com', client.host)
+        self.assertEqual(1234, client.port)
         self.assertEqual('/bar/baz', path)
 
     def test_get_transport_and_path_ssh_implicit(self):
         client, path = get_transport_and_path('foo:/bar/baz')
         self.assertTrue(isinstance(client, SSHGitClient))
-        self.assertEquals('foo', client.host)
-        self.assertEquals(None, client.port)
-        self.assertEquals(None, client.username)
+        self.assertEqual('foo', client.host)
+        self.assertEqual(None, client.port)
+        self.assertEqual(None, client.username)
         self.assertEqual('/bar/baz', path)
 
         client, path = get_transport_and_path('foo.com:/bar/baz')
         self.assertTrue(isinstance(client, SSHGitClient))
-        self.assertEquals('foo.com', client.host)
-        self.assertEquals(None, client.port)
-        self.assertEquals(None, client.username)
+        self.assertEqual('foo.com', client.host)
+        self.assertEqual(None, client.port)
+        self.assertEqual(None, client.username)
         self.assertEqual('/bar/baz', path)
 
         client, path = get_transport_and_path('user@foo.com:/bar/baz')
         self.assertTrue(isinstance(client, SSHGitClient))
-        self.assertEquals('foo.com', client.host)
-        self.assertEquals(None, client.port)
-        self.assertEquals('user', client.username)
+        self.assertEqual('foo.com', client.host)
+        self.assertEqual(None, client.port)
+        self.assertEqual('user', client.username)
         self.assertEqual('/bar/baz', path)
 
     def test_get_transport_and_path_subprocess(self):
         client, path = get_transport_and_path('foo.bar/baz')
         self.assertTrue(isinstance(client, SubprocessGitClient))
-        self.assertEquals('foo.bar/baz', path)
+        self.assertEqual('foo.bar/baz', path)
 
     def test_get_transport_and_path_error(self):
         # Need to use a known urlparse.uses_netloc URL scheme to get the
@@ -150,7 +150,24 @@ class GitClientTests(TestCase):
         url = 'https://github.com/jelmer/dulwich'
         client, path = get_transport_and_path(url)
         self.assertTrue(isinstance(client, HttpGitClient))
-        self.assertEquals('/jelmer/dulwich', path)
+        self.assertEqual('/jelmer/dulwich', path)
+
+    def test_send_pack_no_sideband64k_with_update_ref_error(self):
+        # No side-bank-64k reported by server shouldn't try to parse
+        # side band data
+        pkts = ['55dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 capabilities^{}\x00 report-status ofs-delta\n',
+                '',
+                "unpack ok",
+                "ng refs/foo/bar pre-receive hook declined",
+                '']
+        for pkt in pkts:
+            if pkt == '':
+                self.rin.write("0000")
+            else:
+                self.rin.write("%04x%s" % (len(pkt)+4, pkt))
+        self.rin.seek(0)
+        self.assertRaises(UpdateRefsError,
+            self.client.send_pack, "blah", lambda x: {}, lambda h,w: [])
 
 
 class SSHGitClientTests(TestCase):
@@ -160,13 +177,13 @@ class SSHGitClientTests(TestCase):
         self.client = SSHGitClient('git.samba.org')
 
     def test_default_command(self):
-        self.assertEquals('git-upload-pack',
+        self.assertEqual('git-upload-pack',
                 self.client._get_cmd_path('upload-pack'))
 
     def test_alternative_command_path(self):
         self.client.alternative_paths['upload-pack'] = (
             '/usr/lib/git/git-upload-pack')
-        self.assertEquals('/usr/lib/git/git-upload-pack',
+        self.assertEqual('/usr/lib/git/git-upload-pack',
             self.client._get_cmd_path('upload-pack'))
 
 

+ 41 - 37
dulwich/tests/test_config.py

@@ -42,7 +42,7 @@ class ConfigFileTests(TestCase):
         ConfigFile()
 
     def test_eq(self):
-        self.assertEquals(ConfigFile(), ConfigFile())
+        self.assertEqual(ConfigFile(), ConfigFile())
 
     def test_default_config(self):
         cf = self.from_file("""[core]
@@ -51,7 +51,7 @@ class ConfigFileTests(TestCase):
 	bare = false
 	logallrefupdates = true
 """)
-        self.assertEquals(ConfigFile({("core", ): {
+        self.assertEqual(ConfigFile({("core", ): {
             "repositoryformatversion": "0",
             "filemode": "true",
             "bare": "false",
@@ -59,37 +59,37 @@ class ConfigFileTests(TestCase):
 
     def test_from_file_empty(self):
         cf = self.from_file("")
-        self.assertEquals(ConfigFile(), cf)
+        self.assertEqual(ConfigFile(), cf)
 
     def test_empty_line_before_section(self):
         cf = self.from_file("\n[section]\n")
-        self.assertEquals(ConfigFile({("section", ): {}}), cf)
+        self.assertEqual(ConfigFile({("section", ): {}}), cf)
 
     def test_comment_before_section(self):
         cf = self.from_file("# foo\n[section]\n")
-        self.assertEquals(ConfigFile({("section", ): {}}), cf)
+        self.assertEqual(ConfigFile({("section", ): {}}), cf)
 
     def test_comment_after_section(self):
         cf = self.from_file("[section] # foo\n")
-        self.assertEquals(ConfigFile({("section", ): {}}), cf)
+        self.assertEqual(ConfigFile({("section", ): {}}), cf)
 
     def test_comment_after_variable(self):
         cf = self.from_file("[section]\nbar= foo # a comment\n")
-        self.assertEquals(ConfigFile({("section", ): {"bar": "foo"}}), cf)
+        self.assertEqual(ConfigFile({("section", ): {"bar": "foo"}}), cf)
 
     def test_from_file_section(self):
         cf = self.from_file("[core]\nfoo = bar\n")
-        self.assertEquals("bar", cf.get(("core", ), "foo"))
-        self.assertEquals("bar", cf.get(("core", "foo"), "foo"))
+        self.assertEqual("bar", cf.get(("core", ), "foo"))
+        self.assertEqual("bar", cf.get(("core", "foo"), "foo"))
 
     def test_from_file_section_case_insensitive(self):
         cf = self.from_file("[cOre]\nfOo = bar\n")
-        self.assertEquals("bar", cf.get(("core", ), "foo"))
-        self.assertEquals("bar", cf.get(("core", "foo"), "foo"))
+        self.assertEqual("bar", cf.get(("core", ), "foo"))
+        self.assertEqual("bar", cf.get(("core", "foo"), "foo"))
 
     def test_from_file_with_mixed_quoted(self):
         cf = self.from_file("[core]\nfoo = \"bar\"la\n")
-        self.assertEquals("barla", cf.get(("core", ), "foo"))
+        self.assertEqual("barla", cf.get(("core", ), "foo"))
 
     def test_from_file_with_open_quoted(self):
         self.assertRaises(ValueError,
@@ -99,24 +99,24 @@ class ConfigFileTests(TestCase):
         cf = self.from_file(
             "[core]\n"
             'foo = " bar"\n')
-        self.assertEquals(" bar", cf.get(("core", ), "foo"))
+        self.assertEqual(" bar", cf.get(("core", ), "foo"))
 
     def test_from_file_with_interrupted_line(self):
         cf = self.from_file(
             "[core]\n"
             'foo = bar\\\n'
             ' la\n')
-        self.assertEquals("barla", cf.get(("core", ), "foo"))
+        self.assertEqual("barla", cf.get(("core", ), "foo"))
 
     def test_from_file_with_boolean_setting(self):
         cf = self.from_file(
             "[core]\n"
             'foo\n')
-        self.assertEquals("true", cf.get(("core", ), "foo"))
+        self.assertEqual("true", cf.get(("core", ), "foo"))
 
     def test_from_file_subsection(self):
         cf = self.from_file("[branch \"foo\"]\nfoo = bar\n")
-        self.assertEquals("bar", cf.get(("branch", "foo"), "foo"))
+        self.assertEqual("bar", cf.get(("branch", "foo"), "foo"))
 
     def test_from_file_subsection_invalid(self):
         self.assertRaises(ValueError,
@@ -124,27 +124,31 @@ class ConfigFileTests(TestCase):
 
     def test_from_file_subsection_not_quoted(self):
         cf = self.from_file("[branch.foo]\nfoo = bar\n")
-        self.assertEquals("bar", cf.get(("branch", "foo"), "foo"))
+        self.assertEqual("bar", cf.get(("branch", "foo"), "foo"))
 
     def test_write_to_file_empty(self):
         c = ConfigFile()
         f = StringIO()
         c.write_to_file(f)
-        self.assertEquals("", f.getvalue())
+        self.assertEqual("", f.getvalue())
 
     def test_write_to_file_section(self):
         c = ConfigFile()
         c.set(("core", ), "foo", "bar")
         f = StringIO()
         c.write_to_file(f)
-        self.assertEquals("[core]\nfoo = bar\n", f.getvalue())
+        self.assertEqual("[core]\nfoo = bar\n", f.getvalue())
 
     def test_write_to_file_subsection(self):
         c = ConfigFile()
         c.set(("branch", "blie"), "foo", "bar")
         f = StringIO()
         c.write_to_file(f)
-        self.assertEquals("[branch \"blie\"]\nfoo = bar\n", f.getvalue())
+        self.assertEqual("[branch \"blie\"]\nfoo = bar\n", f.getvalue())
+
+    def test_same_line(self):
+        cf = self.from_file("[branch.foo] foo = bar\n")
+        self.assertEqual("bar", cf.get(("branch", "foo"), "foo"))
 
 
 class ConfigDictTests(TestCase):
@@ -153,9 +157,9 @@ class ConfigDictTests(TestCase):
         cd = ConfigDict()
         self.assertRaises(KeyError, cd.get, "foo", "core")
         cd.set(("core", ), "foo", "bla")
-        self.assertEquals("bla", cd.get(("core", ), "foo"))
+        self.assertEqual("bla", cd.get(("core", ), "foo"))
         cd.set(("core", ), "foo", "bloe")
-        self.assertEquals("bloe", cd.get(("core", ), "foo"))
+        self.assertEqual("bloe", cd.get(("core", ), "foo"))
 
     def test_get_boolean(self):
         cd = ConfigDict()
@@ -176,50 +180,50 @@ class StackedConfigTests(TestCase):
 class UnescapeTests(TestCase):
 
     def test_nothing(self):
-        self.assertEquals("", _unescape_value(""))
+        self.assertEqual("", _unescape_value(""))
 
     def test_tab(self):
-        self.assertEquals("\tbar\t", _unescape_value("\\tbar\\t"))
+        self.assertEqual("\tbar\t", _unescape_value("\\tbar\\t"))
 
     def test_newline(self):
-        self.assertEquals("\nbar\t", _unescape_value("\\nbar\\t"))
+        self.assertEqual("\nbar\t", _unescape_value("\\nbar\\t"))
 
     def test_quote(self):
-        self.assertEquals("\"foo\"", _unescape_value("\\\"foo\\\""))
+        self.assertEqual("\"foo\"", _unescape_value("\\\"foo\\\""))
 
 
 class EscapeValueTests(TestCase):
 
     def test_nothing(self):
-        self.assertEquals("foo", _escape_value("foo"))
+        self.assertEqual("foo", _escape_value("foo"))
 
     def test_backslash(self):
-        self.assertEquals("foo\\\\", _escape_value("foo\\"))
+        self.assertEqual("foo\\\\", _escape_value("foo\\"))
 
     def test_newline(self):
-        self.assertEquals("foo\\n", _escape_value("foo\n"))
+        self.assertEqual("foo\\n", _escape_value("foo\n"))
 
 
 class FormatStringTests(TestCase):
 
     def test_quoted(self):
-        self.assertEquals('" foo"', _format_string(" foo"))
-        self.assertEquals('"\\tfoo"', _format_string("\tfoo"))
+        self.assertEqual('" foo"', _format_string(" foo"))
+        self.assertEqual('"\\tfoo"', _format_string("\tfoo"))
 
     def test_not_quoted(self):
-        self.assertEquals('foo', _format_string("foo"))
-        self.assertEquals('foo bar', _format_string("foo bar"))
+        self.assertEqual('foo', _format_string("foo"))
+        self.assertEqual('foo bar', _format_string("foo bar"))
 
 
 class ParseStringTests(TestCase):
 
     def test_quoted(self):
-        self.assertEquals(' foo', _parse_string('" foo"'))
-        self.assertEquals('\tfoo', _parse_string('"\\tfoo"'))
+        self.assertEqual(' foo', _parse_string('" foo"'))
+        self.assertEqual('\tfoo', _parse_string('"\\tfoo"'))
 
     def test_not_quoted(self):
-        self.assertEquals('foo', _parse_string("foo"))
-        self.assertEquals('foo bar', _parse_string("foo bar"))
+        self.assertEqual('foo', _parse_string("foo"))
+        self.assertEqual('foo bar', _parse_string("foo bar"))
 
 
 class CheckVariableNameTests(TestCase):

+ 17 - 17
dulwich/tests/test_fastexport.py

@@ -54,7 +54,7 @@ class GitFastExporterTests(TestCase):
         b = Blob()
         b.data = "fooBAR"
         self.fastexporter.emit_blob(b)
-        self.assertEquals('blob\nmark :1\ndata 6\nfooBAR\n',
+        self.assertEqual('blob\nmark :1\ndata 6\nfooBAR\n',
             self.stream.getvalue())
 
     def test_emit_commit(self):
@@ -70,7 +70,7 @@ class GitFastExporterTests(TestCase):
         c.tree = t.id
         self.store.add_objects([(b, None), (t, None), (c, None)])
         self.fastexporter.emit_commit(c, "refs/heads/master")
-        self.assertEquals("""blob
+        self.assertEqual("""blob
 mark :1
 data 3
 FOO
@@ -104,15 +104,15 @@ class GitImportProcessorTests(TestCase):
             "FOO", None, [], [])
         self.processor.commit_handler(cmd)
         commit = self.repo[self.processor.last_commit]
-        self.assertEquals("Jelmer <jelmer@samba.org>", commit.author)
-        self.assertEquals("Jelmer <jelmer@samba.org>", commit.committer)
-        self.assertEquals("FOO", commit.message)
-        self.assertEquals([], commit.parents)
-        self.assertEquals(432432432.0, commit.commit_time)
-        self.assertEquals(432432432.0, commit.author_time)
-        self.assertEquals(3600, commit.commit_timezone)
-        self.assertEquals(3600, commit.author_timezone)
-        self.assertEquals(commit, self.repo["refs/heads/foo"])
+        self.assertEqual("Jelmer <jelmer@samba.org>", commit.author)
+        self.assertEqual("Jelmer <jelmer@samba.org>", commit.committer)
+        self.assertEqual("FOO", commit.message)
+        self.assertEqual([], commit.parents)
+        self.assertEqual(432432432.0, commit.commit_time)
+        self.assertEqual(432432432.0, commit.author_time)
+        self.assertEqual(3600, commit.commit_timezone)
+        self.assertEqual(3600, commit.author_timezone)
+        self.assertEqual(commit, self.repo["refs/heads/foo"])
 
     def test_import_stream(self):
         markers = self.processor.import_stream(StringIO("""blob
@@ -128,7 +128,7 @@ data 20
 M 100644 :1 a
 
 """))
-        self.assertEquals(2, len(markers))
+        self.assertEqual(2, len(markers))
         self.assertTrue(isinstance(self.repo[markers["1"]], Blob))
         self.assertTrue(isinstance(self.repo[markers["2"]], Commit))
 
@@ -142,7 +142,7 @@ M 100644 :1 a
             "FOO", None, [], [commands.FileModifyCommand("path", 0100644, ":23", None)])
         self.processor.commit_handler(cmd)
         commit = self.repo[self.processor.last_commit]
-        self.assertEquals([
+        self.assertEqual([
             ('path', 0100644, '6320cd248dd8aeaab759d5871f8781b5c0505172')],
             self.repo[commit.tree].items())
 
@@ -176,7 +176,7 @@ M 100644 :1 a
         from fastimport import commands
         self.simple_commit()
         commit = self.make_file_commit([commands.FileCopyCommand("path", "new_path")])
-        self.assertEquals([
+        self.assertEqual([
             ('new_path', 0100644, '6320cd248dd8aeaab759d5871f8781b5c0505172'),
             ('path', 0100644, '6320cd248dd8aeaab759d5871f8781b5c0505172'),
             ], self.repo[commit.tree].items())
@@ -185,7 +185,7 @@ M 100644 :1 a
         from fastimport import commands
         self.simple_commit()
         commit = self.make_file_commit([commands.FileRenameCommand("path", "new_path")])
-        self.assertEquals([
+        self.assertEqual([
             ('new_path', 0100644, '6320cd248dd8aeaab759d5871f8781b5c0505172'),
             ], self.repo[commit.tree].items())
 
@@ -193,10 +193,10 @@ M 100644 :1 a
         from fastimport import commands
         self.simple_commit()
         commit = self.make_file_commit([commands.FileDeleteCommand("path")])
-        self.assertEquals([], self.repo[commit.tree].items())
+        self.assertEqual([], self.repo[commit.tree].items())
 
     def test_file_deleteall(self):
         from fastimport import commands
         self.simple_commit()
         commit = self.make_file_commit([commands.FileDeleteAllCommand()])
-        self.assertEquals([], self.repo[commit.tree].items())
+        self.assertEqual([], self.repo[commit.tree].items())

+ 14 - 14
dulwich/tests/test_file.py

@@ -56,7 +56,7 @@ class FancyRenameTests(TestCase):
         self.assertFalse(os.path.exists(self.foo))
 
         new_f = open(self.bar, 'rb')
-        self.assertEquals('foo contents', new_f.read())
+        self.assertEqual('foo contents', new_f.read())
         new_f.close()
          
     def test_dest_exists(self):
@@ -65,7 +65,7 @@ class FancyRenameTests(TestCase):
         self.assertFalse(os.path.exists(self.foo))
 
         new_f = open(self.bar, 'rb')
-        self.assertEquals('foo contents', new_f.read())
+        self.assertEqual('foo contents', new_f.read())
         new_f.close()
 
     def test_dest_opened(self):
@@ -78,11 +78,11 @@ class FancyRenameTests(TestCase):
         self.assertTrue(os.path.exists(self.path('foo')))
 
         new_f = open(self.foo, 'rb')
-        self.assertEquals('foo contents', new_f.read())
+        self.assertEqual('foo contents', new_f.read())
         new_f.close()
 
         new_f = open(self.bar, 'rb')
-        self.assertEquals('bar contents', new_f.read())
+        self.assertEqual('bar contents', new_f.read())
         new_f.close()
 
 
@@ -113,15 +113,15 @@ class GitFileTests(TestCase):
     def test_readonly(self):
         f = GitFile(self.path('foo'), 'rb')
         self.assertTrue(isinstance(f, file))
-        self.assertEquals('foo contents', f.read())
-        self.assertEquals('', f.read())
+        self.assertEqual('foo contents', f.read())
+        self.assertEqual('', f.read())
         f.seek(4)
-        self.assertEquals('contents', f.read())
+        self.assertEqual('contents', f.read())
         f.close()
 
     def test_default_mode(self):
         f = GitFile(self.path('foo'))
-        self.assertEquals('foo contents', f.read())
+        self.assertEqual('foo contents', f.read())
         f.close()
 
     def test_write(self):
@@ -129,7 +129,7 @@ class GitFileTests(TestCase):
         foo_lock = '%s.lock' % foo
 
         orig_f = open(foo, 'rb')
-        self.assertEquals(orig_f.read(), 'foo contents')
+        self.assertEqual(orig_f.read(), 'foo contents')
         orig_f.close()
 
         self.assertFalse(os.path.exists(foo_lock))
@@ -145,7 +145,7 @@ class GitFileTests(TestCase):
         self.assertFalse(os.path.exists(foo_lock))
 
         new_f = open(foo, 'rb')
-        self.assertEquals('new contents', new_f.read())
+        self.assertEqual('new contents', new_f.read())
         new_f.close()
 
     def test_open_twice(self):
@@ -156,13 +156,13 @@ class GitFileTests(TestCase):
             f2 = GitFile(foo, 'wb')
             self.fail()
         except OSError, e:
-            self.assertEquals(errno.EEXIST, e.errno)
+            self.assertEqual(errno.EEXIST, e.errno)
         f1.write(' contents')
         f1.close()
 
         # Ensure trying to open twice doesn't affect original.
         f = open(foo, 'rb')
-        self.assertEquals('new contents', f.read())
+        self.assertEqual('new contents', f.read())
         f.close()
 
     def test_abort(self):
@@ -170,7 +170,7 @@ class GitFileTests(TestCase):
         foo_lock = '%s.lock' % foo
 
         orig_f = open(foo, 'rb')
-        self.assertEquals(orig_f.read(), 'foo contents')
+        self.assertEqual(orig_f.read(), 'foo contents')
         orig_f.close()
 
         f = GitFile(foo, 'wb')
@@ -180,7 +180,7 @@ class GitFileTests(TestCase):
         self.assertFalse(os.path.exists(foo_lock))
 
         new_orig_f = open(foo, 'rb')
-        self.assertEquals(new_orig_f.read(), 'foo contents')
+        self.assertEqual(new_orig_f.read(), 'foo contents')
         new_orig_f.close()
 
     def test_abort_close(self):

+ 157 - 23
dulwich/tests/test_index.py

@@ -30,6 +30,7 @@ import tempfile
 
 from dulwich.index import (
     Index,
+    build_index_from_tree,
     cleanup_mode,
     commit_tree,
     index_entry_from_stat,
@@ -42,7 +43,9 @@ from dulwich.object_store import (
     )
 from dulwich.objects import (
     Blob,
+    Tree,
     )
+from dulwich.repo import Repo
 from dulwich.tests import TestCase
 
 
@@ -57,20 +60,20 @@ class IndexTestCase(TestCase):
 class SimpleIndexTestCase(IndexTestCase):
 
     def test_len(self):
-        self.assertEquals(1, len(self.get_simple_index("index")))
+        self.assertEqual(1, len(self.get_simple_index("index")))
 
     def test_iter(self):
-        self.assertEquals(['bla'], list(self.get_simple_index("index")))
+        self.assertEqual(['bla'], list(self.get_simple_index("index")))
 
     def test_getitem(self):
-        self.assertEquals(((1230680220, 0), (1230680220, 0), 2050, 3761020,
+        self.assertEqual(((1230680220, 0), (1230680220, 0), 2050, 3761020,
                            33188, 1000, 1000, 0,
                            'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', 0),
                           self.get_simple_index("index")["bla"])
 
     def test_empty(self):
         i = self.get_simple_index("notanindex")
-        self.assertEquals(0, len(i))
+        self.assertEqual(0, len(i))
         self.assertFalse(os.path.exists(i._filename))
 
 
@@ -96,7 +99,7 @@ class SimpleIndexWriterTestCase(IndexTestCase):
             x.close()
         x = open(filename, 'r')
         try:
-            self.assertEquals(entries, list(read_index(x)))
+            self.assertEqual(entries, list(read_index(x)))
         finally:
             x.close()
 
@@ -113,9 +116,9 @@ class CommitTreeTests(TestCase):
         self.store.add_object(blob)
         blobs = [("bla", blob.id, stat.S_IFREG)]
         rootid = commit_tree(self.store, blobs)
-        self.assertEquals(rootid, "1a1e80437220f9312e855c37ac4398b68e5c1d50")
-        self.assertEquals((stat.S_IFREG, blob.id), self.store[rootid]["bla"])
-        self.assertEquals(set([rootid, blob.id]), set(self.store._data.keys()))
+        self.assertEqual(rootid, "1a1e80437220f9312e855c37ac4398b68e5c1d50")
+        self.assertEqual((stat.S_IFREG, blob.id), self.store[rootid]["bla"])
+        self.assertEqual(set([rootid, blob.id]), set(self.store._data.keys()))
 
     def test_nested(self):
         blob = Blob()
@@ -123,31 +126,31 @@ class CommitTreeTests(TestCase):
         self.store.add_object(blob)
         blobs = [("bla/bar", blob.id, stat.S_IFREG)]
         rootid = commit_tree(self.store, blobs)
-        self.assertEquals(rootid, "d92b959b216ad0d044671981196781b3258fa537")
+        self.assertEqual(rootid, "d92b959b216ad0d044671981196781b3258fa537")
         dirid = self.store[rootid]["bla"][1]
-        self.assertEquals(dirid, "c1a1deb9788150829579a8b4efa6311e7b638650")
-        self.assertEquals((stat.S_IFDIR, dirid), self.store[rootid]["bla"])
-        self.assertEquals((stat.S_IFREG, blob.id), self.store[dirid]["bar"])
-        self.assertEquals(set([rootid, dirid, blob.id]),
+        self.assertEqual(dirid, "c1a1deb9788150829579a8b4efa6311e7b638650")
+        self.assertEqual((stat.S_IFDIR, dirid), self.store[rootid]["bla"])
+        self.assertEqual((stat.S_IFREG, blob.id), self.store[dirid]["bar"])
+        self.assertEqual(set([rootid, dirid, blob.id]),
                           set(self.store._data.keys()))
 
 
 class CleanupModeTests(TestCase):
 
     def test_file(self):
-        self.assertEquals(0100644, cleanup_mode(0100000))
+        self.assertEqual(0100644, cleanup_mode(0100000))
 
     def test_executable(self):
-        self.assertEquals(0100755, cleanup_mode(0100711))
+        self.assertEqual(0100755, cleanup_mode(0100711))
 
     def test_symlink(self):
-        self.assertEquals(0120000, cleanup_mode(0120711))
+        self.assertEqual(0120000, cleanup_mode(0120711))
 
     def test_dir(self):
-        self.assertEquals(0040000, cleanup_mode(040531))
+        self.assertEqual(0040000, cleanup_mode(040531))
 
     def test_submodule(self):
-        self.assertEquals(0160000, cleanup_mode(0160744))
+        self.assertEqual(0160000, cleanup_mode(0160744))
 
 
 class WriteCacheTimeTests(TestCase):
@@ -159,17 +162,17 @@ class WriteCacheTimeTests(TestCase):
     def test_write_int(self):
         f = StringIO()
         write_cache_time(f, 434343)
-        self.assertEquals(struct.pack(">LL", 434343, 0), f.getvalue())
+        self.assertEqual(struct.pack(">LL", 434343, 0), f.getvalue())
 
     def test_write_tuple(self):
         f = StringIO()
         write_cache_time(f, (434343, 21))
-        self.assertEquals(struct.pack(">LL", 434343, 21), f.getvalue())
+        self.assertEqual(struct.pack(">LL", 434343, 21), f.getvalue())
 
     def test_write_float(self):
         f = StringIO()
         write_cache_time(f, 434343.000000021)
-        self.assertEquals(struct.pack(">LL", 434343, 21), f.getvalue())
+        self.assertEqual(struct.pack(">LL", 434343, 21), f.getvalue())
 
 
 class IndexEntryFromStatTests(TestCase):
@@ -179,7 +182,7 @@ class IndexEntryFromStatTests(TestCase):
                 154, 1000, 1000, 12288,
                 1323629595, 1324180496, 1324180496))
         entry = index_entry_from_stat(st, "22" * 20, 0)
-        self.assertEquals(entry, (
+        self.assertEqual(entry, (
             1324180496,
             1324180496,
             64769L,
@@ -197,7 +200,7 @@ class IndexEntryFromStatTests(TestCase):
                 1323629595, 1324180496, 1324180496))
         entry = index_entry_from_stat(st, "22" * 20, 0,
                 mode=stat.S_IFREG + 0755)
-        self.assertEquals(entry, (
+        self.assertEqual(entry, (
             1324180496,
             1324180496,
             64769L,
@@ -208,3 +211,134 @@ class IndexEntryFromStatTests(TestCase):
             12288,
             '2222222222222222222222222222222222222222',
             0))
+
+
+class BuildIndexTests(TestCase):
+
+    def assertReasonableIndexEntry(self, index_entry, values):
+        delta = 1000000
+        self.assertEquals(index_entry[0], index_entry[1])  # ctime and atime
+        self.assertTrue(index_entry[0] > values[0] - delta)
+        self.assertEquals(index_entry[4], values[4])  # mode
+        self.assertEquals(index_entry[5], values[5])  # uid
+        self.assertTrue(index_entry[6] in values[6])  # gid
+        self.assertEquals(index_entry[7], values[7])  # filesize
+        self.assertEquals(index_entry[8], values[8])  # sha
+
+    def assertFileContents(self, path, contents, symlink=False):
+        if symlink:
+            self.assertEquals(os.readlink(path), contents)
+        else:
+            f = open(path, 'rb')
+            try:
+                self.assertEquals(f.read(), contents)
+            finally:
+                f.close()
+
+    def test_empty(self):
+        repo_dir = tempfile.mkdtemp()
+        repo = Repo.init(repo_dir)
+        self.addCleanup(shutil.rmtree, repo_dir)
+
+        tree = Tree()
+        repo.object_store.add_object(tree)
+
+        build_index_from_tree(repo.path, repo.index_path(),
+                repo.object_store, tree.id)
+
+        # Verify index entries
+        index = repo.open_index()
+        self.assertEquals(len(index), 0)
+
+        # Verify no files
+        self.assertEquals(['.git'], os.listdir(repo.path))
+
+    def test_nonempty(self):
+        if os.name != 'posix':
+            self.skip("test depends on POSIX shell")
+
+        repo_dir = tempfile.mkdtemp()
+        repo = Repo.init(repo_dir)
+        self.addCleanup(shutil.rmtree, repo_dir)
+
+        # Populate repo
+        filea = Blob.from_string('file a')
+        fileb = Blob.from_string('file b')
+        filed = Blob.from_string('file d')
+        filee = Blob.from_string('d')
+
+        tree = Tree()
+        tree['a'] = (stat.S_IFREG | 0644, filea.id)
+        tree['b'] = (stat.S_IFREG | 0644, fileb.id)
+        tree['c/d'] = (stat.S_IFREG | 0644, filed.id)
+        tree['c/e'] = (stat.S_IFLNK, filee.id)  # symlink
+
+        repo.object_store.add_objects([(o, None)
+            for o in [filea, fileb, filed, filee, tree]])
+
+        build_index_from_tree(repo.path, repo.index_path(),
+                repo.object_store, tree.id)
+
+        # Verify index entries
+        import time
+        ctime = time.time()
+        index = repo.open_index()
+        self.assertEquals(len(index), 4)
+
+        # filea
+        apath = os.path.join(repo.path, 'a')
+        self.assertTrue(os.path.exists(apath))
+        self.assertReasonableIndexEntry(index['a'], (
+            ctime, ctime,
+            None, None,
+            stat.S_IFREG | 0644,
+            os.getuid(), os.getgroups(),
+            6,
+            filea.id,
+            None))
+        self.assertFileContents(apath, 'file a')
+
+        # fileb
+        bpath = os.path.join(repo.path, 'b')
+        self.assertTrue(os.path.exists(bpath))
+        self.assertReasonableIndexEntry(index['b'], (
+            ctime, ctime,
+            None, None,
+            stat.S_IFREG | 0644,
+            os.getuid(), os.getgroups(),
+            6,
+            fileb.id,
+            None))
+        self.assertFileContents(bpath, 'file b')
+
+        # filed
+        dpath = os.path.join(repo.path, 'c', 'd')
+        self.assertTrue(os.path.exists(dpath))
+        self.assertReasonableIndexEntry(index['c/d'], (
+            ctime, ctime,
+            None, None,
+            stat.S_IFREG | 0644,
+            os.getuid(), os.getgroups(),
+            6,
+            filed.id,
+            None))
+        self.assertFileContents(dpath, 'file d')
+
+        # symlink to d
+        epath = os.path.join(repo.path, 'c', 'e')
+        self.assertTrue(os.path.exists(epath))
+        self.assertReasonableIndexEntry(index['c/e'], (
+            ctime, ctime,
+            None, None,
+            stat.S_IFLNK,
+            os.getuid(), os.getgroups(),
+            1,
+            filee.id,
+            None))
+        self.assertFileContents(epath, 'd', symlink=True)
+
+        # Verify no extra files
+        self.assertEquals(['.git', 'a', 'b', 'c'],
+            sorted(os.listdir(repo.path)))
+        self.assertEquals(['d', 'e'], 
+            sorted(os.listdir(os.path.join(repo.path, 'c'))))

+ 1 - 1
dulwich/tests/test_lru_cache.py

@@ -228,7 +228,7 @@ class TestLRUCache(TestCase):
         cache.add(1, 10)
         cache.add(2, 20)
         self.assertEqual(20, cache.get(2))
-        self.assertEquals(None, cache.get(3))
+        self.assertEqual(None, cache.get(3))
         obj = object()
         self.assertTrue(obj is cache.get(3, obj))
         self.assertEqual([2, 1], [n.key for n in cache._walk_lru()])

+ 29 - 29
dulwich/tests/test_object_store.py

@@ -63,15 +63,15 @@ testobject = make_object(Blob, data="yummy data")
 class ObjectStoreTests(object):
 
     def test_determine_wants_all(self):
-        self.assertEquals(["1" * 40],
+        self.assertEqual(["1" * 40],
             self.store.determine_wants_all({"refs/heads/foo": "1" * 40}))
 
     def test_determine_wants_all_zero(self):
-        self.assertEquals([],
+        self.assertEqual([],
             self.store.determine_wants_all({"refs/heads/foo": "0" * 40}))
 
     def test_iter(self):
-        self.assertEquals([], list(self.store))
+        self.assertEqual([], list(self.store))
 
     def test_get_nonexistant(self):
         self.assertRaises(KeyError, lambda: self.store["a" * 40])
@@ -89,18 +89,18 @@ class ObjectStoreTests(object):
 
     def test_add_object(self):
         self.store.add_object(testobject)
-        self.assertEquals(set([testobject.id]), set(self.store))
+        self.assertEqual(set([testobject.id]), set(self.store))
         self.assertTrue(testobject.id in self.store)
         r = self.store[testobject.id]
-        self.assertEquals(r, testobject)
+        self.assertEqual(r, testobject)
 
     def test_add_objects(self):
         data = [(testobject, "mypath")]
         self.store.add_objects(data)
-        self.assertEquals(set([testobject.id]), set(self.store))
+        self.assertEqual(set([testobject.id]), set(self.store))
         self.assertTrue(testobject.id in self.store)
         r = self.store[testobject.id]
-        self.assertEquals(r, testobject)
+        self.assertEqual(r, testobject)
 
     def test_tree_changes(self):
         blob_a1 = make_object(Blob, data='a1')
@@ -114,9 +114,9 @@ class ObjectStoreTests(object):
         blobs_2 = [('a', blob_a2.id, 0100644), ('b', blob_b.id, 0100644)]
         tree2_id = commit_tree(self.store, blobs_2)
         change_a = (('a', 'a'), (0100644, 0100644), (blob_a1.id, blob_a2.id))
-        self.assertEquals([change_a],
+        self.assertEqual([change_a],
                           list(self.store.tree_changes(tree1_id, tree2_id)))
-        self.assertEquals(
+        self.assertEqual(
           [change_a, (('b', 'b'), (0100644, 0100644), (blob_b.id, blob_b.id))],
           list(self.store.tree_changes(tree1_id, tree2_id,
                                        want_unchanged=True)))
@@ -136,7 +136,7 @@ class ObjectStoreTests(object):
           ('c', blob_c.id, 0100644),
           ]
         tree_id = commit_tree(self.store, blobs)
-        self.assertEquals([TreeEntry(p, m, h) for (p, h, m) in blobs],
+        self.assertEqual([TreeEntry(p, m, h) for (p, h, m) in blobs],
                           list(self.store.iter_tree_contents(tree_id)))
 
     def test_iter_tree_contents_include_trees(self):
@@ -165,7 +165,7 @@ class ObjectStoreTests(object):
           TreeEntry('ad/bd/c', 0100755, blob_c.id),
           ]
         actual = self.store.iter_tree_contents(tree_id, include_trees=True)
-        self.assertEquals(expected, list(actual))
+        self.assertEqual(expected, list(actual))
 
     def make_tag(self, name, obj):
         tag = make_object(Tag, name=name, message='',
@@ -203,17 +203,17 @@ class PackBasedObjectStoreTests(ObjectStoreTests):
             pack.close()
 
     def test_empty_packs(self):
-        self.assertEquals([], self.store.packs)
+        self.assertEqual([], self.store.packs)
 
     def test_pack_loose_objects(self):
         b1 = make_object(Blob, data="yummy data")
         self.store.add_object(b1)
         b2 = make_object(Blob, data="more yummy data")
         self.store.add_object(b2)
-        self.assertEquals([], self.store.packs)
-        self.assertEquals(2, self.store.pack_loose_objects())
+        self.assertEqual([], self.store.packs)
+        self.assertEqual(2, self.store.pack_loose_objects())
         self.assertNotEquals([], self.store.packs)
-        self.assertEquals(0, self.store.pack_loose_objects())
+        self.assertEqual(0, self.store.pack_loose_objects())
 
 
 class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
@@ -237,21 +237,21 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
         store = DiskObjectStore(self.store_dir)
         self.assertRaises(KeyError, store.__getitem__, b2.id)
         store.add_alternate_path(alternate_dir)
-        self.assertEquals(b2, store[b2.id])
+        self.assertEqual(b2, store[b2.id])
 
     def test_add_alternate_path(self):
         store = DiskObjectStore(self.store_dir)
-        self.assertEquals([], store._read_alternate_paths())
+        self.assertEqual([], store._read_alternate_paths())
         store.add_alternate_path("/foo/path")
-        self.assertEquals(["/foo/path"], store._read_alternate_paths())
+        self.assertEqual(["/foo/path"], store._read_alternate_paths())
         store.add_alternate_path("/bar/path")
-        self.assertEquals(
+        self.assertEqual(
             ["/foo/path", "/bar/path"],
             store._read_alternate_paths())
 
     def test_pack_dir(self):
         o = DiskObjectStore(self.store_dir)
-        self.assertEquals(os.path.join(self.store_dir, "pack"), o.pack_dir)
+        self.assertEqual(os.path.join(self.store_dir, "pack"), o.pack_dir)
 
     def test_add_pack(self):
         o = DiskObjectStore(self.store_dir)
@@ -342,8 +342,8 @@ class ObjectStoreGraphWalkerTests(TestCase):
 
     def test_descends(self):
         gw = self.get_walker(["a"], {"a": ["b"], "b": []})
-        self.assertEquals("a", gw.next())
-        self.assertEquals("b", gw.next())
+        self.assertEqual("a", gw.next())
+        self.assertEqual("b", gw.next())
 
     def test_present(self):
         gw = self.get_walker(["a"], {"a": ["b"], "b": []})
@@ -352,14 +352,14 @@ class ObjectStoreGraphWalkerTests(TestCase):
 
     def test_parent_present(self):
         gw = self.get_walker(["a"], {"a": ["b"], "b": []})
-        self.assertEquals("a", gw.next())
+        self.assertEqual("a", gw.next())
         gw.ack("a")
         self.assertIs(None, gw.next())
 
     def test_child_ack_later(self):
         gw = self.get_walker(["a"], {"a": ["b"], "b": ["c"], "c": []})
-        self.assertEquals("a", gw.next())
-        self.assertEquals("b", gw.next())
+        self.assertEqual("a", gw.next())
+        self.assertEqual("b", gw.next())
         gw.ack("a")
         self.assertIs(None, gw.next())
 
@@ -376,9 +376,9 @@ class ObjectStoreGraphWalkerTests(TestCase):
                 "d": ["e"],
                 "e": [],
                 })
-        self.assertEquals("a", gw.next())
-        self.assertEquals("c", gw.next())
+        self.assertEqual("a", gw.next())
+        self.assertEqual("c", gw.next())
         gw.ack("a")
-        self.assertEquals("b", gw.next())
-        self.assertEquals("d", gw.next())
+        self.assertEqual("b", gw.next())
+        self.assertEqual("d", gw.next())
         self.assertIs(None, gw.next())

+ 62 - 52
dulwich/tests/test_objects.py

@@ -73,10 +73,10 @@ tag_sha = '71033db03a03c6a36721efcf1968dd8f8e0cf023'
 class TestHexToSha(TestCase):
 
     def test_simple(self):
-        self.assertEquals("\xab\xcd" * 10, hex_to_sha("abcd" * 10))
+        self.assertEqual("\xab\xcd" * 10, hex_to_sha("abcd" * 10))
 
     def test_reverse(self):
-        self.assertEquals("abcd" * 10, sha_to_hex("\xab\xcd" * 10))
+        self.assertEqual("abcd" * 10, sha_to_hex("\xab\xcd" * 10))
 
 
 class BlobReadTests(TestCase):
@@ -125,7 +125,7 @@ class BlobReadTests(TestCase):
         b1 = Blob.from_string("foo")
         b_raw = b1.as_legacy_object()
         b2 = b1.from_file(StringIO(b_raw))
-        self.assertEquals(b1, b2)
+        self.assertEqual(b1, b2)
 
     def test_chunks(self):
         string = 'test 5\n'
@@ -251,8 +251,8 @@ class ShaFileTests(TestCase):
         # resulting in a different header.
         # See https://github.com/libgit2/libgit2/pull/464
         sf = ShaFile.from_file(StringIO(small_buffer_zlib_object))
-        self.assertEquals(sf.type_name, "tag")
-        self.assertEquals(sf.tagger, " <@localhost>")
+        self.assertEqual(sf.type_name, "tag")
+        self.assertEqual(sf.tagger, " <@localhost>")
 
 
 class CommitSerializationTests(TestCase):
@@ -279,16 +279,16 @@ class CommitSerializationTests(TestCase):
         c = self.make_commit(commit_time=30)
         c1 = Commit()
         c1.set_raw_string(c.as_raw_string())
-        self.assertEquals(30, c1.commit_time)
+        self.assertEqual(30, c1.commit_time)
 
     def test_raw_length(self):
         c = self.make_commit()
-        self.assertEquals(len(c.as_raw_string()), c.raw_length())
+        self.assertEqual(len(c.as_raw_string()), c.raw_length())
 
     def test_simple(self):
         c = self.make_commit()
-        self.assertEquals(c.id, '5dac377bdded4c9aeb8dff595f0faeebcc8498cc')
-        self.assertEquals(
+        self.assertEqual(c.id, '5dac377bdded4c9aeb8dff595f0faeebcc8498cc')
+        self.assertEqual(
                 'tree d80c186a03f423a81b39df39dc87fd269736ca86\n'
                 'parent ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd\n'
                 'parent 4cffe90e0a41ad3f5190079d7c8f036bde29cbe6\n'
@@ -345,31 +345,31 @@ class CommitParseTests(ShaFileCheckTests):
 
     def test_simple(self):
         c = Commit.from_string(self.make_commit_text())
-        self.assertEquals('Merge ../b\n', c.message)
-        self.assertEquals('James Westby <jw+debian@jameswestby.net>', c.author)
-        self.assertEquals('James Westby <jw+debian@jameswestby.net>',
+        self.assertEqual('Merge ../b\n', c.message)
+        self.assertEqual('James Westby <jw+debian@jameswestby.net>', c.author)
+        self.assertEqual('James Westby <jw+debian@jameswestby.net>',
                           c.committer)
-        self.assertEquals('d80c186a03f423a81b39df39dc87fd269736ca86', c.tree)
-        self.assertEquals(['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
+        self.assertEqual('d80c186a03f423a81b39df39dc87fd269736ca86', c.tree)
+        self.assertEqual(['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
                            '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'],
                           c.parents)
         expected_time = datetime.datetime(2007, 3, 24, 22, 1, 59)
-        self.assertEquals(expected_time,
+        self.assertEqual(expected_time,
                           datetime.datetime.utcfromtimestamp(c.commit_time))
-        self.assertEquals(0, c.commit_timezone)
-        self.assertEquals(expected_time,
+        self.assertEqual(0, c.commit_timezone)
+        self.assertEqual(expected_time,
                           datetime.datetime.utcfromtimestamp(c.author_time))
-        self.assertEquals(0, c.author_timezone)
-        self.assertEquals(None, c.encoding)
+        self.assertEqual(0, c.author_timezone)
+        self.assertEqual(None, c.encoding)
 
     def test_custom(self):
         c = Commit.from_string(self.make_commit_text(
           extra={'extra-field': 'data'}))
-        self.assertEquals([('extra-field', 'data')], c.extra)
+        self.assertEqual([('extra-field', 'data')], c.extra)
 
     def test_encoding(self):
         c = Commit.from_string(self.make_commit_text(encoding='UTF-8'))
-        self.assertEquals('UTF-8', c.encoding)
+        self.assertEqual('UTF-8', c.encoding)
 
     def test_check(self):
         self.assertCheckSucceeds(Commit, self.make_commit_text())
@@ -435,8 +435,8 @@ class TreeTests(ShaFileCheckTests):
         myhexsha = "d80c186a03f423a81b39df39dc87fd269736ca86"
         x = Tree()
         x.add("myname", 0100755, myhexsha)
-        self.assertEquals(x["myname"], (0100755, myhexsha))
-        self.assertEquals('100755 myname\0' + hex_to_sha(myhexsha),
+        self.assertEqual(x["myname"], (0100755, myhexsha))
+        self.assertEqual('100755 myname\0' + hex_to_sha(myhexsha),
                 x.as_raw_string())
 
     def test_add_old_order(self):
@@ -447,40 +447,40 @@ class TreeTests(ShaFileCheckTests):
             x.add(0100755, "myname", myhexsha)
         finally:
             warnings.resetwarnings()
-        self.assertEquals(x["myname"], (0100755, myhexsha))
-        self.assertEquals('100755 myname\0' + hex_to_sha(myhexsha),
+        self.assertEqual(x["myname"], (0100755, myhexsha))
+        self.assertEqual('100755 myname\0' + hex_to_sha(myhexsha),
                 x.as_raw_string())
 
     def test_simple(self):
         myhexsha = "d80c186a03f423a81b39df39dc87fd269736ca86"
         x = Tree()
         x["myname"] = (0100755, myhexsha)
-        self.assertEquals('100755 myname\0' + hex_to_sha(myhexsha),
+        self.assertEqual('100755 myname\0' + hex_to_sha(myhexsha),
                 x.as_raw_string())
 
     def test_tree_update_id(self):
         x = Tree()
         x["a.c"] = (0100755, "d80c186a03f423a81b39df39dc87fd269736ca86")
-        self.assertEquals("0c5c6bc2c081accfbc250331b19e43b904ab9cdd", x.id)
+        self.assertEqual("0c5c6bc2c081accfbc250331b19e43b904ab9cdd", x.id)
         x["a.b"] = (stat.S_IFDIR, "d80c186a03f423a81b39df39dc87fd269736ca86")
-        self.assertEquals("07bfcb5f3ada15bbebdfa3bbb8fd858a363925c8", x.id)
+        self.assertEqual("07bfcb5f3ada15bbebdfa3bbb8fd858a363925c8", x.id)
 
     def test_tree_iteritems_dir_sort(self):
         x = Tree()
         for name, item in _TREE_ITEMS.iteritems():
             x[name] = item
-        self.assertEquals(_SORTED_TREE_ITEMS, list(x.iteritems()))
+        self.assertEqual(_SORTED_TREE_ITEMS, list(x.iteritems()))
 
     def test_tree_items_dir_sort(self):
         x = Tree()
         for name, item in _TREE_ITEMS.iteritems():
             x[name] = item
-        self.assertEquals(_SORTED_TREE_ITEMS, x.items())
+        self.assertEqual(_SORTED_TREE_ITEMS, x.items())
 
     def _do_test_parse_tree(self, parse_tree):
         dir = os.path.join(os.path.dirname(__file__), 'data', 'trees')
         o = Tree.from_path(hex_to_filename(dir, tree_sha))
-        self.assertEquals([('a', 0100644, a_sha), ('b', 0100644, b_sha)],
+        self.assertEqual([('a', 0100644, a_sha), ('b', 0100644, b_sha)],
                           list(parse_tree(o.as_raw_string())))
         # test a broken tree that has a leading 0 on the file mode
         broken_tree = '0100644 foo\0' + hex_to_sha(a_sha)
@@ -488,7 +488,7 @@ class TreeTests(ShaFileCheckTests):
         def eval_parse_tree(*args, **kwargs):
             return list(parse_tree(*args, **kwargs))
 
-        self.assertEquals([('foo', 0100644, a_sha)],
+        self.assertEqual([('foo', 0100644, a_sha)],
                           eval_parse_tree(broken_tree))
         self.assertRaises(ObjectFormatException,
                           eval_parse_tree, broken_tree, strict=True)
@@ -572,7 +572,7 @@ class TreeTests(ShaFileCheckTests):
     def test_iter(self):
         t = Tree()
         t["foo"] = (0100644, a_sha)
-        self.assertEquals(set(["foo"]), set(t))
+        self.assertEqual(set(["foo"]), set(t))
 
 
 class TagSerializeTests(TestCase):
@@ -585,7 +585,7 @@ class TagSerializeTests(TestCase):
                         object=(Blob, 'd80c186a03f423a81b39df39dc87fd269736ca86'),
                         tag_time=423423423,
                         tag_timezone=0)
-        self.assertEquals(('object d80c186a03f423a81b39df39dc87fd269736ca86\n'
+        self.assertEqual(('object d80c186a03f423a81b39df39dc87fd269736ca86\n'
                            'type blob\n'
                            'tag 0.1\n'
                            'tagger Jelmer Vernooij <jelmer@samba.org> '
@@ -635,22 +635,22 @@ class TagParseTests(ShaFileCheckTests):
     def test_parse(self):
         x = Tag()
         x.set_raw_string(self.make_tag_text())
-        self.assertEquals(
+        self.assertEqual(
             "Linus Torvalds <torvalds@woody.linux-foundation.org>", x.tagger)
-        self.assertEquals("v2.6.22-rc7", x.name)
+        self.assertEqual("v2.6.22-rc7", x.name)
         object_type, object_sha = x.object
-        self.assertEquals("a38d6181ff27824c79fc7df825164a212eff6a3f",
+        self.assertEqual("a38d6181ff27824c79fc7df825164a212eff6a3f",
                           object_sha)
-        self.assertEquals(Commit, object_type)
-        self.assertEquals(datetime.datetime.utcfromtimestamp(x.tag_time),
+        self.assertEqual(Commit, object_type)
+        self.assertEqual(datetime.datetime.utcfromtimestamp(x.tag_time),
                           datetime.datetime(2007, 7, 1, 19, 54, 34))
-        self.assertEquals(-25200, x.tag_timezone)
+        self.assertEqual(-25200, x.tag_timezone)
 
     def test_parse_no_tagger(self):
         x = Tag()
         x.set_raw_string(self.make_tag_text(tagger=None))
-        self.assertEquals(None, x.tagger)
-        self.assertEquals("v2.6.22-rc7", x.name)
+        self.assertEqual(None, x.tagger)
+        self.assertEqual("v2.6.22-rc7", x.name)
 
     def test_check(self):
         self.assertCheckSucceeds(Tag, self.make_tag_text())
@@ -722,33 +722,43 @@ class CheckTests(TestCase):
 class TimezoneTests(TestCase):
 
     def test_parse_timezone_utc(self):
-        self.assertEquals((0, False), parse_timezone("+0000"))
+        self.assertEqual((0, False), parse_timezone("+0000"))
 
     def test_parse_timezone_utc_negative(self):
-        self.assertEquals((0, True), parse_timezone("-0000"))
+        self.assertEqual((0, True), parse_timezone("-0000"))
 
     def test_generate_timezone_utc(self):
-        self.assertEquals("+0000", format_timezone(0))
+        self.assertEqual("+0000", format_timezone(0))
 
     def test_generate_timezone_utc_negative(self):
-        self.assertEquals("-0000", format_timezone(0, True))
+        self.assertEqual("-0000", format_timezone(0, True))
 
     def test_parse_timezone_cet(self):
-        self.assertEquals((60 * 60, False), parse_timezone("+0100"))
+        self.assertEqual((60 * 60, False), parse_timezone("+0100"))
 
     def test_format_timezone_cet(self):
-        self.assertEquals("+0100", format_timezone(60 * 60))
+        self.assertEqual("+0100", format_timezone(60 * 60))
 
     def test_format_timezone_pdt(self):
-        self.assertEquals("-0400", format_timezone(-4 * 60 * 60))
+        self.assertEqual("-0400", format_timezone(-4 * 60 * 60))
 
     def test_parse_timezone_pdt(self):
-        self.assertEquals((-4 * 60 * 60, False), parse_timezone("-0400"))
+        self.assertEqual((-4 * 60 * 60, False), parse_timezone("-0400"))
 
     def test_format_timezone_pdt_half(self):
-        self.assertEquals("-0440",
+        self.assertEqual("-0440",
             format_timezone(int(((-4 * 60) - 40) * 60)))
 
+    def test_format_timezone_double_negative(self):
+        self.assertEqual("--700",
+            format_timezone(int(((7 * 60)) * 60), True))
+
     def test_parse_timezone_pdt_half(self):
-        self.assertEquals((((-4 * 60) - 40) * 60, False),
+        self.assertEqual((((-4 * 60) - 40) * 60, False),
             parse_timezone("-0440"))
+
+    def test_parse_timezone_double_negative(self):
+        self.assertEqual(
+            (int(((7 * 60)) * 60), False), parse_timezone("+700"))
+        self.assertEqual(
+            (int(((7 * 60)) * 60), True), parse_timezone("--700"))

+ 44 - 44
dulwich/tests/test_pack.py

@@ -127,13 +127,13 @@ class PackIndexTests(PackTests):
 
     def test_index_len(self):
         p = self.get_pack_index(pack1_sha)
-        self.assertEquals(3, len(p))
+        self.assertEqual(3, len(p))
 
     def test_get_stored_checksum(self):
         p = self.get_pack_index(pack1_sha)
-        self.assertEquals('f2848e2ad16f329ae1c92e3b95e91888daa5bd01',
+        self.assertEqual('f2848e2ad16f329ae1c92e3b95e91888daa5bd01',
                           sha_to_hex(p.get_stored_checksum()))
-        self.assertEquals('721980e866af9a5f93ad674144e1459b8ba3e7b7',
+        self.assertEqual('721980e866af9a5f93ad674144e1459b8ba3e7b7',
                           sha_to_hex(p.get_pack_checksum()))
 
     def test_index_check(self):
@@ -143,7 +143,7 @@ class PackIndexTests(PackTests):
     def test_iterentries(self):
         p = self.get_pack_index(pack1_sha)
         entries = [(sha_to_hex(s), o, c) for s, o, c in p.iterentries()]
-        self.assertEquals([
+        self.assertEqual([
           ('6f670c0fb53f9463760b7295fbb814e965fb20c8', 178, None),
           ('b2a2766a2879c209ab1176e7e778b81ae422eeaa', 138, None),
           ('f18faa16531ac570a3fdc8c7ca16682548dafd12', 12, None)
@@ -151,7 +151,7 @@ class PackIndexTests(PackTests):
 
     def test_iter(self):
         p = self.get_pack_index(pack1_sha)
-        self.assertEquals(set([tree_sha, commit_sha, a_sha]), set(p))
+        self.assertEqual(set([tree_sha, commit_sha, a_sha]), set(p))
 
 
 class TestPackDeltas(TestCase):
@@ -164,7 +164,7 @@ class TestPackDeltas(TestCase):
     test_string_big = 'Z' * 8192
 
     def _test_roundtrip(self, base, target):
-        self.assertEquals(target,
+        self.assertEqual(target,
                           ''.join(apply_delta(base, create_delta(base, target))))
 
     def test_nochange(self):
@@ -192,7 +192,7 @@ class TestPackData(PackTests):
 
     def test_pack_len(self):
         p = self.get_pack_data(pack1_sha)
-        self.assertEquals(3, len(p))
+        self.assertEqual(3, len(p))
 
     def test_index_check(self):
         p = self.get_pack_data(pack1_sha)
@@ -212,7 +212,7 @@ class TestPackData(PackTests):
         actual = []
         for offset, type_num, chunks, crc32 in p.iterobjects():
             actual.append((offset, type_num, ''.join(chunks), crc32))
-        self.assertEquals([
+        self.assertEqual([
           (12, 1, commit_data, 3775879613L),
           (138, 2, tree_data, 912998690L),
           (178, 3, 'test 1\n', 1373561701L)
@@ -221,7 +221,7 @@ class TestPackData(PackTests):
     def test_iterentries(self):
         p = self.get_pack_data(pack1_sha)
         entries = set((sha_to_hex(s), o, c) for s, o, c in p.iterentries())
-        self.assertEquals(set([
+        self.assertEqual(set([
           ('6f670c0fb53f9463760b7295fbb814e965fb20c8', 178, 1373561701L),
           ('b2a2766a2879c209ab1176e7e778b81ae422eeaa', 138, 912998690L),
           ('f18faa16531ac570a3fdc8c7ca16682548dafd12', 12, 3775879613L),
@@ -233,7 +233,7 @@ class TestPackData(PackTests):
         p.create_index_v1(filename)
         idx1 = load_pack_index(filename)
         idx2 = self.get_pack_index(pack1_sha)
-        self.assertEquals(idx1, idx2)
+        self.assertEqual(idx1, idx2)
 
     def test_create_index_v2(self):
         p = self.get_pack_data(pack1_sha)
@@ -241,7 +241,7 @@ class TestPackData(PackTests):
         p.create_index_v2(filename)
         idx1 = load_pack_index(filename)
         idx2 = self.get_pack_index(pack1_sha)
-        self.assertEquals(idx1, idx2)
+        self.assertEqual(idx1, idx2)
 
     def test_compute_file_sha(self):
         f = StringIO('abcd1234wxyz')
@@ -262,7 +262,7 @@ class TestPack(PackTests):
 
     def test_len(self):
         p = self.get_pack(pack1_sha)
-        self.assertEquals(3, len(p))
+        self.assertEqual(3, len(p))
 
     def test_contains(self):
         p = self.get_pack(pack1_sha)
@@ -270,24 +270,24 @@ class TestPack(PackTests):
 
     def test_get(self):
         p = self.get_pack(pack1_sha)
-        self.assertEquals(type(p[tree_sha]), Tree)
+        self.assertEqual(type(p[tree_sha]), Tree)
 
     def test_iter(self):
         p = self.get_pack(pack1_sha)
-        self.assertEquals(set([tree_sha, commit_sha, a_sha]), set(p))
+        self.assertEqual(set([tree_sha, commit_sha, a_sha]), set(p))
 
     def test_iterobjects(self):
         p = self.get_pack(pack1_sha)
         expected = set([p[s] for s in [commit_sha, tree_sha, a_sha]])
-        self.assertEquals(expected, set(list(p.iterobjects())))
+        self.assertEqual(expected, set(list(p.iterobjects())))
 
     def test_pack_tuples(self):
         p = self.get_pack(pack1_sha)
         tuples = p.pack_tuples()
         expected = set([(p[s], None) for s in [commit_sha, tree_sha, a_sha]])
-        self.assertEquals(expected, set(list(tuples)))
-        self.assertEquals(expected, set(list(tuples)))
-        self.assertEquals(3, len(tuples))
+        self.assertEqual(expected, set(list(tuples)))
+        self.assertEqual(expected, set(list(tuples)))
+        self.assertEqual(3, len(tuples))
 
     def test_get_object_at(self):
         """Tests random access for non-delta objects"""
@@ -312,10 +312,10 @@ class TestPack(PackTests):
             newpack = Pack(basename)
 
             try:
-                self.assertEquals(origpack, newpack)
+                self.assertEqual(origpack, newpack)
                 self.assertSucceeds(newpack.index.check)
-                self.assertEquals(origpack.name(), newpack.name())
-                self.assertEquals(origpack.index.get_pack_checksum(),
+                self.assertEqual(origpack.name(), newpack.name())
+                self.assertEqual(origpack.index.get_pack_checksum(),
                                   newpack.index.get_pack_checksum())
 
                 wrong_version = origpack.index.version != newpack.index.version
@@ -330,9 +330,9 @@ class TestPack(PackTests):
     def test_commit_obj(self):
         p = self.get_pack(pack1_sha)
         commit = p[commit_sha]
-        self.assertEquals('James Westby <jw+debian@jameswestby.net>',
+        self.assertEqual('James Westby <jw+debian@jameswestby.net>',
                           commit.author)
-        self.assertEquals([], commit.parents)
+        self.assertEqual([], commit.parents)
 
     def _copy_pack(self, origpack):
         basename = os.path.join(self.tempdir, 'somepack')
@@ -374,7 +374,7 @@ class TestPack(PackTests):
 
     def test_name(self):
         p = self.get_pack(pack1_sha)
-        self.assertEquals(pack1_sha, p.name())
+        self.assertEqual(pack1_sha, p.name())
 
     def test_length_mismatch(self):
         data = self.get_pack_data(pack1_sha)
@@ -408,8 +408,8 @@ class TestPack(PackTests):
     def test_iterobjects(self):
         p = self.get_pack(pack1_sha)
         objs = dict((o.id, o) for o in p.iterobjects())
-        self.assertEquals(3, len(objs))
-        self.assertEquals(sorted(objs), sorted(p.index))
+        self.assertEqual(3, len(objs))
+        self.assertEqual(sorted(objs), sorted(p.index))
         self.assertTrue(isinstance(objs[a_sha], Blob))
         self.assertTrue(isinstance(objs[tree_sha], Tree))
         self.assertTrue(isinstance(objs[commit_sha], Commit))
@@ -420,7 +420,7 @@ class WritePackTests(TestCase):
     def test_write_pack_header(self):
         f = StringIO()
         write_pack_header(f, 42)
-        self.assertEquals('PACK\x00\x00\x00\x02\x00\x00\x00*',
+        self.assertEqual('PACK\x00\x00\x00\x02\x00\x00\x00*',
                 f.getvalue())
 
     def test_write_pack_object(self):
@@ -468,24 +468,24 @@ class BaseTestPackIndexWriting(object):
 
     def test_empty(self):
         idx = self.index('empty.idx', [], pack_checksum)
-        self.assertEquals(idx.get_pack_checksum(), pack_checksum)
-        self.assertEquals(0, len(idx))
+        self.assertEqual(idx.get_pack_checksum(), pack_checksum)
+        self.assertEqual(0, len(idx))
 
     def test_single(self):
         entry_sha = hex_to_sha('6f670c0fb53f9463760b7295fbb814e965fb20c8')
         my_entries = [(entry_sha, 178, 42)]
         idx = self.index('single.idx', my_entries, pack_checksum)
-        self.assertEquals(idx.get_pack_checksum(), pack_checksum)
-        self.assertEquals(1, len(idx))
+        self.assertEqual(idx.get_pack_checksum(), pack_checksum)
+        self.assertEqual(1, len(idx))
         actual_entries = list(idx.iterentries())
-        self.assertEquals(len(my_entries), len(actual_entries))
+        self.assertEqual(len(my_entries), len(actual_entries))
         for mine, actual in zip(my_entries, actual_entries):
             my_sha, my_offset, my_crc = mine
             actual_sha, actual_offset, actual_crc = actual
-            self.assertEquals(my_sha, actual_sha)
-            self.assertEquals(my_offset, actual_offset)
+            self.assertEqual(my_sha, actual_sha)
+            self.assertEqual(my_offset, actual_offset)
             if self._has_crc32_checksum:
-                self.assertEquals(my_crc, actual_crc)
+                self.assertEqual(my_crc, actual_crc)
             else:
                 self.assertTrue(actual_crc is None)
 
@@ -503,7 +503,7 @@ class BaseTestFilePackIndexWriting(BaseTestPackIndexWriting):
         self.writeIndex(path, entries, pack_checksum)
         idx = load_pack_index(path)
         self.assertSucceeds(idx.check)
-        self.assertEquals(idx.version, self._expected_version)
+        self.assertEqual(idx.version, self._expected_version)
         return idx
 
     def writeIndex(self, filename, entries, pack_checksum):
@@ -599,20 +599,20 @@ class ReadZlibTests(TestCase):
         unused = read_zlib_chunks(read, unpacked)
         self.assertEqual('', ''.join(unpacked.decomp_chunks))
         self.assertNotEquals('', unused)
-        self.assertEquals(self.extra, unused + read())
+        self.assertEqual(self.extra, unused + read())
 
     def test_decompress_no_crc32(self):
         self.unpacked.crc32 = None
         read_zlib_chunks(self.read, self.unpacked)
-        self.assertEquals(None, self.unpacked.crc32)
+        self.assertEqual(None, self.unpacked.crc32)
 
     def _do_decompress_test(self, buffer_size, **kwargs):
         unused = read_zlib_chunks(self.read, self.unpacked,
                                   buffer_size=buffer_size, **kwargs)
-        self.assertEquals(self.decomp, ''.join(self.unpacked.decomp_chunks))
-        self.assertEquals(zlib.crc32(self.comp), self.unpacked.crc32)
+        self.assertEqual(self.decomp, ''.join(self.unpacked.decomp_chunks))
+        self.assertEqual(zlib.crc32(self.comp), self.unpacked.crc32)
         self.assertNotEquals('', unused)
-        self.assertEquals(self.extra, unused + self.read())
+        self.assertEqual(self.extra, unused + self.read())
 
     def test_simple_decompress(self):
         self._do_decompress_test(4096)
@@ -640,11 +640,11 @@ class ReadZlibTests(TestCase):
 class DeltifyTests(TestCase):
 
     def test_empty(self):
-        self.assertEquals([], list(deltify_pack_objects([])))
+        self.assertEqual([], list(deltify_pack_objects([])))
 
     def test_single(self):
         b = Blob.from_string("foo")
-        self.assertEquals(
+        self.assertEqual(
             [(b.type_num, b.sha().digest(), None, b.as_raw_string())],
             list(deltify_pack_objects([(b, "")])))
 
@@ -652,7 +652,7 @@ class DeltifyTests(TestCase):
         b1 = Blob.from_string("a" * 101)
         b2 = Blob.from_string("a" * 100)
         delta = create_delta(b1.as_raw_string(), b2.as_raw_string())
-        self.assertEquals([
+        self.assertEqual([
             (b1.type_num, b1.sha().digest(), None, b1.as_raw_string()),
             (b2.type_num, b2.sha().digest(), b1.sha().digest(), delta)
             ],

+ 25 - 25
dulwich/tests/test_patch.py

@@ -56,20 +56,20 @@ class WriteCommitPatchTests(TestCase):
         f.seek(0)
         lines = f.readlines()
         self.assertTrue(lines[0].startswith("From 0b0d34d1b5b596c928adc9a727a4b9e03d025298"))
-        self.assertEquals(lines[1], "From: Jelmer <jelmer@samba.org>\n")
+        self.assertEqual(lines[1], "From: Jelmer <jelmer@samba.org>\n")
         self.assertTrue(lines[2].startswith("Date: "))
-        self.assertEquals([
+        self.assertEqual([
             "Subject: [PATCH 1/1] This is the first line\n",
             "And this is the second line.\n",
             "\n",
             "\n",
             "---\n"], lines[3:8])
-        self.assertEquals([
+        self.assertEqual([
             "CONTENTS-- \n",
             "custom\n"], lines[-2:])
         if len(lines) >= 12:
             # diffstat may not be present
-            self.assertEquals(lines[8], " 0 files changed\n")
+            self.assertEqual(lines[8], " 0 files changed\n")
 
 
 class ReadGitAmPatch(TestCase):
@@ -89,16 +89,16 @@ Subject: [PATCH 1/2] Remove executable bit from prey.ico (triggers a lintian war
 1.7.0.4
 """
         c, diff, version = git_am_patch_split(StringIO(text))
-        self.assertEquals("Jelmer Vernooij <jelmer@samba.org>", c.committer)
-        self.assertEquals("Jelmer Vernooij <jelmer@samba.org>", c.author)
-        self.assertEquals("Remove executable bit from prey.ico "
+        self.assertEqual("Jelmer Vernooij <jelmer@samba.org>", c.committer)
+        self.assertEqual("Jelmer Vernooij <jelmer@samba.org>", c.author)
+        self.assertEqual("Remove executable bit from prey.ico "
             "(triggers a lintian warning).\n", c.message)
-        self.assertEquals(""" pixmaps/prey.ico |  Bin 9662 -> 9662 bytes
+        self.assertEqual(""" pixmaps/prey.ico |  Bin 9662 -> 9662 bytes
  1 files changed, 0 insertions(+), 0 deletions(-)
  mode change 100755 => 100644 pixmaps/prey.ico
 
 """, diff)
-        self.assertEquals("1.7.0.4", version)
+        self.assertEqual("1.7.0.4", version)
 
     def test_extract_spaces(self):
         text = """From ff643aae102d8870cac88e8f007e70f58f3a7363 Mon Sep 17 00:00:00 2001
@@ -119,7 +119,7 @@ Subject:  [Dulwich-users] [PATCH] Added unit tests for
 1.7.0.4
 """
         c, diff, version = git_am_patch_split(StringIO(text))
-        self.assertEquals('Added unit tests for dulwich.object_store.tree_lookup_path.\n\n* dulwich/tests/test_object_store.py\n  (TreeLookupPathTests): This test case contains a few tests that ensure the\n   tree_lookup_path function works as expected.\n', c.message)
+        self.assertEqual('Added unit tests for dulwich.object_store.tree_lookup_path.\n\n* dulwich/tests/test_object_store.py\n  (TreeLookupPathTests): This test case contains a few tests that ensure the\n   tree_lookup_path function works as expected.\n', c.message)
 
     def test_extract_pseudo_from_header(self):
         text = """From ff643aae102d8870cac88e8f007e70f58f3a7363 Mon Sep 17 00:00:00 2001
@@ -142,8 +142,8 @@ From: Jelmer Vernooy <jelmer@debian.org>
 1.7.0.4
 """
         c, diff, version = git_am_patch_split(StringIO(text))
-        self.assertEquals("Jelmer Vernooy <jelmer@debian.org>", c.author)
-        self.assertEquals('Added unit tests for dulwich.object_store.tree_lookup_path.\n\n* dulwich/tests/test_object_store.py\n  (TreeLookupPathTests): This test case contains a few tests that ensure the\n   tree_lookup_path function works as expected.\n', c.message)
+        self.assertEqual("Jelmer Vernooy <jelmer@debian.org>", c.author)
+        self.assertEqual('Added unit tests for dulwich.object_store.tree_lookup_path.\n\n* dulwich/tests/test_object_store.py\n  (TreeLookupPathTests): This test case contains a few tests that ensure the\n   tree_lookup_path function works as expected.\n', c.message)
 
     def test_extract_no_version_tail(self):
         text = """From ff643aae102d8870cac88e8f007e70f58f3a7363 Mon Sep 17 00:00:00 2001
@@ -161,7 +161,7 @@ From: Jelmer Vernooy <jelmer@debian.org>
 
 """
         c, diff, version = git_am_patch_split(StringIO(text))
-        self.assertEquals(None, version)
+        self.assertEqual(None, version)
 
     def test_extract_mercurial(self):
         raise SkipTest("git_am_patch_split doesn't handle Mercurial patches properly yet")
@@ -173,7 +173,7 @@ From: Jelmer Vernooy <jelmer@debian.org>
  '''
          c, diff, version = git_am_patch_split(StringIO(text))
 -        self.assertIs(None, version)
-+        self.assertEquals(None, version)
++        self.assertEqual(None, version)
  
  
  class DiffTests(TestCase):
@@ -197,8 +197,8 @@ More help   : https://help.launchpad.net/ListHelp
 
 """ % expected_diff
         c, diff, version = git_am_patch_split(StringIO(text))
-        self.assertEquals(expected_diff, diff)
-        self.assertEquals(None, version)
+        self.assertEqual(expected_diff, diff)
+        self.assertEqual(None, version)
 
 
 class DiffTests(TestCase):
@@ -208,7 +208,7 @@ class DiffTests(TestCase):
         f = StringIO()
         write_blob_diff(f, ("foo.txt", 0644, Blob.from_string("old\nsame\n")),
                            ("bar.txt", 0644, Blob.from_string("new\nsame\n")))
-        self.assertEquals([
+        self.assertEqual([
             "diff --git a/foo.txt b/bar.txt",
             "index 3b0f961..a116b51 644",
             "--- a/foo.txt",
@@ -223,7 +223,7 @@ class DiffTests(TestCase):
         f = StringIO()
         write_blob_diff(f, (None, None, None),
                            ("bar.txt", 0644, Blob.from_string("new\nsame\n")))
-        self.assertEquals([
+        self.assertEqual([
             'diff --git /dev/null b/bar.txt',
              'new mode 644',
              'index 0000000..a116b51 644',
@@ -238,7 +238,7 @@ class DiffTests(TestCase):
         f = StringIO()
         write_blob_diff(f, ("bar.txt", 0644, Blob.from_string("new\nsame\n")),
                            (None, None, None))
-        self.assertEquals([
+        self.assertEqual([
             'diff --git a/bar.txt /dev/null',
             'deleted mode 644',
             'index a116b51..0000000',
@@ -268,7 +268,7 @@ class DiffTests(TestCase):
         store.add_objects([(o, None) for o in [
             tree1, tree2, added, removed, changed1, changed2, unchanged]])
         write_tree_diff(f, store, tree1.id, tree2.id)
-        self.assertEquals([
+        self.assertEqual([
             'diff --git /dev/null b/added.txt',
             'new mode 644',
             'index 0000000..76d4bb8 644',
@@ -304,7 +304,7 @@ class DiffTests(TestCase):
             "cc975646af69f279396d4d5e1379ac6af80ee637")
         store.add_objects([(o, None) for o in [tree1, tree2]])
         write_tree_diff(f, store, tree1.id, tree2.id)
-        self.assertEquals([
+        self.assertEqual([
             'diff --git a/asubmodule b/asubmodule',
             'index 06d0bdd..cc97564 160000',
             '--- a/asubmodule',
@@ -322,7 +322,7 @@ class DiffTests(TestCase):
         store.add_objects([(b1, None), (b2, None)])
         write_object_diff(f, store, ("foo.txt", 0644, b1.id),
                                     ("bar.txt", 0644, b2.id))
-        self.assertEquals([
+        self.assertEqual([
             "diff --git a/foo.txt b/bar.txt",
             "index 3b0f961..a116b51 644",
             "--- a/foo.txt",
@@ -340,7 +340,7 @@ class DiffTests(TestCase):
         store.add_object(b2)
         write_object_diff(f, store, (None, None, None),
                                     ("bar.txt", 0644, b2.id))
-        self.assertEquals([
+        self.assertEqual([
             'diff --git /dev/null b/bar.txt',
              'new mode 644',
              'index 0000000..a116b51 644',
@@ -358,7 +358,7 @@ class DiffTests(TestCase):
         store.add_object(b1)
         write_object_diff(f, store, ("bar.txt", 0644, b1.id),
                                     (None, None, None))
-        self.assertEquals([
+        self.assertEqual([
             'diff --git a/bar.txt /dev/null',
             'deleted mode 644',
             'index a116b51..0000000',
@@ -376,7 +376,7 @@ class DiffTests(TestCase):
         store.add_object(b1)
         write_object_diff(f, store, ("bar.txt", 0644, b1.id),
             ("bar.txt", 0160000, "06d0bdd9e2e20377b3180e4986b14c8549b393e4"))
-        self.assertEquals([
+        self.assertEqual([
             'diff --git a/bar.txt b/bar.txt',
             'old mode 644',
             'new mode 160000',

+ 38 - 38
dulwich/tests/test_protocol.py

@@ -43,57 +43,57 @@ class BaseProtocolTests(object):
 
     def test_write_pkt_line_none(self):
         self.proto.write_pkt_line(None)
-        self.assertEquals(self.rout.getvalue(), '0000')
+        self.assertEqual(self.rout.getvalue(), '0000')
 
     def test_write_pkt_line(self):
         self.proto.write_pkt_line('bla')
-        self.assertEquals(self.rout.getvalue(), '0007bla')
+        self.assertEqual(self.rout.getvalue(), '0007bla')
 
     def test_read_pkt_line(self):
         self.rin.write('0008cmd ')
         self.rin.seek(0)
-        self.assertEquals('cmd ', self.proto.read_pkt_line())
+        self.assertEqual('cmd ', self.proto.read_pkt_line())
 
     def test_eof(self):
         self.rin.write('0000')
         self.rin.seek(0)
         self.assertFalse(self.proto.eof())
-        self.assertEquals(None, self.proto.read_pkt_line())
+        self.assertEqual(None, self.proto.read_pkt_line())
         self.assertTrue(self.proto.eof())
         self.assertRaises(HangupException, self.proto.read_pkt_line)
 
     def test_unread_pkt_line(self):
         self.rin.write('0007foo0000')
         self.rin.seek(0)
-        self.assertEquals('foo', self.proto.read_pkt_line())
+        self.assertEqual('foo', self.proto.read_pkt_line())
         self.proto.unread_pkt_line('bar')
-        self.assertEquals('bar', self.proto.read_pkt_line())
-        self.assertEquals(None, self.proto.read_pkt_line())
+        self.assertEqual('bar', self.proto.read_pkt_line())
+        self.assertEqual(None, self.proto.read_pkt_line())
         self.proto.unread_pkt_line('baz1')
         self.assertRaises(ValueError, self.proto.unread_pkt_line, 'baz2')
 
     def test_read_pkt_seq(self):
         self.rin.write('0008cmd 0005l0000')
         self.rin.seek(0)
-        self.assertEquals(['cmd ', 'l'], list(self.proto.read_pkt_seq()))
+        self.assertEqual(['cmd ', 'l'], list(self.proto.read_pkt_seq()))
 
     def test_read_pkt_line_none(self):
         self.rin.write('0000')
         self.rin.seek(0)
-        self.assertEquals(None, self.proto.read_pkt_line())
+        self.assertEqual(None, self.proto.read_pkt_line())
 
     def test_write_sideband(self):
         self.proto.write_sideband(3, 'bloe')
-        self.assertEquals(self.rout.getvalue(), '0009\x03bloe')
+        self.assertEqual(self.rout.getvalue(), '0009\x03bloe')
 
     def test_send_cmd(self):
         self.proto.send_cmd('fetch', 'a', 'b')
-        self.assertEquals(self.rout.getvalue(), '000efetch a\x00b\x00')
+        self.assertEqual(self.rout.getvalue(), '000efetch a\x00b\x00')
 
     def test_read_cmd(self):
         self.rin.write('0012cmd arg1\x00arg2\x00')
         self.rin.seek(0)
-        self.assertEquals(('cmd', ['arg1', 'arg2']), self.proto.read_cmd())
+        self.assertEqual(('cmd', ['arg1', 'arg2']), self.proto.read_cmd())
 
     def test_read_cmd_noend0(self):
         self.rin.write('0011cmd arg1\x00arg2')
@@ -155,23 +155,23 @@ class ReceivableProtocolTests(BaseProtocolTests, TestCase):
             data += self.proto.recv(10)
         # any more reads would block
         self.assertRaises(AssertionError, self.proto.recv, 10)
-        self.assertEquals(all_data, data)
+        self.assertEqual(all_data, data)
 
     def test_recv_read(self):
         all_data = '1234567'  # recv exactly in one call
         self.rin.write(all_data)
         self.rin.seek(0)
-        self.assertEquals('1234', self.proto.recv(4))
-        self.assertEquals('567', self.proto.read(3))
+        self.assertEqual('1234', self.proto.recv(4))
+        self.assertEqual('567', self.proto.read(3))
         self.assertRaises(AssertionError, self.proto.recv, 10)
 
     def test_read_recv(self):
         all_data = '12345678abcdefg'
         self.rin.write(all_data)
         self.rin.seek(0)
-        self.assertEquals('1234', self.proto.read(4))
-        self.assertEquals('5678abc', self.proto.recv(8))
-        self.assertEquals('defg', self.proto.read(4))
+        self.assertEqual('1234', self.proto.read(4))
+        self.assertEqual('5678abc', self.proto.recv(8))
+        self.assertEqual('defg', self.proto.read(4))
         self.assertRaises(AssertionError, self.proto.recv, 10)
 
     def test_mixed(self):
@@ -196,34 +196,34 @@ class ReceivableProtocolTests(BaseProtocolTests, TestCase):
             # didn't break, something must have gone wrong
             self.fail()
 
-        self.assertEquals(all_data, data)
+        self.assertEqual(all_data, data)
 
 
 class CapabilitiesTestCase(TestCase):
 
     def test_plain(self):
-        self.assertEquals(('bla', []), extract_capabilities('bla'))
+        self.assertEqual(('bla', []), extract_capabilities('bla'))
 
     def test_caps(self):
-        self.assertEquals(('bla', ['la']), extract_capabilities('bla\0la'))
-        self.assertEquals(('bla', ['la']), extract_capabilities('bla\0la\n'))
-        self.assertEquals(('bla', ['la', 'la']), extract_capabilities('bla\0la la'))
+        self.assertEqual(('bla', ['la']), extract_capabilities('bla\0la'))
+        self.assertEqual(('bla', ['la']), extract_capabilities('bla\0la\n'))
+        self.assertEqual(('bla', ['la', 'la']), extract_capabilities('bla\0la la'))
 
     def test_plain_want_line(self):
-        self.assertEquals(('want bla', []), extract_want_line_capabilities('want bla'))
+        self.assertEqual(('want bla', []), extract_want_line_capabilities('want bla'))
 
     def test_caps_want_line(self):
-        self.assertEquals(('want bla', ['la']), extract_want_line_capabilities('want bla la'))
-        self.assertEquals(('want bla', ['la']), extract_want_line_capabilities('want bla la\n'))
-        self.assertEquals(('want bla', ['la', 'la']), extract_want_line_capabilities('want bla la la'))
+        self.assertEqual(('want bla', ['la']), extract_want_line_capabilities('want bla la'))
+        self.assertEqual(('want bla', ['la']), extract_want_line_capabilities('want bla la\n'))
+        self.assertEqual(('want bla', ['la', 'la']), extract_want_line_capabilities('want bla la la'))
 
     def test_ack_type(self):
-        self.assertEquals(SINGLE_ACK, ack_type(['foo', 'bar']))
-        self.assertEquals(MULTI_ACK, ack_type(['foo', 'bar', 'multi_ack']))
-        self.assertEquals(MULTI_ACK_DETAILED,
+        self.assertEqual(SINGLE_ACK, ack_type(['foo', 'bar']))
+        self.assertEqual(MULTI_ACK, ack_type(['foo', 'bar', 'multi_ack']))
+        self.assertEqual(MULTI_ACK_DETAILED,
                           ack_type(['foo', 'bar', 'multi_ack_detailed']))
         # choose detailed when both present
-        self.assertEquals(MULTI_ACK_DETAILED,
+        self.assertEqual(MULTI_ACK_DETAILED,
                           ack_type(['foo', 'bar', 'multi_ack',
                                     'multi_ack_detailed']))
 
@@ -236,7 +236,7 @@ class BufferedPktLineWriterTests(TestCase):
         self._writer = BufferedPktLineWriter(self._output.write, bufsize=16)
 
     def assertOutputEquals(self, expected):
-        self.assertEquals(expected, self._output.getvalue())
+        self.assertEqual(expected, self._output.getvalue())
 
     def _truncate(self):
         self._output.seek(0)
@@ -289,8 +289,8 @@ class PktLineParserTests(TestCase):
         pktlines = []
         parser = PktLineParser(pktlines.append)
         parser.parse("0000")
-        self.assertEquals(pktlines, [None])
-        self.assertEquals("", parser.get_tail())
+        self.assertEqual(pktlines, [None])
+        self.assertEqual("", parser.get_tail())
 
     def test_small_fragments(self):
         pktlines = []
@@ -298,12 +298,12 @@ class PktLineParserTests(TestCase):
         parser.parse("00")
         parser.parse("05")
         parser.parse("z0000")
-        self.assertEquals(pktlines, ["z", None])
-        self.assertEquals("", parser.get_tail())
+        self.assertEqual(pktlines, ["z", None])
+        self.assertEqual("", parser.get_tail())
 
     def test_multiple_packets(self):
         pktlines = []
         parser = PktLineParser(pktlines.append)
         parser.parse("0005z0006aba")
-        self.assertEquals(pktlines, ["z", "ab"])
-        self.assertEquals("a", parser.get_tail())
+        self.assertEqual(pktlines, ["z", "ab"])
+        self.assertEqual("a", parser.get_tail())

+ 12 - 12
dulwich/tests/test_repository.py

@@ -69,7 +69,7 @@ class CreateRepositoryTests(TestCase):
                 f.close()
 
     def _check_repo_contents(self, repo, expect_bare):
-        self.assertEquals(expect_bare, repo.bare)
+        self.assertEqual(expect_bare, repo.bare)
         self.assertFileContentsEqual('Unnamed repository', repo, 'description')
         self.assertFileContentsEqual('', repo, os.path.join('info', 'exclude'))
         self.assertFileContentsEqual(None, repo, 'nonexistent file')
@@ -81,14 +81,14 @@ class CreateRepositoryTests(TestCase):
         tmp_dir = tempfile.mkdtemp()
         self.addCleanup(shutil.rmtree, tmp_dir)
         repo = Repo.init_bare(tmp_dir)
-        self.assertEquals(tmp_dir, repo._controldir)
+        self.assertEqual(tmp_dir, repo._controldir)
         self._check_repo_contents(repo, True)
 
     def test_create_disk_non_bare(self):
         tmp_dir = tempfile.mkdtemp()
         self.addCleanup(shutil.rmtree, tmp_dir)
         repo = Repo.init(tmp_dir)
-        self.assertEquals(os.path.join(tmp_dir, '.git'), repo._controldir)
+        self.assertEqual(os.path.join(tmp_dir, '.git'), repo._controldir)
         self._check_repo_contents(repo, False)
 
     def test_create_memory(self):
@@ -119,7 +119,7 @@ class RepositoryTests(TestCase):
     def test_setitem(self):
         r = self._repo = open_repo('a.git')
         r["refs/tags/foo"] = 'a90fa2d900a17e99b433217e988c4eb4a2e9a097'
-        self.assertEquals('a90fa2d900a17e99b433217e988c4eb4a2e9a097',
+        self.assertEqual('a90fa2d900a17e99b433217e988c4eb4a2e9a097',
                           r["refs/tags/foo"].id)
 
     def test_delitem(self):
@@ -455,7 +455,7 @@ class BuildRepoTests(TestCase):
              commit_timestamp=12395, commit_timezone=0,
              author_timestamp=12395, author_timezone=0,
              encoding="iso8859-1")
-        self.assertEquals("iso8859-1", r[commit_sha].encoding)
+        self.assertEqual("iso8859-1", r[commit_sha].encoding)
 
     def test_commit_config_identity(self):
         # commit falls back to the users' identity if it wasn't specified
@@ -465,10 +465,10 @@ class BuildRepoTests(TestCase):
         c.set(("user", ), "email", "jelmer@apache.org")
         c.write_to_path()
         commit_sha = r.do_commit('message')
-        self.assertEquals(
+        self.assertEqual(
             "Jelmer <jelmer@apache.org>",
             r[commit_sha].author)
-        self.assertEquals(
+        self.assertEqual(
             "Jelmer <jelmer@apache.org>",
             r[commit_sha].committer)
 
@@ -536,7 +536,7 @@ class BuildRepoTests(TestCase):
              commit_timestamp=12395, commit_timezone=0,
              author_timestamp=12395, author_timezone=0,
              merge_heads=[merge_1])
-        self.assertEquals(
+        self.assertEqual(
             [self._root_commit, merge_1],
             r[commit_sha].parents)
 
@@ -745,7 +745,7 @@ class DictRefsContainerTests(RefsContainerTests, TestCase):
         self._refs._refs["refs/stash"] = "00" * 20
         expected_refs = dict(_TEST_REFS)
         expected_refs["refs/stash"] = "00" * 20
-        self.assertEquals(expected_refs, self._refs.as_dict())
+        self.assertEqual(expected_refs, self._refs.as_dict())
 
 
 class DiskRefsContainerTests(RefsContainerTests, TestCase):
@@ -839,10 +839,10 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
         self.assertEqual(nines, refs['refs/heads/master'])
 
     def test_follow(self):
-        self.assertEquals(
+        self.assertEqual(
           ('refs/heads/master', '42d06bd4b77fed026b154d16493e5deab78f02ec'),
           self._refs._follow('HEAD'))
-        self.assertEquals(
+        self.assertEqual(
           ('refs/heads/master', '42d06bd4b77fed026b154d16493e5deab78f02ec'),
           self._refs._follow('refs/heads/master'))
         self.assertRaises(KeyError, self._refs._follow, 'refs/heads/loop')
@@ -926,7 +926,7 @@ class InfoRefsContainerTests(TestCase):
         expected_refs = dict(_TEST_REFS)
         del expected_refs['HEAD']
         expected_refs["refs/stash"] = "00" * 20
-        self.assertEquals(expected_refs, refs.as_dict())
+        self.assertEqual(expected_refs, refs.as_dict())
 
     def test_keys(self):
         refs = InfoRefsContainer(StringIO(_TEST_REFS_SERIALIZED))

+ 23 - 23
dulwich/tests/test_server.py

@@ -113,7 +113,7 @@ class HandlerTestCase(TestCase):
             self.fail(e)
 
     def test_capability_line(self):
-        self.assertEquals('cap1 cap2 cap3', self._handler.capability_line())
+        self.assertEqual('cap1 cap2 cap3', self._handler.capability_line())
 
     def test_set_client_capabilities(self):
         set_caps = self._handler.set_client_capabilities
@@ -186,13 +186,13 @@ class UploadPackHandlerTestCase(TestCase):
 
         caps = list(self._handler.required_capabilities()) + ['include-tag']
         self._handler.set_client_capabilities(caps)
-        self.assertEquals({'1234' * 10: ONE, '5678' * 10: TWO},
+        self.assertEqual({'1234' * 10: ONE, '5678' * 10: TWO},
                           self._handler.get_tagged(refs, repo=self._repo))
 
         # non-include-tag case
         caps = self._handler.required_capabilities()
         self._handler.set_client_capabilities(caps)
-        self.assertEquals({}, self._handler.get_tagged(refs, repo=self._repo))
+        self.assertEqual({}, self._handler.get_tagged(refs, repo=self._repo))
 
 
 class TestUploadPackHandler(UploadPackHandler):
@@ -249,9 +249,9 @@ class ProtocolGraphWalkerTestCase(TestCase):
 
     def test_split_proto_line(self):
         allowed = ('want', 'done', None)
-        self.assertEquals(('want', ONE),
+        self.assertEqual(('want', ONE),
                           _split_proto_line('want %s\n' % ONE, allowed))
-        self.assertEquals(('want', TWO),
+        self.assertEqual(('want', TWO),
                           _split_proto_line('want %s\n' % TWO, allowed))
         self.assertRaises(GitProtocolError, _split_proto_line,
                           'want xxxx\n', allowed)
@@ -260,8 +260,8 @@ class ProtocolGraphWalkerTestCase(TestCase):
         self.assertRaises(GitProtocolError, _split_proto_line,
                           'foo %s\n' % FOUR, allowed)
         self.assertRaises(GitProtocolError, _split_proto_line, 'bar', allowed)
-        self.assertEquals(('done', None), _split_proto_line('done\n', allowed))
-        self.assertEquals((None, None), _split_proto_line('', allowed))
+        self.assertEqual(('done', None), _split_proto_line('done\n', allowed))
+        self.assertEqual((None, None), _split_proto_line('', allowed))
 
     def test_determine_wants(self):
         self.assertEqual(None, self._walker.determine_wants({}))
@@ -277,13 +277,13 @@ class ProtocolGraphWalkerTestCase(TestCase):
           'refs/heads/ref3': THREE,
           }
         self._repo.refs._update(heads)
-        self.assertEquals([ONE, TWO], self._walker.determine_wants(heads))
+        self.assertEqual([ONE, TWO], self._walker.determine_wants(heads))
 
         self._walker.proto.set_output(['want %s multi_ack' % FOUR])
         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
 
         self._walker.proto.set_output([])
-        self.assertEquals([], self._walker.determine_wants(heads))
+        self.assertEqual([], self._walker.determine_wants(heads))
 
         self._walker.proto.set_output(['want %s multi_ack' % ONE, 'foo'])
         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
@@ -313,7 +313,7 @@ class ProtocolGraphWalkerTestCase(TestCase):
                 line = line[:line.index('\x00')]
             lines.append(line.rstrip())
 
-        self.assertEquals([
+        self.assertEqual([
           '%s refs/heads/ref4' % FOUR,
           '%s refs/heads/ref5' % FIVE,
           '%s refs/heads/tag6^{}' % FIVE,
@@ -323,7 +323,7 @@ class ProtocolGraphWalkerTestCase(TestCase):
         # ensure peeled tag was advertised immediately following tag
         for i, line in enumerate(lines):
             if line.endswith(' refs/heads/tag6'):
-                self.assertEquals('%s refs/heads/tag6^{}' % FIVE, lines[i+1])
+                self.assertEqual('%s refs/heads/tag6^{}' % FIVE, lines[i+1])
 
     # TODO: test commit time cutoff
 
@@ -373,11 +373,11 @@ class AckGraphWalkerImplTestCase(TestCase):
         self._impl = self.impl_cls(self._walker)
 
     def assertNoAck(self):
-        self.assertEquals(None, self._walker.pop_ack())
+        self.assertEqual(None, self._walker.pop_ack())
 
     def assertAcks(self, acks):
         for sha, ack_type in acks:
-            self.assertEquals((sha, ack_type), self._walker.pop_ack())
+            self.assertEqual((sha, ack_type), self._walker.pop_ack())
         self.assertNoAck()
 
     def assertAck(self, sha, ack_type=''):
@@ -387,7 +387,7 @@ class AckGraphWalkerImplTestCase(TestCase):
         self.assertAck(None, 'nak')
 
     def assertNextEquals(self, sha):
-        self.assertEquals(sha, self._impl.next())
+        self.assertEqual(sha, self._impl.next())
 
 
 class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
@@ -660,7 +660,7 @@ class FileSystemBackendTests(TestCase):
 
     def test_absolute(self):
         repo = self.backend.open_repository(self.path)
-        self.assertEquals(repo.path, self.repo.path)
+        self.assertEqual(repo.path, self.repo.path)
 
     def test_child(self):
         self.assertRaises(NotGitRepository,
@@ -685,11 +685,11 @@ class ServeCommandTests(TestCase):
         outf = StringIO()
         exitcode = self.serve_command(ReceivePackHandler, ["/"], StringIO("0000"), outf)
         outlines = outf.getvalue().splitlines()
-        self.assertEquals(2, len(outlines))
-        self.assertEquals("1111111111111111111111111111111111111111 refs/heads/master",
+        self.assertEqual(2, len(outlines))
+        self.assertEqual("1111111111111111111111111111111111111111 refs/heads/master",
             outlines[0][4:].split("\x00")[0])
-        self.assertEquals("0000", outlines[-1])
-        self.assertEquals(0, exitcode)
+        self.assertEqual("0000", outlines[-1])
+        self.assertEqual(0, exitcode)
 
 
 class UpdateServerInfoTests(TestCase):
@@ -702,9 +702,9 @@ class UpdateServerInfoTests(TestCase):
 
     def test_empty(self):
         update_server_info(self.repo)
-        self.assertEquals("",
+        self.assertEqual("",
             open(os.path.join(self.path, ".git", "info", "refs"), 'r').read())
-        self.assertEquals("",
+        self.assertEqual("",
             open(os.path.join(self.path, ".git", "objects", "info", "packs"), 'r').read())
 
     def test_simple(self):
@@ -714,6 +714,6 @@ class UpdateServerInfoTests(TestCase):
             ref="refs/heads/foo")
         update_server_info(self.repo)
         ref_text = open(os.path.join(self.path, ".git", "info", "refs"), 'r').read()
-        self.assertEquals(ref_text, "%s\trefs/heads/foo\n" % commit_id)
+        self.assertEqual(ref_text, "%s\trefs/heads/foo\n" % commit_id)
         packs_text = open(os.path.join(self.path, ".git", "objects", "info", "packs"), 'r').read()
-        self.assertEquals(packs_text, "")
+        self.assertEqual(packs_text, "")

+ 99 - 55
dulwich/tests/test_web.py

@@ -19,6 +19,7 @@
 """Tests for the Git HTTP server."""
 
 from cStringIO import StringIO
+import gzip
 import re
 import os
 
@@ -44,6 +45,7 @@ from dulwich.web import (
     HTTP_NOT_FOUND,
     HTTP_FORBIDDEN,
     HTTP_ERROR,
+    GunzipFilter,
     send_file,
     get_text_file,
     get_loose_object,
@@ -117,13 +119,13 @@ class DumbHandlersTestCase(WebTestCase):
 
     def test_send_file_not_found(self):
         list(send_file(self._req, None, 'text/plain'))
-        self.assertEquals(HTTP_NOT_FOUND, self._status)
+        self.assertEqual(HTTP_NOT_FOUND, self._status)
 
     def test_send_file(self):
         f = StringIO('foobar')
         output = ''.join(send_file(self._req, f, 'some/thing'))
-        self.assertEquals('foobar', output)
-        self.assertEquals(HTTP_OK, self._status)
+        self.assertEqual('foobar', output)
+        self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('some/thing')
         self.assertTrue(f.closed)
 
@@ -131,9 +133,9 @@ class DumbHandlersTestCase(WebTestCase):
         bufsize = 10240
         xs = 'x' * bufsize
         f = StringIO(2 * xs)
-        self.assertEquals([xs, xs],
+        self.assertEqual([xs, xs],
                           list(send_file(self._req, f, 'some/thing')))
-        self.assertEquals(HTTP_OK, self._status)
+        self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('some/thing')
         self.assertTrue(f.closed)
 
@@ -151,7 +153,7 @@ class DumbHandlersTestCase(WebTestCase):
 
         f = TestFile(IOError)
         list(send_file(self._req, f, 'some/thing'))
-        self.assertEquals(HTTP_ERROR, self._status)
+        self.assertEqual(HTTP_ERROR, self._status)
         self.assertTrue(f.closed)
         self.assertFalse(self._req.cached)
 
@@ -166,8 +168,8 @@ class DumbHandlersTestCase(WebTestCase):
         backend = _test_backend([], named_files={'description': 'foo'})
         mat = re.search('.*', 'description')
         output = ''.join(get_text_file(self._req, backend, mat))
-        self.assertEquals('foo', output)
-        self.assertEquals(HTTP_OK, self._status)
+        self.assertEqual('foo', output)
+        self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('text/plain')
         self.assertFalse(self._req.cached)
 
@@ -176,15 +178,15 @@ class DumbHandlersTestCase(WebTestCase):
         backend = _test_backend([blob])
         mat = re.search('^(..)(.{38})$', blob.id)
         output = ''.join(get_loose_object(self._req, backend, mat))
-        self.assertEquals(blob.as_legacy_object(), output)
-        self.assertEquals(HTTP_OK, self._status)
+        self.assertEqual(blob.as_legacy_object(), output)
+        self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('application/x-git-loose-object')
         self.assertTrue(self._req.cached)
 
     def test_get_loose_object_missing(self):
         mat = re.search('^(..)(.{38})$', '1' * 40)
         list(get_loose_object(self._req, _test_backend([]), mat))
-        self.assertEquals(HTTP_NOT_FOUND, self._status)
+        self.assertEqual(HTTP_NOT_FOUND, self._status)
 
     def test_get_loose_object_error(self):
         blob = make_object(Blob, data='foo')
@@ -196,15 +198,15 @@ class DumbHandlersTestCase(WebTestCase):
 
         blob.as_legacy_object = as_legacy_object_error
         list(get_loose_object(self._req, backend, mat))
-        self.assertEquals(HTTP_ERROR, self._status)
+        self.assertEqual(HTTP_ERROR, self._status)
 
     def test_get_pack_file(self):
         pack_name = os.path.join('objects', 'pack', 'pack-%s.pack' % ('1' * 40))
         backend = _test_backend([], named_files={pack_name: 'pack contents'})
         mat = re.search('.*', pack_name)
         output = ''.join(get_pack_file(self._req, backend, mat))
-        self.assertEquals('pack contents', output)
-        self.assertEquals(HTTP_OK, self._status)
+        self.assertEqual('pack contents', output)
+        self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('application/x-git-packed-objects')
         self.assertTrue(self._req.cached)
 
@@ -213,8 +215,8 @@ class DumbHandlersTestCase(WebTestCase):
         backend = _test_backend([], named_files={idx_name: 'idx contents'})
         mat = re.search('.*', idx_name)
         output = ''.join(get_idx_file(self._req, backend, mat))
-        self.assertEquals('idx contents', output)
-        self.assertEquals(HTTP_OK, self._status)
+        self.assertEqual('idx contents', output)
+        self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('application/x-git-packed-objects-toc')
         self.assertTrue(self._req.cached)
 
@@ -242,12 +244,12 @@ class DumbHandlersTestCase(WebTestCase):
         backend = _test_backend(objects, refs=refs)
 
         mat = re.search('.*', '//info/refs')
-        self.assertEquals(['%s\trefs/heads/master\n' % blob1.id,
+        self.assertEqual(['%s\trefs/heads/master\n' % blob1.id,
                            '%s\trefs/tags/blob-tag\n' % blob3.id,
                            '%s\trefs/tags/tag-tag\n' % tag1.id,
                            '%s\trefs/tags/tag-tag^{}\n' % blob2.id],
                           list(get_info_refs(self._req, backend, mat)))
-        self.assertEquals(HTTP_OK, self._status)
+        self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('text/plain')
         self.assertFalse(self._req.cached)
 
@@ -274,8 +276,8 @@ class DumbHandlersTestCase(WebTestCase):
         output = ''.join(get_info_packs(self._req, backend, mat))
         expected = 'P pack-%s.pack\n' * 3
         expected %= ('1' * 40, '2' * 40, '3' * 40)
-        self.assertEquals(expected, output)
-        self.assertEquals(HTTP_OK, self._status)
+        self.assertEqual(expected, output)
+        self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('text/plain')
         self.assertFalse(self._req.cached)
 
@@ -303,7 +305,7 @@ class SmartHandlersTestCase(WebTestCase):
     def test_handle_service_request_unknown(self):
         mat = re.search('.*', '/git-evil-handler')
         list(handle_service_request(self._req, 'backend', mat))
-        self.assertEquals(HTTP_FORBIDDEN, self._status)
+        self.assertEqual(HTTP_FORBIDDEN, self._status)
         self.assertFalse(self._req.cached)
 
     def _run_handle_service_request(self, content_length=None):
@@ -334,7 +336,7 @@ class SmartHandlersTestCase(WebTestCase):
     def test_get_info_refs_unknown(self):
         self._environ['QUERY_STRING'] = 'service=git-evil-handler'
         list(get_info_refs(self._req, 'backend', None))
-        self.assertEquals(HTTP_FORBIDDEN, self._status)
+        self.assertEqual(HTTP_FORBIDDEN, self._status)
         self.assertFalse(self._req.cached)
 
     def test_get_info_refs(self):
@@ -344,12 +346,12 @@ class SmartHandlersTestCase(WebTestCase):
         mat = re.search('.*', '/git-upload-pack')
         handler_output = ''.join(get_info_refs(self._req, 'backend', mat))
         write_output = self._output.getvalue()
-        self.assertEquals(('001e# service=git-upload-pack\n'
+        self.assertEqual(('001e# service=git-upload-pack\n'
                            '0000'
                            # input is ignored by the handler
                            'handled input: '), write_output)
         # Ensure all output was written via the write callback.
-        self.assertEquals('', handler_output)
+        self.assertEqual('', handler_output)
         self.assertTrue(self._handler.advertise_refs)
         self.assertTrue(self._handler.http_req)
         self.assertFalse(self._req.cached)
@@ -358,18 +360,18 @@ class SmartHandlersTestCase(WebTestCase):
 class LengthLimitedFileTestCase(TestCase):
     def test_no_cutoff(self):
         f = _LengthLimitedFile(StringIO('foobar'), 1024)
-        self.assertEquals('foobar', f.read())
+        self.assertEqual('foobar', f.read())
 
     def test_cutoff(self):
         f = _LengthLimitedFile(StringIO('foobar'), 3)
-        self.assertEquals('foo', f.read())
-        self.assertEquals('', f.read())
+        self.assertEqual('foo', f.read())
+        self.assertEqual('', f.read())
 
     def test_multiple_reads(self):
         f = _LengthLimitedFile(StringIO('foobar'), 3)
-        self.assertEquals('fo', f.read(2))
-        self.assertEquals('o', f.read(2))
-        self.assertEquals('', f.read())
+        self.assertEqual('fo', f.read(2))
+        self.assertEqual('o', f.read(2))
+        self.assertEqual('', f.read())
 
 
 class HTTPGitRequestTestCase(WebTestCase):
@@ -380,29 +382,29 @@ class HTTPGitRequestTestCase(WebTestCase):
     def test_not_found(self):
         self._req.cache_forever()  # cache headers should be discarded
         message = 'Something not found'
-        self.assertEquals(message, self._req.not_found(message))
-        self.assertEquals(HTTP_NOT_FOUND, self._status)
-        self.assertEquals(set([('Content-Type', 'text/plain')]),
+        self.assertEqual(message, self._req.not_found(message))
+        self.assertEqual(HTTP_NOT_FOUND, self._status)
+        self.assertEqual(set([('Content-Type', 'text/plain')]),
                           set(self._headers))
 
     def test_forbidden(self):
         self._req.cache_forever()  # cache headers should be discarded
         message = 'Something not found'
-        self.assertEquals(message, self._req.forbidden(message))
-        self.assertEquals(HTTP_FORBIDDEN, self._status)
-        self.assertEquals(set([('Content-Type', 'text/plain')]),
+        self.assertEqual(message, self._req.forbidden(message))
+        self.assertEqual(HTTP_FORBIDDEN, self._status)
+        self.assertEqual(set([('Content-Type', 'text/plain')]),
                           set(self._headers))
 
     def test_respond_ok(self):
         self._req.respond()
-        self.assertEquals([], self._headers)
-        self.assertEquals(HTTP_OK, self._status)
+        self.assertEqual([], self._headers)
+        self.assertEqual(HTTP_OK, self._status)
 
     def test_respond(self):
         self._req.nocache()
         self._req.respond(status=402, content_type='some/type',
                           headers=[('X-Foo', 'foo'), ('X-Bar', 'bar')])
-        self.assertEquals(set([
+        self.assertEqual(set([
           ('X-Foo', 'foo'),
           ('X-Bar', 'bar'),
           ('Content-Type', 'some/type'),
@@ -410,7 +412,7 @@ class HTTPGitRequestTestCase(WebTestCase):
           ('Pragma', 'no-cache'),
           ('Cache-Control', 'no-cache, max-age=0, must-revalidate'),
           ]), set(self._headers))
-        self.assertEquals(402, self._status)
+        self.assertEqual(402, self._status)
 
 
 class HTTPGitApplicationTestCase(TestCase):
@@ -419,19 +421,61 @@ class HTTPGitApplicationTestCase(TestCase):
         super(HTTPGitApplicationTestCase, self).setUp()
         self._app = HTTPGitApplication('backend')
 
-    def test_call(self):
-        def test_handler(req, backend, mat):
-            # tests interface used by all handlers
-            self.assertEquals(environ, req.environ)
-            self.assertEquals('backend', backend)
-            self.assertEquals('/foo', mat.group(0))
-            return 'output'
-
-        self._app.services = {
-          ('GET', re.compile('/foo$')): test_handler,
+        self._environ = {
+            'PATH_INFO': '/foo',
+            'REQUEST_METHOD': 'GET',
         }
-        environ = {
-          'PATH_INFO': '/foo',
-          'REQUEST_METHOD': 'GET',
-          }
-        self.assertEquals('output', self._app(environ, None))
+
+    def _test_handler(self, req, backend, mat):
+        # tests interface used by all handlers
+        self.assertEqual(self._environ, req.environ)
+        self.assertEqual('backend', backend)
+        self.assertEqual('/foo', mat.group(0))
+        return 'output'
+
+    def _add_handler(self, app):
+        req = self._environ['REQUEST_METHOD']
+        app.services = {
+          (req, re.compile('/foo$')): self._test_handler,
+        }
+
+    def test_call(self):
+        self._add_handler(self._app)
+        self.assertEqual('output', self._app(self._environ, None))
+
+
+class GunzipTestCase(HTTPGitApplicationTestCase):
+    """TestCase for testing the GunzipFilter, ensuring the wsgi.input
+    is correctly decompressed and headers are corrected.
+    """
+
+    def setUp(self):
+        super(GunzipTestCase, self).setUp()
+        self._app = GunzipFilter(self._app)
+        self._environ['HTTP_CONTENT_ENCODING'] = 'gzip'
+        self._environ['REQUEST_METHOD'] = 'POST'
+
+    def _get_zstream(self, text):
+        zstream = StringIO()
+        zfile = gzip.GzipFile(fileobj=zstream, mode='w')
+        zfile.write(text)
+        zfile.close()
+        return zstream
+
+    def test_call(self):
+        self._add_handler(self._app.app)
+        orig = self.__class__.__doc__
+        zstream = self._get_zstream(orig)
+        zlength = zstream.tell()
+        zstream.seek(0)
+        self.assertLess(zlength, len(orig))
+        self.assertEqual(self._environ['HTTP_CONTENT_ENCODING'], 'gzip')
+        self._environ['CONTENT_LENGTH'] = zlength
+        self._environ['wsgi.input'] = zstream
+        app_output = self._app(self._environ, None)
+        buf = self._environ['wsgi.input']
+        self.assertIsNot(buf, zstream)
+        buf.seek(0)
+        self.assertEqual(orig, buf.read())
+        self.assertIs(None, self._environ.get('CONTENT_LENGTH'))
+        self.assertNotIn('HTTP_CONTENT_ENCODING', self._environ)

+ 1 - 1
dulwich/tests/utils.py

@@ -159,7 +159,7 @@ def ext_functest_builder(method, func):
 
     def do_test(self):
         if not isinstance(func, types.BuiltinFunctionType):
-            raise SkipTest("%s extension not found", func.func_name)
+            raise SkipTest("%s extension not found" % func.func_name)
         method(self, func)
 
     return do_test

+ 50 - 11
dulwich/web.py

@@ -19,6 +19,7 @@
 """HTTP server for dulwich that implements the git smart HTTP protocol."""
 
 from cStringIO import StringIO
+import gzip
 import os
 import re
 import sys
@@ -225,16 +226,7 @@ def handle_service_request(req, backend, mat):
         return
     req.nocache()
     write = req.respond(HTTP_OK, 'application/x-%s-result' % service)
-
-    input = req.environ['wsgi.input']
-    # This is not necessary if this app is run from a conforming WSGI server.
-    # Unfortunately, there's no way to tell that at this point.
-    # TODO: git may used HTTP/1.1 chunked encoding instead of specifying
-    # content-length
-    content_length = req.environ.get('CONTENT_LENGTH', '')
-    if content_length:
-        input = _LengthLimitedFile(input, int(content_length))
-    proto = ReceivableProtocol(input.read, write)
+    proto = ReceivableProtocol(req.environ['wsgi.input'].read, write)
     handler = handler_cls(backend, [url_prefix(mat)], proto, http_req=req)
     handler.handle()
 
@@ -352,6 +344,53 @@ class HTTPGitApplication(object):
         return handler(req, self.backend, mat)
 
 
+class GunzipFilter(object):
+    """WSGI middleware that unzips gzip-encoded requests before
+    passing on to the underlying application.
+    """
+
+    def __init__(self, application):
+        self.app = application
+
+    def __call__(self, environ, start_response):
+        if environ.get('HTTP_CONTENT_ENCODING', '') == 'gzip':
+            environ.pop('HTTP_CONTENT_ENCODING')
+            if 'CONTENT_LENGTH' in environ:
+                del environ['CONTENT_LENGTH']
+            environ['wsgi.input'] = gzip.GzipFile(filename=None,
+                fileobj=environ['wsgi.input'], mode='r')
+        return self.app(environ, start_response)
+
+
+class LimitedInputFilter(object):
+    """WSGI middleware that limits the input length of a request to that
+    specified in Content-Length.
+    """
+
+    def __init__(self, application):
+        self.app = application
+
+    def __call__(self, environ, start_response):
+        # This is not necessary if this app is run from a conforming WSGI
+        # server. Unfortunately, there's no way to tell that at this point.
+        # TODO: git may used HTTP/1.1 chunked encoding instead of specifying
+        # content-length
+        content_length = environ.get('CONTENT_LENGTH', '')
+        if content_length:
+            environ['wsgi.input'] = _LengthLimitedFile(
+                environ['wsgi.input'], int(content_length))
+        return self.app(environ, start_response)
+
+
+def make_wsgi_chain(backend, dumb=False, handlers=None):
+    """Factory function to create an instance of HTTPGitApplication,
+    correctly wrapped with needed middleware.
+    """
+    app = HTTPGitApplication(backend, dumb, handlers)
+    wrapped_app = GunzipFilter(LimitedInputFilter(app))
+    return wrapped_app
+
+
 # The reference server implementation is based on wsgiref, which is not
 # distributed with python 2.4. If wsgiref is not present, users will not be able
 # to use the HTTP server without a little extra work.
@@ -388,7 +427,7 @@ try:
 
         log_utils.default_logging_config()
         backend = DictBackend({'/': Repo(gitdir)})
-        app = HTTPGitApplication(backend)
+        app = make_wsgi_chain(backend)
         server = make_server(listen_addr, port, app,
                              handler_class=HTTPGitRequestHandler)
         logger.info('Listening for HTTP connections on %s:%d', listen_addr,

+ 1 - 1
setup.py

@@ -10,7 +10,7 @@ except ImportError:
     has_setuptools = False
 from distutils.core import Distribution
 
-dulwich_version_string = '0.8.3'
+dulwich_version_string = '0.8.4'
 
 include_dirs = []
 # Windows MSVC support