Browse Source

Merge branch 'master' of git://git.samba.org/jelmer/dulwich into experimental

Conflicts:
	.travis.yml
	setup.py
Jelmer Vernooij 10 years ago
parent
commit
98d0526f5d
67 changed files with 2536 additions and 2498 deletions
  1. 16 0
      .travis.yml
  2. 7 0
      NEWS
  3. 0 26
      PKG-INFO
  4. 14 0
      appveyor.yml
  5. 9 8
      bin/dulwich
  6. 26 25
      docs/tutorial/object-store.txt
  7. 12 11
      docs/tutorial/remote.txt
  8. 9 8
      docs/tutorial/repo.txt
  9. 0 26
      dulwich.egg-info/PKG-INFO
  10. 0 180
      dulwich.egg-info/SOURCES.txt
  11. 0 1
      dulwich.egg-info/dependency_links.txt
  12. 0 1
      dulwich.egg-info/top_level.txt
  13. 1 1
      dulwich/__init__.py
  14. 105 84
      dulwich/client.py
  15. 71 54
      dulwich/config.py
  16. 1 1
      dulwich/contrib/swift.py
  17. 3 3
      dulwich/contrib/test_swift.py
  18. 6 14
      dulwich/diff_tree.py
  19. 1 1
      dulwich/file.py
  20. 1 1
      dulwich/greenthreads.py
  21. 3 3
      dulwich/hooks.py
  22. 37 22
      dulwich/index.py
  23. 40 33
      dulwich/object_store.py
  24. 17 15
      dulwich/objects.py
  25. 4 0
      dulwich/objectspec.py
  26. 87 84
      dulwich/pack.py
  27. 48 53
      dulwich/patch.py
  28. 72 48
      dulwich/porcelain.py
  29. 45 19
      dulwich/protocol.py
  30. 16 18
      dulwich/refs.py
  31. 87 71
      dulwich/repo.py
  32. 132 93
      dulwich/server.py
  33. 2 1
      dulwich/tests/__init__.py
  34. 12 0
      dulwich/tests/compat/server_utils.py
  35. 44 35
      dulwich/tests/compat/test_client.py
  36. 11 8
      dulwich/tests/compat/test_pack.py
  37. 6 8
      dulwich/tests/compat/test_repository.py
  38. 4 1
      dulwich/tests/compat/test_server.py
  39. 6 0
      dulwich/tests/compat/test_web.py
  40. 2 2
      dulwich/tests/compat/utils.py
  41. 7 12
      dulwich/tests/test_blackbox.py
  42. 117 130
      dulwich/tests/test_client.py
  43. 98 108
      dulwich/tests/test_config.py
  44. 8 11
      dulwich/tests/test_diff_tree.py
  45. 14 12
      dulwich/tests/test_file.py
  46. 22 28
      dulwich/tests/test_grafts.py
  47. 2 6
      dulwich/tests/test_greenthreads.py
  48. 30 20
      dulwich/tests/test_hooks.py
  49. 65 77
      dulwich/tests/test_index.py
  50. 26 29
      dulwich/tests/test_missing_obj_finder.py
  51. 112 111
      dulwich/tests/test_object_store.py
  52. 73 6
      dulwich/tests/test_objects.py
  53. 1 4
      dulwich/tests/test_objectspec.py
  54. 197 204
      dulwich/tests/test_pack.py
  55. 167 192
      dulwich/tests/test_porcelain.py
  56. 85 87
      dulwich/tests/test_protocol.py
  57. 10 12
      dulwich/tests/test_refs.py
  58. 312 235
      dulwich/tests/test_repository.py
  59. 138 158
      dulwich/tests/test_server.py
  60. 10 10
      dulwich/tests/test_utils.py
  61. 0 2
      dulwich/tests/test_walk.py
  62. 54 60
      dulwich/tests/test_web.py
  63. 8 6
      dulwich/tests/utils.py
  64. 2 0
      dulwich/walk.py
  65. 4 4
      dulwich/web.py
  66. 0 5
      setup.cfg
  67. 17 10
      setup.py

+ 16 - 0
.travis.yml

@@ -0,0 +1,16 @@
+language: python
+# Workaround to make 2.7 use system site packages, and 2.6 and 3.4 not use system
+# site packages.
+# https://github.com/travis-ci/travis-ci/issues/2219#issuecomment-41804942
+python:
+- "2.6"
+- "2.7_with_system_site_packages"
+- "3.4"
+- "pypy"
+script:
+  - PYTHONHASHSEED=random python setup.py test
+  - make check-noextensions
+install:
+  - sudo apt-get update
+  - sudo apt-get install -qq git python-setuptools python-gevent python-fastimport python-mock
+  - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install unittest2; fi

+ 7 - 0
NEWS

@@ -1,3 +1,10 @@
+0.10.2  UNRELEASED
+
+ IMPROVEMENTS
+
+  * Extended Python3 support to most of the codebase.
+    (Gary van der Merwe, Jelmer Vernooij)
+
 0.10.1  2015-03-25
 0.10.1  2015-03-25
 
 
  BUG FIXES
  BUG FIXES

+ 0 - 26
PKG-INFO

@@ -1,26 +0,0 @@
-Metadata-Version: 1.1
-Name: dulwich
-Version: 0.10.1a
-Summary: Python Git Library
-Home-page: https://samba.org/~jelmer/dulwich
-Author: Jelmer Vernooij
-Author-email: jelmer@samba.org
-License: GPLv2 or later
-Description: 
-              Python implementation of the Git file formats and protocols,
-              without the need to have git installed.
-        
-              All functionality is available in pure Python. Optional
-              C extensions can be built for improved performance.
-        
-              The project is named after the part of London that Mr. and Mrs. Git live in
-              in the particular Monty Python sketch.
-              
-Keywords: git
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Operating System :: POSIX
-Classifier: Topic :: Software Development :: Version Control

+ 14 - 0
appveyor.yml

@@ -0,0 +1,14 @@
+environment:
+  matrix:
+    - PYTHON: "C:\\Python27"
+      PYTHON_ARCH: "32"
+
+    - PYTHON: "C:\\Python34"
+      PYTHON_ARCH: "32"
+
+build: off
+
+test_script:
+  - "%WITH_COMPILER% %PYTHON%/python setup.py test"
+
+

+ 9 - 8
bin/dulwich

@@ -88,7 +88,7 @@ def cmd_fetch(args):
         determine_wants = r.object_store.determine_wants_all
         determine_wants = r.object_store.determine_wants_all
     refs = client.fetch(path, r, progress=sys.stdout.write)
     refs = client.fetch(path, r, progress=sys.stdout.write)
     print("Remote refs:")
     print("Remote refs:")
-    for item in refs.iteritems():
+    for item in refs.items():
         print("%s -> %s" % item)
         print("%s -> %s" % item)
 
 
 
 
@@ -132,9 +132,9 @@ def cmd_dump_pack(args):
     for name in x:
     for name in x:
         try:
         try:
             print("\t%s" % x[name])
             print("\t%s" % x[name])
-        except KeyError, k:
+        except KeyError as k:
             print("\t%s: Unable to resolve base %s" % (name, k))
             print("\t%s: Unable to resolve base %s" % (name, k))
-        except ApplyDeltaError, e:
+        except ApplyDeltaError as e:
             print("\t%s: Unable to apply delta: %r" % (name, e))
             print("\t%s: Unable to apply delta: %r" % (name, e))
 
 
 
 
@@ -234,7 +234,7 @@ def cmd_rev_list(args):
 def cmd_tag(args):
 def cmd_tag(args):
     opts, args = getopt(args, '', [])
     opts, args = getopt(args, '', [])
     if len(args) < 2:
     if len(args) < 2:
-        print 'Usage: dulwich tag NAME'
+        print('Usage: dulwich tag NAME')
         sys.exit(1)
         sys.exit(1)
     porcelain.tag('.', args[0])
     porcelain.tag('.', args[0])
 
 
@@ -291,8 +291,8 @@ def cmd_web_daemon(args):
     else:
     else:
         gitdir = '.'
         gitdir = '.'
     from dulwich import porcelain
     from dulwich import porcelain
-    porcelain.daemon(gitdir, address=options.listen_address,
-                     port=options.port)
+    porcelain.web_daemon(gitdir, address=options.listen_address,
+                         port=options.port)
 
 
 
 
 def cmd_receive_pack(args):
 def cmd_receive_pack(args):
@@ -325,14 +325,15 @@ def cmd_status(args):
     status = porcelain.status(gitdir)
     status = porcelain.status(gitdir)
     if status.staged:
     if status.staged:
         sys.stdout.write("Changes to be committed:\n\n")
         sys.stdout.write("Changes to be committed:\n\n")
-        for kind, names in status.staged.iteritems():
+        for kind, names in status.staged.items():
             for name in names:
             for name in names:
                 sys.stdout.write("\t%s: %s\n" % (kind, name))
                 sys.stdout.write("\t%s: %s\n" % (kind, name))
         sys.stdout.write("\n")
         sys.stdout.write("\n")
     if status.unstaged:
     if status.unstaged:
         sys.stdout.write("Changes not staged for commit:\n\n")
         sys.stdout.write("Changes not staged for commit:\n\n")
         for name in status.unstaged:
         for name in status.unstaged:
-            sys.stdout.write("\t%s\n" % name)
+            sys.stdout.write("\t%s\n" %
+                    name.decode(sys.getfilesystemencoding()))
         sys.stdout.write("\n")
         sys.stdout.write("\n")
     if status.untracked:
     if status.untracked:
         sys.stdout.write("Untracked files:\n\n")
         sys.stdout.write("Untracked files:\n\n")

+ 26 - 25
docs/tutorial/object-store.txt

@@ -15,9 +15,9 @@ When you use Git, you generally add or modify content. As our repository is
 empty for now, we'll start by adding a new file::
 empty for now, we'll start by adding a new file::
 
 
   >>> from dulwich.objects import Blob
   >>> from dulwich.objects import Blob
-  >>> blob = Blob.from_string("My file content\n")
-  >>> blob.id
-  'c55063a4d5d37aa1af2b2dad3a70aa34dae54dc6'
+  >>> blob = Blob.from_string(b"My file content\n")
+  >>> print(blob.id.decode('ascii'))
+  c55063a4d5d37aa1af2b2dad3a70aa34dae54dc6
 
 
 Of course you could create a blob from an existing file using ``from_file``
 Of course you could create a blob from an existing file using ``from_file``
 instead.
 instead.
@@ -27,9 +27,9 @@ give this content a name::
 
 
   >>> from dulwich.objects import Tree
   >>> from dulwich.objects import Tree
   >>> tree = Tree()
   >>> tree = Tree()
-  >>> tree.add("spam", 0100644, blob.id)
+  >>> tree.add(b"spam", 0o100644, blob.id)
 
 
-Note that "0100644" is the octal form for a regular file with common
+Note that "0o100644" is the octal form for a regular file with common
 permissions. You can hardcode them or you can use the ``stat`` module.
 permissions. You can hardcode them or you can use the ``stat`` module.
 
 
 The tree state of our repository still needs to be placed in time. That's the
 The tree state of our repository still needs to be placed in time. That's the
@@ -39,13 +39,13 @@ job of the commit::
   >>> from time import time
   >>> from time import time
   >>> commit = Commit()
   >>> commit = Commit()
   >>> commit.tree = tree.id
   >>> commit.tree = tree.id
-  >>> author = "Your Name <your.email@example.com>"
+  >>> author = b"Your Name <your.email@example.com>"
   >>> commit.author = commit.committer = author
   >>> commit.author = commit.committer = author
   >>> commit.commit_time = commit.author_time = int(time())
   >>> commit.commit_time = commit.author_time = int(time())
-  >>> tz = parse_timezone('-0200')[0]
+  >>> tz = parse_timezone(b'-0200')[0]
   >>> commit.commit_timezone = commit.author_timezone = tz
   >>> commit.commit_timezone = commit.author_timezone = tz
-  >>> commit.encoding = "UTF-8"
-  >>> commit.message = "Initial commit"
+  >>> commit.encoding = b"UTF-8"
+  >>> commit.message = b"Initial commit"
 
 
 Note that the initial commit has no parents.
 Note that the initial commit has no parents.
 
 
@@ -64,23 +64,24 @@ saving the changes::
 Now the physical repository contains three objects but still has no branch.
 Now the physical repository contains three objects but still has no branch.
 Let's create the master branch like Git would::
 Let's create the master branch like Git would::
 
 
-  >>> repo.refs['refs/heads/master'] = commit.id
+  >>> repo.refs[b'refs/heads/master'] = commit.id
 
 
 The master branch now has a commit where to start. When we commit to master, we
 The master branch now has a commit where to start. When we commit to master, we
 are also moving HEAD, which is Git's currently checked out branch:
 are also moving HEAD, which is Git's currently checked out branch:
 
 
-  >>> head = repo.refs['HEAD']
+  >>> head = repo.refs[b'HEAD']
   >>> head == commit.id
   >>> head == commit.id
   True
   True
-  >>> head == repo.refs['refs/heads/master']
+  >>> head == repo.refs[b'refs/heads/master']
   True
   True
 
 
 How did that work? As it turns out, HEAD is a special kind of ref called a
 How did that work? As it turns out, HEAD is a special kind of ref called a
 symbolic ref, and it points at master. Most functions on the refs container
 symbolic ref, and it points at master. Most functions on the refs container
 work transparently with symbolic refs, but we can also take a peek inside HEAD:
 work transparently with symbolic refs, but we can also take a peek inside HEAD:
 
 
-  >>> repo.refs.read_ref('HEAD')
-  'ref: refs/heads/master'
+  >>> import sys
+  >>> print(repo.refs.read_ref(b'HEAD').decode(sys.getfilesystemencoding()))
+  ref: refs/heads/master
 
 
 Normally, you won't need to use read_ref. If you want to change what ref HEAD
 Normally, you won't need to use read_ref. If you want to change what ref HEAD
 points to, in order to check out another branch, just use set_symbolic_ref.
 points to, in order to check out another branch, just use set_symbolic_ref.
@@ -122,20 +123,20 @@ and the new commit'task is to point to this new version.
 Let's first build the blob::
 Let's first build the blob::
 
 
   >>> from dulwich.objects import Blob
   >>> from dulwich.objects import Blob
-  >>> spam = Blob.from_string("My new file content\n")
-  >>> spam.id
-  '16ee2682887a962f854ebd25a61db16ef4efe49f'
+  >>> spam = Blob.from_string(b"My new file content\n")
+  >>> print(spam.id.decode('ascii'))
+  16ee2682887a962f854ebd25a61db16ef4efe49f
 
 
 An alternative is to alter the previously constructed blob object::
 An alternative is to alter the previously constructed blob object::
 
 
-  >>> blob.data = "My new file content\n"
-  >>> blob.id
-  '16ee2682887a962f854ebd25a61db16ef4efe49f'
+  >>> blob.data = b"My new file content\n"
+  >>> print(blob.id.decode('ascii'))
+  16ee2682887a962f854ebd25a61db16ef4efe49f
 
 
 In any case, update the blob id known as "spam". You also have the
 In any case, update the blob id known as "spam". You also have the
 opportunity of changing its mode::
 opportunity of changing its mode::
 
 
-  >>> tree["spam"] = (0100644, spam.id)
+  >>> tree[b"spam"] = (0o100644, spam.id)
 
 
 Now let's record the change::
 Now let's record the change::
 
 
@@ -144,11 +145,11 @@ Now let's record the change::
   >>> c2 = Commit()
   >>> c2 = Commit()
   >>> c2.tree = tree.id
   >>> c2.tree = tree.id
   >>> c2.parents = [commit.id]
   >>> c2.parents = [commit.id]
-  >>> c2.author = c2.committer = "John Doe <john@example.com>"
+  >>> c2.author = c2.committer = b"John Doe <john@example.com>"
   >>> c2.commit_time = c2.author_time = int(time())
   >>> c2.commit_time = c2.author_time = int(time())
   >>> c2.commit_timezone = c2.author_timezone = 0
   >>> c2.commit_timezone = c2.author_timezone = 0
-  >>> c2.encoding = "UTF-8"
-  >>> c2.message = 'Changing "spam"'
+  >>> c2.encoding = b"UTF-8"
+  >>> c2.message = b'Changing "spam"'
 
 
 In this new commit we record the changed tree id, and most important, the
 In this new commit we record the changed tree id, and most important, the
 previous commit as the parent. Parents are actually a list because a commit
 previous commit as the parent. Parents are actually a list because a commit
@@ -181,6 +182,6 @@ write_tree_diff::
 You won't see it using git log because the head is still the previous
 You won't see it using git log because the head is still the previous
 commit. It's easy to remedy::
 commit. It's easy to remedy::
 
 
-  >>> repo.refs['refs/heads/master'] = c2.id
+  >>> repo.refs[b'refs/heads/master'] = c2.id
 
 
 Now all git tools will work as expected.
 Now all git tools will work as expected.

+ 12 - 11
docs/tutorial/remote.txt

@@ -5,12 +5,12 @@ Most of the tests in this file require a Dulwich server, so let's start one:
     >>> from dulwich.repo import Repo
     >>> from dulwich.repo import Repo
     >>> from dulwich.server import DictBackend, TCPGitServer
     >>> from dulwich.server import DictBackend, TCPGitServer
     >>> import threading
     >>> import threading
-    >>> repo = Repo.init("remote", mkdir=True)
-    >>> cid = repo.do_commit("message", committer="Jelmer <jelmer@samba.org>")
-    >>> backend = DictBackend({'/': repo})
-    >>> dul_server = TCPGitServer(backend, 'localhost', 0)
+    >>> repo = Repo.init(b"remote", mkdir=True)
+    >>> cid = repo.do_commit(b"message", committer=b"Jelmer <jelmer@samba.org>")
+    >>> backend = DictBackend({b'/': repo})
+    >>> dul_server = TCPGitServer(backend, b'localhost', 0)
     >>> threading.Thread(target=dul_server.serve).start()
     >>> threading.Thread(target=dul_server.serve).start()
-    >>> server_address, server_port = dul_server.socket.getsockname()
+    >>> server_address, server_port=dul_server.socket.getsockname()
 
 
 Remote repositories
 Remote repositories
 ===================
 ===================
@@ -32,7 +32,7 @@ Dulwich provides support for accessing remote repositories in
 one manually::
 one manually::
 
 
    >>> from dulwich.client import TCPGitClient
    >>> from dulwich.client import TCPGitClient
-   >>> client = TCPGitClient(server_address, server_port)
+   >>> client = TCPGitClient(server_address.encode('ascii'), server_port)
 
 
 Retrieving raw pack files
 Retrieving raw pack files
 -------------------------
 -------------------------
@@ -53,19 +53,20 @@ which claims that the client doesn't have any objects::
    >>> class DummyGraphWalker(object):
    >>> class DummyGraphWalker(object):
    ...     def ack(self, sha): pass
    ...     def ack(self, sha): pass
    ...     def next(self): pass
    ...     def next(self): pass
+   ...     def __next__(self): pass
 
 
 With the ``determine_wants`` function in place, we can now fetch a pack,
 With the ``determine_wants`` function in place, we can now fetch a pack,
 which we will write to a ``BytesIO`` object::
 which we will write to a ``BytesIO`` object::
 
 
    >>> from io import BytesIO
    >>> from io import BytesIO
    >>> f = BytesIO()
    >>> f = BytesIO()
-   >>> remote_refs = client.fetch_pack("/", determine_wants,
+   >>> remote_refs = client.fetch_pack(b"/", determine_wants,
    ...    DummyGraphWalker(), pack_data=f.write)
    ...    DummyGraphWalker(), pack_data=f.write)
 
 
 ``f`` will now contain a full pack file::
 ``f`` will now contain a full pack file::
 
 
-   >>> f.getvalue()[:4]
-   'PACK'
+   >>> print(f.getvalue()[:4].decode('ascii'))
+   PACK
 
 
 Fetching objects into a local repository
 Fetching objects into a local repository
 ----------------------------------------
 ----------------------------------------
@@ -75,8 +76,8 @@ in which case Dulwich takes care of providing the right graph walker, and
 importing the received pack file into the local repository::
 importing the received pack file into the local repository::
 
 
    >>> from dulwich.repo import Repo
    >>> from dulwich.repo import Repo
-   >>> local = Repo.init("local", mkdir=True)
-   >>> remote_refs = client.fetch("/", local)
+   >>> local = Repo.init(b"local", mkdir=True)
+   >>> remote_refs = client.fetch(b"/", local)
 
 
 Let's shut down the server now that all tests have been run::
 Let's shut down the server now that all tests have been run::
 
 

+ 9 - 8
docs/tutorial/repo.txt

@@ -24,6 +24,7 @@ Creating a repository
 Let's create a folder and turn it into a repository, like ``git init`` would::
 Let's create a folder and turn it into a repository, like ``git init`` would::
 
 
   >>> from os import mkdir
   >>> from os import mkdir
+  >>> import sys
   >>> mkdir("myrepo")
   >>> mkdir("myrepo")
   >>> repo = Repo.init("myrepo")
   >>> repo = Repo.init("myrepo")
   >>> repo
   >>> repo
@@ -52,8 +53,8 @@ so only non-bare repositories will have an index, too. To open the index, simply
 call::
 call::
 
 
     >>> index = repo.open_index()
     >>> index = repo.open_index()
-    >>> repr(index).replace('\\\\', '/')
-    "Index('myrepo/.git/index')"
+    >>> print(index.path.decode(sys.getfilesystemencoding()))
+    myrepo/.git/index
 
 
 Since the repository was just created, the index will be empty::
 Since the repository was just created, the index will be empty::
 
 
@@ -66,16 +67,16 @@ Staging new files
 The repository allows "staging" files. Only files can be staged - directories
 The repository allows "staging" files. Only files can be staged - directories
 aren't tracked explicitly by git. Let's create a simple text file and stage it::
 aren't tracked explicitly by git. Let's create a simple text file and stage it::
 
 
-    >>> f = open('myrepo/foo', 'w')
-    >>> f.write("monty")
+    >>> f = open('myrepo/foo', 'wb')
+    >>> _ = f.write(b"monty")
     >>> f.close()
     >>> f.close()
 
 
-    >>> repo.stage(["foo"])
+    >>> repo.stage([b"foo"])
 
 
 It will now show up in the index::
 It will now show up in the index::
 
 
-    >>> list(repo.open_index())
-    ['foo']
+    >>> print(",".join([f.decode(sys.getfilesystemencoding()) for f in repo.open_index()]))
+    foo
 
 
 
 
 Creating new commits
 Creating new commits
@@ -91,7 +92,7 @@ to specify the message. The committer and author will be retrieved from the
 repository configuration or global configuration if they are not specified::
 repository configuration or global configuration if they are not specified::
 
 
     >>> commit_id = repo.do_commit(
     >>> commit_id = repo.do_commit(
-    ...     "The first commit", committer="Jelmer Vernooij <jelmer@samba.org>")
+    ...     b"The first commit", committer=b"Jelmer Vernooij <jelmer@samba.org>")
 
 
 ``do_commit`` returns the SHA1 of the commit. Since the commit was to the 
 ``do_commit`` returns the SHA1 of the commit. Since the commit was to the 
 default branch, the repository's head will now be set to that commit::
 default branch, the repository's head will now be set to that commit::

+ 0 - 26
dulwich.egg-info/PKG-INFO

@@ -1,26 +0,0 @@
-Metadata-Version: 1.1
-Name: dulwich
-Version: 0.10.1a
-Summary: Python Git Library
-Home-page: https://samba.org/~jelmer/dulwich
-Author: Jelmer Vernooij
-Author-email: jelmer@samba.org
-License: GPLv2 or later
-Description: 
-              Python implementation of the Git file formats and protocols,
-              without the need to have git installed.
-        
-              All functionality is available in pure Python. Optional
-              C extensions can be built for improved performance.
-        
-              The project is named after the part of London that Mr. and Mrs. Git live in
-              in the particular Monty Python sketch.
-              
-Keywords: git
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Operating System :: POSIX
-Classifier: Topic :: Software Development :: Version Control

+ 0 - 180
dulwich.egg-info/SOURCES.txt

@@ -1,180 +0,0 @@
-AUTHORS
-COPYING
-HACKING
-MANIFEST.in
-Makefile
-NEWS
-README.md
-setup.cfg
-setup.py
-bin/dul-receive-pack
-bin/dul-upload-pack
-bin/dulwich
-docs/Makefile
-docs/conf.py
-docs/index.txt
-docs/make.bat
-docs/performance.txt
-docs/protocol.txt
-docs/tutorial/Makefile
-docs/tutorial/conclusion.txt
-docs/tutorial/file-format.txt
-docs/tutorial/index.txt
-docs/tutorial/introduction.txt
-docs/tutorial/object-store.txt
-docs/tutorial/remote.txt
-docs/tutorial/repo.txt
-docs/tutorial/tag.txt
-dulwich/__init__.py
-dulwich/_compat.py
-dulwich/_diff_tree.c
-dulwich/_objects.c
-dulwich/_pack.c
-dulwich/client.py
-dulwich/config.py
-dulwich/diff_tree.py
-dulwich/errors.py
-dulwich/fastexport.py
-dulwich/file.py
-dulwich/greenthreads.py
-dulwich/hooks.py
-dulwich/index.py
-dulwich/log_utils.py
-dulwich/lru_cache.py
-dulwich/object_store.py
-dulwich/objects.py
-dulwich/objectspec.py
-dulwich/pack.py
-dulwich/patch.py
-dulwich/porcelain.py
-dulwich/protocol.py
-dulwich/refs.py
-dulwich/repo.py
-dulwich/server.py
-dulwich/stdint.h
-dulwich/walk.py
-dulwich/web.py
-dulwich.egg-info/PKG-INFO
-dulwich.egg-info/SOURCES.txt
-dulwich.egg-info/dependency_links.txt
-dulwich.egg-info/top_level.txt
-dulwich/contrib/__init__.py
-dulwich/contrib/swift.py
-dulwich/contrib/test_swift.py
-dulwich/contrib/test_swift_smoke.py
-dulwich/tests/__init__.py
-dulwich/tests/test_blackbox.py
-dulwich/tests/test_client.py
-dulwich/tests/test_config.py
-dulwich/tests/test_diff_tree.py
-dulwich/tests/test_fastexport.py
-dulwich/tests/test_file.py
-dulwich/tests/test_grafts.py
-dulwich/tests/test_greenthreads.py
-dulwich/tests/test_hooks.py
-dulwich/tests/test_index.py
-dulwich/tests/test_lru_cache.py
-dulwich/tests/test_missing_obj_finder.py
-dulwich/tests/test_object_store.py
-dulwich/tests/test_objects.py
-dulwich/tests/test_objectspec.py
-dulwich/tests/test_pack.py
-dulwich/tests/test_patch.py
-dulwich/tests/test_porcelain.py
-dulwich/tests/test_protocol.py
-dulwich/tests/test_refs.py
-dulwich/tests/test_repository.py
-dulwich/tests/test_server.py
-dulwich/tests/test_utils.py
-dulwich/tests/test_walk.py
-dulwich/tests/test_web.py
-dulwich/tests/utils.py
-dulwich/tests/compat/__init__.py
-dulwich/tests/compat/server_utils.py
-dulwich/tests/compat/test_client.py
-dulwich/tests/compat/test_pack.py
-dulwich/tests/compat/test_repository.py
-dulwich/tests/compat/test_server.py
-dulwich/tests/compat/test_utils.py
-dulwich/tests/compat/test_web.py
-dulwich/tests/compat/utils.py
-dulwich/tests/data/blobs/11/11111111111111111111111111111111111111
-dulwich/tests/data/blobs/6f/670c0fb53f9463760b7295fbb814e965fb20c8
-dulwich/tests/data/blobs/95/4a536f7819d40e6f637f849ee187dd10066349
-dulwich/tests/data/blobs/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
-dulwich/tests/data/commits/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
-dulwich/tests/data/commits/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc
-dulwich/tests/data/commits/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
-dulwich/tests/data/indexes/index
-dulwich/tests/data/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.idx
-dulwich/tests/data/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.pack
-dulwich/tests/data/repos/.gitattributes
-dulwich/tests/data/repos/server_new.export
-dulwich/tests/data/repos/server_old.export
-dulwich/tests/data/repos/a.git/HEAD
-dulwich/tests/data/repos/a.git/packed-refs
-dulwich/tests/data/repos/a.git/objects/28/237f4dc30d0d462658d6b937b08a0f0b6ef55a
-dulwich/tests/data/repos/a.git/objects/2a/72d929692c41d8554c07f6301757ba18a65d91
-dulwich/tests/data/repos/a.git/objects/4e/f30bbfe26431a69c3820d3a683df54d688f2ec
-dulwich/tests/data/repos/a.git/objects/4f/2e6529203aa6d44b5af6e3292c837ceda003f9
-dulwich/tests/data/repos/a.git/objects/7d/9a07d797595ef11344549b8d08198e48c15364
-dulwich/tests/data/repos/a.git/objects/a2/96d0bb611188cabb256919f36bc30117cca005
-dulwich/tests/data/repos/a.git/objects/a9/0fa2d900a17e99b433217e988c4eb4a2e9a097
-dulwich/tests/data/repos/a.git/objects/b0/931cadc54336e78a1d980420e3268903b57a50
-dulwich/tests/data/repos/a.git/objects/ff/d47d45845a8f6576491e1edb97e3fe6a850e7f
-dulwich/tests/data/repos/a.git/refs/heads/master
-dulwich/tests/data/repos/a.git/refs/tags/mytag
-dulwich/tests/data/repos/empty.git/HEAD
-dulwich/tests/data/repos/empty.git/config
-dulwich/tests/data/repos/empty.git/objects/info/.gitignore
-dulwich/tests/data/repos/empty.git/objects/pack/.gitignore
-dulwich/tests/data/repos/empty.git/refs/heads/.gitignore
-dulwich/tests/data/repos/empty.git/refs/tags/.gitignore
-dulwich/tests/data/repos/ooo_merge.git/HEAD
-dulwich/tests/data/repos/ooo_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b
-dulwich/tests/data/repos/ooo_merge.git/objects/38/74e9c60a6d149c44c928140f250d81e6381520
-dulwich/tests/data/repos/ooo_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8
-dulwich/tests/data/repos/ooo_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
-dulwich/tests/data/repos/ooo_merge.git/objects/76/01d7f6231db6a57f7bbb79ee52e4d462fd44d1
-dulwich/tests/data/repos/ooo_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870
-dulwich/tests/data/repos/ooo_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349
-dulwich/tests/data/repos/ooo_merge.git/objects/b2/a2766a2879c209ab1176e7e778b81ae422eeaa
-dulwich/tests/data/repos/ooo_merge.git/objects/f5/07291b64138b875c28e03469025b1ea20bc614
-dulwich/tests/data/repos/ooo_merge.git/objects/f9/e39b120c68182a4ba35349f832d0e4e61f485c
-dulwich/tests/data/repos/ooo_merge.git/objects/fb/5b0425c7ce46959bec94d54b9a157645e114f5
-dulwich/tests/data/repos/ooo_merge.git/refs/heads/master
-dulwich/tests/data/repos/refs.git/HEAD
-dulwich/tests/data/repos/refs.git/packed-refs
-dulwich/tests/data/repos/refs.git/objects/3b/9e5457140e738c2dcd39bf6d7acf88379b90d1
-dulwich/tests/data/repos/refs.git/objects/3e/c9c43c84ff242e3ef4a9fc5bc111fd780a76a8
-dulwich/tests/data/repos/refs.git/objects/42/d06bd4b77fed026b154d16493e5deab78f02ec
-dulwich/tests/data/repos/refs.git/objects/a1/8114c31713746a33a2e70d9914d1ef3e781425
-dulwich/tests/data/repos/refs.git/objects/cd/a609072918d7b70057b6bef9f4c2537843fcfe
-dulwich/tests/data/repos/refs.git/objects/df/6800012397fb85c56e7418dd4eb9405dee075c
-dulwich/tests/data/repos/refs.git/refs/heads/40-char-ref-aaaaaaaaaaaaaaaaaa
-dulwich/tests/data/repos/refs.git/refs/heads/loop
-dulwich/tests/data/repos/refs.git/refs/heads/master
-dulwich/tests/data/repos/refs.git/refs/tags/refs-0.2
-dulwich/tests/data/repos/simple_merge.git/HEAD
-dulwich/tests/data/repos/simple_merge.git/objects/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
-dulwich/tests/data/repos/simple_merge.git/objects/1b/6318f651a534b38f9c7aedeebbd56c1e896853
-dulwich/tests/data/repos/simple_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b
-dulwich/tests/data/repos/simple_merge.git/objects/4c/ffe90e0a41ad3f5190079d7c8f036bde29cbe6
-dulwich/tests/data/repos/simple_merge.git/objects/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc
-dulwich/tests/data/repos/simple_merge.git/objects/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
-dulwich/tests/data/repos/simple_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8
-dulwich/tests/data/repos/simple_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
-dulwich/tests/data/repos/simple_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870
-dulwich/tests/data/repos/simple_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349
-dulwich/tests/data/repos/simple_merge.git/objects/ab/64bbdcc51b170d21588e5c5d391ee5c0c96dfd
-dulwich/tests/data/repos/simple_merge.git/objects/d4/bdad6549dfedf25d3b89d21f506aff575b28a7
-dulwich/tests/data/repos/simple_merge.git/objects/d8/0c186a03f423a81b39df39dc87fd269736ca86
-dulwich/tests/data/repos/simple_merge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
-dulwich/tests/data/repos/simple_merge.git/refs/heads/master
-dulwich/tests/data/repos/submodule/dotgit
-dulwich/tests/data/tags/71/033db03a03c6a36721efcf1968dd8f8e0cf023
-dulwich/tests/data/trees/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
-examples/clone.py
-examples/config.py
-examples/diff.py
-examples/latest_change.py

+ 0 - 1
dulwich.egg-info/dependency_links.txt

@@ -1 +0,0 @@
-

+ 0 - 1
dulwich.egg-info/top_level.txt

@@ -1 +0,0 @@
-dulwich

+ 1 - 1
dulwich/__init__.py

@@ -21,4 +21,4 @@
 
 
 """Python implementation of the Git file formats and protocols."""
 """Python implementation of the Git file formats and protocols."""
 
 
-__version__ = (0, 10, 1)
+__version__ = (0, 10, 2)

+ 105 - 84
dulwich/client.py

@@ -38,7 +38,7 @@ Known capabilities that are not supported:
 
 
 __docformat__ = 'restructuredText'
 __docformat__ = 'restructuredText'
 
 
-from io import BytesIO
+from io import BytesIO, BufferedReader
 import dulwich
 import dulwich
 import select
 import select
 import socket
 import socket
@@ -60,6 +60,19 @@ from dulwich.errors import (
     )
     )
 from dulwich.protocol import (
 from dulwich.protocol import (
     _RBUFSIZE,
     _RBUFSIZE,
+    CAPABILITY_DELETE_REFS,
+    CAPABILITY_MULTI_ACK,
+    CAPABILITY_MULTI_ACK_DETAILED,
+    CAPABILITY_OFS_DELTA,
+    CAPABILITY_REPORT_STATUS,
+    CAPABILITY_SIDE_BAND_64K,
+    CAPABILITY_THIN_PACK,
+    COMMAND_DONE,
+    COMMAND_HAVE,
+    COMMAND_WANT,
+    SIDE_BAND_CHANNEL_DATA,
+    SIDE_BAND_CHANNEL_PROGRESS,
+    SIDE_BAND_CHANNEL_FATAL,
     PktLineParser,
     PktLineParser,
     Protocol,
     Protocol,
     ProtocolFile,
     ProtocolFile,
@@ -79,10 +92,11 @@ def _fileno_can_read(fileno):
     """Check if a file descriptor is readable."""
     """Check if a file descriptor is readable."""
     return len(select.select([fileno], [], [], 0)[0]) > 0
     return len(select.select([fileno], [], [], 0)[0]) > 0
 
 
-COMMON_CAPABILITIES = ['ofs-delta', 'side-band-64k']
-FETCH_CAPABILITIES = (['thin-pack', 'multi_ack', 'multi_ack_detailed'] +
+COMMON_CAPABILITIES = [CAPABILITY_OFS_DELTA, CAPABILITY_SIDE_BAND_64K]
+FETCH_CAPABILITIES = ([CAPABILITY_THIN_PACK, CAPABILITY_MULTI_ACK,
+                       CAPABILITY_MULTI_ACK_DETAILED] +
                       COMMON_CAPABILITIES)
                       COMMON_CAPABILITIES)
-SEND_CAPABILITIES = ['report-status'] + COMMON_CAPABILITIES
+SEND_CAPABILITIES = [CAPABILITY_REPORT_STATUS] + COMMON_CAPABILITIES
 
 
 
 
 class ReportStatusParser(object):
 class ReportStatusParser(object):
@@ -101,27 +115,27 @@ class ReportStatusParser(object):
         :raise SendPackError: Raised when the server could not unpack
         :raise SendPackError: Raised when the server could not unpack
         :raise UpdateRefsError: Raised when refs could not be updated
         :raise UpdateRefsError: Raised when refs could not be updated
         """
         """
-        if self._pack_status not in ('unpack ok', None):
+        if self._pack_status not in (b'unpack ok', None):
             raise SendPackError(self._pack_status)
             raise SendPackError(self._pack_status)
         if not self._ref_status_ok:
         if not self._ref_status_ok:
             ref_status = {}
             ref_status = {}
             ok = set()
             ok = set()
             for status in self._ref_statuses:
             for status in self._ref_statuses:
-                if ' ' not in status:
+                if b' ' not in status:
                     # malformed response, move on to the next one
                     # malformed response, move on to the next one
                     continue
                     continue
-                status, ref = status.split(' ', 1)
+                status, ref = status.split(b' ', 1)
 
 
-                if status == 'ng':
-                    if ' ' in ref:
-                        ref, status = ref.split(' ', 1)
+                if status == b'ng':
+                    if b' ' in ref:
+                        ref, status = ref.split(b' ', 1)
                 else:
                 else:
                     ok.add(ref)
                     ok.add(ref)
                 ref_status[ref] = status
                 ref_status[ref] = status
-            raise UpdateRefsError('%s failed to update' %
-                                  ', '.join([ref for ref in ref_status
-                                             if ref not in ok]),
-                                  ref_status=ref_status)
+            # TODO(jelmer): don't assume encoding of refs is ascii.
+            raise UpdateRefsError(', '.join([
+                ref.decode('ascii') for ref in ref_status if ref not in ok]) +
+                ' failed to update', ref_status=ref_status)
 
 
     def handle_packet(self, pkt):
     def handle_packet(self, pkt):
         """Handle a packet.
         """Handle a packet.
@@ -139,7 +153,7 @@ class ReportStatusParser(object):
         else:
         else:
             ref_status = pkt.strip()
             ref_status = pkt.strip()
             self._ref_statuses.append(ref_status)
             self._ref_statuses.append(ref_status)
-            if not ref_status.startswith('ok '):
+            if not ref_status.startswith(b'ok '):
                 self._ref_status_ok = False
                 self._ref_status_ok = False
 
 
 
 
@@ -148,8 +162,8 @@ def read_pkt_refs(proto):
     refs = {}
     refs = {}
     # Receive refs from server
     # Receive refs from server
     for pkt in proto.read_pkt_seq():
     for pkt in proto.read_pkt_seq():
-        (sha, ref) = pkt.rstrip('\n').split(None, 1)
-        if sha == 'ERR':
+        (sha, ref) = pkt.rstrip(b'\n').split(None, 1)
+        if sha == b'ERR':
             raise GitProtocolError(ref)
             raise GitProtocolError(ref)
         if server_capabilities is None:
         if server_capabilities is None:
             (ref, server_capabilities) = extract_capabilities(ref)
             (ref, server_capabilities) = extract_capabilities(ref)
@@ -180,7 +194,7 @@ class GitClient(object):
         self._fetch_capabilities = set(FETCH_CAPABILITIES)
         self._fetch_capabilities = set(FETCH_CAPABILITIES)
         self._send_capabilities = set(SEND_CAPABILITIES)
         self._send_capabilities = set(SEND_CAPABILITIES)
         if not thin_packs:
         if not thin_packs:
-            self._fetch_capabilities.remove('thin-pack')
+            self._fetch_capabilities.remove(CAPABILITY_THIN_PACK)
 
 
     def send_pack(self, path, determine_wants, generate_pack_contents,
     def send_pack(self, path, determine_wants, generate_pack_contents,
                   progress=None, write_pack=write_pack_objects):
                   progress=None, write_pack=write_pack_objects):
@@ -236,7 +250,7 @@ class GitClient(object):
 
 
     def _parse_status_report(self, proto):
     def _parse_status_report(self, proto):
         unpack = proto.read_pkt_line().strip()
         unpack = proto.read_pkt_line().strip()
-        if unpack != 'unpack ok':
+        if unpack != b'unpack ok':
             st = True
             st = True
             # flush remaining error data
             # flush remaining error data
             while st is not None:
             while st is not None:
@@ -248,7 +262,7 @@ class GitClient(object):
         while ref_status:
         while ref_status:
             ref_status = ref_status.strip()
             ref_status = ref_status.strip()
             statuses.append(ref_status)
             statuses.append(ref_status)
-            if not ref_status.startswith('ok '):
+            if not ref_status.startswith(b'ok '):
                 errs = True
                 errs = True
             ref_status = proto.read_pkt_line()
             ref_status = proto.read_pkt_line()
 
 
@@ -256,20 +270,20 @@ class GitClient(object):
             ref_status = {}
             ref_status = {}
             ok = set()
             ok = set()
             for status in statuses:
             for status in statuses:
-                if ' ' not in status:
+                if b' ' not in status:
                     # malformed response, move on to the next one
                     # malformed response, move on to the next one
                     continue
                     continue
-                status, ref = status.split(' ', 1)
+                status, ref = status.split(b' ', 1)
 
 
-                if status == 'ng':
-                    if ' ' in ref:
-                        ref, status = ref.split(' ', 1)
+                if status == b'ng':
+                    if b' ' in ref:
+                        ref, status = ref.split(b' ', 1)
                 else:
                 else:
                     ok.add(ref)
                     ok.add(ref)
                 ref_status[ref] = status
                 ref_status[ref] = status
-            raise UpdateRefsError('%s failed to update' %
-                                  ', '.join([ref for ref in ref_status
-                                             if ref not in ok]),
+            raise UpdateRefsError(', '.join([ref for ref in ref_status
+                                             if ref not in ok]) +
+                                             b' failed to update',
                                   ref_status=ref_status)
                                   ref_status=ref_status)
 
 
     def _read_side_band64k_data(self, proto, channel_callbacks):
     def _read_side_band64k_data(self, proto, channel_callbacks):
@@ -282,7 +296,7 @@ class GitClient(object):
             handlers to use. None for a callback discards channel data.
             handlers to use. None for a callback discards channel data.
         """
         """
         for pkt in proto.read_pkt_seq():
         for pkt in proto.read_pkt_seq():
-            channel = ord(pkt[0])
+            channel = ord(pkt[:1])
             pkt = pkt[1:]
             pkt = pkt[1:]
             try:
             try:
                 cb = channel_callbacks[channel]
                 cb = channel_callbacks[channel]
@@ -306,18 +320,18 @@ class GitClient(object):
         have = [x for x in old_refs.values() if not x == ZERO_SHA]
         have = [x for x in old_refs.values() if not x == ZERO_SHA]
         sent_capabilities = False
         sent_capabilities = False
 
 
-        for refname in set(new_refs.keys() + old_refs.keys()):
+        all_refs = set(new_refs.keys()).union(set(old_refs.keys()))
+        for refname in all_refs:
             old_sha1 = old_refs.get(refname, ZERO_SHA)
             old_sha1 = old_refs.get(refname, ZERO_SHA)
             new_sha1 = new_refs.get(refname, ZERO_SHA)
             new_sha1 = new_refs.get(refname, ZERO_SHA)
 
 
             if old_sha1 != new_sha1:
             if old_sha1 != new_sha1:
                 if sent_capabilities:
                 if sent_capabilities:
-                    proto.write_pkt_line('%s %s %s' % (
-                        old_sha1, new_sha1, refname))
+                    proto.write_pkt_line(old_sha1 + b' ' + new_sha1 + b' ' + refname)
                 else:
                 else:
                     proto.write_pkt_line(
                     proto.write_pkt_line(
-                        '%s %s %s\0%s' % (old_sha1, new_sha1, refname,
-                                          ' '.join(capabilities)))
+                        old_sha1 + b' ' + new_sha1 + b' ' + refname + b'\0' +
+                        b' '.join(capabilities))
                     sent_capabilities = True
                     sent_capabilities = True
             if new_sha1 not in have and new_sha1 != ZERO_SHA:
             if new_sha1 not in have and new_sha1 != ZERO_SHA:
                 want.append(new_sha1)
                 want.append(new_sha1)
@@ -331,16 +345,16 @@ class GitClient(object):
         :param capabilities: List of negotiated capabilities
         :param capabilities: List of negotiated capabilities
         :param progress: Optional progress reporting function
         :param progress: Optional progress reporting function
         """
         """
-        if "side-band-64k" in capabilities:
+        if b"side-band-64k" in capabilities:
             if progress is None:
             if progress is None:
                 progress = lambda x: None
                 progress = lambda x: None
             channel_callbacks = {2: progress}
             channel_callbacks = {2: progress}
-            if 'report-status' in capabilities:
+            if CAPABILITY_REPORT_STATUS in capabilities:
                 channel_callbacks[1] = PktLineParser(
                 channel_callbacks[1] = PktLineParser(
                     self._report_status_parser.handle_packet).parse
                     self._report_status_parser.handle_packet).parse
             self._read_side_band64k_data(proto, channel_callbacks)
             self._read_side_band64k_data(proto, channel_callbacks)
         else:
         else:
-            if 'report-status' in capabilities:
+            if CAPABILITY_REPORT_STATUS in capabilities:
                 for pkt in proto.read_pkt_seq():
                 for pkt in proto.read_pkt_seq():
                     self._report_status_parser.handle_packet(pkt)
                     self._report_status_parser.handle_packet(pkt)
         if self._report_status_parser is not None:
         if self._report_status_parser is not None:
@@ -357,30 +371,29 @@ class GitClient(object):
         :param can_read: function that returns a boolean that indicates
         :param can_read: function that returns a boolean that indicates
             whether there is extra graph data to read on proto
             whether there is extra graph data to read on proto
         """
         """
-        assert isinstance(wants, list) and isinstance(wants[0], str)
-        proto.write_pkt_line('want %s %s\n' % (
-            wants[0], ' '.join(capabilities)))
+        assert isinstance(wants, list) and isinstance(wants[0], bytes)
+        proto.write_pkt_line(COMMAND_WANT + b' ' + wants[0] + b' ' + b' '.join(capabilities) + b'\n')
         for want in wants[1:]:
         for want in wants[1:]:
-            proto.write_pkt_line('want %s\n' % want)
+            proto.write_pkt_line(COMMAND_WANT + b' ' + want + b'\n')
         proto.write_pkt_line(None)
         proto.write_pkt_line(None)
         have = next(graph_walker)
         have = next(graph_walker)
         while have:
         while have:
-            proto.write_pkt_line('have %s\n' % have)
+            proto.write_pkt_line(COMMAND_HAVE + b' ' + have + b'\n')
             if can_read():
             if can_read():
                 pkt = proto.read_pkt_line()
                 pkt = proto.read_pkt_line()
-                parts = pkt.rstrip('\n').split(' ')
-                if parts[0] == 'ACK':
+                parts = pkt.rstrip(b'\n').split(b' ')
+                if parts[0] == b'ACK':
                     graph_walker.ack(parts[1])
                     graph_walker.ack(parts[1])
-                    if parts[2] in ('continue', 'common'):
+                    if parts[2] in (b'continue', b'common'):
                         pass
                         pass
-                    elif parts[2] == 'ready':
+                    elif parts[2] == b'ready':
                         break
                         break
                     else:
                     else:
                         raise AssertionError(
                         raise AssertionError(
                             "%s not in ('continue', 'ready', 'common)" %
                             "%s not in ('continue', 'ready', 'common)" %
                             parts[2])
                             parts[2])
             have = next(graph_walker)
             have = next(graph_walker)
-        proto.write_pkt_line('done\n')
+        proto.write_pkt_line(COMMAND_DONE + b'\n')
 
 
     def _handle_upload_pack_tail(self, proto, capabilities, graph_walker,
     def _handle_upload_pack_tail(self, proto, capabilities, graph_walker,
                                  pack_data, progress=None, rbufsize=_RBUFSIZE):
                                  pack_data, progress=None, rbufsize=_RBUFSIZE):
@@ -395,22 +408,25 @@ class GitClient(object):
         """
         """
         pkt = proto.read_pkt_line()
         pkt = proto.read_pkt_line()
         while pkt:
         while pkt:
-            parts = pkt.rstrip('\n').split(' ')
-            if parts[0] == 'ACK':
+            parts = pkt.rstrip(b'\n').split(b' ')
+            if parts[0] == b'ACK':
                 graph_walker.ack(parts[1])
                 graph_walker.ack(parts[1])
             if len(parts) < 3 or parts[2] not in (
             if len(parts) < 3 or parts[2] not in (
-                    'ready', 'continue', 'common'):
+                    b'ready', b'continue', b'common'):
                 break
                 break
             pkt = proto.read_pkt_line()
             pkt = proto.read_pkt_line()
-        if "side-band-64k" in capabilities:
+        if CAPABILITY_SIDE_BAND_64K in capabilities:
             if progress is None:
             if progress is None:
                 # Just ignore progress data
                 # Just ignore progress data
                 progress = lambda x: None
                 progress = lambda x: None
-            self._read_side_band64k_data(proto, {1: pack_data, 2: progress})
+            self._read_side_band64k_data(proto, {
+                SIDE_BAND_CHANNEL_DATA: pack_data,
+                SIDE_BAND_CHANNEL_PROGRESS: progress}
+            )
         else:
         else:
             while True:
             while True:
                 data = proto.read(rbufsize)
                 data = proto.read(rbufsize)
-                if data == "":
+                if data == b"":
                     break
                     break
                 pack_data(data)
                 pack_data(data)
 
 
@@ -447,12 +463,12 @@ class TraditionalGitClient(GitClient):
         :raises UpdateRefsError: if the server supports report-status
         :raises UpdateRefsError: if the server supports report-status
                                  and rejects ref updates
                                  and rejects ref updates
         """
         """
-        proto, unused_can_read = self._connect('receive-pack', path)
+        proto, unused_can_read = self._connect(b'receive-pack', path)
         with proto:
         with proto:
             old_refs, server_capabilities = read_pkt_refs(proto)
             old_refs, server_capabilities = read_pkt_refs(proto)
             negotiated_capabilities = self._send_capabilities & server_capabilities
             negotiated_capabilities = self._send_capabilities & server_capabilities
 
 
-            if 'report-status' in negotiated_capabilities:
+            if CAPABILITY_REPORT_STATUS in negotiated_capabilities:
                 self._report_status_parser = ReportStatusParser()
                 self._report_status_parser = ReportStatusParser()
             report_status_parser = self._report_status_parser
             report_status_parser = self._report_status_parser
 
 
@@ -462,15 +478,14 @@ class TraditionalGitClient(GitClient):
                 proto.write_pkt_line(None)
                 proto.write_pkt_line(None)
                 raise
                 raise
 
 
-            if not 'delete-refs' in server_capabilities:
+            if not CAPABILITY_DELETE_REFS in server_capabilities:
                 # Server does not support deletions. Fail later.
                 # Server does not support deletions. Fail later.
                 new_refs = dict(orig_new_refs)
                 new_refs = dict(orig_new_refs)
-                for ref, sha in orig_new_refs.iteritems():
+                for ref, sha in orig_new_refs.items():
                     if sha == ZERO_SHA:
                     if sha == ZERO_SHA:
-                        if 'report-status' in negotiated_capabilities:
+                        if CAPABILITY_REPORT_STATUS in negotiated_capabilities:
                             report_status_parser._ref_statuses.append(
                             report_status_parser._ref_statuses.append(
-                                'ng %s remote does not support deleting refs'
-                                % sha)
+                                b'ng ' + sha + b' remote does not support deleting refs')
                             report_status_parser._ref_status_ok = False
                             report_status_parser._ref_status_ok = False
                         del new_refs[ref]
                         del new_refs[ref]
 
 
@@ -493,7 +508,7 @@ class TraditionalGitClient(GitClient):
 
 
             dowrite = len(objects) > 0
             dowrite = len(objects) > 0
             dowrite = dowrite or any(old_refs.get(ref) != sha
             dowrite = dowrite or any(old_refs.get(ref) != sha
-                                     for (ref, sha) in new_refs.iteritems()
+                                     for (ref, sha) in new_refs.items()
                                      if sha != ZERO_SHA)
                                      if sha != ZERO_SHA)
             if dowrite:
             if dowrite:
                 write_pack(proto.write_file(), objects)
                 write_pack(proto.write_file(), objects)
@@ -511,7 +526,7 @@ class TraditionalGitClient(GitClient):
         :param pack_data: Callback called for each bit of data in the pack
         :param pack_data: Callback called for each bit of data in the pack
         :param progress: Callback for progress reports (strings)
         :param progress: Callback for progress reports (strings)
         """
         """
-        proto, can_read = self._connect('upload-pack', path)
+        proto, can_read = self._connect(b'upload-pack', path)
         with proto:
         with proto:
             refs, server_capabilities = read_pkt_refs(proto)
             refs, server_capabilities = read_pkt_refs(proto)
             negotiated_capabilities = (
             negotiated_capabilities = (
@@ -541,22 +556,24 @@ class TraditionalGitClient(GitClient):
                 write_error=None):
                 write_error=None):
         proto, can_read = self._connect(b'upload-archive', path)
         proto, can_read = self._connect(b'upload-archive', path)
         with proto:
         with proto:
-            proto.write_pkt_line("argument %s" % committish)
+            proto.write_pkt_line(b"argument " + committish)
             proto.write_pkt_line(None)
             proto.write_pkt_line(None)
             pkt = proto.read_pkt_line()
             pkt = proto.read_pkt_line()
-            if pkt == "NACK\n":
+            if pkt == b"NACK\n":
                 return
                 return
-            elif pkt == "ACK\n":
+            elif pkt == b"ACK\n":
                 pass
                 pass
-            elif pkt.startswith("ERR "):
-                raise GitProtocolError(pkt[4:].rstrip("\n"))
+            elif pkt.startswith(b"ERR "):
+                raise GitProtocolError(pkt[4:].rstrip(b"\n"))
             else:
             else:
                 raise AssertionError("invalid response %r" % pkt)
                 raise AssertionError("invalid response %r" % pkt)
             ret = proto.read_pkt_line()
             ret = proto.read_pkt_line()
             if ret is not None:
             if ret is not None:
                 raise AssertionError("expected pkt tail")
                 raise AssertionError("expected pkt tail")
             self._read_side_band64k_data(proto, {
             self._read_side_band64k_data(proto, {
-                1: write_data, 2: progress, 3: write_error})
+                SIDE_BAND_CHANNEL_DATA: write_data,
+                SIDE_BAND_CHANNEL_PROGRESS: progress,
+                SIDE_BAND_CHANNEL_FATAL: write_error})
 
 
 
 
 class TCPGitClient(TraditionalGitClient):
 class TCPGitClient(TraditionalGitClient):
@@ -597,9 +614,9 @@ class TCPGitClient(TraditionalGitClient):
 
 
         proto = Protocol(rfile.read, wfile.write, close,
         proto = Protocol(rfile.read, wfile.write, close,
                          report_activity=self._report_activity)
                          report_activity=self._report_activity)
-        if path.startswith("/~"):
+        if path.startswith(b"/~"):
             path = path[1:]
             path = path[1:]
-        proto.send_cmd('git-%s' % cmd, path, 'host=%s' % self._host)
+        proto.send_cmd(b'git-' + cmd, path, b'host=' + self._host)
         return proto, lambda: _fileno_can_read(s)
         return proto, lambda: _fileno_can_read(s)
 
 
 
 
@@ -608,7 +625,10 @@ class SubprocessWrapper(object):
 
 
     def __init__(self, proc):
     def __init__(self, proc):
         self.proc = proc
         self.proc = proc
-        self.read = proc.stdout.read
+        if sys.version_info[0] == 2:
+            self.read = proc.stdout.read
+        else:
+            self.read = BufferedReader(proc.stdout).read
         self.write = proc.stdin.write
         self.write = proc.stdin.write
 
 
     def can_read(self):
     def can_read(self):
@@ -688,7 +708,8 @@ class LocalGitClient(GitClient):
 
 
         have = [sha1 for sha1 in old_refs.values() if sha1 != ZERO_SHA]
         have = [sha1 for sha1 in old_refs.values() if sha1 != ZERO_SHA]
         want = []
         want = []
-        for refname in set(new_refs.keys() + old_refs.keys()):
+        all_refs = set(new_refs.keys()).union(set(old_refs.keys()))
+        for refname in all_refs:
             old_sha1 = old_refs.get(refname, ZERO_SHA)
             old_sha1 = old_refs.get(refname, ZERO_SHA)
             new_sha1 = new_refs.get(refname, ZERO_SHA)
             new_sha1 = new_refs.get(refname, ZERO_SHA)
             if new_sha1 not in have and new_sha1 != ZERO_SHA:
             if new_sha1 not in have and new_sha1 != ZERO_SHA:
@@ -699,7 +720,7 @@ class LocalGitClient(GitClient):
 
 
         target.object_store.add_objects(generate_pack_contents(have, want))
         target.object_store.add_objects(generate_pack_contents(have, want))
 
 
-        for name, sha in new_refs.iteritems():
+        for name, sha in new_refs.items():
             target.refs[name] = sha
             target.refs[name] = sha
 
 
         return new_refs
         return new_refs
@@ -909,16 +930,16 @@ class SSHGitClient(TraditionalGitClient):
         self.alternative_paths = {}
         self.alternative_paths = {}
 
 
     def _get_cmd_path(self, cmd):
     def _get_cmd_path(self, cmd):
-        return self.alternative_paths.get(cmd, 'git-%s' % cmd)
+        return self.alternative_paths.get(cmd, b'git-' + cmd)
 
 
     def _connect(self, cmd, path):
     def _connect(self, cmd, path):
-        if path.startswith("/~"):
+        if path.startswith(b"/~"):
             path = path[1:]
             path = path[1:]
         con = get_ssh_vendor().run_command(
         con = get_ssh_vendor().run_command(
-            self.host, ["%s '%s'" % (self._get_cmd_path(cmd), path)],
+            self.host, [self._get_cmd_path(cmd) + b" '" + path + b"'"],
             port=self.port, username=self.username)
             port=self.port, username=self.username)
-        return (Protocol(con.read, con.write, con.close, 
-                         report_activity=self._report_activity), 
+        return (Protocol(con.read, con.write, con.close,
+                         report_activity=self._report_activity),
                 con.can_read)
                 con.can_read)
 
 
 
 
@@ -1021,10 +1042,10 @@ class HttpGitClient(GitClient):
         """
         """
         url = self._get_url(path)
         url = self._get_url(path)
         old_refs, server_capabilities = self._discover_references(
         old_refs, server_capabilities = self._discover_references(
-            "git-receive-pack", url)
+            b"git-receive-pack", url)
         negotiated_capabilities = self._send_capabilities & server_capabilities
         negotiated_capabilities = self._send_capabilities & server_capabilities
 
 
-        if 'report-status' in negotiated_capabilities:
+        if CAPABILITY_REPORT_STATUS in negotiated_capabilities:
             self._report_status_parser = ReportStatusParser()
             self._report_status_parser = ReportStatusParser()
 
 
         new_refs = determine_wants(dict(old_refs))
         new_refs = determine_wants(dict(old_refs))
@@ -1041,7 +1062,7 @@ class HttpGitClient(GitClient):
         objects = generate_pack_contents(have, want)
         objects = generate_pack_contents(have, want)
         if len(objects) > 0:
         if len(objects) > 0:
             write_pack(req_proto.write_file(), objects)
             write_pack(req_proto.write_file(), objects)
-        resp = self._smart_request("git-receive-pack", url,
+        resp = self._smart_request(b"git-receive-pack", url,
                                    data=req_data.getvalue())
                                    data=req_data.getvalue())
         try:
         try:
             resp_proto = Protocol(resp.read, None)
             resp_proto = Protocol(resp.read, None)
@@ -1064,7 +1085,7 @@ class HttpGitClient(GitClient):
         """
         """
         url = self._get_url(path)
         url = self._get_url(path)
         refs, server_capabilities = self._discover_references(
         refs, server_capabilities = self._discover_references(
-            "git-upload-pack", url)
+            b"git-upload-pack", url)
         negotiated_capabilities = self._fetch_capabilities & server_capabilities
         negotiated_capabilities = self._fetch_capabilities & server_capabilities
         wants = determine_wants(refs)
         wants = determine_wants(refs)
         if wants is not None:
         if wants is not None:
@@ -1079,7 +1100,7 @@ class HttpGitClient(GitClient):
             req_proto, negotiated_capabilities, graph_walker, wants,
             req_proto, negotiated_capabilities, graph_walker, wants,
             lambda: False)
             lambda: False)
         resp = self._smart_request(
         resp = self._smart_request(
-            "git-upload-pack", url, data=req_data.getvalue())
+            b"git-upload-pack", url, data=req_data.getvalue())
         try:
         try:
             resp_proto = Protocol(resp.read, None)
             resp_proto = Protocol(resp.read, None)
             self._handle_upload_pack_tail(resp_proto, negotiated_capabilities,
             self._handle_upload_pack_tail(resp_proto, negotiated_capabilities,
@@ -1135,7 +1156,7 @@ def get_transport_and_path(location, **kwargs):
         pass
         pass
 
 
     if (sys.platform == 'win32' and
     if (sys.platform == 'win32' and
-            location[0].isalpha() and location[1:2] == ':\\'):
+            location[0].isalpha() and location[1:3] == ':\\'):
         # Windows local path
         # Windows local path
         return default_local_git_client_cls(**kwargs), location
         return default_local_git_client_cls(**kwargs), location
 
 

+ 71 - 54
dulwich/config.py

@@ -26,7 +26,6 @@ TODO:
 
 
 import errno
 import errno
 import os
 import os
-import re
 
 
 try:
 try:
     from collections import (
     from collections import (
@@ -69,9 +68,9 @@ class Config(object):
             value = self.get(section, name)
             value = self.get(section, name)
         except KeyError:
         except KeyError:
             return default
             return default
-        if value.lower() == "true":
+        if value.lower() == b"true":
             return True
             return True
-        elif value.lower() == "false":
+        elif value.lower() == b"false":
             return False
             return False
         raise ValueError("not a valid boolean string: %r" % value)
         raise ValueError("not a valid boolean string: %r" % value)
 
 
@@ -142,7 +141,7 @@ class ConfigDict(Config, MutableMapping):
             return (parts[0], None, parts[1])
             return (parts[0], None, parts[1])
 
 
     def get(self, section, name):
     def get(self, section, name):
-        if isinstance(section, basestring):
+        if not isinstance(section, tuple):
             section = (section, )
             section = (section, )
         if len(section) > 1:
         if len(section) > 1:
             try:
             try:
@@ -152,37 +151,37 @@ class ConfigDict(Config, MutableMapping):
         return self._values[(section[0],)][name]
         return self._values[(section[0],)][name]
 
 
     def set(self, section, name, value):
     def set(self, section, name, value):
-        if isinstance(section, basestring):
+        if not isinstance(section, tuple):
             section = (section, )
             section = (section, )
         self._values.setdefault(section, OrderedDict())[name] = value
         self._values.setdefault(section, OrderedDict())[name] = value
 
 
     def iteritems(self, section):
     def iteritems(self, section):
-        return self._values.get(section, OrderedDict()).iteritems()
+        return self._values.get(section, OrderedDict()).items()
 
 
     def itersections(self):
     def itersections(self):
         return self._values.keys()
         return self._values.keys()
 
 
 
 
 def _format_string(value):
 def _format_string(value):
-    if (value.startswith(" ") or
-        value.startswith("\t") or
-        value.endswith(" ") or
-        value.endswith("\t")):
-        return '"%s"' % _escape_value(value)
+    if (value.startswith(b" ") or
+        value.startswith(b"\t") or
+        value.endswith(b" ") or
+        value.endswith(b"\t")):
+        return b'"' + _escape_value(value) + b'"'
     return _escape_value(value)
     return _escape_value(value)
 
 
 
 
 def _parse_string(value):
 def _parse_string(value):
-    value = value.strip()
-    ret = []
-    block = []
+    value = bytearray(value.strip())
+    ret = bytearray()
+    block = bytearray()
     in_quotes = False
     in_quotes = False
     for c in value:
     for c in value:
-        if c == "\"":
+        if c == ord(b"\""):
             in_quotes = (not in_quotes)
             in_quotes = (not in_quotes)
-            ret.append(_unescape_value("".join(block)))
-            block = []
-        elif c in ("#", ";") and not in_quotes:
+            ret.extend(_unescape_value(block))
+            block = bytearray()
+        elif c in (ord(b"#"), ord(b";")) and not in_quotes:
             # the rest of the line is a comment
             # the rest of the line is a comment
             break
             break
         else:
         else:
@@ -191,46 +190,58 @@ def _parse_string(value):
     if in_quotes:
     if in_quotes:
         raise ValueError("value starts with quote but lacks end quote")
         raise ValueError("value starts with quote but lacks end quote")
 
 
-    ret.append(_unescape_value("".join(block)).rstrip())
+    ret.extend(_unescape_value(block).rstrip())
 
 
-    return "".join(ret)
+    return bytes(ret)
 
 
 
 
 def _unescape_value(value):
 def _unescape_value(value):
     """Unescape a value."""
     """Unescape a value."""
-    def unescape(c):
-        return {
-            "\\\\": "\\",
-            "\\\"": "\"",
-            "\\n": "\n",
-            "\\t": "\t",
-            "\\b": "\b",
-            }[c.group(0)]
-    return re.sub(r"(\\.)", unescape, value)
+    if type(value) != bytearray:
+        raise TypeError("expected: bytearray")
+    table = {
+        ord(b"\\"): ord(b"\\"),
+        ord(b"\""): ord(b"\""),
+        ord(b"n"): ord(b"\n"),
+        ord(b"t"): ord(b"\t"),
+        ord(b"b"): ord(b"\b"),
+        }
+    ret = bytearray()
+    i = 0
+    while i < len(value):
+        if value[i] == ord(b"\\"):
+            i += 1
+            ret.append(table[value[i]])
+        else:
+            ret.append(value[i])
+        i += 1
+    return ret
 
 
 
 
 def _escape_value(value):
 def _escape_value(value):
     """Escape a value."""
     """Escape a value."""
-    return value.replace("\\", "\\\\").replace("\n", "\\n").replace("\t", "\\t").replace("\"", "\\\"")
+    return value.replace(b"\\", b"\\\\").replace(b"\n", b"\\n").replace(b"\t", b"\\t").replace(b"\"", b"\\\"")
 
 
 
 
 def _check_variable_name(name):
 def _check_variable_name(name):
-    for c in name:
-        if not c.isalnum() and c != '-':
+    for i in range(len(name)):
+        c = name[i:i+1]
+        if not c.isalnum() and c != b'-':
             return False
             return False
     return True
     return True
 
 
 
 
 def _check_section_name(name):
 def _check_section_name(name):
-    for c in name:
-        if not c.isalnum() and c not in ('-', '.'):
+    for i in range(len(name)):
+        c = name[i:i+1]
+        if not c.isalnum() and c not in (b'-', b'.'):
             return False
             return False
     return True
     return True
 
 
 
 
 def _strip_comments(line):
 def _strip_comments(line):
-    line = line.split("#")[0]
-    line = line.split(";")[0]
+    line = line.split(b"#")[0]
+    line = line.split(b";")[0]
     return line
     return line
 
 
 
 
@@ -247,47 +258,47 @@ class ConfigFile(ConfigDict):
         for lineno, line in enumerate(f.readlines()):
         for lineno, line in enumerate(f.readlines()):
             line = line.lstrip()
             line = line.lstrip()
             if setting is None:
             if setting is None:
-                if len(line) > 0 and line[0] == "[":
+                if len(line) > 0 and line[:1] == b"[":
                     line = _strip_comments(line).rstrip()
                     line = _strip_comments(line).rstrip()
-                    last = line.index("]")
+                    last = line.index(b"]")
                     if last == -1:
                     if last == -1:
                         raise ValueError("expected trailing ]")
                         raise ValueError("expected trailing ]")
-                    pts = line[1:last].split(" ", 1)
+                    pts = line[1:last].split(b" ", 1)
                     line = line[last+1:]
                     line = line[last+1:]
                     pts[0] = pts[0].lower()
                     pts[0] = pts[0].lower()
                     if len(pts) == 2:
                     if len(pts) == 2:
-                        if pts[1][0] != "\"" or pts[1][-1] != "\"":
+                        if pts[1][:1] != b"\"" or pts[1][-1:] != b"\"":
                             raise ValueError(
                             raise ValueError(
-                                "Invalid subsection " + pts[1])
+                                "Invalid subsection %r" % pts[1])
                         else:
                         else:
                             pts[1] = pts[1][1:-1]
                             pts[1] = pts[1][1:-1]
                         if not _check_section_name(pts[0]):
                         if not _check_section_name(pts[0]):
-                            raise ValueError("invalid section name %s" %
+                            raise ValueError("invalid section name %r" %
                                              pts[0])
                                              pts[0])
                         section = (pts[0], pts[1])
                         section = (pts[0], pts[1])
                     else:
                     else:
                         if not _check_section_name(pts[0]):
                         if not _check_section_name(pts[0]):
-                            raise ValueError("invalid section name %s" %
+                            raise ValueError("invalid section name %r" %
                                     pts[0])
                                     pts[0])
-                        pts = pts[0].split(".", 1)
+                        pts = pts[0].split(b".", 1)
                         if len(pts) == 2:
                         if len(pts) == 2:
                             section = (pts[0], pts[1])
                             section = (pts[0], pts[1])
                         else:
                         else:
                             section = (pts[0], )
                             section = (pts[0], )
                     ret._values[section] = OrderedDict()
                     ret._values[section] = OrderedDict()
-                if _strip_comments(line).strip() == "":
+                if _strip_comments(line).strip() == b"":
                     continue
                     continue
                 if section is None:
                 if section is None:
                     raise ValueError("setting %r without section" % line)
                     raise ValueError("setting %r without section" % line)
                 try:
                 try:
-                    setting, value = line.split("=", 1)
+                    setting, value = line.split(b"=", 1)
                 except ValueError:
                 except ValueError:
                     setting = line
                     setting = line
-                    value = "true"
+                    value = b"true"
                 setting = setting.strip().lower()
                 setting = setting.strip().lower()
                 if not _check_variable_name(setting):
                 if not _check_variable_name(setting):
                     raise ValueError("invalid variable name %s" % setting)
                     raise ValueError("invalid variable name %s" % setting)
-                if value.endswith("\\\n"):
+                if value.endswith(b"\\\n"):
                     value = value[:-2]
                     value = value[:-2]
                     continuation = True
                     continuation = True
                 else:
                 else:
@@ -297,7 +308,7 @@ class ConfigFile(ConfigDict):
                 if not continuation:
                 if not continuation:
                     setting = None
                     setting = None
             else:  # continuation line
             else:  # continuation line
-                if line.endswith("\\\n"):
+                if line.endswith(b"\\\n"):
                     line = line[:-2]
                     line = line[:-2]
                     continuation = True
                     continuation = True
                 else:
                 else:
@@ -325,18 +336,24 @@ class ConfigFile(ConfigDict):
 
 
     def write_to_file(self, f):
     def write_to_file(self, f):
         """Write configuration to a file-like object."""
         """Write configuration to a file-like object."""
-        for section, values in self._values.iteritems():
+        for section, values in self._values.items():
             try:
             try:
                 section_name, subsection_name = section
                 section_name, subsection_name = section
             except ValueError:
             except ValueError:
                 (section_name, ) = section
                 (section_name, ) = section
                 subsection_name = None
                 subsection_name = None
             if subsection_name is None:
             if subsection_name is None:
-                f.write("[%s]\n" % section_name)
+                f.write(b"[" + section_name + b"]\n")
             else:
             else:
-                f.write("[%s \"%s\"]\n" % (section_name, subsection_name))
-            for key, value in values.iteritems():
-                f.write("\t%s = %s\n" % (key, _escape_value(value)))
+                f.write(b"[" + section_name + b" \"" + subsection_name + b"\"]\n")
+            for key, value in values.items():
+                if value is True:
+                    value = b"true"
+                elif value is False:
+                    value = b"false"
+                else:
+                    value = _escape_value(value)
+                f.write(b"\t" + key + b" = " + value + b"\n")
 
 
 
 
 class StackedConfig(Config):
 class StackedConfig(Config):

+ 1 - 1
dulwich/contrib/swift.py

@@ -126,7 +126,7 @@ class PackInfoObjectStoreIterator(GreenThreadsObjectStoreIterator):
 
 
     def __len__(self):
     def __len__(self):
         while len(self.finder.objects_to_send):
         while len(self.finder.objects_to_send):
-            for _ in xrange(0, len(self.finder.objects_to_send)):
+            for _ in range(0, len(self.finder.objects_to_send)):
                 sha = self.finder.next()
                 sha = self.finder.next()
                 self._shas.append(sha)
                 self._shas.append(sha)
         return len(self._shas)
         return len(self._shas)

+ 3 - 3
dulwich/contrib/test_swift.py

@@ -193,7 +193,7 @@ def create_commit(data, marker='Default', blob=None):
 
 
 def create_commits(length=1, marker='Default'):
 def create_commits(length=1, marker='Default'):
     data = []
     data = []
-    for i in xrange(0, length):
+    for i in range(0, length):
         _marker = "%s_%s" % (marker, i)
         _marker = "%s_%s" % (marker, i)
         blob, tree, tag, cmt = create_commit(data, _marker)
         blob, tree, tag, cmt = create_commit(data, _marker)
         data.extend([blob, tree, tag, cmt])
         data.extend([blob, tree, tag, cmt])
@@ -449,11 +449,11 @@ class TestPackInfoLoadDump(TestCase):
 #    def test_pack_info_perf(self):
 #    def test_pack_info_perf(self):
 #        dump_time = []
 #        dump_time = []
 #        load_time = []
 #        load_time = []
-#        for i in xrange(0, 100):
+#        for i in range(0, 100):
 #            start = time()
 #            start = time()
 #            dumps = swift.pack_info_create(self.pack_data, self.pack_index)
 #            dumps = swift.pack_info_create(self.pack_data, self.pack_index)
 #            dump_time.append(time() - start)
 #            dump_time.append(time() - start)
-#        for i in xrange(0, 100):
+#        for i in range(0, 100):
 #            start = time()
 #            start = time()
 #            pack_infos = swift.load_pack_info('', file=BytesIO(dumps))
 #            pack_infos = swift.load_pack_info('', file=BytesIO(dumps))
 #            load_time.append(time() - start)
 #            load_time.append(time() - start)

+ 6 - 14
dulwich/diff_tree.py

@@ -27,14 +27,6 @@ from io import BytesIO
 from itertools import chain
 from itertools import chain
 import stat
 import stat
 
 
-if sys.version_info[0] == 3:
-    xrange = range
-    izip = zip
-    iteritems = lambda d: d.items()
-else:
-    from itertools import izip
-    iteritems = lambda d: d.iteritems()
-
 from dulwich.objects import (
 from dulwich.objects import (
     S_ISGITLINK,
     S_ISGITLINK,
     TreeEntry,
     TreeEntry,
@@ -111,9 +103,9 @@ def _merge_entries(path, tree1, tree2):
             result.append((entry1, entry2))
             result.append((entry1, entry2))
             i1 += 1
             i1 += 1
             i2 += 1
             i2 += 1
-    for i in xrange(i1, len1):
+    for i in range(i1, len1):
         result.append((entries1[i], _NULL_ENTRY))
         result.append((entries1[i], _NULL_ENTRY))
-    for i in xrange(i2, len2):
+    for i in range(i2, len2):
         result.append((_NULL_ENTRY, entries2[i]))
         result.append((_NULL_ENTRY, entries2[i]))
     return result
     return result
 
 
@@ -265,7 +257,7 @@ def tree_changes_for_merge(store, parent_tree_ids, tree_id,
     change_type = lambda c: c.type
     change_type = lambda c: c.type
 
 
     # Yield only conflicting changes.
     # Yield only conflicting changes.
-    for _, changes in sorted(iteritems(changes_by_path)):
+    for _, changes in sorted(changes_by_path.items()):
         assert len(changes) == num_parents
         assert len(changes) == num_parents
         have = [c for c in changes if c is not None]
         have = [c for c in changes if c is not None]
         if _all_eq(have, change_type, CHANGE_DELETE):
         if _all_eq(have, change_type, CHANGE_DELETE):
@@ -330,7 +322,7 @@ def _common_bytes(blocks1, blocks2):
     if len(blocks1) > len(blocks2):
     if len(blocks1) > len(blocks2):
         blocks1, blocks2 = blocks2, blocks1
         blocks1, blocks2 = blocks2, blocks1
     score = 0
     score = 0
-    for block, count1 in iteritems(blocks1):
+    for block, count1 in blocks1.items():
         count2 = blocks2.get(block)
         count2 = blocks2.get(block)
         if count2:
         if count2:
             score += min(count1, count2)
             score += min(count1, count2)
@@ -458,9 +450,9 @@ class RenameDetector(object):
 
 
         add_paths = set()
         add_paths = set()
         delete_paths = set()
         delete_paths = set()
-        for sha, sha_deletes in iteritems(delete_map):
+        for sha, sha_deletes in delete_map.items():
             sha_adds = add_map[sha]
             sha_adds = add_map[sha]
-            for (old, is_delete), new in izip(sha_deletes, sha_adds):
+            for (old, is_delete), new in zip(sha_deletes, sha_adds):
                 if stat.S_IFMT(old.mode) != stat.S_IFMT(new.mode):
                 if stat.S_IFMT(old.mode) != stat.S_IFMT(new.mode):
                     continue
                     continue
                 if is_delete:
                 if is_delete:

+ 1 - 1
dulwich/file.py

@@ -104,7 +104,7 @@ class _GitFile(object):
                      'truncate', 'write', 'writelines')
                      'truncate', 'write', 'writelines')
     def __init__(self, filename, mode, bufsize):
     def __init__(self, filename, mode, bufsize):
         self._filename = filename
         self._filename = filename
-        self._lockfilename = '%s.lock' % self._filename
+        self._lockfilename = self._filename + b'.lock'
         fd = os.open(self._lockfilename,
         fd = os.open(self._lockfilename,
             os.O_RDWR | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0))
             os.O_RDWR | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0))
         self._file = os.fdopen(fd, mode, bufsize)
         self._file = os.fdopen(fd, mode, bufsize)

+ 1 - 1
dulwich/greenthreads.py

@@ -132,7 +132,7 @@ class GreenThreadsObjectStoreIterator(ObjectStoreIterator):
             return len(self._shas)
             return len(self._shas)
         while len(self.finder.objects_to_send):
         while len(self.finder.objects_to_send):
             jobs = []
             jobs = []
-            for _ in xrange(0, len(self.finder.objects_to_send)):
+            for _ in range(0, len(self.finder.objects_to_send)):
                 jobs.append(self.p.spawn(self.finder.next))
                 jobs.append(self.p.spawn(self.finder.next))
             gevent.joinall(jobs)
             gevent.joinall(jobs)
             for j in jobs:
             for j in jobs:

+ 3 - 3
dulwich/hooks.py

@@ -100,7 +100,7 @@ class PreCommitShellHook(ShellHook):
     """pre-commit shell hook"""
     """pre-commit shell hook"""
 
 
     def __init__(self, controldir):
     def __init__(self, controldir):
-        filepath = os.path.join(controldir, 'hooks', 'pre-commit')
+        filepath = os.path.join(controldir, b'hooks', b'pre-commit')
 
 
         ShellHook.__init__(self, 'pre-commit', filepath, 0)
         ShellHook.__init__(self, 'pre-commit', filepath, 0)
 
 
@@ -109,7 +109,7 @@ class PostCommitShellHook(ShellHook):
     """post-commit shell hook"""
     """post-commit shell hook"""
 
 
     def __init__(self, controldir):
     def __init__(self, controldir):
-        filepath = os.path.join(controldir, 'hooks', 'post-commit')
+        filepath = os.path.join(controldir, b'hooks', b'post-commit')
 
 
         ShellHook.__init__(self, 'post-commit', filepath, 0)
         ShellHook.__init__(self, 'post-commit', filepath, 0)
 
 
@@ -122,7 +122,7 @@ class CommitMsgShellHook(ShellHook):
     """
     """
 
 
     def __init__(self, controldir):
     def __init__(self, controldir):
-        filepath = os.path.join(controldir, 'hooks', 'commit-msg')
+        filepath = os.path.join(controldir, b'hooks', b'commit-msg')
 
 
         def prepare_msg(*args):
         def prepare_msg(*args):
             (fd, path) = tempfile.mkstemp()
             (fd, path) = tempfile.mkstemp()

+ 37 - 22
dulwich/index.py

@@ -23,6 +23,7 @@ import errno
 import os
 import os
 import stat
 import stat
 import struct
 import struct
+import sys
 
 
 from dulwich.file import GitFile
 from dulwich.file import GitFile
 from dulwich.objects import (
 from dulwich.objects import (
@@ -52,9 +53,9 @@ def pathsplit(path):
     :return: Tuple with directory name and basename
     :return: Tuple with directory name and basename
     """
     """
     try:
     try:
-        (dirname, basename) = path.rsplit("/", 1)
+        (dirname, basename) = path.rsplit(b"/", 1)
     except ValueError:
     except ValueError:
-        return ("", path)
+        return (b"", path)
     else:
     else:
         return (dirname, basename)
         return (dirname, basename)
 
 
@@ -63,7 +64,7 @@ def pathjoin(*args):
     """Join a /-delimited path.
     """Join a /-delimited path.
 
 
     """
     """
-    return "/".join([p for p in args if p])
+    return b"/".join([p for p in args if p])
 
 
 
 
 def read_cache_time(f):
 def read_cache_time(f):
@@ -122,18 +123,18 @@ def write_cache_entry(f, entry):
     write_cache_time(f, ctime)
     write_cache_time(f, ctime)
     write_cache_time(f, mtime)
     write_cache_time(f, mtime)
     flags = len(name) | (flags &~ 0x0fff)
     flags = len(name) | (flags &~ 0x0fff)
-    f.write(struct.pack(">LLLLLL20sH", dev & 0xFFFFFFFF, ino & 0xFFFFFFFF, mode, uid, gid, size, hex_to_sha(sha), flags))
+    f.write(struct.pack(b'>LLLLLL20sH', dev & 0xFFFFFFFF, ino & 0xFFFFFFFF, mode, uid, gid, size, hex_to_sha(sha), flags))
     f.write(name)
     f.write(name)
     real_size = ((f.tell() - beginoffset + 8) & ~7)
     real_size = ((f.tell() - beginoffset + 8) & ~7)
-    f.write("\0" * ((beginoffset + real_size) - f.tell()))
+    f.write(b'\0' * ((beginoffset + real_size) - f.tell()))
 
 
 
 
 def read_index(f):
 def read_index(f):
     """Read an index file, yielding the individual entries."""
     """Read an index file, yielding the individual entries."""
     header = f.read(4)
     header = f.read(4)
-    if header != "DIRC":
+    if header != b'DIRC':
         raise AssertionError("Invalid index file header: %r" % header)
         raise AssertionError("Invalid index file header: %r" % header)
-    (version, num_entries) = struct.unpack(">LL", f.read(4 * 2))
+    (version, num_entries) = struct.unpack(b'>LL', f.read(4 * 2))
     assert version in (1, 2)
     assert version in (1, 2)
     for i in range(num_entries):
     for i in range(num_entries):
         yield read_cache_entry(f)
         yield read_cache_entry(f)
@@ -156,8 +157,8 @@ def write_index(f, entries):
     :param f: File-like object to write to
     :param f: File-like object to write to
     :param entries: Iterable over the entries to write
     :param entries: Iterable over the entries to write
     """
     """
-    f.write("DIRC")
-    f.write(struct.pack(">LL", 2, len(entries)))
+    f.write(b'DIRC')
+    f.write(struct.pack(b'>LL', 2, len(entries)))
     for x in entries:
     for x in entries:
         write_cache_entry(f, x)
         write_cache_entry(f, x)
 
 
@@ -202,6 +203,10 @@ class Index(object):
         self.clear()
         self.clear()
         self.read()
         self.read()
 
 
+    @property
+    def path(self):
+        return self._filename
+
     def __repr__(self):
     def __repr__(self):
         return "%s(%r)" % (self.__class__.__name__, self._filename)
         return "%s(%r)" % (self.__class__.__name__, self._filename)
 
 
@@ -263,20 +268,20 @@ class Index(object):
         self._byname = {}
         self._byname = {}
 
 
     def __setitem__(self, name, x):
     def __setitem__(self, name, x):
-        assert isinstance(name, str)
+        assert isinstance(name, bytes)
         assert len(x) == 10
         assert len(x) == 10
         # Remove the old entry if any
         # Remove the old entry if any
         self._byname[name] = x
         self._byname[name] = x
 
 
     def __delitem__(self, name):
     def __delitem__(self, name):
-        assert isinstance(name, str)
+        assert isinstance(name, bytes)
         del self._byname[name]
         del self._byname[name]
 
 
     def iteritems(self):
     def iteritems(self):
-        return self._byname.iteritems()
+        return self._byname.items()
 
 
     def update(self, entries):
     def update(self, entries):
-        for name, value in entries.iteritems():
+        for name, value in entries.items():
             self[name] = value
             self[name] = value
 
 
     def changes_from_tree(self, object_store, tree, want_unchanged=False):
     def changes_from_tree(self, object_store, tree, want_unchanged=False):
@@ -312,14 +317,14 @@ def commit_tree(object_store, blobs):
     :return: SHA1 of the created tree.
     :return: SHA1 of the created tree.
     """
     """
 
 
-    trees = {"": {}}
+    trees = {b'': {}}
 
 
     def add_tree(path):
     def add_tree(path):
         if path in trees:
         if path in trees:
             return trees[path]
             return trees[path]
         dirname, basename = pathsplit(path)
         dirname, basename = pathsplit(path)
         t = add_tree(dirname)
         t = add_tree(dirname)
-        assert isinstance(basename, str)
+        assert isinstance(basename, bytes)
         newtree = {}
         newtree = {}
         t[basename] = newtree
         t[basename] = newtree
         trees[path] = newtree
         trees[path] = newtree
@@ -332,7 +337,7 @@ def commit_tree(object_store, blobs):
 
 
     def build_tree(path):
     def build_tree(path):
         tree = Tree()
         tree = Tree()
-        for basename, entry in trees[path].iteritems():
+        for basename, entry in trees[path].items():
             if isinstance(entry, dict):
             if isinstance(entry, dict):
                 mode = stat.S_IFDIR
                 mode = stat.S_IFDIR
                 sha = build_tree(pathjoin(path, basename))
                 sha = build_tree(pathjoin(path, basename))
@@ -341,7 +346,7 @@ def commit_tree(object_store, blobs):
             tree.add(basename, mode, sha)
             tree.add(basename, mode, sha)
         object_store.add_object(tree)
         object_store.add_object(tree)
         return tree.id
         return tree.id
-    return build_tree("")
+    return build_tree(b'')
 
 
 
 
 def commit_index(object_store, index):
 def commit_index(object_store, index):
@@ -431,7 +436,7 @@ def build_file_from_blob(blob, mode, target_path, honor_filemode=True):
             os.chmod(target_path, mode)
             os.chmod(target_path, mode)
 
 
 
 
-INVALID_DOTNAMES = (".git", ".", "..", "")
+INVALID_DOTNAMES = (b".git", b".", b"..", b"")
 
 
 
 
 def validate_path_element_default(element):
 def validate_path_element_default(element):
@@ -439,17 +444,17 @@ def validate_path_element_default(element):
 
 
 
 
 def validate_path_element_ntfs(element):
 def validate_path_element_ntfs(element):
-    stripped = element.rstrip(". ").lower()
+    stripped = element.rstrip(b". ").lower()
     if stripped in INVALID_DOTNAMES:
     if stripped in INVALID_DOTNAMES:
         return False
         return False
-    if stripped == "git~1":
+    if stripped == b"git~1":
         return False
         return False
     return True
     return True
 
 
 
 
 def validate_path(path, element_validator=validate_path_element_default):
 def validate_path(path, element_validator=validate_path_element_default):
     """Default path validator that just checks for .git/."""
     """Default path validator that just checks for .git/."""
-    parts = path.split("/")
+    parts = path.split(b"/")
     for p in parts:
     for p in parts:
         if not element_validator(p):
         if not element_validator(p):
             return False
             return False
@@ -475,6 +480,9 @@ def build_index_from_tree(prefix, index_path, object_store, tree_id,
         in a working dir. Suiteable only for fresh clones.
         in a working dir. Suiteable only for fresh clones.
     """
     """
 
 
+    if not isinstance(prefix, bytes):
+        prefix = prefix.encode(sys.getfilesystemencoding())
+
     index = Index(index_path)
     index = Index(index_path)
 
 
     for entry in object_store.iter_tree_contents(tree_id):
     for entry in object_store.iter_tree_contents(tree_id):
@@ -508,7 +516,11 @@ def blob_from_path_and_stat(path, st):
         with open(path, 'rb') as f:
         with open(path, 'rb') as f:
             blob.data = f.read()
             blob.data = f.read()
     else:
     else:
-        blob.data = os.readlink(path)
+        if not isinstance(path, bytes):
+            blob.data = os.readlink(path.encode(sys.getfilesystemencoding()))
+        else:
+            blob.data = os.readlink(path)
+
     return blob
     return blob
 
 
 
 
@@ -520,6 +532,9 @@ def get_unstaged_changes(index, path):
     :return: iterator over paths with unstaged changes
     :return: iterator over paths with unstaged changes
     """
     """
     # For each entry in the index check the sha1 & ensure not staged
     # For each entry in the index check the sha1 & ensure not staged
+    if not isinstance(path, bytes):
+        path = path.encode(sys.getfilesystemencoding())
+
     for name, entry in index.iteritems():
     for name, entry in index.iteritems():
         fp = os.path.join(path, name)
         fp = os.path.join(path, name)
         blob = blob_from_path_and_stat(fp, os.lstat(fp))
         blob = blob_from_path_and_stat(fp, os.lstat(fp))

+ 40 - 33
dulwich/object_store.py

@@ -26,6 +26,7 @@ import errno
 from itertools import chain
 from itertools import chain
 import os
 import os
 import stat
 import stat
+import sys
 import tempfile
 import tempfile
 
 
 from dulwich.diff_tree import (
 from dulwich.diff_tree import (
@@ -62,16 +63,16 @@ from dulwich.pack import (
     PackStreamCopier,
     PackStreamCopier,
     )
     )
 
 
-INFODIR = 'info'
-PACKDIR = 'pack'
+INFODIR = b'info'
+PACKDIR = b'pack'
 
 
 
 
 class BaseObjectStore(object):
 class BaseObjectStore(object):
     """Object store interface."""
     """Object store interface."""
 
 
     def determine_wants_all(self, refs):
     def determine_wants_all(self, refs):
-        return [sha for (ref, sha) in refs.iteritems()
-                if not sha in self and not ref.endswith("^{}") and
+        return [sha for (ref, sha) in refs.items()
+                if not sha in self and not ref.endswith(b"^{}") and
                    not sha == ZERO_SHA]
                    not sha == ZERO_SHA]
 
 
     def iter_shas(self, shas):
     def iter_shas(self, shas):
@@ -335,7 +336,7 @@ class PackBasedObjectStore(BaseObjectStore):
 
 
     def __iter__(self):
     def __iter__(self):
         """Iterate over the SHAs that are present in this store."""
         """Iterate over the SHAs that are present in this store."""
-        iterables = self.packs + [self._iter_loose_objects()] + [self._iter_alternate_objects()]
+        iterables = list(self.packs) + [self._iter_loose_objects()] + [self._iter_alternate_objects()]
         return chain(*iterables)
         return chain(*iterables)
 
 
     def contains_loose(self, sha):
     def contains_loose(self, sha):
@@ -424,33 +425,31 @@ class DiskObjectStore(PackBasedObjectStore):
 
 
     def _read_alternate_paths(self):
     def _read_alternate_paths(self):
         try:
         try:
-            f = GitFile(os.path.join(self.path, "info", "alternates"),
+            f = GitFile(os.path.join(self.path, INFODIR, b'alternates'),
                     'rb')
                     'rb')
         except (OSError, IOError) as e:
         except (OSError, IOError) as e:
             if e.errno == errno.ENOENT:
             if e.errno == errno.ENOENT:
-                return []
+                return
             raise
             raise
-        ret = []
         with f:
         with f:
             for l in f.readlines():
             for l in f.readlines():
-                l = l.rstrip("\n")
-                if l[0] == "#":
+                l = l.rstrip(b"\n")
+                if l[0] == b"#":
                     continue
                     continue
                 if os.path.isabs(l):
                 if os.path.isabs(l):
-                    ret.append(l)
+                    yield l
                 else:
                 else:
-                    ret.append(os.path.join(self.path, l))
-            return ret
+                    yield os.path.join(self.path, l)
 
 
     def add_alternate_path(self, path):
     def add_alternate_path(self, path):
         """Add an alternate path to this object store.
         """Add an alternate path to this object store.
         """
         """
         try:
         try:
-            os.mkdir(os.path.join(self.path, "info"))
+            os.mkdir(os.path.join(self.path, INFODIR))
         except OSError as e:
         except OSError as e:
             if e.errno != errno.EEXIST:
             if e.errno != errno.EEXIST:
                 raise
                 raise
-        alternates_path = os.path.join(self.path, "info/alternates")
+        alternates_path = os.path.join(self.path, INFODIR, b'alternates')
         with GitFile(alternates_path, 'wb') as f:
         with GitFile(alternates_path, 'wb') as f:
             try:
             try:
                 orig_f = open(alternates_path, 'rb')
                 orig_f = open(alternates_path, 'rb')
@@ -460,7 +459,7 @@ class DiskObjectStore(PackBasedObjectStore):
             else:
             else:
                 with orig_f:
                 with orig_f:
                     f.write(orig_f.read())
                     f.write(orig_f.read())
-            f.write("%s\n" % path)
+            f.write(path + b"\n")
 
 
         if not os.path.isabs(path):
         if not os.path.isabs(path):
             path = os.path.join(self.path, path)
             path = os.path.join(self.path, path)
@@ -478,9 +477,10 @@ class DiskObjectStore(PackBasedObjectStore):
         self._pack_cache_time = os.stat(self.pack_dir).st_mtime
         self._pack_cache_time = os.stat(self.pack_dir).st_mtime
         pack_files = set()
         pack_files = set()
         for name in pack_dir_contents:
         for name in pack_dir_contents:
+            assert type(name) is bytes
             # TODO: verify that idx exists first
             # TODO: verify that idx exists first
-            if name.startswith("pack-") and name.endswith(".pack"):
-                pack_files.add(name[:-len(".pack")])
+            if name.startswith(b'pack-') and name.endswith(b'.pack'):
+                pack_files.add(name[:-len(b'.pack')])
 
 
         # Open newly appeared pack files
         # Open newly appeared pack files
         for f in pack_files:
         for f in pack_files:
@@ -507,7 +507,7 @@ class DiskObjectStore(PackBasedObjectStore):
             if len(base) != 2:
             if len(base) != 2:
                 continue
                 continue
             for rest in os.listdir(os.path.join(self.path, base)):
             for rest in os.listdir(os.path.join(self.path, base)):
-                yield base+rest
+                yield (base+rest)
 
 
     def _get_loose_object(self, sha):
     def _get_loose_object(self, sha):
         path = self._get_shafile_path(sha)
         path = self._get_shafile_path(sha)
@@ -521,6 +521,11 @@ class DiskObjectStore(PackBasedObjectStore):
     def _remove_loose_object(self, sha):
     def _remove_loose_object(self, sha):
         os.remove(self._get_shafile_path(sha))
         os.remove(self._get_shafile_path(sha))
 
 
+    def _get_pack_basepath(self, entries):
+        suffix = iter_sha1(entry[0] for entry in entries)
+        # TODO: Handle self.pack_dir being bytes
+        return os.path.join(self.pack_dir, b"pack-" + suffix)
+
     def _complete_thin_pack(self, f, path, copier, indexer):
     def _complete_thin_pack(self, f, path, copier, indexer):
         """Move a specific file containing a pack into the pack directory.
         """Move a specific file containing a pack into the pack directory.
 
 
@@ -560,12 +565,11 @@ class DiskObjectStore(PackBasedObjectStore):
 
 
         # Move the pack in.
         # Move the pack in.
         entries.sort()
         entries.sort()
-        pack_base_name = os.path.join(
-          self.pack_dir, 'pack-' + iter_sha1(e[0] for e in entries))
-        os.rename(path, pack_base_name + '.pack')
+        pack_base_name = self._get_pack_basepath(entries)
+        os.rename(path, pack_base_name + b'.pack')
 
 
         # Write the index.
         # Write the index.
-        index_file = GitFile(pack_base_name + '.idx', 'wb')
+        index_file = GitFile(pack_base_name + b'.idx', 'wb')
         try:
         try:
             write_pack_index_v2(index_file, entries, pack_sha)
             write_pack_index_v2(index_file, entries, pack_sha)
             index_file.close()
             index_file.close()
@@ -592,7 +596,10 @@ class DiskObjectStore(PackBasedObjectStore):
         :return: A Pack object pointing at the now-completed thin pack in the
         :return: A Pack object pointing at the now-completed thin pack in the
             objects/pack directory.
             objects/pack directory.
         """
         """
-        fd, path = tempfile.mkstemp(dir=self.path, prefix='tmp_pack_')
+        fd, path = tempfile.mkstemp(
+            dir=self.path.decode(sys.getfilesystemencoding()),
+            prefix='tmp_pack_')
+        path = path.encode(sys.getfilesystemencoding())
         with os.fdopen(fd, 'w+b') as f:
         with os.fdopen(fd, 'w+b') as f:
             indexer = PackIndexer(f, resolve_ext_ref=self.get_raw)
             indexer = PackIndexer(f, resolve_ext_ref=self.get_raw)
             copier = PackStreamCopier(read_all, read_some, f,
             copier = PackStreamCopier(read_all, read_some, f,
@@ -610,11 +617,10 @@ class DiskObjectStore(PackBasedObjectStore):
         """
         """
         with PackData(path) as p:
         with PackData(path) as p:
             entries = p.sorted_entries()
             entries = p.sorted_entries()
-            basename = os.path.join(self.pack_dir,
-                "pack-%s" % iter_sha1(entry[0] for entry in entries))
-            with GitFile(basename+".idx", "wb") as f:
+            basename = self._get_pack_basepath(entries)
+            with GitFile(basename+b'.idx', "wb") as f:
                 write_pack_index_v2(f, entries, p.get_stored_checksum())
                 write_pack_index_v2(f, entries, p.get_stored_checksum())
-        os.rename(path, basename + ".pack")
+        os.rename(path, basename + b'.pack')
         final_pack = Pack(basename)
         final_pack = Pack(basename)
         self._add_known_pack(basename, final_pack)
         self._add_known_pack(basename, final_pack)
         return final_pack
         return final_pack
@@ -626,7 +632,8 @@ class DiskObjectStore(PackBasedObjectStore):
             call when the pack is finished and an abort
             call when the pack is finished and an abort
             function.
             function.
         """
         """
-        fd, path = tempfile.mkstemp(dir=self.pack_dir, suffix=".pack")
+        pack_dir_str = self.pack_dir.decode(sys.getfilesystemencoding())
+        fd, path = tempfile.mkstemp(dir=pack_dir_str, suffix='.pack')
         f = os.fdopen(fd, 'wb')
         f = os.fdopen(fd, 'wb')
         def commit():
         def commit():
             os.fsync(fd)
             os.fsync(fd)
@@ -646,13 +653,13 @@ class DiskObjectStore(PackBasedObjectStore):
 
 
         :param obj: Object to add
         :param obj: Object to add
         """
         """
-        dir = os.path.join(self.path, obj.id[:2])
+        path = self._get_shafile_path(obj.id)
+        dir = os.path.dirname(path)
         try:
         try:
             os.mkdir(dir)
             os.mkdir(dir)
         except OSError as e:
         except OSError as e:
             if e.errno != errno.EEXIST:
             if e.errno != errno.EEXIST:
                 raise
                 raise
-        path = os.path.join(dir, obj.id[2:])
         if os.path.exists(path):
         if os.path.exists(path):
             return # Already there, no need to write again
             return # Already there, no need to write again
         with GitFile(path, 'wb') as f:
         with GitFile(path, 'wb') as f:
@@ -665,7 +672,7 @@ class DiskObjectStore(PackBasedObjectStore):
         except OSError as e:
         except OSError as e:
             if e.errno != errno.EEXIST:
             if e.errno != errno.EEXIST:
                 raise
                 raise
-        os.mkdir(os.path.join(path, "info"))
+        os.mkdir(os.path.join(path, INFODIR))
         os.mkdir(os.path.join(path, PACKDIR))
         os.mkdir(os.path.join(path, PACKDIR))
         return cls(path)
         return cls(path)
 
 
@@ -695,7 +702,7 @@ class MemoryObjectStore(BaseObjectStore):
 
 
     def __iter__(self):
     def __iter__(self):
         """Iterate over the SHAs that are present in this store."""
         """Iterate over the SHAs that are present in this store."""
-        return self._data.iterkeys()
+        return iter(self._data.keys())
 
 
     @property
     @property
     def packs(self):
     def packs(self):

+ 17 - 15
dulwich/objects.py

@@ -40,11 +40,6 @@ from dulwich.errors import (
     )
     )
 from dulwich.file import GitFile
 from dulwich.file import GitFile
 
 
-if sys.version_info[0] == 2:
-    iteritems = lambda d: d.iteritems()
-else:
-    iteritems = lambda d: d.items()
-
 
 
 ZERO_SHA = b'0' * 40
 ZERO_SHA = b'0' * 40
 
 
@@ -92,7 +87,7 @@ def sha_to_hex(sha):
 
 
 def hex_to_sha(hex):
 def hex_to_sha(hex):
     """Takes a hex sha and returns a binary sha"""
     """Takes a hex sha and returns a binary sha"""
-    assert len(hex) == 40, "Incorrent length of hexsha: %s" % hex
+    assert len(hex) == 40, "Incorrect length of hexsha: %s" % hex
     try:
     try:
         return binascii.unhexlify(hex)
         return binascii.unhexlify(hex)
     except TypeError as exc:
     except TypeError as exc:
@@ -101,17 +96,26 @@ def hex_to_sha(hex):
         raise ValueError(exc.args[0])
         raise ValueError(exc.args[0])
 
 
 
 
+def valid_hexsha(hex):
+    if len(hex) != 40:
+        return False
+    try:
+        binascii.unhexlify(hex)
+    except (TypeError, binascii.Error):
+        return False
+    else:
+        return True
+
+
 def hex_to_filename(path, hex):
 def hex_to_filename(path, hex):
     """Takes a hex sha and returns its filename relative to the given path."""
     """Takes a hex sha and returns its filename relative to the given path."""
     # os.path.join accepts bytes or unicode, but all args must be of the same
     # os.path.join accepts bytes or unicode, but all args must be of the same
     # type. Make sure that hex which is expected to be bytes, is the same type
     # type. Make sure that hex which is expected to be bytes, is the same type
     # as path.
     # as path.
-    if getattr(path, 'encode', None) is not None:
-        hex = hex.decode('ascii')
-    dir = hex[:2]
+    directory = hex[:2]
     file = hex[2:]
     file = hex[2:]
     # Check from object dir
     # Check from object dir
-    return os.path.join(path, dir, file)
+    return os.path.join(path, directory, file)
 
 
 
 
 def filename_to_hex(filename):
 def filename_to_hex(filename):
@@ -162,9 +166,7 @@ def check_hexsha(hex, error_msg):
     :param error_msg: Error message to use in exception
     :param error_msg: Error message to use in exception
     :raise ObjectFormatException: Raised when the string is not valid
     :raise ObjectFormatException: Raised when the string is not valid
     """
     """
-    try:
-        hex_to_sha(hex)
-    except (TypeError, AssertionError, ValueError):
+    if not valid_hexsha(hex):
         raise ObjectFormatException("%s %s" % (error_msg, hex))
         raise ObjectFormatException("%s %s" % (error_msg, hex))
 
 
 
 
@@ -273,6 +275,7 @@ class ShaFile(object):
             self._ensure_parsed()
             self._ensure_parsed()
         elif self._needs_serialization:
         elif self._needs_serialization:
             self._chunked_text = self._serialize()
             self._chunked_text = self._serialize()
+            self._needs_serialization = False
         return self._chunked_text
         return self._chunked_text
 
 
     def as_raw_string(self):
     def as_raw_string(self):
@@ -549,7 +552,6 @@ class Blob(ShaFile):
     def _serialize(self):
     def _serialize(self):
         if not self._chunked_text:
         if not self._chunked_text:
             self._ensure_parsed()
             self._ensure_parsed()
-        self._needs_serialization = False
         return self._chunked_text
         return self._chunked_text
 
 
     def _deserialize(self, chunks):
     def _deserialize(self, chunks):
@@ -785,7 +787,7 @@ def sorted_tree_items(entries, name_order):
     :return: Iterator over (name, mode, hexsha)
     :return: Iterator over (name, mode, hexsha)
     """
     """
     key_func = name_order and key_entry_name_order or key_entry
     key_func = name_order and key_entry_name_order or key_entry
-    for name, entry in sorted(iteritems(entries), key=key_func):
+    for name, entry in sorted(entries.items(), key=key_func):
         mode, hexsha = entry
         mode, hexsha = entry
         # Stricter type checks than normal to mirror checks in the C version.
         # Stricter type checks than normal to mirror checks in the C version.
         mode = int(mode)
         mode = int(mode)

+ 4 - 0
dulwich/objectspec.py

@@ -27,6 +27,8 @@ def parse_object(repo, objectish):
     :return: A git object
     :return: A git object
     :raise KeyError: If the object can not be found
     :raise KeyError: If the object can not be found
     """
     """
+    if getattr(objectish, "encode", None) is not None:
+        objectish = objectish.encode('ascii')
     return repo[objectish]
     return repo[objectish]
 
 
 
 
@@ -39,4 +41,6 @@ def parse_commit_range(repo, committishs):
     :raise KeyError: When the reference commits can not be found
     :raise KeyError: When the reference commits can not be found
     :raise ValueError: If the range can not be parsed
     :raise ValueError: If the range can not be parsed
     """
     """
+    if getattr(committishs, "encode", None) is not None:
+        committishs = committishs.encode('ascii')
     return iter([repo[committishs]])
     return iter([repo[committishs]])

+ 87 - 84
dulwich/pack.py

@@ -38,6 +38,7 @@ from collections import (
     deque,
     deque,
     )
     )
 import difflib
 import difflib
+import struct
 
 
 from itertools import chain
 from itertools import chain
 try:
 try:
@@ -48,6 +49,7 @@ except ImportError:
     izip = zip
     izip = zip
 
 
 import os
 import os
+import sys
 
 
 try:
 try:
     import mmap
     import mmap
@@ -56,8 +58,8 @@ except ImportError:
 else:
 else:
     has_mmap = True
     has_mmap = True
 
 
-# For some reason the above try, except fails to set has_mmap = False
-if os.uname()[0] == 'Plan9':
+# For some reason the above try, except fails to set has_mmap = False for plan9
+if sys.platform == 'Plan9':
     has_mmap = False
     has_mmap = False
 
 
 from hashlib import sha1
 from hashlib import sha1
@@ -65,7 +67,6 @@ from os import (
     SEEK_CUR,
     SEEK_CUR,
     SEEK_END,
     SEEK_END,
     )
     )
-import struct
 from struct import unpack_from
 from struct import unpack_from
 import zlib
 import zlib
 
 
@@ -104,7 +105,7 @@ def take_msb_bytes(read, crc32=None):
         b = read(1)
         b = read(1)
         if crc32 is not None:
         if crc32 is not None:
             crc32 = binascii.crc32(b, crc32)
             crc32 = binascii.crc32(b, crc32)
-        ret.append(ord(b))
+        ret.append(ord(b[:1]))
     return ret, crc32
     return ret, crc32
 
 
 
 
@@ -260,7 +261,7 @@ def iter_sha1(iter):
     sha = sha1()
     sha = sha1()
     for name in iter:
     for name in iter:
         sha.update(name)
         sha.update(name)
-    return sha.hexdigest()
+    return sha.hexdigest().encode('ascii')
 
 
 
 
 def load_pack_index(path):
 def load_pack_index(path):
@@ -303,8 +304,8 @@ def load_pack_index_file(path, f):
     :return: A PackIndex loaded from the given file
     :return: A PackIndex loaded from the given file
     """
     """
     contents, size = _load_file_contents(f)
     contents, size = _load_file_contents(f)
-    if contents[:4] == '\377tOc':
-        version = struct.unpack('>L', contents[4:8])[0]
+    if contents[:4] == b'\377tOc':
+        version = struct.unpack(b'>L', contents[4:8])[0]
         if version == 2:
         if version == 2:
             return PackIndex2(path, file=f, contents=contents,
             return PackIndex2(path, file=f, contents=contents,
                 size=size)
                 size=size)
@@ -327,10 +328,9 @@ def bisect_find_sha(start, end, sha, unpack_name):
     while start <= end:
     while start <= end:
         i = (start + end) // 2
         i = (start + end) // 2
         file_sha = unpack_name(i)
         file_sha = unpack_name(i)
-        x = cmp(file_sha, sha)
-        if x < 0:
+        if file_sha < sha:
             start = i + 1
             start = i + 1
-        elif x > 0:
+        elif file_sha > sha:
             end = i - 1
             end = i - 1
         else:
         else:
             return i
             return i
@@ -546,14 +546,14 @@ class FilePackIndex(PackIndex):
 
 
         :return: 20-byte binary digest
         :return: 20-byte binary digest
         """
         """
-        return str(self._contents[-40:-20])
+        return bytes(self._contents[-40:-20])
 
 
     def get_stored_checksum(self):
     def get_stored_checksum(self):
         """Return the SHA1 checksum stored for this index.
         """Return the SHA1 checksum stored for this index.
 
 
         :return: 20-byte binary digest
         :return: 20-byte binary digest
         """
         """
-        return str(self._contents[-20:])
+        return bytes(self._contents[-20:])
 
 
     def _object_index(self, sha):
     def _object_index(self, sha):
         """See object_index.
         """See object_index.
@@ -561,7 +561,7 @@ class FilePackIndex(PackIndex):
         :param sha: A *binary* SHA string. (20 characters long)_
         :param sha: A *binary* SHA string. (20 characters long)_
         """
         """
         assert len(sha) == 20
         assert len(sha) == 20
-        idx = ord(sha[0])
+        idx = ord(sha[:1])
         if idx == 0:
         if idx == 0:
             start = 0
             start = 0
         else:
         else:
@@ -604,9 +604,9 @@ class PackIndex2(FilePackIndex):
 
 
     def __init__(self, filename, file=None, contents=None, size=None):
     def __init__(self, filename, file=None, contents=None, size=None):
         super(PackIndex2, self).__init__(filename, file, contents, size)
         super(PackIndex2, self).__init__(filename, file, contents, size)
-        if self._contents[:4] != '\377tOc':
+        if self._contents[:4] != b'\377tOc':
             raise AssertionError('Not a v2 pack index file')
             raise AssertionError('Not a v2 pack index file')
-        (self.version, ) = unpack_from('>L', self._contents, 4)
+        (self.version, ) = unpack_from(b'>L', self._contents, 4)
         if self.version != 2:
         if self.version != 2:
             raise AssertionError('Version was %d' % self.version)
             raise AssertionError('Version was %d' % self.version)
         self._fan_out_table = self._read_fan_out_table(8)
         self._fan_out_table = self._read_fan_out_table(8)
@@ -648,17 +648,20 @@ def read_pack_header(read):
     header = read(12)
     header = read(12)
     if not header:
     if not header:
         return None, None
         return None, None
-    if header[:4] != 'PACK':
+    if header[:4] != b'PACK':
         raise AssertionError('Invalid pack header %r' % header)
         raise AssertionError('Invalid pack header %r' % header)
-    (version,) = unpack_from('>L', header, 4)
+    (version,) = unpack_from(b'>L', header, 4)
     if version not in (2, 3):
     if version not in (2, 3):
         raise AssertionError('Version was %d' % version)
         raise AssertionError('Version was %d' % version)
-    (num_objects,) = unpack_from('>L', header, 8)
+    (num_objects,) = unpack_from(b'>L', header, 8)
     return (version, num_objects)
     return (version, num_objects)
 
 
 
 
 def chunks_length(chunks):
 def chunks_length(chunks):
-    return sum(imap(len, chunks))
+    if isinstance(chunks, bytes):
+        return len(chunks)
+    else:
+        return sum(imap(len, chunks))
 
 
 
 
 def unpack_object(read_all, read_some=None, compute_crc32=False,
 def unpack_object(read_all, read_some=None, compute_crc32=False,
@@ -774,8 +777,7 @@ class PackStreamReader(object):
         else:
         else:
             to_pop = max(n + tn - 20, 0)
             to_pop = max(n + tn - 20, 0)
             to_add = n
             to_add = n
-        for _ in xrange(to_pop):
-            self.sha.update(self._trailer.popleft())
+        self.sha.update(bytes(bytearray([self._trailer.popleft() for _ in range(to_pop)])))
         self._trailer.extend(data[-to_add:])
         self._trailer.extend(data[-to_add:])
 
 
         # hash everything but the trailer
         # hash everything but the trailer
@@ -838,7 +840,7 @@ class PackStreamReader(object):
         if pack_version is None:
         if pack_version is None:
             return
             return
 
 
-        for i in xrange(self._num_objects):
+        for i in range(self._num_objects):
             offset = self.offset
             offset = self.offset
             unpacked, unused = unpack_object(
             unpacked, unused = unpack_object(
               self.read, read_some=self.recv, compute_crc32=compute_crc32,
               self.read, read_some=self.recv, compute_crc32=compute_crc32,
@@ -861,7 +863,7 @@ class PackStreamReader(object):
             # read buffer and (20 - N) come from the wire.
             # read buffer and (20 - N) come from the wire.
             self.read(20)
             self.read(20)
 
 
-        pack_sha = ''.join(self._trailer)
+        pack_sha = bytearray(self._trailer)
         if pack_sha != self.sha.digest():
         if pack_sha != self.sha.digest():
             raise ChecksumMismatch(sha_to_hex(pack_sha), self.sha.hexdigest())
             raise ChecksumMismatch(sha_to_hex(pack_sha), self.sha.hexdigest())
 
 
@@ -912,8 +914,11 @@ def obj_sha(type, chunks):
     """Compute the SHA for a numeric type and object chunks."""
     """Compute the SHA for a numeric type and object chunks."""
     sha = sha1()
     sha = sha1()
     sha.update(object_header(type, chunks_length(chunks)))
     sha.update(object_header(type, chunks_length(chunks)))
-    for chunk in chunks:
-        sha.update(chunk)
+    if isinstance(chunks, bytes):
+        sha.update(chunks)
+    else:
+        for chunk in chunks:
+            sha.update(chunk)
     return sha.digest()
     return sha.digest()
 
 
 
 
@@ -1071,7 +1076,7 @@ class PackData(object):
             assert isinstance(type, int)
             assert isinstance(type, int)
         elif type == REF_DELTA:
         elif type == REF_DELTA:
             (basename, delta) = obj
             (basename, delta) = obj
-            assert isinstance(basename, str) and len(basename) == 20
+            assert isinstance(basename, bytes) and len(basename) == 20
             base_offset, type, base_obj = get_ref(basename)
             base_offset, type, base_obj = get_ref(basename)
             assert isinstance(type, int)
             assert isinstance(type, int)
         type, base_chunks = self.resolve_object(base_offset, type, base_obj)
         type, base_chunks = self.resolve_object(base_offset, type, base_obj)
@@ -1086,7 +1091,7 @@ class PackData(object):
 
 
     def iterobjects(self, progress=None, compute_crc32=True):
     def iterobjects(self, progress=None, compute_crc32=True):
         self._file.seek(self._header_size)
         self._file.seek(self._header_size)
-        for i in xrange(1, self._num_objects + 1):
+        for i in range(1, self._num_objects + 1):
             offset = self._file.tell()
             offset = self._file.tell()
             unpacked, unused = unpack_object(
             unpacked, unused = unpack_object(
               self._file.read, compute_crc32=compute_crc32)
               self._file.read, compute_crc32=compute_crc32)
@@ -1104,7 +1109,7 @@ class PackData(object):
         if self._num_objects is None:
         if self._num_objects is None:
             return
             return
 
 
-        for _ in xrange(self._num_objects):
+        for _ in range(self._num_objects):
             offset = self._file.tell()
             offset = self._file.tell()
             unpacked, unused = unpack_object(
             unpacked, unused = unpack_object(
               self._file.read, compute_crc32=False)
               self._file.read, compute_crc32=False)
@@ -1200,8 +1205,6 @@ class PackData(object):
             return self._offset_cache[offset]
             return self._offset_cache[offset]
         except KeyError:
         except KeyError:
             pass
             pass
-        assert isinstance(offset, long) or isinstance(offset, int),\
-                'offset was %r' % offset
         assert offset >= self._header_size
         assert offset >= self._header_size
         self._file.seek(offset)
         self._file.seek(offset)
         unpacked, _ = unpack_object(self._file.read)
         unpacked, _ = unpack_object(self._file.read)
@@ -1280,7 +1283,7 @@ class DeltaChainIterator(object):
             self._ensure_no_pending()
             self._ensure_no_pending()
             return
             return
 
 
-        for base_sha, pending in sorted(self._pending_ref.iteritems()):
+        for base_sha, pending in sorted(self._pending_ref.items()):
             if base_sha not in self._pending_ref:
             if base_sha not in self._pending_ref:
                 continue
                 continue
             try:
             try:
@@ -1357,7 +1360,7 @@ class SHA1Reader(object):
 
 
     def __init__(self, f):
     def __init__(self, f):
         self.f = f
         self.f = f
-        self.sha1 = sha1('')
+        self.sha1 = sha1(b'')
 
 
     def read(self, num=None):
     def read(self, num=None):
         data = self.f.read(num)
         data = self.f.read(num)
@@ -1382,7 +1385,7 @@ class SHA1Writer(object):
     def __init__(self, f):
     def __init__(self, f):
         self.f = f
         self.f = f
         self.length = 0
         self.length = 0
-        self.sha1 = sha1('')
+        self.sha1 = sha1(b'')
 
 
     def write(self, data):
     def write(self, data):
         self.sha1.update(data)
         self.sha1.update(data)
@@ -1416,14 +1419,14 @@ def pack_object_header(type_num, delta_base, size):
     :param size: Uncompressed object size.
     :param size: Uncompressed object size.
     :return: A header for a packed object.
     :return: A header for a packed object.
     """
     """
-    header = ''
+    header = []
     c = (type_num << 4) | (size & 15)
     c = (type_num << 4) | (size & 15)
     size >>= 4
     size >>= 4
     while size:
     while size:
-        header += (chr(c | 0x80))
+        header.append(c | 0x80)
         c = size & 0x7f
         c = size & 0x7f
         size >>= 7
         size >>= 7
-    header += chr(c)
+    header.append(c)
     if type_num == OFS_DELTA:
     if type_num == OFS_DELTA:
         ret = [delta_base & 0x7f]
         ret = [delta_base & 0x7f]
         delta_base >>= 7
         delta_base >>= 7
@@ -1431,11 +1434,11 @@ def pack_object_header(type_num, delta_base, size):
             delta_base -= 1
             delta_base -= 1
             ret.insert(0, 0x80 | (delta_base & 0x7f))
             ret.insert(0, 0x80 | (delta_base & 0x7f))
             delta_base >>= 7
             delta_base >>= 7
-        header += ''.join([chr(x) for x in ret])
+        header.extend(ret)
     elif type_num == REF_DELTA:
     elif type_num == REF_DELTA:
         assert len(delta_base) == 20
         assert len(delta_base) == 20
         header += delta_base
         header += delta_base
-    return header
+    return bytearray(header)
 
 
 
 
 def write_pack_object(f, type, object, sha=None):
 def write_pack_object(f, type, object, sha=None):
@@ -1450,7 +1453,7 @@ def write_pack_object(f, type, object, sha=None):
         delta_base, object = object
         delta_base, object = object
     else:
     else:
         delta_base = None
         delta_base = None
-    header = pack_object_header(type, delta_base, len(object))
+    header = bytes(pack_object_header(type, delta_base, len(object)))
     comp_data = zlib.compress(object)
     comp_data = zlib.compress(object)
     crc32 = 0
     crc32 = 0
     for data in (header, comp_data):
     for data in (header, comp_data):
@@ -1471,20 +1474,20 @@ def write_pack(filename, objects, deltify=None, delta_window_size=None):
     :param deltify: Whether to deltify pack objects
     :param deltify: Whether to deltify pack objects
     :return: Tuple with checksum of pack file and index file
     :return: Tuple with checksum of pack file and index file
     """
     """
-    with GitFile(filename + '.pack', 'wb') as f:
+    with GitFile(filename + b'.pack', 'wb') as f:
         entries, data_sum = write_pack_objects(f, objects,
         entries, data_sum = write_pack_objects(f, objects,
             delta_window_size=delta_window_size, deltify=deltify)
             delta_window_size=delta_window_size, deltify=deltify)
-    entries = [(k, v[0], v[1]) for (k, v) in entries.iteritems()]
+    entries = [(k, v[0], v[1]) for (k, v) in entries.items()]
     entries.sort()
     entries.sort()
-    with GitFile(filename + '.idx', 'wb') as f:
+    with GitFile(filename + b'.idx', 'wb') as f:
         return data_sum, write_pack_index_v2(f, entries, data_sum)
         return data_sum, write_pack_index_v2(f, entries, data_sum)
 
 
 
 
 def write_pack_header(f, num_objects):
 def write_pack_header(f, num_objects):
     """Write a pack header for the given number of objects."""
     """Write a pack header for the given number of objects."""
-    f.write('PACK')                          # Pack header
-    f.write(struct.pack('>L', 2))            # Pack version
-    f.write(struct.pack('>L', num_objects))  # Number of objects in pack
+    f.write(b'PACK')                          # Pack header
+    f.write(struct.pack(b'>L', 2))            # Pack version
+    f.write(struct.pack(b'>L', num_objects))  # Number of objects in pack
 
 
 
 
 def deltify_pack_objects(objects, window_size=None):
 def deltify_pack_objects(objects, window_size=None):
@@ -1584,7 +1587,7 @@ def write_pack_index_v1(f, entries, pack_checksum):
     f = SHA1Writer(f)
     f = SHA1Writer(f)
     fan_out_table = defaultdict(lambda: 0)
     fan_out_table = defaultdict(lambda: 0)
     for (name, offset, entry_checksum) in entries:
     for (name, offset, entry_checksum) in entries:
-        fan_out_table[ord(name[0])] += 1
+        fan_out_table[ord(name[:1])] += 1
     # Fan-out table
     # Fan-out table
     for i in range(0x100):
     for i in range(0x100):
         f.write(struct.pack('>L', fan_out_table[i]))
         f.write(struct.pack('>L', fan_out_table[i]))
@@ -1599,14 +1602,14 @@ def write_pack_index_v1(f, entries, pack_checksum):
 
 
 
 
 def _delta_encode_size(size):
 def _delta_encode_size(size):
-    ret = ''
+    ret = bytearray()
     c = size & 0x7f
     c = size & 0x7f
     size >>= 7
     size >>= 7
     while size:
     while size:
-        ret += chr(c | 0x80)
+        ret.append(c | 0x80)
         c = size & 0x7f
         c = size & 0x7f
         size >>= 7
         size >>= 7
-    ret += chr(c)
+    ret.append(c)
     return ret
     return ret
 
 
 
 
@@ -1616,17 +1619,17 @@ def _delta_encode_size(size):
 _MAX_COPY_LEN = 0xffff
 _MAX_COPY_LEN = 0xffff
 
 
 def _encode_copy_operation(start, length):
 def _encode_copy_operation(start, length):
-    scratch = ''
+    scratch = []
     op = 0x80
     op = 0x80
     for i in range(4):
     for i in range(4):
         if start & 0xff << i*8:
         if start & 0xff << i*8:
-            scratch += chr((start >> i*8) & 0xff)
+            scratch.append((start >> i*8) & 0xff)
             op |= 1 << i
             op |= 1 << i
     for i in range(2):
     for i in range(2):
         if length & 0xff << i*8:
         if length & 0xff << i*8:
-            scratch += chr((length >> i*8) & 0xff)
+            scratch.append((length >> i*8) & 0xff)
             op |= 1 << (4+i)
             op |= 1 << (4+i)
-    return chr(op) + scratch
+    return bytearray([op] + scratch)
 
 
 
 
 def create_delta(base_buf, target_buf):
 def create_delta(base_buf, target_buf):
@@ -1635,10 +1638,10 @@ def create_delta(base_buf, target_buf):
     :param base_buf: Base buffer
     :param base_buf: Base buffer
     :param target_buf: Target buffer
     :param target_buf: Target buffer
     """
     """
-    assert isinstance(base_buf, str)
-    assert isinstance(target_buf, str)
-    out_buf = ''
-     # write delta header
+    assert isinstance(base_buf, bytes)
+    assert isinstance(target_buf, bytes)
+    out_buf = bytearray()
+    # write delta header
     out_buf += _delta_encode_size(len(base_buf))
     out_buf += _delta_encode_size(len(base_buf))
     out_buf += _delta_encode_size(len(target_buf))
     out_buf += _delta_encode_size(len(target_buf))
     # write out delta opcodes
     # write out delta opcodes
@@ -1663,13 +1666,13 @@ def create_delta(base_buf, target_buf):
             s = j2 - j1
             s = j2 - j1
             o = j1
             o = j1
             while s > 127:
             while s > 127:
-                out_buf += chr(127)
-                out_buf += target_buf[o:o+127]
+                out_buf.append(127)
+                out_buf += bytearray(target_buf[o:o+127])
                 s -= 127
                 s -= 127
                 o += 127
                 o += 127
-            out_buf += chr(s)
-            out_buf += target_buf[o:o+s]
-    return out_buf
+            out_buf.append(s)
+            out_buf += bytearray(target_buf[o:o+s])
+    return bytes(out_buf)
 
 
 
 
 def apply_delta(src_buf, delta):
 def apply_delta(src_buf, delta):
@@ -1678,10 +1681,10 @@ def apply_delta(src_buf, delta):
     :param src_buf: Source buffer
     :param src_buf: Source buffer
     :param delta: Delta instructions
     :param delta: Delta instructions
     """
     """
-    if not isinstance(src_buf, str):
-        src_buf = ''.join(src_buf)
-    if not isinstance(delta, str):
-        delta = ''.join(delta)
+    if not isinstance(src_buf, bytes):
+        src_buf = b''.join(src_buf)
+    if not isinstance(delta, bytes):
+        delta = b''.join(delta)
     out = []
     out = []
     index = 0
     index = 0
     delta_length = len(delta)
     delta_length = len(delta)
@@ -1689,7 +1692,7 @@ def apply_delta(src_buf, delta):
         size = 0
         size = 0
         i = 0
         i = 0
         while delta:
         while delta:
-            cmd = ord(delta[index])
+            cmd = ord(delta[index:index+1])
             index += 1
             index += 1
             size |= (cmd & ~0x80) << i
             size |= (cmd & ~0x80) << i
             i += 7
             i += 7
@@ -1700,20 +1703,20 @@ def apply_delta(src_buf, delta):
     dest_size, index = get_delta_header_size(delta, index)
     dest_size, index = get_delta_header_size(delta, index)
     assert src_size == len(src_buf), '%d vs %d' % (src_size, len(src_buf))
     assert src_size == len(src_buf), '%d vs %d' % (src_size, len(src_buf))
     while index < delta_length:
     while index < delta_length:
-        cmd = ord(delta[index])
+        cmd = ord(delta[index:index+1])
         index += 1
         index += 1
         if cmd & 0x80:
         if cmd & 0x80:
             cp_off = 0
             cp_off = 0
             for i in range(4):
             for i in range(4):
                 if cmd & (1 << i):
                 if cmd & (1 << i):
-                    x = ord(delta[index])
+                    x = ord(delta[index:index+1])
                     index += 1
                     index += 1
                     cp_off |= x << (i * 8)
                     cp_off |= x << (i * 8)
             cp_size = 0
             cp_size = 0
             # Version 3 packs can contain copy sizes larger than 64K.
             # Version 3 packs can contain copy sizes larger than 64K.
             for i in range(3):
             for i in range(3):
                 if cmd & (1 << (4+i)):
                 if cmd & (1 << (4+i)):
-                    x = ord(delta[index])
+                    x = ord(delta[index:index+1])
                     index += 1
                     index += 1
                     cp_size |= x << (i * 8)
                     cp_size |= x << (i * 8)
             if cp_size == 0:
             if cp_size == 0:
@@ -1748,28 +1751,28 @@ def write_pack_index_v2(f, entries, pack_checksum):
     :return: The SHA of the index file written
     :return: The SHA of the index file written
     """
     """
     f = SHA1Writer(f)
     f = SHA1Writer(f)
-    f.write('\377tOc') # Magic!
+    f.write(b'\377tOc')  # Magic!
     f.write(struct.pack('>L', 2))
     f.write(struct.pack('>L', 2))
     fan_out_table = defaultdict(lambda: 0)
     fan_out_table = defaultdict(lambda: 0)
     for (name, offset, entry_checksum) in entries:
     for (name, offset, entry_checksum) in entries:
-        fan_out_table[ord(name[0])] += 1
+        fan_out_table[ord(name[:1])] += 1
     # Fan-out table
     # Fan-out table
     largetable = []
     largetable = []
     for i in range(0x100):
     for i in range(0x100):
-        f.write(struct.pack('>L', fan_out_table[i]))
+        f.write(struct.pack(b'>L', fan_out_table[i]))
         fan_out_table[i+1] += fan_out_table[i]
         fan_out_table[i+1] += fan_out_table[i]
     for (name, offset, entry_checksum) in entries:
     for (name, offset, entry_checksum) in entries:
         f.write(name)
         f.write(name)
     for (name, offset, entry_checksum) in entries:
     for (name, offset, entry_checksum) in entries:
-        f.write(struct.pack('>L', entry_checksum))
+        f.write(struct.pack(b'>L', entry_checksum))
     for (name, offset, entry_checksum) in entries:
     for (name, offset, entry_checksum) in entries:
         if offset < 2**31:
         if offset < 2**31:
-            f.write(struct.pack('>L', offset))
+            f.write(struct.pack(b'>L', offset))
         else:
         else:
-            f.write(struct.pack('>L', 2**31 + len(largetable)))
+            f.write(struct.pack(b'>L', 2**31 + len(largetable)))
             largetable.append(offset)
             largetable.append(offset)
     for offset in largetable:
     for offset in largetable:
-        f.write(struct.pack('>Q', offset))
+        f.write(struct.pack(b'>Q', offset))
     assert len(pack_checksum) == 20
     assert len(pack_checksum) == 20
     f.write(pack_checksum)
     f.write(pack_checksum)
     return f.write_sha()
     return f.write_sha()
@@ -1782,8 +1785,8 @@ class Pack(object):
         self._basename = basename
         self._basename = basename
         self._data = None
         self._data = None
         self._idx = None
         self._idx = None
-        self._idx_path = self._basename + '.idx'
-        self._data_path = self._basename + '.pack'
+        self._idx_path = self._basename + b'.idx'
+        self._data_path = self._basename + b'.pack'
         self._data_load = lambda: PackData(self._data_path)
         self._data_load = lambda: PackData(self._data_path)
         self._idx_load = lambda: load_pack_index(self._idx_path)
         self._idx_load = lambda: load_pack_index(self._idx_path)
         self.resolve_ext_ref = resolve_ext_ref
         self.resolve_ext_ref = resolve_ext_ref
@@ -1792,7 +1795,7 @@ class Pack(object):
     def from_lazy_objects(self, data_fn, idx_fn):
     def from_lazy_objects(self, data_fn, idx_fn):
         """Create a new pack object from callables to load pack data and
         """Create a new pack object from callables to load pack data and
         index objects."""
         index objects."""
-        ret = Pack('')
+        ret = Pack(b'')
         ret._data_load = data_fn
         ret._data_load = data_fn
         ret._idx_load = idx_fn
         ret._idx_load = idx_fn
         return ret
         return ret
@@ -1800,7 +1803,7 @@ class Pack(object):
     @classmethod
     @classmethod
     def from_objects(self, data, idx):
     def from_objects(self, data, idx):
         """Create a new pack object from pack data and index objects."""
         """Create a new pack object from pack data and index objects."""
-        ret = Pack('')
+        ret = Pack(b'')
         ret._data_load = lambda: data
         ret._data_load = lambda: data
         ret._idx_load = lambda: idx
         ret._idx_load = lambda: idx
         return ret
         return ret
@@ -1889,7 +1892,7 @@ class Pack(object):
         offset = self.index.object_index(sha1)
         offset = self.index.object_index(sha1)
         obj_type, obj = self.data.get_object_at(offset)
         obj_type, obj = self.data.get_object_at(offset)
         type_num, chunks = self.data.resolve_object(offset, obj_type, obj)
         type_num, chunks = self.data.resolve_object(offset, obj_type, obj)
-        return type_num, ''.join(chunks)
+        return type_num, b''.join(chunks)
 
 
     def __getitem__(self, sha1):
     def __getitem__(self, sha1):
         """Retrieve the specified SHA1."""
         """Retrieve the specified SHA1."""
@@ -1927,11 +1930,11 @@ class Pack(object):
                     determine whether or not a .keep file is obsolete.
                     determine whether or not a .keep file is obsolete.
         :return: The path of the .keep file, as a string.
         :return: The path of the .keep file, as a string.
         """
         """
-        keepfile_name = '%s.keep' % self._basename
+        keepfile_name = self._basename + b'.keep'
         with GitFile(keepfile_name, 'wb') as keepfile:
         with GitFile(keepfile_name, 'wb') as keepfile:
             if msg:
             if msg:
                 keepfile.write(msg)
                 keepfile.write(msg)
-                keepfile.write('\n')
+                keepfile.write(b'\n')
         return keepfile_name
         return keepfile_name
 
 
 
 

+ 48 - 53
dulwich/patch.py

@@ -115,6 +115,20 @@ def is_binary(content):
     return '\0' in content[:FIRST_FEW_BYTES]
     return '\0' in content[:FIRST_FEW_BYTES]
 
 
 
 
+def shortid(hexsha):
+    if hexsha is None:
+        return "0" * 7
+    else:
+        return hexsha[:7]
+
+
+def patch_filename(p, root):
+    if p is None:
+        return "/dev/null"
+    else:
+        return root + "/" + p
+
+
 def write_object_diff(f, store, old_file, new_file, diff_binary=False):
 def write_object_diff(f, store, old_file, new_file, diff_binary=False):
     """Write the diff for an object.
     """Write the diff for an object.
 
 
@@ -129,12 +143,8 @@ def write_object_diff(f, store, old_file, new_file, diff_binary=False):
     """
     """
     (old_path, old_mode, old_id) = old_file
     (old_path, old_mode, old_id) = old_file
     (new_path, new_mode, new_id) = new_file
     (new_path, new_mode, new_id) = new_file
-    def shortid(hexsha):
-        if hexsha is None:
-            return "0" * 7
-        else:
-            return hexsha[:7]
-
+    old_path = patch_filename(old_path, "a")
+    new_path = patch_filename(new_path, "b")
     def content(mode, hexsha):
     def content(mode, hexsha):
         if hexsha is None:
         if hexsha is None:
             return ''
             return ''
@@ -148,27 +158,8 @@ def write_object_diff(f, store, old_file, new_file, diff_binary=False):
             return []
             return []
         else:
         else:
             return content.splitlines(True)
             return content.splitlines(True)
-
-    if old_path is None:
-        old_path = "/dev/null"
-    else:
-        old_path = "a/%s" % old_path
-    if new_path is None:
-        new_path = "/dev/null"
-    else:
-        new_path = "b/%s" % new_path
-    f.write("diff --git %s %s\n" % (old_path, new_path))
-    if old_mode != new_mode:
-        if new_mode is not None:
-            if old_mode is not None:
-                f.write("old mode %o\n" % old_mode)
-            f.write("new mode %o\n" % new_mode)
-        else:
-            f.write("deleted mode %o\n" % old_mode)
-    f.write("index %s..%s" % (shortid(old_id), shortid(new_id)))
-    if new_mode is not None:
-        f.write(" %o" % new_mode)
-    f.write("\n")
+    f.writelines(gen_diff_header(
+        (old_path, new_path), (old_mode, new_mode), (old_id, new_id)))
     old_content = content(old_mode, old_id)
     old_content = content(old_mode, old_id)
     new_content = content(new_mode, new_id)
     new_content = content(new_mode, new_id)
     if not diff_binary and (is_binary(old_content) or is_binary(new_content)):
     if not diff_binary and (is_binary(old_content) or is_binary(new_content)):
@@ -178,8 +169,32 @@ def write_object_diff(f, store, old_file, new_file, diff_binary=False):
             old_path, new_path))
             old_path, new_path))
 
 
 
 
+def gen_diff_header(paths, modes, shas):
+    """Write a blob diff header.
+
+    :param paths: Tuple with old and new path
+    :param modes: Tuple with old and new modes
+    :param shas: Tuple with old and new shas
+    """
+    (old_path, new_path) = paths
+    (old_mode, new_mode) = modes
+    (old_sha, new_sha) = shas
+    yield "diff --git %s %s\n" % (old_path, new_path)
+    if old_mode != new_mode:
+        if new_mode is not None:
+            if old_mode is not None:
+                yield "old mode %o\n" % old_mode
+            yield "new mode %o\n" % new_mode
+        else:
+            yield "deleted mode %o\n" % old_mode
+    yield "index " + shortid(old_sha) + ".." + shortid(new_sha)
+    if new_mode is not None:
+        yield " %o" % new_mode
+    yield "\n"
+
+
 def write_blob_diff(f, old_file, new_file):
 def write_blob_diff(f, old_file, new_file):
-    """Write diff file header.
+    """Write blob diff.
 
 
     :param f: File-like object to write to
     :param f: File-like object to write to
     :param old_file: (path, mode, hexsha) tuple (None if nonexisting)
     :param old_file: (path, mode, hexsha) tuple (None if nonexisting)
@@ -189,36 +204,16 @@ def write_blob_diff(f, old_file, new_file):
     """
     """
     (old_path, old_mode, old_blob) = old_file
     (old_path, old_mode, old_blob) = old_file
     (new_path, new_mode, new_blob) = new_file
     (new_path, new_mode, new_blob) = new_file
-    def blob_id(blob):
-        if blob is None:
-            return "0" * 7
-        else:
-            return blob.id[:7]
+    old_path = patch_filename(old_path, "a")
+    new_path = patch_filename(new_path, "b")
     def lines(blob):
     def lines(blob):
         if blob is not None:
         if blob is not None:
             return blob.data.splitlines(True)
             return blob.data.splitlines(True)
         else:
         else:
             return []
             return []
-    if old_path is None:
-        old_path = "/dev/null"
-    else:
-        old_path = "a/%s" % old_path
-    if new_path is None:
-        new_path = "/dev/null"
-    else:
-        new_path = "b/%s" % new_path
-    f.write("diff --git %s %s\n" % (old_path, new_path))
-    if old_mode != new_mode:
-        if new_mode is not None:
-            if old_mode is not None:
-                f.write("old mode %o\n" % old_mode)
-            f.write("new mode %o\n" % new_mode)
-        else:
-            f.write("deleted mode %o\n" % old_mode)
-    f.write("index %s..%s" % (blob_id(old_blob), blob_id(new_blob)))
-    if new_mode is not None:
-        f.write(" %o" % new_mode)
-    f.write("\n")
+    f.writelines(gen_diff_header(
+        (old_path, new_path), (old_mode, new_mode),
+        (getattr(old_blob, "id", None), getattr(new_blob, "id", None))))
     old_contents = lines(old_blob)
     old_contents = lines(old_blob)
     new_contents = lines(new_blob)
     new_contents = lines(new_blob)
     f.writelines(unified_diff(old_contents, new_contents,
     f.writelines(unified_diff(old_contents, new_contents,

+ 72 - 48
dulwich/porcelain.py

@@ -52,7 +52,6 @@ import os
 import sys
 import sys
 import time
 import time
 
 
-from dulwich import index
 from dulwich.client import get_transport_and_path
 from dulwich.client import get_transport_and_path
 from dulwich.errors import (
 from dulwich.errors import (
     SendPackError,
     SendPackError,
@@ -123,10 +122,10 @@ def symbolic_ref(repo, ref_name, force=False):
     :param force: force settings without checking if it exists in refs/heads
     :param force: force settings without checking if it exists in refs/heads
     """
     """
     repo_obj = open_repo(repo)
     repo_obj = open_repo(repo)
-    ref_path = 'refs/heads/%s' % ref_name
+    ref_path = b'refs/heads/' + ref_name
     if not force and ref_path not in repo_obj.refs.keys():
     if not force and ref_path not in repo_obj.refs.keys():
         raise ValueError('fatal: ref `%s` is not a ref' % ref_name)
         raise ValueError('fatal: ref `%s` is not a ref' % ref_name)
-    repo_obj.refs.set_symbolic_ref('HEAD', ref_path)
+    repo_obj.refs.set_symbolic_ref(b'HEAD', ref_path)
 
 
 
 
 def commit(repo=".", message=None, author=None, committer=None):
 def commit(repo=".", message=None, author=None, committer=None):
@@ -174,15 +173,22 @@ def init(path=".", bare=False):
         return Repo.init(path)
         return Repo.init(path)
 
 
 
 
-def clone(source, target=None, bare=False, checkout=None, outstream=sys.stdout):
+def clone(source, target=None, bare=False, checkout=None, errstream=sys.stdout, outstream=None):
     """Clone a local or remote git repository.
     """Clone a local or remote git repository.
 
 
     :param source: Path or URL for source repository
     :param source: Path or URL for source repository
     :param target: Path to target repository (optional)
     :param target: Path to target repository (optional)
     :param bare: Whether or not to create a bare repository
     :param bare: Whether or not to create a bare repository
-    :param outstream: Optional stream to write progress to
+    :param errstream: Optional stream to write progress to
+    :param outstream: Optional stream to write progress to (deprecated)
     :return: The new repository
     :return: The new repository
     """
     """
+    if outstream is not None:
+        import warnings
+        warnings.warn("outstream= has been deprecated in favour of errstream=.", DeprecationWarning,
+                stacklevel=3)
+        errstream = outstream
+
     if checkout is None:
     if checkout is None:
         checkout = (not bare)
         checkout = (not bare)
     if checkout and bare:
     if checkout and bare:
@@ -200,10 +206,10 @@ def clone(source, target=None, bare=False, checkout=None, outstream=sys.stdout):
         r = Repo.init(target)
         r = Repo.init(target)
     remote_refs = client.fetch(host_path, r,
     remote_refs = client.fetch(host_path, r,
         determine_wants=r.object_store.determine_wants_all,
         determine_wants=r.object_store.determine_wants_all,
-        progress=outstream.write)
-    r["HEAD"] = remote_refs["HEAD"]
+        progress=errstream.write)
+    r[b"HEAD"] = remote_refs[b"HEAD"]
     if checkout:
     if checkout:
-        outstream.write('Checking out HEAD')
+        errstream.write(b'Checking out HEAD')
         r.reset_index()
         r.reset_index()
 
 
     return r
     return r
@@ -238,25 +244,31 @@ def rm(repo=".", paths=None):
     r = open_repo(repo)
     r = open_repo(repo)
     index = r.open_index()
     index = r.open_index()
     for p in paths:
     for p in paths:
-        del index[p]
+        del index[p.encode(sys.getfilesystemencoding())]
     index.write()
     index.write()
 
 
 
 
+def commit_decode(commit, contents):
+    if commit.encoding is not None:
+        return contents.decode(commit.encoding, "replace")
+    return contents.decode("utf-8", "replace")
+
+
 def print_commit(commit, outstream=sys.stdout):
 def print_commit(commit, outstream=sys.stdout):
     """Write a human-readable commit log entry.
     """Write a human-readable commit log entry.
 
 
     :param commit: A `Commit` object
     :param commit: A `Commit` object
     :param outstream: A stream file to write to
     :param outstream: A stream file to write to
     """
     """
-    outstream.write("-" * 50 + "\n")
-    outstream.write("commit: %s\n" % commit.id)
+    outstream.write(b"-" * 50 + b"\n")
+    outstream.write(b"commit: " + commit.id + b"\n")
     if len(commit.parents) > 1:
     if len(commit.parents) > 1:
-        outstream.write("merge: %s\n" % "...".join(commit.parents[1:]))
-    outstream.write("author: %s\n" % commit.author)
-    outstream.write("committer: %s\n" % commit.committer)
-    outstream.write("\n")
-    outstream.write(commit.message + "\n")
-    outstream.write("\n")
+        outstream.write(b"merge: " + b"...".join(commit.parents[1:]) + b"\n")
+    outstream.write(b"author: " + commit.author + b"\n")
+    outstream.write(b"committer: " + commit.committer + b"\n")
+    outstream.write(b"\n")
+    outstream.write(commit.message + b"\n")
+    outstream.write(b"\n")
 
 
 
 
 def print_tag(tag, outstream=sys.stdout):
 def print_tag(tag, outstream=sys.stdout):
@@ -265,11 +277,11 @@ def print_tag(tag, outstream=sys.stdout):
     :param tag: A `Tag` object
     :param tag: A `Tag` object
     :param outstream: A stream to write to
     :param outstream: A stream to write to
     """
     """
-    outstream.write("Tagger: %s\n" % tag.tagger)
-    outstream.write("Date:   %s\n" % tag.tag_time)
-    outstream.write("\n")
-    outstream.write("%s\n" % tag.message)
-    outstream.write("\n")
+    outstream.write(b"Tagger: " + tag.tagger + b"\n")
+    outstream.write(b"Date:   " + tag.tag_time + b"\n")
+    outstream.write(b"\n")
+    outstream.write(tag.message + b"\n")
+    outstream.write(b"\n")
 
 
 
 
 def show_blob(repo, blob, outstream=sys.stdout):
 def show_blob(repo, blob, outstream=sys.stdout):
@@ -318,10 +330,10 @@ def show_tag(repo, tag, outstream=sys.stdout):
 
 
 def show_object(repo, obj, outstream):
 def show_object(repo, obj, outstream):
     return {
     return {
-        "tree": show_tree,
-        "blob": show_blob,
-        "commit": show_commit,
-        "tag": show_tag,
+        b"tree": show_tree,
+        b"blob": show_blob,
+        b"commit": show_commit,
+        b"tag": show_tag,
             }[obj.type_name](repo, obj, outstream)
             }[obj.type_name](repo, obj, outstream)
 
 
 
 
@@ -375,12 +387,12 @@ def rev_list(repo, commits, outstream=sys.stdout):
     """
     """
     r = open_repo(repo)
     r = open_repo(repo)
     for entry in r.get_walker(include=[r[c].id for c in commits]):
     for entry in r.get_walker(include=[r[c].id for c in commits]):
-        outstream.write("%s\n" % entry.commit.id)
+        outstream.write(entry.commit.id + b"\n")
 
 
 
 
 def tag(*args, **kwargs):
 def tag(*args, **kwargs):
     import warnings
     import warnings
-    warnings.warn(DeprecationWarning, "tag has been deprecated in favour of tag_create.")
+    warnings.warn("tag has been deprecated in favour of tag_create.", DeprecationWarning)
     return tag_create(*args, **kwargs)
     return tag_create(*args, **kwargs)
 
 
 
 
@@ -425,12 +437,12 @@ def tag_create(repo, tag, author=None, message=None, annotated=False,
     else:
     else:
         tag_id = object.id
         tag_id = object.id
 
 
-    r.refs['refs/tags/' + tag] = tag_id
+    r.refs[b'refs/tags/' + tag] = tag_id
 
 
 
 
 def list_tags(*args, **kwargs):
 def list_tags(*args, **kwargs):
     import warnings
     import warnings
-    warnings.warn(DeprecationWarning, "list_tags has been deprecated in favour of tag_list.")
+    warnings.warn("list_tags has been deprecated in favour of tag_list.", DeprecationWarning)
     return tag_list(*args, **kwargs)
     return tag_list(*args, **kwargs)
 
 
 
 
@@ -441,7 +453,7 @@ def tag_list(repo, outstream=sys.stdout):
     :param outstream: Stream to write tags to
     :param outstream: Stream to write tags to
     """
     """
     r = open_repo(repo)
     r = open_repo(repo)
-    tags = list(r.refs.as_dict("refs/tags"))
+    tags = list(r.refs.as_dict(b"refs/tags"))
     tags.sort()
     tags.sort()
     return tags
     return tags
 
 
@@ -453,14 +465,14 @@ def tag_delete(repo, name):
     :param name: Name of tag to remove
     :param name: Name of tag to remove
     """
     """
     r = open_repo(repo)
     r = open_repo(repo)
-    if isinstance(name, str):
+    if isinstance(name, bytes):
         names = [name]
         names = [name]
     elif isinstance(name, list):
     elif isinstance(name, list):
         names = name
         names = name
     else:
     else:
         raise TypeError("Unexpected tag name type %r" % name)
         raise TypeError("Unexpected tag name type %r" % name)
     for name in names:
     for name in names:
-        del r.refs["refs/tags/" + name]
+        del r.refs[b"refs/tags/" + name]
 
 
 
 
 def reset(repo, mode, committish="HEAD"):
 def reset(repo, mode, committish="HEAD"):
@@ -498,17 +510,21 @@ def push(repo, remote_location, refs_path,
 
 
     def update_refs(refs):
     def update_refs(refs):
         new_refs = r.get_refs()
         new_refs = r.get_refs()
-        refs[refs_path] = new_refs['HEAD']
-        del new_refs['HEAD']
+        refs[refs_path] = new_refs[b'HEAD']
+        del new_refs[b'HEAD']
         return refs
         return refs
 
 
+    err_encoding = getattr(errstream, 'encoding', 'utf-8')
+    if not isinstance(remote_location, bytes):
+        remote_location_bytes = remote_location.encode(err_encoding)
+    else:
+        remote_location_bytes = remote_location
     try:
     try:
         client.send_pack(path, update_refs,
         client.send_pack(path, update_refs,
             r.object_store.generate_pack_contents, progress=errstream.write)
             r.object_store.generate_pack_contents, progress=errstream.write)
-        outstream.write("Push to %s successful.\n" % remote_location)
+        errstream.write(b"Push to " + remote_location_bytes + b" successful.\n")
     except (UpdateRefsError, SendPackError) as e:
     except (UpdateRefsError, SendPackError) as e:
-        outstream.write("Push to %s failed.\n" % remote_location)
-        errstream.write("Push to %s failed -> '%s'\n" % e.message)
+        errstream.write(b"Push to " + remote_location_bytes + b" failed -> " + e.message.encode(err_encoding) + b"\n")
 
 
 
 
 def pull(repo, remote_location, refs_path,
 def pull(repo, remote_location, refs_path,
@@ -527,10 +543,10 @@ def pull(repo, remote_location, refs_path,
 
 
     client, path = get_transport_and_path(remote_location)
     client, path = get_transport_and_path(remote_location)
     remote_refs = client.fetch(path, r, progress=errstream.write)
     remote_refs = client.fetch(path, r, progress=errstream.write)
-    r['HEAD'] = remote_refs[refs_path]
+    r[b'HEAD'] = remote_refs[refs_path]
 
 
     # Perform 'git checkout .' - syncs staged changes
     # Perform 'git checkout .' - syncs staged changes
-    tree = r["HEAD"].tree
+    tree = r[b"HEAD"].tree
     r.reset_index()
     r.reset_index()
 
 
 
 
@@ -571,7 +587,7 @@ def get_tree_changes(repo):
         'delete': [],
         'delete': [],
         'modify': [],
         'modify': [],
     }
     }
-    for change in index.changes_from_tree(r.object_store, r['HEAD'].tree):
+    for change in index.changes_from_tree(r.object_store, r[b'HEAD'].tree):
         if not change[0][0]:
         if not change[0][0]:
             tracked_changes['add'].append(change[0][1])
             tracked_changes['add'].append(change[0][1])
         elif not change[0][1]:
         elif not change[0][1]:
@@ -617,13 +633,17 @@ def web_daemon(path=".", address=None, port=None):
     server.serve_forever()
     server.serve_forever()
 
 
 
 
-def upload_pack(path=".", inf=sys.stdin, outf=sys.stdout):
+def upload_pack(path=".", inf=None, outf=None):
     """Upload a pack file after negotiating its contents using smart protocol.
     """Upload a pack file after negotiating its contents using smart protocol.
 
 
     :param path: Path to the repository
     :param path: Path to the repository
     :param inf: Input stream to communicate with client
     :param inf: Input stream to communicate with client
     :param outf: Output stream to communicate with client
     :param outf: Output stream to communicate with client
     """
     """
+    if outf is None:
+        outf = getattr(sys.stdout, 'buffer', sys.stdout)
+    if inf is None:
+        inf = getattr(sys.stdin, 'buffer', sys.stdin)
     backend = FileSystemBackend()
     backend = FileSystemBackend()
     def send_fn(data):
     def send_fn(data):
         outf.write(data)
         outf.write(data)
@@ -635,13 +655,17 @@ def upload_pack(path=".", inf=sys.stdin, outf=sys.stdout):
     return 0
     return 0
 
 
 
 
-def receive_pack(path=".", inf=sys.stdin, outf=sys.stdout):
+def receive_pack(path=".", inf=None, outf=None):
     """Receive a pack file after negotiating its contents using smart protocol.
     """Receive a pack file after negotiating its contents using smart protocol.
 
 
     :param path: Path to the repository
     :param path: Path to the repository
     :param inf: Input stream to communicate with client
     :param inf: Input stream to communicate with client
     :param outf: Output stream to communicate with client
     :param outf: Output stream to communicate with client
     """
     """
+    if outf is None:
+        outf = getattr(sys.stdout, 'buffer', sys.stdout)
+    if inf is None:
+        inf = getattr(sys.stdin, 'buffer', sys.stdin)
     backend = FileSystemBackend()
     backend = FileSystemBackend()
     def send_fn(data):
     def send_fn(data):
         outf.write(data)
         outf.write(data)
@@ -660,14 +684,14 @@ def branch_delete(repo, name):
     :param name: Name of the branch
     :param name: Name of the branch
     """
     """
     r = open_repo(repo)
     r = open_repo(repo)
-    if isinstance(name, str):
+    if isinstance(name, bytes):
         names = [name]
         names = [name]
     elif isinstance(name, list):
     elif isinstance(name, list):
         names = name
         names = name
     else:
     else:
         raise TypeError("Unexpected branch name type %r" % name)
         raise TypeError("Unexpected branch name type %r" % name)
     for name in names:
     for name in names:
-        del r.refs["refs/heads/" + name]
+        del r.refs[b"refs/heads/" + name]
 
 
 
 
 def branch_create(repo, name, objectish=None, force=False):
 def branch_create(repo, name, objectish=None, force=False):
@@ -679,7 +703,7 @@ def branch_create(repo, name, objectish=None, force=False):
     :param force: Force creation of branch, even if it already exists
     :param force: Force creation of branch, even if it already exists
     """
     """
     r = open_repo(repo)
     r = open_repo(repo)
-    if isinstance(name, str):
+    if isinstance(name, bytes):
         names = [name]
         names = [name]
     elif isinstance(name, list):
     elif isinstance(name, list):
         names = name
         names = name
@@ -688,7 +712,7 @@ def branch_create(repo, name, objectish=None, force=False):
     if objectish is None:
     if objectish is None:
         objectish = "HEAD"
         objectish = "HEAD"
     object = parse_object(r, objectish)
     object = parse_object(r, objectish)
-    refname = "refs/heads/" + name
+    refname = b"refs/heads/" + name
     if refname in r.refs and not force:
     if refname in r.refs and not force:
         raise KeyError("Branch with name %s already exists." % name)
         raise KeyError("Branch with name %s already exists." % name)
     r.refs[refname] = object.id
     r.refs[refname] = object.id
@@ -700,7 +724,7 @@ def branch_list(repo):
     :param repo: Path to the repository
     :param repo: Path to the repository
     """
     """
     r = open_repo(repo)
     r = open_repo(repo)
-    return r.refs.keys(base="refs/heads/")
+    return r.refs.keys(base=b"refs/heads/")
 
 
 
 
 def fetch(repo, remote_location, outstream=sys.stdout, errstream=sys.stderr):
 def fetch(repo, remote_location, outstream=sys.stdout, errstream=sys.stderr):

+ 45 - 19
dulwich/protocol.py

@@ -32,12 +32,37 @@ from dulwich.errors import (
 
 
 TCP_GIT_PORT = 9418
 TCP_GIT_PORT = 9418
 
 
-ZERO_SHA = "0" * 40
+ZERO_SHA = b"0" * 40
 
 
 SINGLE_ACK = 0
 SINGLE_ACK = 0
 MULTI_ACK = 1
 MULTI_ACK = 1
 MULTI_ACK_DETAILED = 2
 MULTI_ACK_DETAILED = 2
 
 
+# pack data
+SIDE_BAND_CHANNEL_DATA = 1
+# progress messages
+SIDE_BAND_CHANNEL_PROGRESS = 2
+# fatal error message just before stream aborts
+SIDE_BAND_CHANNEL_FATAL = 3
+
+CAPABILITY_DELETE_REFS = b'delete-refs'
+CAPABILITY_INCLUDE_TAG = b'include-tag'
+CAPABILITY_MULTI_ACK = b'multi_ack'
+CAPABILITY_MULTI_ACK_DETAILED = b'multi_ack_detailed'
+CAPABILITY_NO_PROGRESS = b'no-progress'
+CAPABILITY_OFS_DELTA = b'ofs-delta'
+CAPABILITY_REPORT_STATUS = b'report-status'
+CAPABILITY_SHALLOW = b'shallow'
+CAPABILITY_SIDE_BAND_64K = b'side-band-64k'
+CAPABILITY_THIN_PACK = b'thin-pack'
+
+COMMAND_DEEPEN = b'deepen'
+COMMAND_SHALLOW = b'shallow'
+COMMAND_UNSHALLOW = b'unshallow'
+COMMAND_DONE = b'done'
+COMMAND_WANT = b'want'
+COMMAND_HAVE = b'have'
+
 
 
 class ProtocolFile(object):
 class ProtocolFile(object):
     """A dummy file for network ops that expect file-like objects."""
     """A dummy file for network ops that expect file-like objects."""
@@ -61,8 +86,8 @@ def pkt_line(data):
         None, returns the flush-pkt ('0000').
         None, returns the flush-pkt ('0000').
     """
     """
     if data is None:
     if data is None:
-        return '0000'
-    return '%04x%s' % (len(data) + 4, data)
+        return b'0000'
+    return ('%04x' % (len(data) + 4)).encode('ascii') + data
 
 
 
 
 class Protocol(object):
 class Protocol(object):
@@ -120,12 +145,13 @@ class Protocol(object):
             if self.report_activity:
             if self.report_activity:
                 self.report_activity(size, 'read')
                 self.report_activity(size, 'read')
             pkt_contents = read(size-4)
             pkt_contents = read(size-4)
-            if len(pkt_contents) + 4 != size:
-                raise AssertionError('Length of pkt read %04x does not match length prefix %04x.'
-                                     .format(len(pkt_contents) + 4, size))
-            return pkt_contents
         except socket.error as e:
         except socket.error as e:
             raise GitProtocolError(e)
             raise GitProtocolError(e)
+        else:
+            if len(pkt_contents) + 4 != size:
+                raise GitProtocolError(
+                    'Length of pkt read %04x does not match length prefix %04x' % (len(pkt_contents) + 4, size))
+            return pkt_contents
 
 
     def eof(self):
     def eof(self):
         """Test whether the protocol stream has reached EOF.
         """Test whether the protocol stream has reached EOF.
@@ -209,7 +235,7 @@ class Protocol(object):
         # 65520-5 = 65515
         # 65520-5 = 65515
         # WTF: Why have the len in ASCII, but the channel in binary.
         # WTF: Why have the len in ASCII, but the channel in binary.
         while blob:
         while blob:
-            self.write_pkt_line("%s%s" % (chr(channel), blob[:65515]))
+            self.write_pkt_line(bytes(bytearray([channel])) + blob[:65515])
             blob = blob[65515:]
             blob = blob[65515:]
 
 
     def send_cmd(self, cmd, *args):
     def send_cmd(self, cmd, *args):
@@ -220,7 +246,7 @@ class Protocol(object):
         :param cmd: The remote service to access.
         :param cmd: The remote service to access.
         :param args: List of arguments to send to remove service.
         :param args: List of arguments to send to remove service.
         """
         """
-        self.write_pkt_line("%s %s" % (cmd, "".join(["%s\0" % a for a in args])))
+        self.write_pkt_line(cmd + b" " + b"".join([(a + b"\0") for a in args]))
 
 
     def read_cmd(self):
     def read_cmd(self):
         """Read a command and some arguments from the git client
         """Read a command and some arguments from the git client
@@ -230,10 +256,10 @@ class Protocol(object):
         :return: A tuple of (command, [list of arguments]).
         :return: A tuple of (command, [list of arguments]).
         """
         """
         line = self.read_pkt_line()
         line = self.read_pkt_line()
-        splice_at = line.find(" ")
+        splice_at = line.find(b" ")
         cmd, args = line[:splice_at], line[splice_at+1:]
         cmd, args = line[:splice_at], line[splice_at+1:]
-        assert args[-1] == "\x00"
-        return cmd, args[:-1].split(chr(0))
+        assert args[-1:] == b"\x00"
+        return cmd, args[:-1].split(b"\0")
 
 
 
 
 _RBUFSIZE = 8192  # Default read buffer size.
 _RBUFSIZE = 8192  # Default read buffer size.
@@ -348,10 +374,10 @@ def extract_capabilities(text):
     :param text: String to extract from
     :param text: String to extract from
     :return: Tuple with text with capabilities removed and list of capabilities
     :return: Tuple with text with capabilities removed and list of capabilities
     """
     """
-    if not "\0" in text:
+    if not b"\0" in text:
         return text, []
         return text, []
-    text, capabilities = text.rstrip().split("\0")
-    return (text, capabilities.strip().split(" "))
+    text, capabilities = text.rstrip().split(b"\0")
+    return (text, capabilities.strip().split(b" "))
 
 
 
 
 def extract_want_line_capabilities(text):
 def extract_want_line_capabilities(text):
@@ -365,17 +391,17 @@ def extract_want_line_capabilities(text):
     :param text: Want line to extract from
     :param text: Want line to extract from
     :return: Tuple with text with capabilities removed and list of capabilities
     :return: Tuple with text with capabilities removed and list of capabilities
     """
     """
-    split_text = text.rstrip().split(" ")
+    split_text = text.rstrip().split(b" ")
     if len(split_text) < 3:
     if len(split_text) < 3:
         return text, []
         return text, []
-    return (" ".join(split_text[:2]), split_text[2:])
+    return (b" ".join(split_text[:2]), split_text[2:])
 
 
 
 
 def ack_type(capabilities):
 def ack_type(capabilities):
     """Extract the ack type from a capabilities list."""
     """Extract the ack type from a capabilities list."""
-    if 'multi_ack_detailed' in capabilities:
+    if b'multi_ack_detailed' in capabilities:
         return MULTI_ACK_DETAILED
         return MULTI_ACK_DETAILED
-    elif 'multi_ack' in capabilities:
+    elif b'multi_ack' in capabilities:
         return MULTI_ACK
         return MULTI_ACK
     return SINGLE_ACK
     return SINGLE_ACK
 
 

+ 16 - 18
dulwich/refs.py

@@ -23,6 +23,7 @@
 """
 """
 import errno
 import errno
 import os
 import os
+import sys
 
 
 from dulwich.errors import (
 from dulwich.errors import (
     PackedRefsException,
     PackedRefsException,
@@ -31,6 +32,7 @@ from dulwich.errors import (
 from dulwich.objects import (
 from dulwich.objects import (
     hex_to_sha,
     hex_to_sha,
     git_line,
     git_line,
+    valid_hexsha,
     )
     )
 from dulwich.file import (
 from dulwich.file import (
     GitFile,
     GitFile,
@@ -42,6 +44,8 @@ SYMREF = b'ref: '
 LOCAL_BRANCH_PREFIX = b'refs/heads/'
 LOCAL_BRANCH_PREFIX = b'refs/heads/'
 BAD_REF_CHARS = set(b'\177 ~^:?*[')
 BAD_REF_CHARS = set(b'\177 ~^:?*[')
 
 
+path_sep_bytes = os.path.sep.encode(sys.getfilesystemencoding())
+
 
 
 def check_ref_format(refname):
 def check_ref_format(refname):
     """Check if a refname is correctly formatted.
     """Check if a refname is correctly formatted.
@@ -394,10 +398,9 @@ class DiskRefsContainer(RefsContainer):
         subkeys = set()
         subkeys = set()
         path = self.refpath(base)
         path = self.refpath(base)
         for root, dirs, files in os.walk(path):
         for root, dirs, files in os.walk(path):
-            dir = root[len(path):].strip(os.path.sep).replace(os.path.sep, "/")
+            dir = root[len(path):].strip(path_sep_bytes).replace(path_sep_bytes, b'/')
             for filename in files:
             for filename in files:
-                refname = (("%s/%s" % (dir, filename))
-                           .strip("/").encode('ascii'))
+                refname = (dir + b'/' + filename).strip(b'/')
                 # check_ref_format requires at least one /, so we prepend the
                 # check_ref_format requires at least one /, so we prepend the
                 # base before calling it.
                 # base before calling it.
                 if check_ref_format(base + b'/' + refname):
                 if check_ref_format(base + b'/' + refname):
@@ -413,9 +416,9 @@ class DiskRefsContainer(RefsContainer):
             allkeys.add(b'HEAD')
             allkeys.add(b'HEAD')
         path = self.refpath(b'')
         path = self.refpath(b'')
         for root, dirs, files in os.walk(self.refpath(b'refs')):
         for root, dirs, files in os.walk(self.refpath(b'refs')):
-            dir = root[len(path):].strip(os.path.sep).replace(os.path.sep, "/")
+            dir = root[len(path):].strip(path_sep_bytes).replace(path_sep_bytes, b'/')
             for filename in files:
             for filename in files:
-                refname = ("%s/%s" % (dir, filename)).strip("/").encode('ascii')
+                refname = (dir + b'/' + filename).strip(b'/')
                 if check_ref_format(refname):
                 if check_ref_format(refname):
                     allkeys.add(refname)
                     allkeys.add(refname)
         allkeys.update(self.get_packed_refs())
         allkeys.update(self.get_packed_refs())
@@ -425,9 +428,8 @@ class DiskRefsContainer(RefsContainer):
         """Return the disk path of a ref.
         """Return the disk path of a ref.
 
 
         """
         """
-        name = name.decode('ascii')
-        if os.path.sep != "/":
-            name = name.replace("/", os.path.sep)
+        if path_sep_bytes != b'/':
+            name = name.replace(b'/', path_sep_bytes)
         return os.path.join(self.path, name)
         return os.path.join(self.path, name)
 
 
     def get_packed_refs(self):
     def get_packed_refs(self):
@@ -444,7 +446,7 @@ class DiskRefsContainer(RefsContainer):
             # None if and only if _packed_refs is also None.
             # None if and only if _packed_refs is also None.
             self._packed_refs = {}
             self._packed_refs = {}
             self._peeled_refs = {}
             self._peeled_refs = {}
-            path = os.path.join(self.path, 'packed-refs')
+            path = os.path.join(self.path, b'packed-refs')
             try:
             try:
                 f = GitFile(path, 'rb')
                 f = GitFile(path, 'rb')
             except IOError as e:
             except IOError as e:
@@ -512,7 +514,7 @@ class DiskRefsContainer(RefsContainer):
     def _remove_packed_ref(self, name):
     def _remove_packed_ref(self, name):
         if self._packed_refs is None:
         if self._packed_refs is None:
             return
             return
-        filename = os.path.join(self.path, 'packed-refs')
+        filename = os.path.join(self.path, b'packed-refs')
         # reread cached refs from disk, while holding the lock
         # reread cached refs from disk, while holding the lock
         f = GitFile(filename, 'wb')
         f = GitFile(filename, 'wb')
         try:
         try:
@@ -659,10 +661,8 @@ def _split_ref_line(line):
     if len(fields) != 2:
     if len(fields) != 2:
         raise PackedRefsException("invalid ref line %r" % line)
         raise PackedRefsException("invalid ref line %r" % line)
     sha, name = fields
     sha, name = fields
-    try:
-        hex_to_sha(sha)
-    except (AssertionError, TypeError) as e:
-        raise PackedRefsException(e)
+    if not valid_hexsha(sha):
+        raise PackedRefsException("Invalid hex sha %r" % sha)
     if not check_ref_format(name):
     if not check_ref_format(name):
         raise PackedRefsException("invalid ref name %r" % name)
         raise PackedRefsException("invalid ref name %r" % name)
     return (sha, name)
     return (sha, name)
@@ -700,10 +700,8 @@ def read_packed_refs_with_peeled(f):
         if l.startswith(b'^'):
         if l.startswith(b'^'):
             if not last:
             if not last:
                 raise PackedRefsException("unexpected peeled ref line")
                 raise PackedRefsException("unexpected peeled ref line")
-            try:
-                hex_to_sha(l[1:])
-            except (AssertionError, TypeError) as e:
-                raise PackedRefsException(e)
+            if not valid_hexsha(l[1:]):
+                raise PackedRefsException("Invalid hex sha %r" % l[1:])
             sha, name = _split_ref_line(last)
             sha, name = _split_ref_line(last)
             last = None
             last = None
             yield (sha, name, l[1:])
             yield (sha, name, l[1:])

+ 87 - 71
dulwich/repo.py

@@ -30,6 +30,7 @@ local disk (Repo).
 from io import BytesIO
 from io import BytesIO
 import errno
 import errno
 import os
 import os
+import sys
 
 
 from dulwich.errors import (
 from dulwich.errors import (
     NoIndexPresent,
     NoIndexPresent,
@@ -81,19 +82,19 @@ from dulwich.refs import (
 import warnings
 import warnings
 
 
 
 
-OBJECTDIR = 'objects'
-REFSDIR = 'refs'
-REFSDIR_TAGS = 'tags'
-REFSDIR_HEADS = 'heads'
-INDEX_FILENAME = "index"
+OBJECTDIR = b'objects'
+REFSDIR = b'refs'
+REFSDIR_TAGS = b'tags'
+REFSDIR_HEADS = b'heads'
+INDEX_FILENAME = b'index'
 
 
 BASE_DIRECTORIES = [
 BASE_DIRECTORIES = [
-    ["branches"],
+    [b'branches'],
     [REFSDIR],
     [REFSDIR],
     [REFSDIR, REFSDIR_TAGS],
     [REFSDIR, REFSDIR_TAGS],
     [REFSDIR, REFSDIR_HEADS],
     [REFSDIR, REFSDIR_HEADS],
-    ["hooks"],
-    ["info"]
+    [b'hooks'],
+    [b'info']
     ]
     ]
 
 
 
 
@@ -140,12 +141,12 @@ def serialize_graftpoints(graftpoints):
 
 
     """
     """
     graft_lines = []
     graft_lines = []
-    for commit, parents in graftpoints.iteritems():
+    for commit, parents in graftpoints.items():
         if parents:
         if parents:
-            graft_lines.append('%s %s' % (commit, ' '.join(parents)))
+            graft_lines.append(commit + b' ' + b' '.join(parents))
         else:
         else:
             graft_lines.append(commit)
             graft_lines.append(commit)
-    return '\n'.join(graft_lines)
+    return b'\n'.join(graft_lines)
 
 
 
 
 class BaseRepo(object):
 class BaseRepo(object):
@@ -175,16 +176,16 @@ class BaseRepo(object):
     def _init_files(self, bare):
     def _init_files(self, bare):
         """Initialize a default set of named files."""
         """Initialize a default set of named files."""
         from dulwich.config import ConfigFile
         from dulwich.config import ConfigFile
-        self._put_named_file('description', "Unnamed repository")
+        self._put_named_file(b'description', b"Unnamed repository")
         f = BytesIO()
         f = BytesIO()
         cf = ConfigFile()
         cf = ConfigFile()
-        cf.set("core", "repositoryformatversion", "0")
-        cf.set("core", "filemode", "true")
-        cf.set("core", "bare", str(bare).lower())
-        cf.set("core", "logallrefupdates", "true")
+        cf.set(b"core", b"repositoryformatversion", b"0")
+        cf.set(b"core", b"filemode", b"true")
+        cf.set(b"core", b"bare", bare)
+        cf.set(b"core", b"logallrefupdates", True)
         cf.write_to_file(f)
         cf.write_to_file(f)
-        self._put_named_file('config', f.getvalue())
-        self._put_named_file(os.path.join('info', 'exclude'), '')
+        self._put_named_file(b'config', f.getvalue())
+        self._put_named_file(os.path.join(b'info', b'exclude'), b'')
 
 
     def get_named_file(self, path):
     def get_named_file(self, path):
         """Get a file from the control dir with a specific name.
         """Get a file from the control dir with a specific name.
@@ -291,7 +292,7 @@ class BaseRepo(object):
         :return: A graph walker object
         :return: A graph walker object
         """
         """
         if heads is None:
         if heads is None:
-            heads = self.refs.as_dict('refs/heads').values()
+            heads = self.refs.as_dict(b'refs/heads').values()
         return ObjectStoreGraphWalker(heads, self.get_parents)
         return ObjectStoreGraphWalker(heads, self.get_parents)
 
 
     def get_refs(self):
     def get_refs(self):
@@ -303,7 +304,7 @@ class BaseRepo(object):
 
 
     def head(self):
     def head(self):
         """Return the SHA1 pointed at by HEAD."""
         """Return the SHA1 pointed at by HEAD."""
-        return self.refs['HEAD']
+        return self.refs[b'HEAD']
 
 
     def _get_object(self, sha, cls):
     def _get_object(self, sha, cls):
         assert len(sha) in (20, 40)
         assert len(sha) in (20, 40)
@@ -439,7 +440,7 @@ class BaseRepo(object):
         :return: A `ShaFile` object, such as a Commit or Blob
         :return: A `ShaFile` object, such as a Commit or Blob
         :raise KeyError: when the specified ref or object does not exist
         :raise KeyError: when the specified ref or object does not exist
         """
         """
-        if not isinstance(name, str):
+        if not isinstance(name, bytes):
             raise TypeError("'name' must be bytestring, not %.80s" %
             raise TypeError("'name' must be bytestring, not %.80s" %
                     type(name).__name__)
                     type(name).__name__)
         if len(name) in (20, 40):
         if len(name) in (20, 40):
@@ -468,10 +469,10 @@ class BaseRepo(object):
         :param name: ref name
         :param name: ref name
         :param value: Ref value - either a ShaFile object, or a hex sha
         :param value: Ref value - either a ShaFile object, or a hex sha
         """
         """
-        if name.startswith("refs/") or name == "HEAD":
+        if name.startswith(b"refs/") or name == b'HEAD':
             if isinstance(value, ShaFile):
             if isinstance(value, ShaFile):
                 self.refs[name] = value.id
                 self.refs[name] = value.id
-            elif isinstance(value, str):
+            elif isinstance(value, bytes):
                 self.refs[name] = value
                 self.refs[name] = value
             else:
             else:
                 raise TypeError(value)
                 raise TypeError(value)
@@ -483,7 +484,7 @@ class BaseRepo(object):
 
 
         :param name: Name of the ref to remove
         :param name: Name of the ref to remove
         """
         """
-        if name.startswith("refs/") or name == "HEAD":
+        if name.startswith(b"refs/") or name == b"HEAD":
             del self.refs[name]
             del self.refs[name]
         else:
         else:
             raise ValueError(name)
             raise ValueError(name)
@@ -492,9 +493,8 @@ class BaseRepo(object):
         """Determine the identity to use for new commits.
         """Determine the identity to use for new commits.
         """
         """
         config = self.get_config_stack()
         config = self.get_config_stack()
-        return "%s <%s>" % (
-            config.get(("user", ), "name"),
-            config.get(("user", ), "email"))
+        return (config.get((b"user", ), b"name") + b" <" +
+                config.get((b"user", ), b"email") + b">")
 
 
     def _add_graftpoints(self, updated_graftpoints):
     def _add_graftpoints(self, updated_graftpoints):
         """Add or modify graftpoints
         """Add or modify graftpoints
@@ -503,7 +503,7 @@ class BaseRepo(object):
         """
         """
 
 
         # Simple validation
         # Simple validation
-        for commit, parents in updated_graftpoints.iteritems():
+        for commit, parents in updated_graftpoints.items():
             for sha in [commit] + parents:
             for sha in [commit] + parents:
                 check_hexsha(sha, 'Invalid graftpoint')
                 check_hexsha(sha, 'Invalid graftpoint')
 
 
@@ -521,7 +521,7 @@ class BaseRepo(object):
                   author=None, commit_timestamp=None,
                   author=None, commit_timestamp=None,
                   commit_timezone=None, author_timestamp=None,
                   commit_timezone=None, author_timestamp=None,
                   author_timezone=None, tree=None, encoding=None,
                   author_timezone=None, tree=None, encoding=None,
-                  ref='HEAD', merge_heads=None):
+                  ref=b'HEAD', merge_heads=None):
         """Create a new commit.
         """Create a new commit.
 
 
         :param message: Commit message
         :param message: Commit message
@@ -627,6 +627,7 @@ class BaseRepo(object):
 
 
         return c.id
         return c.id
 
 
+path_sep_bytes = os.path.sep.encode(sys.getfilesystemencoding())
 
 
 class Repo(BaseRepo):
 class Repo(BaseRepo):
     """A git repository backed by local disk.
     """A git repository backed by local disk.
@@ -637,36 +638,40 @@ class Repo(BaseRepo):
     To create a new repository, use the Repo.init class method.
     To create a new repository, use the Repo.init class method.
     """
     """
 
 
-    def __init__(self, root):
-        if os.path.isdir(os.path.join(root, ".git", OBJECTDIR)):
+    def __init__(self, path):
+        self.path = path
+        if not isinstance(path, bytes):
+            self._path_bytes = path.encode(sys.getfilesystemencoding())
+        else:
+            self._path_bytes = path
+        if os.path.isdir(os.path.join(self._path_bytes, b'.git', OBJECTDIR)):
             self.bare = False
             self.bare = False
-            self._controldir = os.path.join(root, ".git")
-        elif (os.path.isdir(os.path.join(root, OBJECTDIR)) and
-              os.path.isdir(os.path.join(root, REFSDIR))):
+            self._controldir = os.path.join(self._path_bytes, b'.git')
+        elif (os.path.isdir(os.path.join(self._path_bytes, OBJECTDIR)) and
+              os.path.isdir(os.path.join(self._path_bytes, REFSDIR))):
             self.bare = True
             self.bare = True
-            self._controldir = root
-        elif (os.path.isfile(os.path.join(root, ".git"))):
+            self._controldir = self._path_bytes
+        elif (os.path.isfile(os.path.join(self._path_bytes, b'.git'))):
             import re
             import re
-            with open(os.path.join(root, ".git"), 'r') as f:
-                _, path = re.match('(gitdir: )(.+$)', f.read()).groups()
+            with open(os.path.join(self._path_bytes, b'.git'), 'rb') as f:
+                _, gitdir = re.match(b'(gitdir: )(.+$)', f.read()).groups()
             self.bare = False
             self.bare = False
-            self._controldir = os.path.join(root, path)
+            self._controldir = os.path.join(self._path_bytes, gitdir)
         else:
         else:
             raise NotGitRepository(
             raise NotGitRepository(
-                "No git repository was found at %(path)s" % dict(path=root)
+                "No git repository was found at %(path)s" % dict(path=path)
             )
             )
-        self.path = root
         object_store = DiskObjectStore(os.path.join(self.controldir(),
         object_store = DiskObjectStore(os.path.join(self.controldir(),
                                                     OBJECTDIR))
                                                     OBJECTDIR))
         refs = DiskRefsContainer(self.controldir())
         refs = DiskRefsContainer(self.controldir())
         BaseRepo.__init__(self, object_store, refs)
         BaseRepo.__init__(self, object_store, refs)
 
 
         self._graftpoints = {}
         self._graftpoints = {}
-        graft_file = self.get_named_file(os.path.join("info", "grafts"))
+        graft_file = self.get_named_file(os.path.join(b'info', b'grafts'))
         if graft_file:
         if graft_file:
             with graft_file:
             with graft_file:
                 self._graftpoints.update(parse_graftpoints(graft_file))
                 self._graftpoints.update(parse_graftpoints(graft_file))
-        graft_file = self.get_named_file("shallow")
+        graft_file = self.get_named_file(b'shallow')
         if graft_file:
         if graft_file:
             with graft_file:
             with graft_file:
                 self._graftpoints.update(parse_graftpoints(graft_file))
                 self._graftpoints.update(parse_graftpoints(graft_file))
@@ -685,7 +690,7 @@ class Repo(BaseRepo):
         :param path: The path to the file, relative to the control dir.
         :param path: The path to the file, relative to the control dir.
         :param contents: A string to write to the file.
         :param contents: A string to write to the file.
         """
         """
-        path = path.lstrip(os.path.sep)
+        path = path.lstrip(path_sep_bytes)
         with GitFile(os.path.join(self.controldir(), path), 'wb') as f:
         with GitFile(os.path.join(self.controldir(), path), 'wb') as f:
             f.write(contents)
             f.write(contents)
 
 
@@ -701,7 +706,7 @@ class Repo(BaseRepo):
         """
         """
         # TODO(dborowitz): sanitize filenames, since this is used directly by
         # TODO(dborowitz): sanitize filenames, since this is used directly by
         # the dumb web serving code.
         # the dumb web serving code.
-        path = path.lstrip(os.path.sep)
+        path = path.lstrip(path_sep_bytes)
         try:
         try:
             return open(os.path.join(self.controldir(), path), 'rb')
             return open(os.path.join(self.controldir(), path), 'rb')
         except (IOError, OSError) as e:
         except (IOError, OSError) as e:
@@ -730,12 +735,12 @@ class Repo(BaseRepo):
         # missing index file, which is treated as empty.
         # missing index file, which is treated as empty.
         return not self.bare
         return not self.bare
 
 
-    def stage(self, paths):
+    def stage(self, paths, fsencoding=sys.getfilesystemencoding()):
         """Stage a set of paths.
         """Stage a set of paths.
 
 
         :param paths: List of paths, relative to the repository path
         :param paths: List of paths, relative to the repository path
         """
         """
-        if isinstance(paths, basestring):
+        if not isinstance(paths, list):
             paths = [paths]
             paths = [paths]
         from dulwich.index import (
         from dulwich.index import (
             blob_from_path_and_stat,
             blob_from_path_and_stat,
@@ -743,23 +748,28 @@ class Repo(BaseRepo):
             )
             )
         index = self.open_index()
         index = self.open_index()
         for path in paths:
         for path in paths:
-            full_path = os.path.join(self.path, path)
+            if not isinstance(path, bytes):
+                disk_path_bytes = path.encode(sys.getfilesystemencoding())
+                repo_path_bytes = path.encode(fsencoding)
+            else:
+                disk_path_bytes, repo_path_bytes = path, path
+            full_path = os.path.join(self._path_bytes, disk_path_bytes)
             try:
             try:
                 st = os.lstat(full_path)
                 st = os.lstat(full_path)
             except OSError:
             except OSError:
                 # File no longer exists
                 # File no longer exists
                 try:
                 try:
-                    del index[path]
+                    del index[repo_path_bytes]
                 except KeyError:
                 except KeyError:
                     pass  # already removed
                     pass  # already removed
             else:
             else:
                 blob = blob_from_path_and_stat(full_path, st)
                 blob = blob_from_path_and_stat(full_path, st)
                 self.object_store.add_object(blob)
                 self.object_store.add_object(blob)
-                index[path] = index_entry_from_stat(st, blob.id, 0)
+                index[repo_path_bytes] = index_entry_from_stat(st, blob.id, 0)
         index.write()
         index.write()
 
 
     def clone(self, target_path, mkdir=True, bare=False,
     def clone(self, target_path, mkdir=True, bare=False,
-            origin="origin"):
+            origin=b"origin"):
         """Clone this repository.
         """Clone this repository.
 
 
         :param target_path: Target path
         :param target_path: Target path
@@ -775,21 +785,21 @@ class Repo(BaseRepo):
             target = self.init_bare(target_path)
             target = self.init_bare(target_path)
         self.fetch(target)
         self.fetch(target)
         target.refs.import_refs(
         target.refs.import_refs(
-            'refs/remotes/' + origin, self.refs.as_dict('refs/heads'))
+            b'refs/remotes/' + origin, self.refs.as_dict(b'refs/heads'))
         target.refs.import_refs(
         target.refs.import_refs(
-            'refs/tags', self.refs.as_dict('refs/tags'))
+            b'refs/tags', self.refs.as_dict(b'refs/tags'))
         try:
         try:
             target.refs.add_if_new(
             target.refs.add_if_new(
-                'refs/heads/master',
-                self.refs['refs/heads/master'])
+                b'refs/heads/master',
+                self.refs[b'refs/heads/master'])
         except KeyError:
         except KeyError:
             pass
             pass
 
 
         # Update target head
         # Update target head
-        head, head_sha = self.refs._follow('HEAD')
+        head, head_sha = self.refs._follow(b'HEAD')
         if head is not None and head_sha is not None:
         if head is not None and head_sha is not None:
-            target.refs.set_symbolic_ref('HEAD', head)
-            target['HEAD'] = head_sha
+            target.refs.set_symbolic_ref(b'HEAD', head)
+            target[b'HEAD'] = head_sha
 
 
             if not bare:
             if not bare:
                 # Checkout HEAD to target dir
                 # Checkout HEAD to target dir
@@ -808,14 +818,14 @@ class Repo(BaseRepo):
             validate_path_element_ntfs,
             validate_path_element_ntfs,
             )
             )
         if tree is None:
         if tree is None:
-            tree = self['HEAD'].tree
+            tree = self[b'HEAD'].tree
         config = self.get_config()
         config = self.get_config()
         honor_filemode = config.get_boolean('core', 'filemode', os.name != "nt")
         honor_filemode = config.get_boolean('core', 'filemode', os.name != "nt")
         if config.get_boolean('core', 'core.protectNTFS', os.name == "nt"):
         if config.get_boolean('core', 'core.protectNTFS', os.name == "nt"):
             validate_path_element = validate_path_element_ntfs
             validate_path_element = validate_path_element_ntfs
         else:
         else:
             validate_path_element = validate_path_element_default
             validate_path_element = validate_path_element_default
-        return build_index_from_tree(self.path, self.index_path(),
+        return build_index_from_tree(self._path_bytes, self.index_path(),
                 self.object_store, tree, honor_filemode=honor_filemode,
                 self.object_store, tree, honor_filemode=honor_filemode,
                 validate_path_element=validate_path_element)
                 validate_path_element=validate_path_element)
 
 
@@ -825,7 +835,7 @@ class Repo(BaseRepo):
         :return: `ConfigFile` object for the ``.git/config`` file.
         :return: `ConfigFile` object for the ``.git/config`` file.
         """
         """
         from dulwich.config import ConfigFile
         from dulwich.config import ConfigFile
-        path = os.path.join(self._controldir, 'config')
+        path = os.path.join(self._controldir, b'config')
         try:
         try:
             return ConfigFile.from_path(path)
             return ConfigFile.from_path(path)
         except (IOError, OSError) as e:
         except (IOError, OSError) as e:
@@ -840,7 +850,7 @@ class Repo(BaseRepo):
 
 
         :return: A string describing the repository or None.
         :return: A string describing the repository or None.
         """
         """
-        path = os.path.join(self._controldir, 'description')
+        path = os.path.join(self._controldir, b'description')
         try:
         try:
             with GitFile(path, 'rb') as f:
             with GitFile(path, 'rb') as f:
                 return f.read()
                 return f.read()
@@ -858,17 +868,19 @@ class Repo(BaseRepo):
         :param description: Text to set as description for this repository.
         :param description: Text to set as description for this repository.
         """
         """
 
 
-        path = os.path.join(self._controldir, 'description')
-        with open(path, 'w') as f:
-            f.write(description)
+        self._put_named_file(b'description', description)
 
 
     @classmethod
     @classmethod
     def _init_maybe_bare(cls, path, bare):
     def _init_maybe_bare(cls, path, bare):
+        if not isinstance(path, bytes):
+            path_bytes = path.encode(sys.getfilesystemencoding())
+        else:
+            path_bytes = path
         for d in BASE_DIRECTORIES:
         for d in BASE_DIRECTORIES:
-            os.mkdir(os.path.join(path, *d))
-        DiskObjectStore.init(os.path.join(path, OBJECTDIR))
+            os.mkdir(os.path.join(path_bytes, *d))
+        DiskObjectStore.init(os.path.join(path_bytes, OBJECTDIR))
         ret = cls(path)
         ret = cls(path)
-        ret.refs.set_symbolic_ref("HEAD", "refs/heads/master")
+        ret.refs.set_symbolic_ref(b'HEAD', b"refs/heads/master")
         ret._init_files(bare)
         ret._init_files(bare)
         return ret
         return ret
 
 
@@ -880,9 +892,13 @@ class Repo(BaseRepo):
         :param mkdir: Whether to create the directory
         :param mkdir: Whether to create the directory
         :return: `Repo` instance
         :return: `Repo` instance
         """
         """
+        if not isinstance(path, bytes):
+            path_bytes = path.encode(sys.getfilesystemencoding())
+        else:
+            path_bytes = path
         if mkdir:
         if mkdir:
-            os.mkdir(path)
-        controldir = os.path.join(path, ".git")
+            os.mkdir(path_bytes)
+        controldir = os.path.join(path_bytes, b'.git')
         os.mkdir(controldir)
         os.mkdir(controldir)
         cls._init_maybe_bare(controldir, False)
         cls._init_maybe_bare(controldir, False)
         return cls(path)
         return cls(path)
@@ -971,7 +987,7 @@ class MemoryRepo(BaseRepo):
         ret = cls()
         ret = cls()
         for obj in objects:
         for obj in objects:
             ret.object_store.add_object(obj)
             ret.object_store.add_object(obj)
-        for refname, sha in refs.iteritems():
+        for refname, sha in refs.items():
             ret.refs[refname] = sha
             ret.refs[refname] = sha
         ret._init_files(bare=True)
         ret._init_files(bare=True)
         return ret
         return ret

+ 132 - 93
dulwich/server.py

@@ -60,19 +60,38 @@ from dulwich.errors import (
     )
     )
 from dulwich import log_utils
 from dulwich import log_utils
 from dulwich.objects import (
 from dulwich.objects import (
-    hex_to_sha,
     Commit,
     Commit,
+    valid_hexsha,
     )
     )
 from dulwich.pack import (
 from dulwich.pack import (
     write_pack_objects,
     write_pack_objects,
     )
     )
 from dulwich.protocol import (
 from dulwich.protocol import (
     BufferedPktLineWriter,
     BufferedPktLineWriter,
+    CAPABILITY_DELETE_REFS,
+    CAPABILITY_INCLUDE_TAG,
+    CAPABILITY_MULTI_ACK_DETAILED,
+    CAPABILITY_MULTI_ACK,
+    CAPABILITY_NO_PROGRESS,
+    CAPABILITY_OFS_DELTA,
+    CAPABILITY_REPORT_STATUS,
+    CAPABILITY_SHALLOW,
+    CAPABILITY_SIDE_BAND_64K,
+    CAPABILITY_THIN_PACK,
+    COMMAND_DEEPEN,
+    COMMAND_DONE,
+    COMMAND_HAVE,
+    COMMAND_SHALLOW,
+    COMMAND_UNSHALLOW,
+    COMMAND_WANT,
     MULTI_ACK,
     MULTI_ACK,
     MULTI_ACK_DETAILED,
     MULTI_ACK_DETAILED,
     Protocol,
     Protocol,
     ProtocolFile,
     ProtocolFile,
     ReceivableProtocol,
     ReceivableProtocol,
+    SIDE_BAND_CHANNEL_DATA,
+    SIDE_BAND_CHANNEL_PROGRESS,
+    SIDE_BAND_CHANNEL_FATAL,
     SINGLE_ACK,
     SINGLE_ACK,
     TCP_GIT_PORT,
     TCP_GIT_PORT,
     ZERO_SHA,
     ZERO_SHA,
@@ -187,7 +206,7 @@ class Handler(object):
 
 
     @classmethod
     @classmethod
     def capability_line(cls):
     def capability_line(cls):
-        return " ".join(cls.capabilities())
+        return b" ".join(cls.capabilities())
 
 
     @classmethod
     @classmethod
     def capabilities(cls):
     def capabilities(cls):
@@ -195,7 +214,8 @@ class Handler(object):
 
 
     @classmethod
     @classmethod
     def innocuous_capabilities(cls):
     def innocuous_capabilities(cls):
-        return ("include-tag", "thin-pack", "no-progress", "ofs-delta")
+        return (CAPABILITY_INCLUDE_TAG, CAPABILITY_THIN_PACK,
+                CAPABILITY_NO_PROGRESS, CAPABILITY_OFS_DELTA)
 
 
     @classmethod
     @classmethod
     def required_capabilities(cls):
     def required_capabilities(cls):
@@ -232,20 +252,26 @@ class UploadPackHandler(Handler):
         self.repo = backend.open_repository(args[0])
         self.repo = backend.open_repository(args[0])
         self._graph_walker = None
         self._graph_walker = None
         self.advertise_refs = advertise_refs
         self.advertise_refs = advertise_refs
+        # A state variable for denoting that the have list is still
+        # being processed, and the client is not accepting any other
+        # data (such as side-band, see the progress method here).
+        self._processing_have_lines = False
 
 
     @classmethod
     @classmethod
     def capabilities(cls):
     def capabilities(cls):
-        return ("multi_ack_detailed", "multi_ack", "side-band-64k", "thin-pack",
-                "ofs-delta", "no-progress", "include-tag", "shallow")
+        return (CAPABILITY_MULTI_ACK_DETAILED, CAPABILITY_MULTI_ACK,
+                CAPABILITY_SIDE_BAND_64K, CAPABILITY_THIN_PACK,
+                CAPABILITY_OFS_DELTA, CAPABILITY_NO_PROGRESS,
+                CAPABILITY_INCLUDE_TAG, CAPABILITY_SHALLOW)
 
 
     @classmethod
     @classmethod
     def required_capabilities(cls):
     def required_capabilities(cls):
-        return ("side-band-64k", "thin-pack", "ofs-delta")
+        return (CAPABILITY_SIDE_BAND_64K, CAPABILITY_THIN_PACK, CAPABILITY_OFS_DELTA)
 
 
     def progress(self, message):
     def progress(self, message):
-        if self.has_capability("no-progress"):
+        if self.has_capability(CAPABILITY_NO_PROGRESS) or self._processing_have_lines:
             return
             return
-        self.proto.write_sideband(2, message)
+        self.proto.write_sideband(SIDE_BAND_CHANNEL_PROGRESS, message)
 
 
     def get_tagged(self, refs=None, repo=None):
     def get_tagged(self, refs=None, repo=None):
         """Get a dict of peeled values of tags to their original tag shas.
         """Get a dict of peeled values of tags to their original tag shas.
@@ -257,7 +283,7 @@ class UploadPackHandler(Handler):
         :return: dict of peeled_sha -> tag_sha, where tag_sha is the sha of a
         :return: dict of peeled_sha -> tag_sha, where tag_sha is the sha of a
             tag whose peeled value is peeled_sha.
             tag whose peeled value is peeled_sha.
         """
         """
-        if not self.has_capability("include-tag"):
+        if not self.has_capability(CAPABILITY_INCLUDE_TAG):
             return {}
             return {}
         if refs is None:
         if refs is None:
             refs = self.repo.get_refs()
             refs = self.repo.get_refs()
@@ -270,32 +296,45 @@ class UploadPackHandler(Handler):
                 # TODO: fix behavior when missing
                 # TODO: fix behavior when missing
                 return {}
                 return {}
         tagged = {}
         tagged = {}
-        for name, sha in refs.iteritems():
+        for name, sha in refs.items():
             peeled_sha = repo.get_peeled(name)
             peeled_sha = repo.get_peeled(name)
             if peeled_sha != sha:
             if peeled_sha != sha:
                 tagged[peeled_sha] = sha
                 tagged[peeled_sha] = sha
         return tagged
         return tagged
 
 
     def handle(self):
     def handle(self):
-        write = lambda x: self.proto.write_sideband(1, x)
+        write = lambda x: self.proto.write_sideband(SIDE_BAND_CHANNEL_DATA, x)
 
 
         graph_walker = ProtocolGraphWalker(self, self.repo.object_store,
         graph_walker = ProtocolGraphWalker(self, self.repo.object_store,
             self.repo.get_peeled)
             self.repo.get_peeled)
         objects_iter = self.repo.fetch_objects(
         objects_iter = self.repo.fetch_objects(
-          graph_walker.determine_wants, graph_walker, self.progress,
-          get_tagged=self.get_tagged)
+            graph_walker.determine_wants, graph_walker, self.progress,
+            get_tagged=self.get_tagged)
+
+        # Note the fact that client is only processing responses related
+        # to the have lines it sent, and any other data (including side-
+        # band) will be be considered a fatal error.
+        self._processing_have_lines = True
 
 
         # Did the process short-circuit (e.g. in a stateless RPC call)? Note
         # Did the process short-circuit (e.g. in a stateless RPC call)? Note
         # that the client still expects a 0-object pack in most cases.
         # that the client still expects a 0-object pack in most cases.
+        # Also, if it also happens that the object_iter is instantiated
+        # with a graph walker with an implementation that talks over the
+        # wire (which is this instance of this class) this will actually
+        # iterate through everything and write things out to the wire.
         if len(objects_iter) == 0:
         if len(objects_iter) == 0:
             return
             return
 
 
-        self.progress("dul-daemon says what\n")
-        self.progress("counting objects: %d, done.\n" % len(objects_iter))
+        # The provided haves are processed, and it is safe to send side-
+        # band data now.
+        self._processing_have_lines = False
+
+        self.progress(b"dul-daemon says what\n")
+        self.progress(("counting objects: %d, done.\n" % len(objects_iter)).encode('ascii'))
         write_pack_objects(ProtocolFile(None, write), objects_iter)
         write_pack_objects(ProtocolFile(None, write), objects_iter)
-        self.progress("how was that, then?\n")
+        self.progress(b"how was that, then?\n")
         # we are done
         # we are done
-        self.proto.write("0000")
+        self.proto.write_pkt_line(None)
 
 
 
 
 def _split_proto_line(line, allowed):
 def _split_proto_line(line, allowed):
@@ -318,22 +357,21 @@ def _split_proto_line(line, allowed):
     if not line:
     if not line:
         fields = [None]
         fields = [None]
     else:
     else:
-        fields = line.rstrip('\n').split(' ', 1)
+        fields = line.rstrip(b'\n').split(b' ', 1)
     command = fields[0]
     command = fields[0]
     if allowed is not None and command not in allowed:
     if allowed is not None and command not in allowed:
         raise UnexpectedCommandError(command)
         raise UnexpectedCommandError(command)
-    try:
-        if len(fields) == 1 and command in ('done', None):
-            return (command, None)
-        elif len(fields) == 2:
-            if command in ('want', 'have', 'shallow', 'unshallow'):
-                hex_to_sha(fields[1])
-                return tuple(fields)
-            elif command == 'deepen':
-                return command, int(fields[1])
-    except (TypeError, AssertionError) as e:
-        raise GitProtocolError(e)
-    raise GitProtocolError('Received invalid line from client: %s' % line)
+    if len(fields) == 1 and command in (COMMAND_DONE, None):
+        return (command, None)
+    elif len(fields) == 2:
+        if command in (COMMAND_WANT, COMMAND_HAVE, COMMAND_SHALLOW,
+                       COMMAND_UNSHALLOW):
+            if not valid_hexsha(fields[1]):
+                raise GitProtocolError("Invalid sha")
+            return tuple(fields)
+        elif command == COMMAND_DEEPEN:
+            return command, int(fields[1])
+    raise GitProtocolError('Received invalid line from client: %r' % line)
 
 
 
 
 def _find_shallow(store, heads, depth):
 def _find_shallow(store, heads, depth):
@@ -381,7 +419,7 @@ def _want_satisfied(store, haves, want, earliest):
         commit = pending.popleft()
         commit = pending.popleft()
         if commit.id in haves:
         if commit.id in haves:
             return True
             return True
-        if commit.type_name != "commit":
+        if commit.type_name != b"commit":
             # non-commit wants are assumed to be satisfied
             # non-commit wants are assumed to be satisfied
             continue
             continue
         for parent in commit.parents:
         for parent in commit.parents:
@@ -460,17 +498,16 @@ class ProtocolGraphWalker(object):
         :param heads: a dict of refname->SHA1 to advertise
         :param heads: a dict of refname->SHA1 to advertise
         :return: a list of SHA1s requested by the client
         :return: a list of SHA1s requested by the client
         """
         """
-        values = set(heads.itervalues())
+        values = set(heads.values())
         if self.advertise_refs or not self.http_req:
         if self.advertise_refs or not self.http_req:
-            for i, (ref, sha) in enumerate(sorted(heads.iteritems())):
-                line = "%s %s" % (sha, ref)
+            for i, (ref, sha) in enumerate(sorted(heads.items())):
+                line = sha + b' ' + ref
                 if not i:
                 if not i:
-                    line = "%s\x00%s" % (line, self.handler.capability_line())
-                self.proto.write_pkt_line("%s\n" % line)
+                    line += b'\x00' + self.handler.capability_line()
+                self.proto.write_pkt_line(line + b'\n')
                 peeled_sha = self.get_peeled(ref)
                 peeled_sha = self.get_peeled(ref)
                 if peeled_sha != sha:
                 if peeled_sha != sha:
-                    self.proto.write_pkt_line('%s %s^{}\n' %
-                                              (peeled_sha, ref))
+                    self.proto.write_pkt_line(peeled_sha + b' ' + ref + b'^{}\n')
 
 
             # i'm done..
             # i'm done..
             self.proto.write_pkt_line(None)
             self.proto.write_pkt_line(None)
@@ -485,11 +522,11 @@ class ProtocolGraphWalker(object):
         line, caps = extract_want_line_capabilities(want)
         line, caps = extract_want_line_capabilities(want)
         self.handler.set_client_capabilities(caps)
         self.handler.set_client_capabilities(caps)
         self.set_ack_type(ack_type(caps))
         self.set_ack_type(ack_type(caps))
-        allowed = ('want', 'shallow', 'deepen', None)
+        allowed = (COMMAND_WANT, COMMAND_SHALLOW, COMMAND_DEEPEN, None)
         command, sha = _split_proto_line(line, allowed)
         command, sha = _split_proto_line(line, allowed)
 
 
         want_revs = []
         want_revs = []
-        while command == 'want':
+        while command == COMMAND_WANT:
             if sha not in values:
             if sha not in values:
                 raise GitProtocolError(
                 raise GitProtocolError(
                   'Client wants invalid object %s' % sha)
                   'Client wants invalid object %s' % sha)
@@ -497,7 +534,7 @@ class ProtocolGraphWalker(object):
             command, sha = self.read_proto_line(allowed)
             command, sha = self.read_proto_line(allowed)
 
 
         self.set_wants(want_revs)
         self.set_wants(want_revs)
-        if command in ('shallow', 'deepen'):
+        if command in (COMMAND_SHALLOW, COMMAND_DEEPEN):
             self.unread_proto_line(command, sha)
             self.unread_proto_line(command, sha)
             self._handle_shallow_request(want_revs)
             self._handle_shallow_request(want_revs)
 
 
@@ -510,7 +547,9 @@ class ProtocolGraphWalker(object):
         return want_revs
         return want_revs
 
 
     def unread_proto_line(self, command, value):
     def unread_proto_line(self, command, value):
-        self.proto.unread_pkt_line('%s %s' % (command, value))
+        if isinstance(value, int):
+            value = str(value).encode('ascii')
+        self.proto.unread_pkt_line(command + b' ' + value)
 
 
     def ack(self, have_ref):
     def ack(self, have_ref):
         if len(have_ref) != 40:
         if len(have_ref) != 40:
@@ -544,8 +583,8 @@ class ProtocolGraphWalker(object):
 
 
     def _handle_shallow_request(self, wants):
     def _handle_shallow_request(self, wants):
         while True:
         while True:
-            command, val = self.read_proto_line(('deepen', 'shallow'))
-            if command == 'deepen':
+            command, val = self.read_proto_line((COMMAND_DEEPEN, COMMAND_SHALLOW))
+            if command == COMMAND_DEEPEN:
                 depth = val
                 depth = val
                 break
                 break
             self.client_shallow.add(val)
             self.client_shallow.add(val)
@@ -560,19 +599,19 @@ class ProtocolGraphWalker(object):
         unshallow = self.unshallow = not_shallow & self.client_shallow
         unshallow = self.unshallow = not_shallow & self.client_shallow
 
 
         for sha in sorted(new_shallow):
         for sha in sorted(new_shallow):
-            self.proto.write_pkt_line('shallow %s' % sha)
+            self.proto.write_pkt_line(COMMAND_SHALLOW + b' ' + sha)
         for sha in sorted(unshallow):
         for sha in sorted(unshallow):
-            self.proto.write_pkt_line('unshallow %s' % sha)
+            self.proto.write_pkt_line(COMMAND_UNSHALLOW + b' ' + sha)
 
 
         self.proto.write_pkt_line(None)
         self.proto.write_pkt_line(None)
 
 
-    def send_ack(self, sha, ack_type=''):
+    def send_ack(self, sha, ack_type=b''):
         if ack_type:
         if ack_type:
-            ack_type = ' %s' % ack_type
-        self.proto.write_pkt_line('ACK %s%s\n' % (sha, ack_type))
+            ack_type = b' ' + ack_type
+        self.proto.write_pkt_line(b'ACK ' + sha + ack_type + b'\n')
 
 
     def send_nak(self):
     def send_nak(self):
-        self.proto.write_pkt_line('NAK\n')
+        self.proto.write_pkt_line(b'NAK\n')
 
 
     def set_wants(self, wants):
     def set_wants(self, wants):
         self._wants = wants
         self._wants = wants
@@ -595,7 +634,7 @@ class ProtocolGraphWalker(object):
         self._impl = impl_classes[ack_type](self)
         self._impl = impl_classes[ack_type](self)
 
 
 
 
-_GRAPH_WALKER_COMMANDS = ('have', 'done', None)
+_GRAPH_WALKER_COMMANDS = (COMMAND_HAVE, COMMAND_DONE, None)
 
 
 
 
 class SingleAckGraphWalkerImpl(object):
 class SingleAckGraphWalkerImpl(object):
@@ -612,11 +651,11 @@ class SingleAckGraphWalkerImpl(object):
 
 
     def next(self):
     def next(self):
         command, sha = self.walker.read_proto_line(_GRAPH_WALKER_COMMANDS)
         command, sha = self.walker.read_proto_line(_GRAPH_WALKER_COMMANDS)
-        if command in (None, 'done'):
+        if command in (None, COMMAND_DONE):
             if not self._sent_ack:
             if not self._sent_ack:
                 self.walker.send_nak()
                 self.walker.send_nak()
             return None
             return None
-        elif command == 'have':
+        elif command == COMMAND_HAVE:
             return sha
             return sha
 
 
     __next__ = next
     __next__ = next
@@ -633,7 +672,7 @@ class MultiAckGraphWalkerImpl(object):
     def ack(self, have_ref):
     def ack(self, have_ref):
         self._common.append(have_ref)
         self._common.append(have_ref)
         if not self._found_base:
         if not self._found_base:
-            self.walker.send_ack(have_ref, 'continue')
+            self.walker.send_ack(have_ref, b'continue')
             if self.walker.all_wants_satisfied(self._common):
             if self.walker.all_wants_satisfied(self._common):
                 self._found_base = True
                 self._found_base = True
         # else we blind ack within next
         # else we blind ack within next
@@ -646,7 +685,7 @@ class MultiAckGraphWalkerImpl(object):
                 # in multi-ack mode, a flush-pkt indicates the client wants to
                 # in multi-ack mode, a flush-pkt indicates the client wants to
                 # flush but more have lines are still coming
                 # flush but more have lines are still coming
                 continue
                 continue
-            elif command == 'done':
+            elif command == COMMAND_DONE:
                 # don't nak unless no common commits were found, even if not
                 # don't nak unless no common commits were found, even if not
                 # everything is satisfied
                 # everything is satisfied
                 if self._common:
                 if self._common:
@@ -654,10 +693,10 @@ class MultiAckGraphWalkerImpl(object):
                 else:
                 else:
                     self.walker.send_nak()
                     self.walker.send_nak()
                 return None
                 return None
-            elif command == 'have':
+            elif command == COMMAND_HAVE:
                 if self._found_base:
                 if self._found_base:
                     # blind ack
                     # blind ack
-                    self.walker.send_ack(sha, 'continue')
+                    self.walker.send_ack(sha, b'continue')
                 return sha
                 return sha
 
 
     __next__ = next
     __next__ = next
@@ -674,10 +713,10 @@ class MultiAckDetailedGraphWalkerImpl(object):
     def ack(self, have_ref):
     def ack(self, have_ref):
         self._common.append(have_ref)
         self._common.append(have_ref)
         if not self._found_base:
         if not self._found_base:
-            self.walker.send_ack(have_ref, 'common')
+            self.walker.send_ack(have_ref, b'common')
             if self.walker.all_wants_satisfied(self._common):
             if self.walker.all_wants_satisfied(self._common):
                 self._found_base = True
                 self._found_base = True
-                self.walker.send_ack(have_ref, 'ready')
+                self.walker.send_ack(have_ref, b'ready')
         # else we blind ack within next
         # else we blind ack within next
 
 
     def next(self):
     def next(self):
@@ -688,7 +727,7 @@ class MultiAckDetailedGraphWalkerImpl(object):
                 if self.walker.http_req:
                 if self.walker.http_req:
                     return None
                     return None
                 continue
                 continue
-            elif command == 'done':
+            elif command == COMMAND_DONE:
                 # don't nak unless no common commits were found, even if not
                 # don't nak unless no common commits were found, even if not
                 # everything is satisfied
                 # everything is satisfied
                 if self._common:
                 if self._common:
@@ -696,11 +735,11 @@ class MultiAckDetailedGraphWalkerImpl(object):
                 else:
                 else:
                     self.walker.send_nak()
                     self.walker.send_nak()
                 return None
                 return None
-            elif command == 'have':
+            elif command == COMMAND_HAVE:
                 if self._found_base:
                 if self._found_base:
                     # blind ack; can happen if the client has more requests
                     # blind ack; can happen if the client has more requests
                     # inflight
                     # inflight
-                    self.walker.send_ack(sha, 'ready')
+                    self.walker.send_ack(sha, b'ready')
                 return sha
                 return sha
 
 
     __next__ = next
     __next__ = next
@@ -717,7 +756,7 @@ class ReceivePackHandler(Handler):
 
 
     @classmethod
     @classmethod
     def capabilities(cls):
     def capabilities(cls):
-        return ("report-status", "delete-refs", "side-band-64k")
+        return (CAPABILITY_REPORT_STATUS, CAPABILITY_DELETE_REFS, CAPABILITY_SIDE_BAND_64K)
 
 
     def _apply_pack(self, refs):
     def _apply_pack(self, refs):
         all_exceptions = (IOError, OSError, ChecksumMismatch, ApplyDeltaError,
         all_exceptions = (IOError, OSError, ChecksumMismatch, ApplyDeltaError,
@@ -735,43 +774,43 @@ class ReceivePackHandler(Handler):
             try:
             try:
                 recv = getattr(self.proto, "recv", None)
                 recv = getattr(self.proto, "recv", None)
                 self.repo.object_store.add_thin_pack(self.proto.read, recv)
                 self.repo.object_store.add_thin_pack(self.proto.read, recv)
-                status.append(('unpack', 'ok'))
+                status.append((b'unpack', b'ok'))
             except all_exceptions as e:
             except all_exceptions as e:
-                status.append(('unpack', str(e).replace('\n', '')))
+                status.append((b'unpack', str(e).replace('\n', '')))
                 # The pack may still have been moved in, but it may contain broken
                 # The pack may still have been moved in, but it may contain broken
                 # objects. We trust a later GC to clean it up.
                 # objects. We trust a later GC to clean it up.
         else:
         else:
             # The git protocol want to find a status entry related to unpack process
             # The git protocol want to find a status entry related to unpack process
             # even if no pack data has been sent.
             # even if no pack data has been sent.
-            status.append(('unpack', 'ok'))
+            status.append((b'unpack', b'ok'))
 
 
         for oldsha, sha, ref in refs:
         for oldsha, sha, ref in refs:
-            ref_status = 'ok'
+            ref_status = b'ok'
             try:
             try:
                 if sha == ZERO_SHA:
                 if sha == ZERO_SHA:
-                    if not 'delete-refs' in self.capabilities():
+                    if not CAPABILITY_DELETE_REFS in self.capabilities():
                         raise GitProtocolError(
                         raise GitProtocolError(
                           'Attempted to delete refs without delete-refs '
                           'Attempted to delete refs without delete-refs '
                           'capability.')
                           'capability.')
                     try:
                     try:
                         del self.repo.refs[ref]
                         del self.repo.refs[ref]
                     except all_exceptions:
                     except all_exceptions:
-                        ref_status = 'failed to delete'
+                        ref_status = b'failed to delete'
                 else:
                 else:
                     try:
                     try:
                         self.repo.refs[ref] = sha
                         self.repo.refs[ref] = sha
                     except all_exceptions:
                     except all_exceptions:
-                        ref_status = 'failed to write'
+                        ref_status = b'failed to write'
             except KeyError as e:
             except KeyError as e:
-                ref_status = 'bad ref'
+                ref_status = b'bad ref'
             status.append((ref, ref_status))
             status.append((ref, ref_status))
 
 
         return status
         return status
 
 
     def _report_status(self, status):
     def _report_status(self, status):
-        if self.has_capability('side-band-64k'):
+        if self.has_capability(CAPABILITY_SIDE_BAND_64K):
             writer = BufferedPktLineWriter(
             writer = BufferedPktLineWriter(
-              lambda d: self.proto.write_sideband(1, d))
+              lambda d: self.proto.write_sideband(SIDE_BAND_CHANNEL_DATA, d))
             write = writer.write
             write = writer.write
 
 
             def flush():
             def flush():
@@ -782,31 +821,31 @@ class ReceivePackHandler(Handler):
             flush = lambda: None
             flush = lambda: None
 
 
         for name, msg in status:
         for name, msg in status:
-            if name == 'unpack':
-                write('unpack %s\n' % msg)
-            elif msg == 'ok':
-                write('ok %s\n' % name)
+            if name == b'unpack':
+                write(b'unpack ' + msg + b'\n')
+            elif msg == b'ok':
+                write(b'ok ' + name + b'\n')
             else:
             else:
-                write('ng %s %s\n' % (name, msg))
+                write(b'ng ' + name + b' ' + msg + b'\n')
         write(None)
         write(None)
         flush()
         flush()
 
 
     def handle(self):
     def handle(self):
         if self.advertise_refs or not self.http_req:
         if self.advertise_refs or not self.http_req:
-            refs = sorted(self.repo.get_refs().iteritems())
+            refs = sorted(self.repo.get_refs().items())
 
 
             if refs:
             if refs:
                 self.proto.write_pkt_line(
                 self.proto.write_pkt_line(
-                  "%s %s\x00%s\n" % (refs[0][1], refs[0][0],
-                                     self.capability_line()))
+                  refs[0][1] + b' ' + refs[0][0] + b'\0' +
+                  self.capability_line() + b'\n')
                 for i in range(1, len(refs)):
                 for i in range(1, len(refs)):
                     ref = refs[i]
                     ref = refs[i]
-                    self.proto.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
+                    self.proto.write_pkt_line(ref[1] + b' ' + ref[0] + b'\n')
             else:
             else:
-                self.proto.write_pkt_line("%s capabilities^{}\0%s" % (
-                  ZERO_SHA, self.capability_line()))
+                self.proto.write_pkt_line(ZERO_SHA + b" capabilities^{}\0" +
+                    self.capability_line())
 
 
-            self.proto.write("0000")
+            self.proto.write_pkt_line(None)
             if self.advertise_refs:
             if self.advertise_refs:
                 return
                 return
 
 
@@ -830,14 +869,14 @@ class ReceivePackHandler(Handler):
 
 
         # when we have read all the pack from the client, send a status report
         # when we have read all the pack from the client, send a status report
         # if the client asked for it
         # if the client asked for it
-        if self.has_capability('report-status'):
+        if self.has_capability(CAPABILITY_REPORT_STATUS):
             self._report_status(status)
             self._report_status(status)
 
 
 
 
 # Default handler classes for git services.
 # Default handler classes for git services.
 DEFAULT_HANDLERS = {
 DEFAULT_HANDLERS = {
-  'git-upload-pack': UploadPackHandler,
-  'git-receive-pack': ReceivePackHandler,
+  b'git-upload-pack': UploadPackHandler,
+  b'git-receive-pack': ReceivePackHandler,
   }
   }
 
 
 
 
@@ -941,7 +980,7 @@ def generate_info_refs(repo):
 def generate_objects_info_packs(repo):
 def generate_objects_info_packs(repo):
     """Generate an index for for packs."""
     """Generate an index for for packs."""
     for pack in repo.object_store.packs:
     for pack in repo.object_store.packs:
-        yield 'P %s\n' % pack.data.filename
+        yield b'P ' + pack.data.filename.encode(sys.getfilesystemencoding()) + b'\n'
 
 
 
 
 def update_server_info(repo):
 def update_server_info(repo):
@@ -950,11 +989,11 @@ def update_server_info(repo):
     This generates info/refs and objects/info/packs,
     This generates info/refs and objects/info/packs,
     similar to "git update-server-info".
     similar to "git update-server-info".
     """
     """
-    repo._put_named_file(os.path.join('info', 'refs'),
-        "".join(generate_info_refs(repo)))
+    repo._put_named_file(os.path.join(b'info', b'refs'),
+        b"".join(generate_info_refs(repo)))
 
 
-    repo._put_named_file(os.path.join('objects', 'info', 'packs'),
-        "".join(generate_objects_info_packs(repo)))
+    repo._put_named_file(os.path.join(b'objects', b'info', b'packs'),
+        b"".join(generate_objects_info_packs(repo)))
 
 
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':

+ 2 - 1
dulwich/tests/__init__.py

@@ -170,7 +170,8 @@ def nocompat_test_suite():
     result = unittest.TestSuite()
     result = unittest.TestSuite()
     result.addTests(self_test_suite())
     result.addTests(self_test_suite())
     from dulwich.contrib import test_suite as contrib_test_suite
     from dulwich.contrib import test_suite as contrib_test_suite
-    result.addTests(tutorial_test_suite())
+    if sys.version_info[0] == 2:
+        result.addTests(tutorial_test_suite())
     result.addTests(contrib_test_suite())
     result.addTests(contrib_test_suite())
     return result
     return result
 
 

+ 12 - 0
dulwich/tests/compat/server_utils.py

@@ -23,6 +23,7 @@ import errno
 import os
 import os
 import shutil
 import shutil
 import socket
 import socket
+import sys
 import tempfile
 import tempfile
 
 
 from dulwich.repo import Repo
 from dulwich.repo import Repo
@@ -38,6 +39,7 @@ from dulwich.tests.compat.utils import (
     import_repo,
     import_repo,
     run_git_or_fail,
     run_git_or_fail,
     )
     )
+from dulwich.tests.compat.utils import require_git_version
 
 
 
 
 class _StubRepo(object):
 class _StubRepo(object):
@@ -46,6 +48,11 @@ class _StubRepo(object):
     def __init__(self, name):
     def __init__(self, name):
         temp_dir = tempfile.mkdtemp()
         temp_dir = tempfile.mkdtemp()
         self.path = os.path.join(temp_dir, name)
         self.path = os.path.join(temp_dir, name)
+        if not isinstance(self.path, bytes):
+            self._path_bytes = self.path.encode(sys.getfilesystemencoding())
+        else:
+            self._path_bytes = self.path
+
         os.mkdir(self.path)
         os.mkdir(self.path)
 
 
 
 
@@ -71,6 +78,8 @@ class ServerTests(object):
     Does not inherit from TestCase so tests are not automatically run.
     Does not inherit from TestCase so tests are not automatically run.
     """
     """
 
 
+    min_single_branch_version = (1, 7, 10,)
+
     def import_repos(self):
     def import_repos(self):
         self._old_repo = import_repo('server_old.export')
         self._old_repo = import_repo('server_old.export')
         self.addCleanup(tear_down_repo, self._old_repo)
         self.addCleanup(tear_down_repo, self._old_repo)
@@ -171,6 +180,7 @@ class ServerTests(object):
         self.assertEqual(len(o.split('\n')), 4)
         self.assertEqual(len(o.split('\n')), 4)
 
 
     def test_new_shallow_clone_from_dulwich(self):
     def test_new_shallow_clone_from_dulwich(self):
+        require_git_version(self.min_single_branch_version)
         self._source_repo = import_repo('server_new.export')
         self._source_repo = import_repo('server_new.export')
         self.addCleanup(tear_down_repo, self._source_repo)
         self.addCleanup(tear_down_repo, self._source_repo)
         self._stub_repo = _StubRepo('shallow')
         self._stub_repo = _StubRepo('shallow')
@@ -187,6 +197,7 @@ class ServerTests(object):
         self.assertReposNotEqual(clone, self._source_repo)
         self.assertReposNotEqual(clone, self._source_repo)
 
 
     def test_fetch_same_depth_into_shallow_clone_from_dulwich(self):
     def test_fetch_same_depth_into_shallow_clone_from_dulwich(self):
+        require_git_version(self.min_single_branch_version)
         self._source_repo = import_repo('server_new.export')
         self._source_repo = import_repo('server_new.export')
         self.addCleanup(tear_down_repo, self._source_repo)
         self.addCleanup(tear_down_repo, self._source_repo)
         self._stub_repo = _StubRepo('shallow')
         self._stub_repo = _StubRepo('shallow')
@@ -208,6 +219,7 @@ class ServerTests(object):
         self.assertReposNotEqual(clone, self._source_repo)
         self.assertReposNotEqual(clone, self._source_repo)
 
 
     def test_fetch_full_depth_into_shallow_clone_from_dulwich(self):
     def test_fetch_full_depth_into_shallow_clone_from_dulwich(self):
+        require_git_version(self.min_single_branch_version)
         self._source_repo = import_repo('server_new.export')
         self._source_repo = import_repo('server_new.export')
         self.addCleanup(tear_down_repo, self._source_repo)
         self.addCleanup(tear_down_repo, self._source_repo)
         self._stub_repo = _StubRepo('shallow')
         self._stub_repo = _StubRepo('shallow')

+ 44 - 35
dulwich/tests/compat/test_client.py

@@ -30,7 +30,12 @@ import sys
 import tarfile
 import tarfile
 import tempfile
 import tempfile
 import threading
 import threading
-import urllib
+
+try:
+    from urlparse import unquote
+except ImportError:
+    from urllib.parse import unquote
+
 
 
 try:
 try:
     import BaseHTTPServer
     import BaseHTTPServer
@@ -68,7 +73,6 @@ from dulwich.tests.compat.utils import (
     )
     )
 
 
 
 
-@skipIfPY3
 class DulwichClientTestBase(object):
 class DulwichClientTestBase(object):
     """Tests for client/server compatibility."""
     """Tests for client/server compatibility."""
 
 
@@ -97,7 +101,7 @@ class DulwichClientTestBase(object):
         srcpath = os.path.join(self.gitroot, 'server_new.export')
         srcpath = os.path.join(self.gitroot, 'server_new.export')
         src = repo.Repo(srcpath)
         src = repo.Repo(srcpath)
         sendrefs = dict(src.get_refs())
         sendrefs = dict(src.get_refs())
-        del sendrefs['HEAD']
+        del sendrefs[b'HEAD']
         c.send_pack(self._build_path('/dest'), lambda _: sendrefs,
         c.send_pack(self._build_path('/dest'), lambda _: sendrefs,
                     src.object_store.generate_pack_contents)
                     src.object_store.generate_pack_contents)
 
 
@@ -113,24 +117,24 @@ class DulwichClientTestBase(object):
 
 
     def test_send_without_report_status(self):
     def test_send_without_report_status(self):
         c = self._client()
         c = self._client()
-        c._send_capabilities.remove('report-status')
+        c._send_capabilities.remove(b'report-status')
         srcpath = os.path.join(self.gitroot, 'server_new.export')
         srcpath = os.path.join(self.gitroot, 'server_new.export')
         src = repo.Repo(srcpath)
         src = repo.Repo(srcpath)
         sendrefs = dict(src.get_refs())
         sendrefs = dict(src.get_refs())
-        del sendrefs['HEAD']
+        del sendrefs[b'HEAD']
         c.send_pack(self._build_path('/dest'), lambda _: sendrefs,
         c.send_pack(self._build_path('/dest'), lambda _: sendrefs,
                     src.object_store.generate_pack_contents)
                     src.object_store.generate_pack_contents)
         self.assertDestEqualsSrc()
         self.assertDestEqualsSrc()
 
 
     def make_dummy_commit(self, dest):
     def make_dummy_commit(self, dest):
-        b = objects.Blob.from_string('hi')
+        b = objects.Blob.from_string(b'hi')
         dest.object_store.add_object(b)
         dest.object_store.add_object(b)
-        t = index.commit_tree(dest.object_store, [('hi', b.id, 0o100644)])
+        t = index.commit_tree(dest.object_store, [(b'hi', b.id, 0o100644)])
         c = objects.Commit()
         c = objects.Commit()
-        c.author = c.committer = 'Foo Bar <foo@example.com>'
+        c.author = c.committer = b'Foo Bar <foo@example.com>'
         c.author_time = c.commit_time = 0
         c.author_time = c.commit_time = 0
         c.author_timezone = c.commit_timezone = 0
         c.author_timezone = c.commit_timezone = 0
-        c.message = 'hi'
+        c.message = b'hi'
         c.tree = t
         c.tree = t
         dest.object_store.add_object(c)
         dest.object_store.add_object(c)
         return c.id
         return c.id
@@ -147,26 +151,27 @@ class DulwichClientTestBase(object):
         srcpath = os.path.join(self.gitroot, 'server_new.export')
         srcpath = os.path.join(self.gitroot, 'server_new.export')
         src = repo.Repo(srcpath)
         src = repo.Repo(srcpath)
         sendrefs = dict(src.get_refs())
         sendrefs = dict(src.get_refs())
-        del sendrefs['HEAD']
+        del sendrefs[b'HEAD']
         return sendrefs, src.object_store.generate_pack_contents
         return sendrefs, src.object_store.generate_pack_contents
 
 
     def test_send_pack_one_error(self):
     def test_send_pack_one_error(self):
         dest, dummy_commit = self.disable_ff_and_make_dummy_commit()
         dest, dummy_commit = self.disable_ff_and_make_dummy_commit()
-        dest.refs['refs/heads/master'] = dummy_commit
+        dest.refs[b'refs/heads/master'] = dummy_commit
         sendrefs, gen_pack = self.compute_send()
         sendrefs, gen_pack = self.compute_send()
         c = self._client()
         c = self._client()
         try:
         try:
             c.send_pack(self._build_path('/dest'), lambda _: sendrefs, gen_pack)
             c.send_pack(self._build_path('/dest'), lambda _: sendrefs, gen_pack)
         except errors.UpdateRefsError as e:
         except errors.UpdateRefsError as e:
-            self.assertEqual('refs/heads/master failed to update', str(e))
-            self.assertEqual({'refs/heads/branch': 'ok',
-                              'refs/heads/master': 'non-fast-forward'},
+            self.assertEqual('refs/heads/master failed to update',
+                             e.args[0])
+            self.assertEqual({b'refs/heads/branch': b'ok',
+                              b'refs/heads/master': b'non-fast-forward'},
                              e.ref_status)
                              e.ref_status)
 
 
     def test_send_pack_multiple_errors(self):
     def test_send_pack_multiple_errors(self):
         dest, dummy = self.disable_ff_and_make_dummy_commit()
         dest, dummy = self.disable_ff_and_make_dummy_commit()
         # set up for two non-ff errors
         # set up for two non-ff errors
-        branch, master = 'refs/heads/branch', 'refs/heads/master'
+        branch, master = b'refs/heads/branch', b'refs/heads/master'
         dest.refs[branch] = dest.refs[master] = dummy
         dest.refs[branch] = dest.refs[master] = dummy
         sendrefs, gen_pack = self.compute_send()
         sendrefs, gen_pack = self.compute_send()
         c = self._client()
         c = self._client()
@@ -174,16 +179,18 @@ class DulwichClientTestBase(object):
             c.send_pack(self._build_path('/dest'), lambda _: sendrefs, gen_pack)
             c.send_pack(self._build_path('/dest'), lambda _: sendrefs, gen_pack)
         except errors.UpdateRefsError as e:
         except errors.UpdateRefsError as e:
             self.assertIn(str(e),
             self.assertIn(str(e),
-                          ['{0}, {1} failed to update'.format(branch, master),
-                           '{1}, {0} failed to update'.format(branch, master)])
-            self.assertEqual({branch: 'non-fast-forward',
-                              master: 'non-fast-forward'},
+                          ['{0}, {1} failed to update'.format(
+                              branch.decode('ascii'), master.decode('ascii')),
+                           '{1}, {0} failed to update'.format(
+                               branch.decode('ascii'), master.decode('ascii'))])
+            self.assertEqual({branch: b'non-fast-forward',
+                              master: b'non-fast-forward'},
                              e.ref_status)
                              e.ref_status)
 
 
     def test_archive(self):
     def test_archive(self):
         c = self._client()
         c = self._client()
         f = BytesIO()
         f = BytesIO()
-        c.archive(self._build_path('/server_new.export'), 'HEAD', f.write)
+        c.archive(self._build_path('/server_new.export'), b'HEAD', f.write)
         f.seek(0)
         f.seek(0)
         tf = tarfile.open(fileobj=f)
         tf = tarfile.open(fileobj=f)
         self.assertEqual(['baz', 'foo'], tf.getnames())
         self.assertEqual(['baz', 'foo'], tf.getnames())
@@ -199,7 +206,7 @@ class DulwichClientTestBase(object):
     def test_incremental_fetch_pack(self):
     def test_incremental_fetch_pack(self):
         self.test_fetch_pack()
         self.test_fetch_pack()
         dest, dummy = self.disable_ff_and_make_dummy_commit()
         dest, dummy = self.disable_ff_and_make_dummy_commit()
-        dest.refs['refs/heads/master'] = dummy
+        dest.refs[b'refs/heads/master'] = dummy
         c = self._client()
         c = self._client()
         dest = repo.Repo(os.path.join(self.gitroot, 'server_new.export'))
         dest = repo.Repo(os.path.join(self.gitroot, 'server_new.export'))
         refs = c.fetch(self._build_path('/dest'), dest)
         refs = c.fetch(self._build_path('/dest'), dest)
@@ -209,7 +216,7 @@ class DulwichClientTestBase(object):
 
 
     def test_fetch_pack_no_side_band_64k(self):
     def test_fetch_pack_no_side_band_64k(self):
         c = self._client()
         c = self._client()
-        c._fetch_capabilities.remove('side-band-64k')
+        c._fetch_capabilities.remove(b'side-band-64k')
         dest = repo.Repo(os.path.join(self.gitroot, 'dest'))
         dest = repo.Repo(os.path.join(self.gitroot, 'dest'))
         refs = c.fetch(self._build_path('/server_new.export'), dest)
         refs = c.fetch(self._build_path('/server_new.export'), dest)
         for r in refs.items():
         for r in refs.items():
@@ -229,16 +236,16 @@ class DulwichClientTestBase(object):
     def test_send_remove_branch(self):
     def test_send_remove_branch(self):
         dest = repo.Repo(os.path.join(self.gitroot, 'dest'))
         dest = repo.Repo(os.path.join(self.gitroot, 'dest'))
         dummy_commit = self.make_dummy_commit(dest)
         dummy_commit = self.make_dummy_commit(dest)
-        dest.refs['refs/heads/master'] = dummy_commit
-        dest.refs['refs/heads/abranch'] = dummy_commit
+        dest.refs[b'refs/heads/master'] = dummy_commit
+        dest.refs[b'refs/heads/abranch'] = dummy_commit
         sendrefs = dict(dest.refs)
         sendrefs = dict(dest.refs)
-        sendrefs['refs/heads/abranch'] = "00" * 20
-        del sendrefs['HEAD']
+        sendrefs[b'refs/heads/abranch'] = b"00" * 20
+        del sendrefs[b'HEAD']
         gen_pack = lambda have, want: []
         gen_pack = lambda have, want: []
         c = self._client()
         c = self._client()
-        self.assertEqual(dest.refs["refs/heads/abranch"], dummy_commit)
+        self.assertEqual(dest.refs[b"refs/heads/abranch"], dummy_commit)
         c.send_pack(self._build_path('/dest'), lambda _: sendrefs, gen_pack)
         c.send_pack(self._build_path('/dest'), lambda _: sendrefs, gen_pack)
-        self.assertFalse("refs/heads/abranch" in dest.refs)
+        self.assertFalse(b"refs/heads/abranch" in dest.refs)
 
 
 
 
 class DulwichTCPClientTest(CompatTestCase, DulwichClientTestBase):
 class DulwichTCPClientTest(CompatTestCase, DulwichClientTestBase):
@@ -284,10 +291,10 @@ class DulwichTCPClientTest(CompatTestCase, DulwichClientTestBase):
         CompatTestCase.tearDown(self)
         CompatTestCase.tearDown(self)
 
 
     def _client(self):
     def _client(self):
-        return client.TCPGitClient('localhost')
+        return client.TCPGitClient(b'localhost')
 
 
     def _build_path(self, path):
     def _build_path(self, path):
-        return path
+        return path.encode(sys.getfilesystemencoding())
 
 
 
 
 class TestSSHVendor(object):
 class TestSSHVendor(object):
@@ -300,6 +307,7 @@ class TestSSHVendor(object):
         return client.SubprocessWrapper(p)
         return client.SubprocessWrapper(p)
 
 
 
 
+@skipIfPY3
 class DulwichMockSSHClientTest(CompatTestCase, DulwichClientTestBase):
 class DulwichMockSSHClientTest(CompatTestCase, DulwichClientTestBase):
 
 
     def setUp(self):
     def setUp(self):
@@ -379,7 +387,7 @@ class GitHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
         env['GIT_PROJECT_ROOT'] = self.server.root_path
         env['GIT_PROJECT_ROOT'] = self.server.root_path
         env["GIT_HTTP_EXPORT_ALL"] = "1"
         env["GIT_HTTP_EXPORT_ALL"] = "1"
         env['REQUEST_METHOD'] = self.command
         env['REQUEST_METHOD'] = self.command
-        uqrest = urllib.unquote(rest)
+        uqrest = unquote(rest)
         env['PATH_INFO'] = uqrest
         env['PATH_INFO'] = uqrest
         env['SCRIPT_NAME'] = "/"
         env['SCRIPT_NAME'] = "/"
         if query:
         if query:
@@ -388,7 +396,7 @@ class GitHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
         if host != self.client_address[0]:
         if host != self.client_address[0]:
             env['REMOTE_HOST'] = host
             env['REMOTE_HOST'] = host
         env['REMOTE_ADDR'] = self.client_address[0]
         env['REMOTE_ADDR'] = self.client_address[0]
-        authorization = self.headers.getheader("authorization")
+        authorization = self.headers.get("authorization")
         if authorization:
         if authorization:
             authorization = authorization.split()
             authorization = authorization.split()
             if len(authorization) == 2:
             if len(authorization) == 2:
@@ -408,10 +416,10 @@ class GitHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
             env['CONTENT_TYPE'] = self.headers.type
             env['CONTENT_TYPE'] = self.headers.type
         else:
         else:
             env['CONTENT_TYPE'] = self.headers.typeheader
             env['CONTENT_TYPE'] = self.headers.typeheader
-        length = self.headers.getheader('content-length')
+        length = self.headers.get('content-length')
         if length:
         if length:
             env['CONTENT_LENGTH'] = length
             env['CONTENT_LENGTH'] = length
-        referer = self.headers.getheader('referer')
+        referer = self.headers.get('referer')
         if referer:
         if referer:
             env['HTTP_REFERER'] = referer
             env['HTTP_REFERER'] = referer
         accept = []
         accept = []
@@ -421,7 +429,7 @@ class GitHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
             else:
             else:
                 accept = accept + line[7:].split(',')
                 accept = accept + line[7:].split(',')
         env['HTTP_ACCEPT'] = ','.join(accept)
         env['HTTP_ACCEPT'] = ','.join(accept)
-        ua = self.headers.getheader('user-agent')
+        ua = self.headers.get('user-agent')
         if ua:
         if ua:
             env['HTTP_USER_AGENT'] = ua
             env['HTTP_USER_AGENT'] = ua
         co = filter(None, self.headers.getheaders('cookie'))
         co = filter(None, self.headers.getheaders('cookie'))
@@ -470,6 +478,7 @@ class HTTPGitServer(BaseHTTPServer.HTTPServer):
         return 'http://%s:%s/' % (self.server_name, self.server_port)
         return 'http://%s:%s/' % (self.server_name, self.server_port)
 
 
 
 
+@skipIfPY3
 class DulwichHttpClientTest(CompatTestCase, DulwichClientTestBase):
 class DulwichHttpClientTest(CompatTestCase, DulwichClientTestBase):
 
 
     min_git_version = (1, 7, 0, 2)
     min_git_version = (1, 7, 0, 2)

+ 11 - 8
dulwich/tests/compat/test_pack.py

@@ -24,6 +24,7 @@ import binascii
 import os
 import os
 import re
 import re
 import shutil
 import shutil
+import sys
 import tempfile
 import tempfile
 
 
 from dulwich.pack import (
 from dulwich.pack import (
@@ -45,7 +46,7 @@ from dulwich.tests.compat.utils import (
     run_git_or_fail,
     run_git_or_fail,
     )
     )
 
 
-_NON_DELTA_RE = re.compile('non delta: (?P<non_delta>\d+) objects')
+_NON_DELTA_RE = re.compile(b'non delta: (?P<non_delta>\d+) objects')
 
 
 def _git_verify_pack_object_list(output):
 def _git_verify_pack_object_list(output):
     pack_shas = set()
     pack_shas = set()
@@ -66,12 +67,14 @@ class TestPack(PackTests):
         require_git_version((1, 5, 0))
         require_git_version((1, 5, 0))
         super(TestPack, self).setUp()
         super(TestPack, self).setUp()
         self._tempdir = tempfile.mkdtemp()
         self._tempdir = tempfile.mkdtemp()
+        if not isinstance(self._tempdir, bytes):
+            self._tempdir = self._tempdir.encode(sys.getfilesystemencoding())
         self.addCleanup(shutil.rmtree, self._tempdir)
         self.addCleanup(shutil.rmtree, self._tempdir)
 
 
     def test_copy(self):
     def test_copy(self):
         with self.get_pack(pack1_sha) as origpack:
         with self.get_pack(pack1_sha) as origpack:
             self.assertSucceeds(origpack.index.check)
             self.assertSucceeds(origpack.index.check)
-            pack_path = os.path.join(self._tempdir, "Elch")
+            pack_path = os.path.join(self._tempdir, b'Elch')
             write_pack(pack_path, origpack.pack_tuples())
             write_pack(pack_path, origpack.pack_tuples())
             output = run_git_or_fail(['verify-pack', '-v', pack_path])
             output = run_git_or_fail(['verify-pack', '-v', pack_path])
             orig_shas = set(o.id for o in origpack.iterobjects())
             orig_shas = set(o.id for o in origpack.iterobjects())
@@ -81,9 +84,9 @@ class TestPack(PackTests):
         orig_pack = self.get_pack(pack1_sha)
         orig_pack = self.get_pack(pack1_sha)
         orig_blob = orig_pack[a_sha]
         orig_blob = orig_pack[a_sha]
         new_blob = Blob()
         new_blob = Blob()
-        new_blob.data = orig_blob.data + 'x'
+        new_blob.data = orig_blob.data + b'x'
         all_to_pack = list(orig_pack.pack_tuples()) + [(new_blob, None)]
         all_to_pack = list(orig_pack.pack_tuples()) + [(new_blob, None)]
-        pack_path = os.path.join(self._tempdir, "pack_with_deltas")
+        pack_path = os.path.join(self._tempdir, b'pack_with_deltas')
         write_pack(pack_path, all_to_pack, deltify=True)
         write_pack(pack_path, all_to_pack, deltify=True)
         output = run_git_or_fail(['verify-pack', '-v', pack_path])
         output = run_git_or_fail(['verify-pack', '-v', pack_path])
         self.assertEqual(set(x[0].id for x in all_to_pack),
         self.assertEqual(set(x[0].id for x in all_to_pack),
@@ -102,12 +105,12 @@ class TestPack(PackTests):
         orig_pack = self.get_pack(pack1_sha)
         orig_pack = self.get_pack(pack1_sha)
         orig_blob = orig_pack[a_sha]
         orig_blob = orig_pack[a_sha]
         new_blob = Blob()
         new_blob = Blob()
-        new_blob.data = orig_blob.data + ('x' * 2 ** 20)
+        new_blob.data = orig_blob.data + (b'x' * 2 ** 20)
         new_blob_2 = Blob()
         new_blob_2 = Blob()
-        new_blob_2.data = new_blob.data + 'y'
+        new_blob_2.data = new_blob.data + b'y'
         all_to_pack = list(orig_pack.pack_tuples()) + [(new_blob, None),
         all_to_pack = list(orig_pack.pack_tuples()) + [(new_blob, None),
                                                        (new_blob_2, None)]
                                                        (new_blob_2, None)]
-        pack_path = os.path.join(self._tempdir, "pack_with_deltas")
+        pack_path = os.path.join(self._tempdir, b'pack_with_deltas')
         write_pack(pack_path, all_to_pack, deltify=True)
         write_pack(pack_path, all_to_pack, deltify=True)
         output = run_git_or_fail(['verify-pack', '-v', pack_path])
         output = run_git_or_fail(['verify-pack', '-v', pack_path])
         self.assertEqual(set(x[0].id for x in all_to_pack),
         self.assertEqual(set(x[0].id for x in all_to_pack),
@@ -121,7 +124,7 @@ class TestPack(PackTests):
             'Expected 3 non-delta objects, got %d' % got_non_delta)
             'Expected 3 non-delta objects, got %d' % got_non_delta)
         # We expect one object to have a delta chain length of two
         # We expect one object to have a delta chain length of two
         # (new_blob_2), so let's verify that actually happens:
         # (new_blob_2), so let's verify that actually happens:
-        self.assertIn('chain length = 2', output)
+        self.assertIn(b'chain length = 2', output)
 
 
     # This test is SUPER slow: over 80 seconds on a 2012-era
     # This test is SUPER slow: over 80 seconds on a 2012-era
     # laptop. This is because SequenceMatcher is worst-case quadratic
     # laptop. This is because SequenceMatcher is worst-case quadratic

+ 6 - 8
dulwich/tests/compat/test_repository.py

@@ -32,7 +32,6 @@ from dulwich.repo import (
     )
     )
 from dulwich.tests.utils import (
 from dulwich.tests.utils import (
     tear_down_repo,
     tear_down_repo,
-    skipIfPY3,
     )
     )
 
 
 from dulwich.tests.compat.utils import (
 from dulwich.tests.compat.utils import (
@@ -42,7 +41,6 @@ from dulwich.tests.compat.utils import (
     )
     )
 
 
 
 
-@skipIfPY3
 class ObjectStoreTestCase(CompatTestCase):
 class ObjectStoreTestCase(CompatTestCase):
     """Tests for git repository compatibility."""
     """Tests for git repository compatibility."""
 
 
@@ -57,7 +55,7 @@ class ObjectStoreTestCase(CompatTestCase):
     def _parse_refs(self, output):
     def _parse_refs(self, output):
         refs = {}
         refs = {}
         for line in BytesIO(output):
         for line in BytesIO(output):
-            fields = line.rstrip('\n').split(' ')
+            fields = line.rstrip(b'\n').split(b' ')
             self.assertEqual(3, len(fields))
             self.assertEqual(3, len(fields))
             refname, type_name, sha = fields
             refname, type_name, sha = fields
             check_ref_format(refname[5:])
             check_ref_format(refname[5:])
@@ -66,7 +64,7 @@ class ObjectStoreTestCase(CompatTestCase):
         return refs
         return refs
 
 
     def _parse_objects(self, output):
     def _parse_objects(self, output):
-        return set(s.rstrip('\n').split(' ')[0] for s in BytesIO(output))
+        return set(s.rstrip(b'\n').split(b' ')[0] for s in BytesIO(output))
 
 
     def test_bare(self):
     def test_bare(self):
         self.assertTrue(self._repo.bare)
         self.assertTrue(self._repo.bare)
@@ -74,9 +72,9 @@ class ObjectStoreTestCase(CompatTestCase):
 
 
     def test_head(self):
     def test_head(self):
         output = self._run_git(['rev-parse', 'HEAD'])
         output = self._run_git(['rev-parse', 'HEAD'])
-        head_sha = output.rstrip('\n')
+        head_sha = output.rstrip(b'\n')
         hex_to_sha(head_sha)
         hex_to_sha(head_sha)
-        self.assertEqual(head_sha, self._repo.refs['HEAD'])
+        self.assertEqual(head_sha, self._repo.refs[b'HEAD'])
 
 
     def test_refs(self):
     def test_refs(self):
         output = self._run_git(
         output = self._run_git(
@@ -84,8 +82,8 @@ class ObjectStoreTestCase(CompatTestCase):
         expected_refs = self._parse_refs(output)
         expected_refs = self._parse_refs(output)
 
 
         actual_refs = {}
         actual_refs = {}
-        for refname, sha in self._repo.refs.as_dict().iteritems():
-            if refname == 'HEAD':
+        for refname, sha in self._repo.refs.as_dict().items():
+            if refname == b'HEAD':
                 continue  # handled in test_head
                 continue  # handled in test_head
             obj = self._repo[sha]
             obj = self._repo[sha]
             self.assertEqual(sha, obj.id)
             self.assertEqual(sha, obj.id)

+ 4 - 1
dulwich/tests/compat/test_server.py

@@ -26,11 +26,13 @@ Warning: these tests should be fairly stable, but when writing/debugging new
 
 
 import threading
 import threading
 import os
 import os
+import sys
 
 
 from dulwich.server import (
 from dulwich.server import (
     DictBackend,
     DictBackend,
     TCPGitServer,
     TCPGitServer,
     )
     )
+from dulwich.tests import skipIf
 from dulwich.tests.compat.server_utils import (
 from dulwich.tests.compat.server_utils import (
     ServerTests,
     ServerTests,
     NoSideBand64kReceivePackHandler,
     NoSideBand64kReceivePackHandler,
@@ -40,7 +42,7 @@ from dulwich.tests.compat.utils import (
     require_git_version,
     require_git_version,
     )
     )
 
 
-
+@skipIf(sys.platform == 'win32', 'Broken on windows, with very long fail time.')
 class GitServerTestCase(ServerTests, CompatTestCase):
 class GitServerTestCase(ServerTests, CompatTestCase):
     """Tests for client/server compatibility.
     """Tests for client/server compatibility.
 
 
@@ -70,6 +72,7 @@ class GitServerTestCase(ServerTests, CompatTestCase):
         return port
         return port
 
 
 
 
+@skipIf(sys.platform == 'win32', 'Broken on windows, with very long fail time.')
 class GitServerSideBand64kTestCase(GitServerTestCase):
 class GitServerSideBand64kTestCase(GitServerTestCase):
     """Tests for client/server compatibility with side-band-64k support."""
     """Tests for client/server compatibility with side-band-64k support."""
 
 

+ 6 - 0
dulwich/tests/compat/test_web.py

@@ -26,12 +26,14 @@ warning: these tests should be fairly stable, but when writing/debugging new
 
 
 import threading
 import threading
 from wsgiref import simple_server
 from wsgiref import simple_server
+import sys
 
 
 from dulwich.server import (
 from dulwich.server import (
     DictBackend,
     DictBackend,
     )
     )
 from dulwich.tests import (
 from dulwich.tests import (
     SkipTest,
     SkipTest,
+    skipIf,
     )
     )
 from dulwich.web import (
 from dulwich.web import (
     make_wsgi_chain,
     make_wsgi_chain,
@@ -49,6 +51,7 @@ from dulwich.tests.compat.utils import (
     )
     )
 
 
 
 
+@skipIf(sys.platform == 'win32', 'Broken on windows, with very long fail time.')
 class WebTests(ServerTests):
 class WebTests(ServerTests):
     """Base tests for web server tests.
     """Base tests for web server tests.
 
 
@@ -72,6 +75,7 @@ class WebTests(ServerTests):
         return port
         return port
 
 
 
 
+@skipIf(sys.platform == 'win32', 'Broken on windows, with very long fail time.')
 class SmartWebTestCase(WebTests, CompatTestCase):
 class SmartWebTestCase(WebTests, CompatTestCase):
     """Test cases for smart HTTP server.
     """Test cases for smart HTTP server.
 
 
@@ -98,6 +102,7 @@ class SmartWebTestCase(WebTests, CompatTestCase):
         return app
         return app
 
 
 
 
+@skipIf(sys.platform == 'win32', 'Broken on windows, with very long fail time.')
 class SmartWebSideBand64kTestCase(SmartWebTestCase):
 class SmartWebSideBand64kTestCase(SmartWebTestCase):
     """Test cases for smart HTTP server with side-band-64k support."""
     """Test cases for smart HTTP server with side-band-64k support."""
 
 
@@ -113,6 +118,7 @@ class SmartWebSideBand64kTestCase(SmartWebTestCase):
         self.assertTrue('side-band-64k' in caps)
         self.assertTrue('side-band-64k' in caps)
 
 
 
 
+@skipIf(sys.platform == 'win32', 'Broken on windows, with very long fail time.')
 class DumbWebTestCase(WebTests, CompatTestCase):
 class DumbWebTestCase(WebTests, CompatTestCase):
     """Test cases for dumb HTTP server."""
     """Test cases for dumb HTTP server."""
 
 

+ 2 - 2
dulwich/tests/compat/utils.py

@@ -139,8 +139,8 @@ def run_git_or_fail(args, git_path=_DEFAULT_GIT, input=None, **popen_kwargs):
     returncode, stdout = run_git(args, git_path=git_path, input=input,
     returncode, stdout = run_git(args, git_path=git_path, input=input,
                                  capture_stdout=True, **popen_kwargs)
                                  capture_stdout=True, **popen_kwargs)
     if returncode != 0:
     if returncode != 0:
-        raise AssertionError("git with args %r failed with %d" % (
-            args, returncode))
+        raise AssertionError("git with args %r failed with %d: %r" % (
+            args, returncode, stdout))
     return stdout
     return stdout
 
 
 
 

+ 7 - 12
dulwich/tests/test_blackbox.py

@@ -26,12 +26,8 @@ from dulwich.repo import (
 from dulwich.tests import (
 from dulwich.tests import (
     BlackboxTestCase,
     BlackboxTestCase,
     )
     )
-from dulwich.tests.utils import (
-    skipIfPY3,
-    )
 
 
 
 
-@skipIfPY3
 class GitReceivePackTests(BlackboxTestCase):
 class GitReceivePackTests(BlackboxTestCase):
     """Blackbox tests for dul-receive-pack."""
     """Blackbox tests for dul-receive-pack."""
 
 
@@ -42,20 +38,19 @@ class GitReceivePackTests(BlackboxTestCase):
 
 
     def test_basic(self):
     def test_basic(self):
         process = self.run_command("dul-receive-pack", [self.path])
         process = self.run_command("dul-receive-pack", [self.path])
-        (stdout, stderr) = process.communicate("0000")
-        self.assertEqual('', stderr)
-        self.assertEqual('0000', stdout[-4:])
+        (stdout, stderr) = process.communicate(b"0000")
+        self.assertEqual(b'', stderr, stderr)
+        self.assertEqual(b'0000', stdout[-4:])
         self.assertEqual(0, process.returncode)
         self.assertEqual(0, process.returncode)
 
 
     def test_missing_arg(self):
     def test_missing_arg(self):
         process = self.run_command("dul-receive-pack", [])
         process = self.run_command("dul-receive-pack", [])
         (stdout, stderr) = process.communicate()
         (stdout, stderr) = process.communicate()
-        self.assertEqual(['usage: dul-receive-pack <git-dir>'], stderr.splitlines())
-        self.assertEqual('', stdout)
+        self.assertEqual([b'usage: dul-receive-pack <git-dir>'], stderr.splitlines())
+        self.assertEqual(b'', stdout)
         self.assertEqual(1, process.returncode)
         self.assertEqual(1, process.returncode)
 
 
 
 
-@skipIfPY3
 class GitUploadPackTests(BlackboxTestCase):
 class GitUploadPackTests(BlackboxTestCase):
     """Blackbox tests for dul-upload-pack."""
     """Blackbox tests for dul-upload-pack."""
 
 
@@ -67,6 +62,6 @@ class GitUploadPackTests(BlackboxTestCase):
     def test_missing_arg(self):
     def test_missing_arg(self):
         process = self.run_command("dul-upload-pack", [])
         process = self.run_command("dul-upload-pack", [])
         (stdout, stderr) = process.communicate()
         (stdout, stderr) = process.communicate()
-        self.assertEqual(['usage: dul-upload-pack <git-dir>'], stderr.splitlines())
-        self.assertEqual('', stdout)
+        self.assertEqual([b'usage: dul-upload-pack <git-dir>'], stderr.splitlines())
+        self.assertEqual(b'', stdout)
         self.assertEqual(1, process.returncode)
         self.assertEqual(1, process.returncode)

+ 117 - 130
dulwich/tests/test_client.py

@@ -21,10 +21,6 @@ import sys
 import shutil
 import shutil
 import tempfile
 import tempfile
 
 
-try:
-    from unittest import skipIf
-except ImportError:
-    from unittest2 import skipIf
 
 
 from dulwich import (
 from dulwich import (
     client,
     client,
@@ -60,9 +56,9 @@ from dulwich.repo import (
     MemoryRepo,
     MemoryRepo,
     Repo,
     Repo,
     )
     )
+from dulwich.tests import skipIf
 from dulwich.tests.utils import (
 from dulwich.tests.utils import (
     open_repo,
     open_repo,
-    skipIfPY3,
     )
     )
 
 
 
 
@@ -79,7 +75,6 @@ class DummyClient(TraditionalGitClient):
 
 
 
 
 # TODO(durin42): add unit-level tests of GitClient
 # TODO(durin42): add unit-level tests of GitClient
-@skipIfPY3
 class GitClientTests(TestCase):
 class GitClientTests(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -90,63 +85,63 @@ class GitClientTests(TestCase):
                                   self.rout.write)
                                   self.rout.write)
 
 
     def test_caps(self):
     def test_caps(self):
-        self.assertEqual(set(['multi_ack', 'side-band-64k', 'ofs-delta',
-                               'thin-pack', 'multi_ack_detailed']),
+        self.assertEqual(set([b'multi_ack', b'side-band-64k', b'ofs-delta',
+                               b'thin-pack', b'multi_ack_detailed']),
                           set(self.client._fetch_capabilities))
                           set(self.client._fetch_capabilities))
-        self.assertEqual(set(['ofs-delta', 'report-status', 'side-band-64k']),
+        self.assertEqual(set([b'ofs-delta', b'report-status', b'side-band-64k']),
                           set(self.client._send_capabilities))
                           set(self.client._send_capabilities))
 
 
     def test_archive_ack(self):
     def test_archive_ack(self):
         self.rin.write(
         self.rin.write(
-            '0009NACK\n'
-            '0000')
+            b'0009NACK\n'
+            b'0000')
         self.rin.seek(0)
         self.rin.seek(0)
-        self.client.archive('bla', 'HEAD', None, None)
-        self.assertEqual(self.rout.getvalue(), '0011argument HEAD0000')
+        self.client.archive(b'bla', b'HEAD', None, None)
+        self.assertEqual(self.rout.getvalue(), b'0011argument HEAD0000')
 
 
     def test_fetch_empty(self):
     def test_fetch_empty(self):
-        self.rin.write('0000')
+        self.rin.write(b'0000')
         self.rin.seek(0)
         self.rin.seek(0)
-        self.client.fetch_pack('/', lambda heads: [], None, None)
+        self.client.fetch_pack(b'/', lambda heads: [], None, None)
 
 
     def test_fetch_pack_none(self):
     def test_fetch_pack_none(self):
         self.rin.write(
         self.rin.write(
-            '008855dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 HEAD.multi_ack '
-            'thin-pack side-band side-band-64k ofs-delta shallow no-progress '
-            'include-tag\n'
-            '0000')
+            b'008855dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 HEAD.multi_ack '
+            b'thin-pack side-band side-band-64k ofs-delta shallow no-progress '
+            b'include-tag\n'
+            b'0000')
         self.rin.seek(0)
         self.rin.seek(0)
-        self.client.fetch_pack('bla', lambda heads: [], None, None, None)
-        self.assertEqual(self.rout.getvalue(), '0000')
+        self.client.fetch_pack(b'bla', lambda heads: [], None, None, None)
+        self.assertEqual(self.rout.getvalue(), b'0000')
 
 
     def test_send_pack_no_sideband64k_with_update_ref_error(self):
     def test_send_pack_no_sideband64k_with_update_ref_error(self):
         # No side-bank-64k reported by server shouldn't try to parse
         # No side-bank-64k reported by server shouldn't try to parse
         # side band data
         # side band data
-        pkts = ['55dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 capabilities^{}'
-                '\x00 report-status delete-refs ofs-delta\n',
-                '',
-                "unpack ok",
-                "ng refs/foo/bar pre-receive hook declined",
-                '']
+        pkts = [b'55dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 capabilities^{}'
+                b'\x00 report-status delete-refs ofs-delta\n',
+                b'',
+                b"unpack ok",
+                b"ng refs/foo/bar pre-receive hook declined",
+                b'']
         for pkt in pkts:
         for pkt in pkts:
-            if pkt == '':
-                self.rin.write("0000")
+            if pkt ==  b'':
+                self.rin.write(b"0000")
             else:
             else:
-                self.rin.write("%04x%s" % (len(pkt)+4, pkt))
+                self.rin.write(("%04x" % (len(pkt)+4)).encode('ascii') + pkt)
         self.rin.seek(0)
         self.rin.seek(0)
 
 
         tree = Tree()
         tree = Tree()
         commit = Commit()
         commit = Commit()
         commit.tree = tree
         commit.tree = tree
         commit.parents = []
         commit.parents = []
-        commit.author = commit.committer = 'test user'
+        commit.author = commit.committer = b'test user'
         commit.commit_time = commit.author_time = 1174773719
         commit.commit_time = commit.author_time = 1174773719
         commit.commit_timezone = commit.author_timezone = 0
         commit.commit_timezone = commit.author_timezone = 0
-        commit.encoding = 'UTF-8'
-        commit.message = 'test message'
+        commit.encoding = b'UTF-8'
+        commit.message = b'test message'
 
 
         def determine_wants(refs):
         def determine_wants(refs):
-            return {'refs/foo/bar': commit.id, }
+            return {b'refs/foo/bar': commit.id, }
 
 
         def generate_pack_contents(have, want):
         def generate_pack_contents(have, want):
             return [(commit, None), (tree, ''), ]
             return [(commit, None), (tree, ''), ]
@@ -157,62 +152,62 @@ class GitClientTests(TestCase):
 
 
     def test_send_pack_none(self):
     def test_send_pack_none(self):
         self.rin.write(
         self.rin.write(
-            '0078310ca9477129b8586fa2afc779c1f57cf64bba6c '
-            'refs/heads/master\x00 report-status delete-refs '
-            'side-band-64k quiet ofs-delta\n'
-            '0000')
+            b'0078310ca9477129b8586fa2afc779c1f57cf64bba6c '
+            b'refs/heads/master\x00 report-status delete-refs '
+            b'side-band-64k quiet ofs-delta\n'
+            b'0000')
         self.rin.seek(0)
         self.rin.seek(0)
 
 
         def determine_wants(refs):
         def determine_wants(refs):
             return {
             return {
-                'refs/heads/master': '310ca9477129b8586fa2afc779c1f57cf64bba6c'
+                b'refs/heads/master': b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
             }
             }
 
 
         def generate_pack_contents(have, want):
         def generate_pack_contents(have, want):
             return {}
             return {}
 
 
-        self.client.send_pack('/', determine_wants, generate_pack_contents)
-        self.assertEqual(self.rout.getvalue(), '0000')
+        self.client.send_pack(b'/', determine_wants, generate_pack_contents)
+        self.assertEqual(self.rout.getvalue(), b'0000')
 
 
     def test_send_pack_delete_only(self):
     def test_send_pack_delete_only(self):
         self.rin.write(
         self.rin.write(
-            '0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
-            'refs/heads/master\x00report-status delete-refs ofs-delta\n'
-            '0000000eunpack ok\n'
-            '0019ok refs/heads/master\n'
-            '0000')
+            b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
+            b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
+            b'0000000eunpack ok\n'
+            b'0019ok refs/heads/master\n'
+            b'0000')
         self.rin.seek(0)
         self.rin.seek(0)
 
 
         def determine_wants(refs):
         def determine_wants(refs):
-            return {'refs/heads/master': '0' * 40}
+            return {b'refs/heads/master': b'0' * 40}
 
 
         def generate_pack_contents(have, want):
         def generate_pack_contents(have, want):
             return {}
             return {}
 
 
-        self.client.send_pack('/', determine_wants, generate_pack_contents)
+        self.client.send_pack(b'/', determine_wants, generate_pack_contents)
         self.assertIn(
         self.assertIn(
             self.rout.getvalue(),
             self.rout.getvalue(),
-            ['007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
-             '0000000000000000000000000000000000000000 '
-             'refs/heads/master\x00report-status ofs-delta0000',
-             '007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
-             '0000000000000000000000000000000000000000 '
-             'refs/heads/master\x00ofs-delta report-status0000'])
+            [b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
+             b'0000000000000000000000000000000000000000 '
+             b'refs/heads/master\x00report-status ofs-delta0000',
+             b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
+             b'0000000000000000000000000000000000000000 '
+             b'refs/heads/master\x00ofs-delta report-status0000'])
 
 
     def test_send_pack_new_ref_only(self):
     def test_send_pack_new_ref_only(self):
         self.rin.write(
         self.rin.write(
-            '0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
-            'refs/heads/master\x00report-status delete-refs ofs-delta\n'
-            '0000000eunpack ok\n'
-            '0019ok refs/heads/blah12\n'
-            '0000')
+            b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
+            b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
+            b'0000000eunpack ok\n'
+            b'0019ok refs/heads/blah12\n'
+            b'0000')
         self.rin.seek(0)
         self.rin.seek(0)
 
 
         def determine_wants(refs):
         def determine_wants(refs):
             return {
             return {
-                'refs/heads/blah12':
-                '310ca9477129b8586fa2afc779c1f57cf64bba6c',
-                'refs/heads/master': '310ca9477129b8586fa2afc779c1f57cf64bba6c'
+                b'refs/heads/blah12':
+                b'310ca9477129b8586fa2afc779c1f57cf64bba6c',
+                b'refs/heads/master': b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
             }
             }
 
 
         def generate_pack_contents(have, want):
         def generate_pack_contents(have, want):
@@ -223,80 +218,77 @@ class GitClientTests(TestCase):
         self.client.send_pack('/', determine_wants, generate_pack_contents)
         self.client.send_pack('/', determine_wants, generate_pack_contents)
         self.assertIn(
         self.assertIn(
             self.rout.getvalue(),
             self.rout.getvalue(),
-            ['007f0000000000000000000000000000000000000000 '
-             '310ca9477129b8586fa2afc779c1f57cf64bba6c '
-             'refs/heads/blah12\x00report-status ofs-delta0000%s'
-             % f.getvalue(),
-             '007f0000000000000000000000000000000000000000 '
-             '310ca9477129b8586fa2afc779c1f57cf64bba6c '
-             'refs/heads/blah12\x00ofs-delta report-status0000%s'
-             % f.getvalue()])
+            [b'007f0000000000000000000000000000000000000000 '
+             b'310ca9477129b8586fa2afc779c1f57cf64bba6c '
+             b'refs/heads/blah12\x00report-status ofs-delta0000' +
+             f.getvalue(),
+             b'007f0000000000000000000000000000000000000000 '
+             b'310ca9477129b8586fa2afc779c1f57cf64bba6c '
+             b'refs/heads/blah12\x00ofs-delta report-status0000' +
+             f.getvalue()])
 
 
     def test_send_pack_new_ref(self):
     def test_send_pack_new_ref(self):
         self.rin.write(
         self.rin.write(
-            '0064310ca9477129b8586fa2afc779c1f57cf64bba6c '
-            'refs/heads/master\x00 report-status delete-refs ofs-delta\n'
-            '0000000eunpack ok\n'
-            '0019ok refs/heads/blah12\n'
-            '0000')
+            b'0064310ca9477129b8586fa2afc779c1f57cf64bba6c '
+            b'refs/heads/master\x00 report-status delete-refs ofs-delta\n'
+            b'0000000eunpack ok\n'
+            b'0019ok refs/heads/blah12\n'
+            b'0000')
         self.rin.seek(0)
         self.rin.seek(0)
 
 
         tree = Tree()
         tree = Tree()
         commit = Commit()
         commit = Commit()
         commit.tree = tree
         commit.tree = tree
         commit.parents = []
         commit.parents = []
-        commit.author = commit.committer = 'test user'
+        commit.author = commit.committer = b'test user'
         commit.commit_time = commit.author_time = 1174773719
         commit.commit_time = commit.author_time = 1174773719
         commit.commit_timezone = commit.author_timezone = 0
         commit.commit_timezone = commit.author_timezone = 0
-        commit.encoding = 'UTF-8'
-        commit.message = 'test message'
+        commit.encoding = b'UTF-8'
+        commit.message = b'test message'
 
 
         def determine_wants(refs):
         def determine_wants(refs):
             return {
             return {
-                'refs/heads/blah12': commit.id,
-                'refs/heads/master': '310ca9477129b8586fa2afc779c1f57cf64bba6c'
+                b'refs/heads/blah12': commit.id,
+                b'refs/heads/master': b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
             }
             }
 
 
         def generate_pack_contents(have, want):
         def generate_pack_contents(have, want):
-            return [(commit, None), (tree, ''), ]
+            return [(commit, None), (tree, b''), ]
 
 
         f = BytesIO()
         f = BytesIO()
         write_pack_objects(f, generate_pack_contents(None, None))
         write_pack_objects(f, generate_pack_contents(None, None))
-        self.client.send_pack('/', determine_wants, generate_pack_contents)
+        self.client.send_pack(b'/', determine_wants, generate_pack_contents)
         self.assertIn(
         self.assertIn(
             self.rout.getvalue(),
             self.rout.getvalue(),
-            ['007f0000000000000000000000000000000000000000 %s '
-             'refs/heads/blah12\x00report-status ofs-delta0000%s'
-             % (commit.id, f.getvalue()),
-             '007f0000000000000000000000000000000000000000 %s '
-             'refs/heads/blah12\x00ofs-delta report-status0000%s'
-             % (commit.id, f.getvalue())])
+            [b'007f0000000000000000000000000000000000000000 ' + commit.id +
+             b' refs/heads/blah12\x00report-status ofs-delta0000' + f.getvalue(),
+             b'007f0000000000000000000000000000000000000000 ' + commit.id +
+             b' refs/heads/blah12\x00ofs-delta report-status0000' + f.getvalue()])
 
 
     def test_send_pack_no_deleteref_delete_only(self):
     def test_send_pack_no_deleteref_delete_only(self):
-        pkts = ['310ca9477129b8586fa2afc779c1f57cf64bba6c refs/heads/master'
-                '\x00 report-status ofs-delta\n',
-                '',
-                '']
+        pkts = [b'310ca9477129b8586fa2afc779c1f57cf64bba6c refs/heads/master'
+                b'\x00 report-status ofs-delta\n',
+                b'',
+                b'']
         for pkt in pkts:
         for pkt in pkts:
-            if pkt == '':
-                self.rin.write("0000")
+            if pkt == b'':
+                self.rin.write(b"0000")
             else:
             else:
-                self.rin.write("%04x%s" % (len(pkt)+4, pkt))
+                self.rin.write(("%04x" % (len(pkt)+4)).encode('ascii') + pkt)
         self.rin.seek(0)
         self.rin.seek(0)
 
 
         def determine_wants(refs):
         def determine_wants(refs):
-            return {'refs/heads/master': '0' * 40}
+            return {b'refs/heads/master': b'0' * 40}
 
 
         def generate_pack_contents(have, want):
         def generate_pack_contents(have, want):
             return {}
             return {}
 
 
         self.assertRaises(UpdateRefsError,
         self.assertRaises(UpdateRefsError,
-                          self.client.send_pack, "/",
+                          self.client.send_pack, b"/",
                           determine_wants, generate_pack_contents)
                           determine_wants, generate_pack_contents)
-        self.assertEqual(self.rout.getvalue(), '0000')
+        self.assertEqual(self.rout.getvalue(), b'0000')
 
 
 
 
-@skipIfPY3
 class TestGetTransportAndPath(TestCase):
 class TestGetTransportAndPath(TestCase):
 
 
     def test_tcp(self):
     def test_tcp(self):
@@ -417,7 +409,6 @@ class TestGetTransportAndPath(TestCase):
         self.assertEqual('/jelmer/dulwich', path)
         self.assertEqual('/jelmer/dulwich', path)
 
 
 
 
-@skipIfPY3
 class TestGetTransportAndPathFromUrl(TestCase):
 class TestGetTransportAndPathFromUrl(TestCase):
 
 
     def test_tcp(self):
     def test_tcp(self):
@@ -496,7 +487,6 @@ class TestGetTransportAndPathFromUrl(TestCase):
         self.assertEqual('/home/jelmer/foo', path)
         self.assertEqual('/home/jelmer/foo', path)
 
 
 
 
-@skipIfPY3
 class TestSSHVendor(object):
 class TestSSHVendor(object):
 
 
     def __init__(self):
     def __init__(self):
@@ -519,7 +509,6 @@ class TestSSHVendor(object):
         return Subprocess()
         return Subprocess()
 
 
 
 
-@skipIfPY3
 class SSHGitClientTests(TestCase):
 class SSHGitClientTests(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -536,58 +525,56 @@ class SSHGitClientTests(TestCase):
         client.get_ssh_vendor = self.real_vendor
         client.get_ssh_vendor = self.real_vendor
 
 
     def test_default_command(self):
     def test_default_command(self):
-        self.assertEqual('git-upload-pack',
-                self.client._get_cmd_path('upload-pack'))
+        self.assertEqual(b'git-upload-pack',
+                self.client._get_cmd_path(b'upload-pack'))
 
 
     def test_alternative_command_path(self):
     def test_alternative_command_path(self):
-        self.client.alternative_paths['upload-pack'] = (
-            '/usr/lib/git/git-upload-pack')
-        self.assertEqual('/usr/lib/git/git-upload-pack',
-            self.client._get_cmd_path('upload-pack'))
+        self.client.alternative_paths[b'upload-pack'] = (
+            b'/usr/lib/git/git-upload-pack')
+        self.assertEqual(b'/usr/lib/git/git-upload-pack',
+            self.client._get_cmd_path(b'upload-pack'))
 
 
     def test_connect(self):
     def test_connect(self):
         server = self.server
         server = self.server
         client = self.client
         client = self.client
 
 
-        client.username = "username"
+        client.username = b"username"
         client.port = 1337
         client.port = 1337
 
 
-        client._connect("command", "/path/to/repo")
-        self.assertEqual("username", server.username)
+        client._connect(b"command", b"/path/to/repo")
+        self.assertEqual(b"username", server.username)
         self.assertEqual(1337, server.port)
         self.assertEqual(1337, server.port)
-        self.assertEqual(["git-command '/path/to/repo'"], server.command)
+        self.assertEqual([b"git-command '/path/to/repo'"], server.command)
 
 
-        client._connect("relative-command", "/~/path/to/repo")
-        self.assertEqual(["git-relative-command '~/path/to/repo'"],
+        client._connect(b"relative-command", b"/~/path/to/repo")
+        self.assertEqual([b"git-relative-command '~/path/to/repo'"],
                           server.command)
                           server.command)
 
 
 
 
-@skipIfPY3
 class ReportStatusParserTests(TestCase):
 class ReportStatusParserTests(TestCase):
 
 
     def test_invalid_pack(self):
     def test_invalid_pack(self):
         parser = ReportStatusParser()
         parser = ReportStatusParser()
-        parser.handle_packet("unpack error - foo bar")
-        parser.handle_packet("ok refs/foo/bar")
+        parser.handle_packet(b"unpack error - foo bar")
+        parser.handle_packet(b"ok refs/foo/bar")
         parser.handle_packet(None)
         parser.handle_packet(None)
         self.assertRaises(SendPackError, parser.check)
         self.assertRaises(SendPackError, parser.check)
 
 
     def test_update_refs_error(self):
     def test_update_refs_error(self):
         parser = ReportStatusParser()
         parser = ReportStatusParser()
-        parser.handle_packet("unpack ok")
-        parser.handle_packet("ng refs/foo/bar need to pull")
+        parser.handle_packet(b"unpack ok")
+        parser.handle_packet(b"ng refs/foo/bar need to pull")
         parser.handle_packet(None)
         parser.handle_packet(None)
         self.assertRaises(UpdateRefsError, parser.check)
         self.assertRaises(UpdateRefsError, parser.check)
 
 
     def test_ok(self):
     def test_ok(self):
         parser = ReportStatusParser()
         parser = ReportStatusParser()
-        parser.handle_packet("unpack ok")
-        parser.handle_packet("ok refs/foo/bar")
+        parser.handle_packet(b"unpack ok")
+        parser.handle_packet(b"ok refs/foo/bar")
         parser.handle_packet(None)
         parser.handle_packet(None)
         parser.check()
         parser.check()
 
 
 
 
-@skipIfPY3
 class LocalGitClientTests(TestCase):
 class LocalGitClientTests(TestCase):
 
 
     def test_fetch_into_empty(self):
     def test_fetch_into_empty(self):
@@ -603,8 +590,8 @@ class LocalGitClientTests(TestCase):
         walker = {}
         walker = {}
         c.fetch_pack(s.path, lambda heads: [], graph_walker=walker,
         c.fetch_pack(s.path, lambda heads: [], graph_walker=walker,
             pack_data=out.write)
             pack_data=out.write)
-        self.assertEqual("PACK\x00\x00\x00\x02\x00\x00\x00\x00\x02\x9d\x08"
-            "\x82;\xd8\xa8\xea\xb5\x10\xadj\xc7\\\x82<\xfd>\xd3\x1e", out.getvalue())
+        self.assertEqual(b"PACK\x00\x00\x00\x02\x00\x00\x00\x00\x02\x9d\x08"
+            b"\x82;\xd8\xa8\xea\xb5\x10\xadj\xc7\\\x82<\xfd>\xd3\x1e", out.getvalue())
 
 
     def test_fetch_pack_none(self):
     def test_fetch_pack_none(self):
         c = LocalGitClient()
         c = LocalGitClient()
@@ -612,33 +599,33 @@ class LocalGitClientTests(TestCase):
         out = BytesIO()
         out = BytesIO()
         walker = MemoryRepo().get_graph_walker()
         walker = MemoryRepo().get_graph_walker()
         c.fetch_pack(s.path,
         c.fetch_pack(s.path,
-            lambda heads: ["a90fa2d900a17e99b433217e988c4eb4a2e9a097"],
+            lambda heads: [b"a90fa2d900a17e99b433217e988c4eb4a2e9a097"],
             graph_walker=walker, pack_data=out.write)
             graph_walker=walker, pack_data=out.write)
         # Hardcoding is not ideal, but we'll fix that some other day..
         # Hardcoding is not ideal, but we'll fix that some other day..
-        self.assertTrue(out.getvalue().startswith('PACK\x00\x00\x00\x02\x00\x00\x00\x07'))
+        self.assertTrue(out.getvalue().startswith(b'PACK\x00\x00\x00\x02\x00\x00\x00\x07'))
 
 
     def test_send_pack_without_changes(self):
     def test_send_pack_without_changes(self):
         local = open_repo('a.git')
         local = open_repo('a.git')
         target = open_repo('a.git')
         target = open_repo('a.git')
-        self.send_and_verify("master", local, target)
+        self.send_and_verify(b"master", local, target)
 
 
     def test_send_pack_with_changes(self):
     def test_send_pack_with_changes(self):
         local = open_repo('a.git')
         local = open_repo('a.git')
         target_path = tempfile.mkdtemp()
         target_path = tempfile.mkdtemp()
         self.addCleanup(shutil.rmtree, target_path)
         self.addCleanup(shutil.rmtree, target_path)
         target = Repo.init_bare(target_path)
         target = Repo.init_bare(target_path)
-        self.send_and_verify("master", local, target)
+        self.send_and_verify(b"master", local, target)
 
 
     def send_and_verify(self, branch, local, target):
     def send_and_verify(self, branch, local, target):
         client = LocalGitClient()
         client = LocalGitClient()
-        ref_name = "refs/heads/" + branch
+        ref_name = b"refs/heads/" + branch
         new_refs = client.send_pack(target.path,
         new_refs = client.send_pack(target.path,
                                     lambda _: { ref_name: local.refs[ref_name] },
                                     lambda _: { ref_name: local.refs[ref_name] },
                                     local.object_store.generate_pack_contents)
                                     local.object_store.generate_pack_contents)
 
 
         self.assertEqual(local.refs[ref_name], new_refs[ref_name])
         self.assertEqual(local.refs[ref_name], new_refs[ref_name])
 
 
-        for name, sha in new_refs.iteritems():
+        for name, sha in new_refs.items():
             self.assertEqual(new_refs[name], target.refs[name])
             self.assertEqual(new_refs[name], target.refs[name])
 
 
         obj_local = local.get_object(new_refs[ref_name])
         obj_local = local.get_object(new_refs[ref_name])

+ 98 - 108
dulwich/tests/test_config.py

@@ -31,10 +31,8 @@ from dulwich.config import (
     _unescape_value,
     _unescape_value,
     )
     )
 from dulwich.tests import TestCase
 from dulwich.tests import TestCase
-from dulwich.tests.utils import skipIfPY3
 
 
 
 
-@skipIfPY3
 class ConfigFileTests(TestCase):
 class ConfigFileTests(TestCase):
 
 
     def from_file(self, text):
     def from_file(self, text):
@@ -47,251 +45,243 @@ class ConfigFileTests(TestCase):
         self.assertEqual(ConfigFile(), ConfigFile())
         self.assertEqual(ConfigFile(), ConfigFile())
 
 
     def test_default_config(self):
     def test_default_config(self):
-        cf = self.from_file("""[core]
+        cf = self.from_file(b"""[core]
 	repositoryformatversion = 0
 	repositoryformatversion = 0
 	filemode = true
 	filemode = true
 	bare = false
 	bare = false
 	logallrefupdates = true
 	logallrefupdates = true
 """)
 """)
-        self.assertEqual(ConfigFile({("core", ): {
-            "repositoryformatversion": "0",
-            "filemode": "true",
-            "bare": "false",
-            "logallrefupdates": "true"}}), cf)
+        self.assertEqual(ConfigFile({(b"core", ): {
+            b"repositoryformatversion": b"0",
+            b"filemode": b"true",
+            b"bare": b"false",
+            b"logallrefupdates": b"true"}}), cf)
 
 
     def test_from_file_empty(self):
     def test_from_file_empty(self):
-        cf = self.from_file("")
+        cf = self.from_file(b"")
         self.assertEqual(ConfigFile(), cf)
         self.assertEqual(ConfigFile(), cf)
 
 
     def test_empty_line_before_section(self):
     def test_empty_line_before_section(self):
-        cf = self.from_file("\n[section]\n")
-        self.assertEqual(ConfigFile({("section", ): {}}), cf)
+        cf = self.from_file(b"\n[section]\n")
+        self.assertEqual(ConfigFile({(b"section", ): {}}), cf)
 
 
     def test_comment_before_section(self):
     def test_comment_before_section(self):
-        cf = self.from_file("# foo\n[section]\n")
-        self.assertEqual(ConfigFile({("section", ): {}}), cf)
+        cf = self.from_file(b"# foo\n[section]\n")
+        self.assertEqual(ConfigFile({(b"section", ): {}}), cf)
 
 
     def test_comment_after_section(self):
     def test_comment_after_section(self):
-        cf = self.from_file("[section] # foo\n")
-        self.assertEqual(ConfigFile({("section", ): {}}), cf)
+        cf = self.from_file(b"[section] # foo\n")
+        self.assertEqual(ConfigFile({(b"section", ): {}}), cf)
 
 
     def test_comment_after_variable(self):
     def test_comment_after_variable(self):
-        cf = self.from_file("[section]\nbar= foo # a comment\n")
-        self.assertEqual(ConfigFile({("section", ): {"bar": "foo"}}), cf)
+        cf = self.from_file(b"[section]\nbar= foo # a comment\n")
+        self.assertEqual(ConfigFile({(b"section", ): {b"bar": b"foo"}}), cf)
 
 
     def test_from_file_section(self):
     def test_from_file_section(self):
-        cf = self.from_file("[core]\nfoo = bar\n")
-        self.assertEqual("bar", cf.get(("core", ), "foo"))
-        self.assertEqual("bar", cf.get(("core", "foo"), "foo"))
+        cf = self.from_file(b"[core]\nfoo = bar\n")
+        self.assertEqual(b"bar", cf.get((b"core", ), b"foo"))
+        self.assertEqual(b"bar", cf.get((b"core", b"foo"), b"foo"))
 
 
     def test_from_file_section_case_insensitive(self):
     def test_from_file_section_case_insensitive(self):
-        cf = self.from_file("[cOre]\nfOo = bar\n")
-        self.assertEqual("bar", cf.get(("core", ), "foo"))
-        self.assertEqual("bar", cf.get(("core", "foo"), "foo"))
+        cf = self.from_file(b"[cOre]\nfOo = bar\n")
+        self.assertEqual(b"bar", cf.get((b"core", ), b"foo"))
+        self.assertEqual(b"bar", cf.get((b"core", b"foo"), b"foo"))
 
 
     def test_from_file_with_mixed_quoted(self):
     def test_from_file_with_mixed_quoted(self):
-        cf = self.from_file("[core]\nfoo = \"bar\"la\n")
-        self.assertEqual("barla", cf.get(("core", ), "foo"))
+        cf = self.from_file(b"[core]\nfoo = \"bar\"la\n")
+        self.assertEqual(b"barla", cf.get((b"core", ), b"foo"))
 
 
     def test_from_file_with_open_quoted(self):
     def test_from_file_with_open_quoted(self):
         self.assertRaises(ValueError,
         self.assertRaises(ValueError,
-            self.from_file, "[core]\nfoo = \"bar\n")
+            self.from_file, b"[core]\nfoo = \"bar\n")
 
 
     def test_from_file_with_quotes(self):
     def test_from_file_with_quotes(self):
         cf = self.from_file(
         cf = self.from_file(
-            "[core]\n"
-            'foo = " bar"\n')
-        self.assertEqual(" bar", cf.get(("core", ), "foo"))
+            b"[core]\n"
+            b'foo = " bar"\n')
+        self.assertEqual(b" bar", cf.get((b"core", ), b"foo"))
 
 
     def test_from_file_with_interrupted_line(self):
     def test_from_file_with_interrupted_line(self):
         cf = self.from_file(
         cf = self.from_file(
-            "[core]\n"
-            'foo = bar\\\n'
-            ' la\n')
-        self.assertEqual("barla", cf.get(("core", ), "foo"))
+            b"[core]\n"
+            b'foo = bar\\\n'
+            b' la\n')
+        self.assertEqual(b"barla", cf.get((b"core", ), b"foo"))
 
 
     def test_from_file_with_boolean_setting(self):
     def test_from_file_with_boolean_setting(self):
         cf = self.from_file(
         cf = self.from_file(
-            "[core]\n"
-            'foo\n')
-        self.assertEqual("true", cf.get(("core", ), "foo"))
+            b"[core]\n"
+            b'foo\n')
+        self.assertEqual(b"true", cf.get((b"core", ), b"foo"))
 
 
     def test_from_file_subsection(self):
     def test_from_file_subsection(self):
-        cf = self.from_file("[branch \"foo\"]\nfoo = bar\n")
-        self.assertEqual("bar", cf.get(("branch", "foo"), "foo"))
+        cf = self.from_file(b"[branch \"foo\"]\nfoo = bar\n")
+        self.assertEqual(b"bar", cf.get((b"branch", b"foo"), b"foo"))
 
 
     def test_from_file_subsection_invalid(self):
     def test_from_file_subsection_invalid(self):
         self.assertRaises(ValueError,
         self.assertRaises(ValueError,
-            self.from_file, "[branch \"foo]\nfoo = bar\n")
+            self.from_file, b"[branch \"foo]\nfoo = bar\n")
 
 
     def test_from_file_subsection_not_quoted(self):
     def test_from_file_subsection_not_quoted(self):
-        cf = self.from_file("[branch.foo]\nfoo = bar\n")
-        self.assertEqual("bar", cf.get(("branch", "foo"), "foo"))
+        cf = self.from_file(b"[branch.foo]\nfoo = bar\n")
+        self.assertEqual(b"bar", cf.get((b"branch", b"foo"), b"foo"))
 
 
     def test_write_to_file_empty(self):
     def test_write_to_file_empty(self):
         c = ConfigFile()
         c = ConfigFile()
         f = BytesIO()
         f = BytesIO()
         c.write_to_file(f)
         c.write_to_file(f)
-        self.assertEqual("", f.getvalue())
+        self.assertEqual(b"", f.getvalue())
 
 
     def test_write_to_file_section(self):
     def test_write_to_file_section(self):
         c = ConfigFile()
         c = ConfigFile()
-        c.set(("core", ), "foo", "bar")
+        c.set((b"core", ), b"foo", b"bar")
         f = BytesIO()
         f = BytesIO()
         c.write_to_file(f)
         c.write_to_file(f)
-        self.assertEqual("[core]\n\tfoo = bar\n", f.getvalue())
+        self.assertEqual(b"[core]\n\tfoo = bar\n", f.getvalue())
 
 
     def test_write_to_file_subsection(self):
     def test_write_to_file_subsection(self):
         c = ConfigFile()
         c = ConfigFile()
-        c.set(("branch", "blie"), "foo", "bar")
+        c.set((b"branch", b"blie"), b"foo", b"bar")
         f = BytesIO()
         f = BytesIO()
         c.write_to_file(f)
         c.write_to_file(f)
-        self.assertEqual("[branch \"blie\"]\n\tfoo = bar\n", f.getvalue())
+        self.assertEqual(b"[branch \"blie\"]\n\tfoo = bar\n", f.getvalue())
 
 
     def test_same_line(self):
     def test_same_line(self):
-        cf = self.from_file("[branch.foo] foo = bar\n")
-        self.assertEqual("bar", cf.get(("branch", "foo"), "foo"))
+        cf = self.from_file(b"[branch.foo] foo = bar\n")
+        self.assertEqual(b"bar", cf.get((b"branch", b"foo"), b"foo"))
 
 
 
 
-@skipIfPY3
 class ConfigDictTests(TestCase):
 class ConfigDictTests(TestCase):
 
 
     def test_get_set(self):
     def test_get_set(self):
         cd = ConfigDict()
         cd = ConfigDict()
-        self.assertRaises(KeyError, cd.get, "foo", "core")
-        cd.set(("core", ), "foo", "bla")
-        self.assertEqual("bla", cd.get(("core", ), "foo"))
-        cd.set(("core", ), "foo", "bloe")
-        self.assertEqual("bloe", cd.get(("core", ), "foo"))
+        self.assertRaises(KeyError, cd.get, b"foo", b"core")
+        cd.set((b"core", ), b"foo", b"bla")
+        self.assertEqual(b"bla", cd.get((b"core", ), b"foo"))
+        cd.set((b"core", ), b"foo", b"bloe")
+        self.assertEqual(b"bloe", cd.get((b"core", ), b"foo"))
 
 
     def test_get_boolean(self):
     def test_get_boolean(self):
         cd = ConfigDict()
         cd = ConfigDict()
-        cd.set(("core", ), "foo", "true")
-        self.assertTrue(cd.get_boolean(("core", ), "foo"))
-        cd.set(("core", ), "foo", "false")
-        self.assertFalse(cd.get_boolean(("core", ), "foo"))
-        cd.set(("core", ), "foo", "invalid")
-        self.assertRaises(ValueError, cd.get_boolean, ("core", ), "foo")
+        cd.set((b"core", ), b"foo", b"true")
+        self.assertTrue(cd.get_boolean((b"core", ), b"foo"))
+        cd.set((b"core", ), b"foo", b"false")
+        self.assertFalse(cd.get_boolean((b"core", ), b"foo"))
+        cd.set((b"core", ), b"foo", b"invalid")
+        self.assertRaises(ValueError, cd.get_boolean, (b"core", ), b"foo")
 
 
     def test_dict(self):
     def test_dict(self):
         cd = ConfigDict()
         cd = ConfigDict()
-        cd.set(("core", ), "foo", "bla")
-        cd.set(("core2", ), "foo", "bloe")
+        cd.set((b"core", ), b"foo", b"bla")
+        cd.set((b"core2", ), b"foo", b"bloe")
 
 
-        self.assertEqual([("core", ), ("core2", )], cd.keys())
-        self.assertEqual(cd[("core", )], {'foo': 'bla'})
+        self.assertEqual([(b"core", ), (b"core2", )], list(cd.keys()))
+        self.assertEqual(cd[(b"core", )], {b'foo': b'bla'})
 
 
-        cd['a'] = 'b'
-        self.assertEqual(cd['a'], 'b')
+        cd[b'a'] = b'b'
+        self.assertEqual(cd[b'a'], b'b')
 
 
     def test_iteritems(self):
     def test_iteritems(self):
         cd = ConfigDict()
         cd = ConfigDict()
-        cd.set(("core", ), "foo", "bla")
-        cd.set(("core2", ), "foo", "bloe")
+        cd.set((b"core", ), b"foo", b"bla")
+        cd.set((b"core2", ), b"foo", b"bloe")
 
 
         self.assertEqual(
         self.assertEqual(
-            [('foo', 'bla')],
-            list(cd.iteritems(("core", ))))
+            [(b'foo', b'bla')],
+            list(cd.iteritems((b"core", ))))
 
 
     def test_iteritems_nonexistant(self):
     def test_iteritems_nonexistant(self):
         cd = ConfigDict()
         cd = ConfigDict()
-        cd.set(("core2", ), "foo", "bloe")
+        cd.set((b"core2", ), b"foo", b"bloe")
 
 
         self.assertEqual([],
         self.assertEqual([],
-            list(cd.iteritems(("core", ))))
+            list(cd.iteritems((b"core", ))))
 
 
     def test_itersections(self):
     def test_itersections(self):
         cd = ConfigDict()
         cd = ConfigDict()
-        cd.set(("core2", ), "foo", "bloe")
+        cd.set((b"core2", ), b"foo", b"bloe")
 
 
-        self.assertEqual([("core2", )],
+        self.assertEqual([(b"core2", )],
             list(cd.itersections()))
             list(cd.itersections()))
 
 
 
 
 
 
-@skipIfPY3
 class StackedConfigTests(TestCase):
 class StackedConfigTests(TestCase):
 
 
     def test_default_backends(self):
     def test_default_backends(self):
         StackedConfig.default_backends()
         StackedConfig.default_backends()
 
 
 
 
-@skipIfPY3
 class UnescapeTests(TestCase):
 class UnescapeTests(TestCase):
 
 
     def test_nothing(self):
     def test_nothing(self):
-        self.assertEqual("", _unescape_value(""))
+        self.assertEqual(b"", bytes(_unescape_value(bytearray())))
 
 
     def test_tab(self):
     def test_tab(self):
-        self.assertEqual("\tbar\t", _unescape_value("\\tbar\\t"))
+        self.assertEqual(b"\tbar\t", bytes(_unescape_value(bytearray(b"\\tbar\\t"))))
 
 
     def test_newline(self):
     def test_newline(self):
-        self.assertEqual("\nbar\t", _unescape_value("\\nbar\\t"))
+        self.assertEqual(b"\nbar\t", bytes(_unescape_value(bytearray(b"\\nbar\\t"))))
 
 
     def test_quote(self):
     def test_quote(self):
-        self.assertEqual("\"foo\"", _unescape_value("\\\"foo\\\""))
+        self.assertEqual(b"\"foo\"", bytes(_unescape_value(bytearray(b"\\\"foo\\\""))))
 
 
 
 
-@skipIfPY3
 class EscapeValueTests(TestCase):
 class EscapeValueTests(TestCase):
 
 
     def test_nothing(self):
     def test_nothing(self):
-        self.assertEqual("foo", _escape_value("foo"))
+        self.assertEqual(b"foo", _escape_value(b"foo"))
 
 
     def test_backslash(self):
     def test_backslash(self):
-        self.assertEqual("foo\\\\", _escape_value("foo\\"))
+        self.assertEqual(b"foo\\\\", _escape_value(b"foo\\"))
 
 
     def test_newline(self):
     def test_newline(self):
-        self.assertEqual("foo\\n", _escape_value("foo\n"))
+        self.assertEqual(b"foo\\n", _escape_value(b"foo\n"))
 
 
 
 
-@skipIfPY3
 class FormatStringTests(TestCase):
 class FormatStringTests(TestCase):
 
 
     def test_quoted(self):
     def test_quoted(self):
-        self.assertEqual('" foo"', _format_string(" foo"))
-        self.assertEqual('"\\tfoo"', _format_string("\tfoo"))
+        self.assertEqual(b'" foo"', _format_string(b" foo"))
+        self.assertEqual(b'"\\tfoo"', _format_string(b"\tfoo"))
 
 
     def test_not_quoted(self):
     def test_not_quoted(self):
-        self.assertEqual('foo', _format_string("foo"))
-        self.assertEqual('foo bar', _format_string("foo bar"))
+        self.assertEqual(b'foo', _format_string(b"foo"))
+        self.assertEqual(b'foo bar', _format_string(b"foo bar"))
 
 
 
 
-@skipIfPY3
 class ParseStringTests(TestCase):
 class ParseStringTests(TestCase):
 
 
     def test_quoted(self):
     def test_quoted(self):
-        self.assertEqual(' foo', _parse_string('" foo"'))
-        self.assertEqual('\tfoo', _parse_string('"\\tfoo"'))
+        self.assertEqual(b' foo', _parse_string(b'" foo"'))
+        self.assertEqual(b'\tfoo', _parse_string(b'"\\tfoo"'))
 
 
     def test_not_quoted(self):
     def test_not_quoted(self):
-        self.assertEqual('foo', _parse_string("foo"))
-        self.assertEqual('foo bar', _parse_string("foo bar"))
+        self.assertEqual(b'foo', _parse_string(b"foo"))
+        self.assertEqual(b'foo bar', _parse_string(b"foo bar"))
 
 
 
 
-@skipIfPY3
 class CheckVariableNameTests(TestCase):
 class CheckVariableNameTests(TestCase):
 
 
     def test_invalid(self):
     def test_invalid(self):
-        self.assertFalse(_check_variable_name("foo "))
-        self.assertFalse(_check_variable_name("bar,bar"))
-        self.assertFalse(_check_variable_name("bar.bar"))
+        self.assertFalse(_check_variable_name(b"foo "))
+        self.assertFalse(_check_variable_name(b"bar,bar"))
+        self.assertFalse(_check_variable_name(b"bar.bar"))
 
 
     def test_valid(self):
     def test_valid(self):
-        self.assertTrue(_check_variable_name("FOO"))
-        self.assertTrue(_check_variable_name("foo"))
-        self.assertTrue(_check_variable_name("foo-bar"))
+        self.assertTrue(_check_variable_name(b"FOO"))
+        self.assertTrue(_check_variable_name(b"foo"))
+        self.assertTrue(_check_variable_name(b"foo-bar"))
 
 
 
 
-@skipIfPY3
 class CheckSectionNameTests(TestCase):
 class CheckSectionNameTests(TestCase):
 
 
     def test_invalid(self):
     def test_invalid(self):
-        self.assertFalse(_check_section_name("foo "))
-        self.assertFalse(_check_section_name("bar,bar"))
+        self.assertFalse(_check_section_name(b"foo "))
+        self.assertFalse(_check_section_name(b"bar,bar"))
 
 
     def test_valid(self):
     def test_valid(self):
-        self.assertTrue(_check_section_name("FOO"))
-        self.assertTrue(_check_section_name("foo"))
-        self.assertTrue(_check_section_name("foo-bar"))
-        self.assertTrue(_check_section_name("bar.bar"))
+        self.assertTrue(_check_section_name(b"FOO"))
+        self.assertTrue(_check_section_name(b"foo"))
+        self.assertTrue(_check_section_name(b"foo-bar"))
+        self.assertTrue(_check_section_name(b"bar.bar"))

+ 8 - 11
dulwich/tests/test_diff_tree.py

@@ -57,11 +57,9 @@ from dulwich.tests.utils import (
     make_object,
     make_object,
     functest_builder,
     functest_builder,
     ext_functest_builder,
     ext_functest_builder,
-    skipIfPY3,
     )
     )
 
 
 
 
-@skipIfPY3
 class DiffTestCase(TestCase):
 class DiffTestCase(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -86,7 +84,6 @@ class DiffTestCase(TestCase):
         return self.store[commit_tree(self.store, commit_blobs)]
         return self.store[commit_tree(self.store, commit_blobs)]
 
 
 
 
-@skipIfPY3
 class TreeChangesTest(DiffTestCase):
 class TreeChangesTest(DiffTestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -470,7 +467,6 @@ class TreeChangesTest(DiffTestCase):
             [parent1, parent2], merge, rename_detector=self.detector)
             [parent1, parent2], merge, rename_detector=self.detector)
 
 
 
 
-@skipIfPY3
 class RenameDetectionTest(DiffTestCase):
 class RenameDetectionTest(DiffTestCase):
 
 
     def _do_test_count_blocks(self, count_blocks):
     def _do_test_count_blocks(self, count_blocks):
@@ -759,15 +755,16 @@ class RenameDetectionTest(DiffTestCase):
             self.detect_renames(tree1, tree2))
             self.detect_renames(tree1, tree2))
 
 
     def test_content_rename_with_more_deletions(self):
     def test_content_rename_with_more_deletions(self):
-        blob1 = make_object(Blob, data='')
-        tree1 = self.commit_tree([('a', blob1), ('b', blob1), ('c', blob1), ('d', blob1)])
-        tree2 = self.commit_tree([('e', blob1), ('f', blob1), ('g', blob1)])
+        blob1 = make_object(Blob, data=b'')
+        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob1), (b'c', blob1),
+                                  (b'd', blob1)])
+        tree2 = self.commit_tree([(b'e', blob1), (b'f', blob1), (b'g', blob1)])
         self.maxDiff = None
         self.maxDiff = None
         self.assertEqual(
         self.assertEqual(
-          [TreeChange(CHANGE_RENAME, ('a', F, blob1.id), ('e', F, blob1.id)),
-           TreeChange(CHANGE_RENAME, ('b', F, blob1.id), ('f', F, blob1.id)),
-           TreeChange(CHANGE_RENAME, ('c', F, blob1.id), ('g', F, blob1.id)),
-           TreeChange.delete(('d', F, blob1.id))],
+          [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'e', F, blob1.id)),
+           TreeChange(CHANGE_RENAME, (b'b', F, blob1.id), (b'f', F, blob1.id)),
+           TreeChange(CHANGE_RENAME, (b'c', F, blob1.id), (b'g', F, blob1.id)),
+           TreeChange.delete((b'd', F, blob1.id))],
           self.detect_renames(tree1, tree2))
           self.detect_renames(tree1, tree2))
 
 
     def test_content_rename_gitlink(self):
     def test_content_rename_gitlink(self):

+ 14 - 12
dulwich/tests/test_file.py

@@ -92,7 +92,9 @@ class GitFileTests(TestCase):
     def setUp(self):
     def setUp(self):
         super(GitFileTests, self).setUp()
         super(GitFileTests, self).setUp()
         self._tempdir = tempfile.mkdtemp()
         self._tempdir = tempfile.mkdtemp()
-        f = open(self.path('foo'), 'wb')
+        if not isinstance(self._tempdir, bytes):
+            self._tempdir = self._tempdir.encode(sys.getfilesystemencoding())
+        f = open(self.path(b'foo'), 'wb')
         f.write(b'foo contents')
         f.write(b'foo contents')
         f.close()
         f.close()
 
 
@@ -104,7 +106,7 @@ class GitFileTests(TestCase):
         return os.path.join(self._tempdir, filename)
         return os.path.join(self._tempdir, filename)
 
 
     def test_invalid(self):
     def test_invalid(self):
-        foo = self.path('foo')
+        foo = self.path(b'foo')
         self.assertRaises(IOError, GitFile, foo, mode='r')
         self.assertRaises(IOError, GitFile, foo, mode='r')
         self.assertRaises(IOError, GitFile, foo, mode='ab')
         self.assertRaises(IOError, GitFile, foo, mode='ab')
         self.assertRaises(IOError, GitFile, foo, mode='r+b')
         self.assertRaises(IOError, GitFile, foo, mode='r+b')
@@ -112,7 +114,7 @@ class GitFileTests(TestCase):
         self.assertRaises(IOError, GitFile, foo, mode='a+bU')
         self.assertRaises(IOError, GitFile, foo, mode='a+bU')
 
 
     def test_readonly(self):
     def test_readonly(self):
-        f = GitFile(self.path('foo'), 'rb')
+        f = GitFile(self.path(b'foo'), 'rb')
         self.assertTrue(isinstance(f, io.IOBase))
         self.assertTrue(isinstance(f, io.IOBase))
         self.assertEqual(b'foo contents', f.read())
         self.assertEqual(b'foo contents', f.read())
         self.assertEqual(b'', f.read())
         self.assertEqual(b'', f.read())
@@ -121,13 +123,13 @@ class GitFileTests(TestCase):
         f.close()
         f.close()
 
 
     def test_default_mode(self):
     def test_default_mode(self):
-        f = GitFile(self.path('foo'))
+        f = GitFile(self.path(b'foo'))
         self.assertEqual(b'foo contents', f.read())
         self.assertEqual(b'foo contents', f.read())
         f.close()
         f.close()
 
 
     def test_write(self):
     def test_write(self):
-        foo = self.path('foo')
-        foo_lock = '%s.lock' % foo
+        foo = self.path(b'foo')
+        foo_lock = foo + b'.lock'
 
 
         orig_f = open(foo, 'rb')
         orig_f = open(foo, 'rb')
         self.assertEqual(orig_f.read(), b'foo contents')
         self.assertEqual(orig_f.read(), b'foo contents')
@@ -150,7 +152,7 @@ class GitFileTests(TestCase):
         new_f.close()
         new_f.close()
 
 
     def test_open_twice(self):
     def test_open_twice(self):
-        foo = self.path('foo')
+        foo = self.path(b'foo')
         f1 = GitFile(foo, 'wb')
         f1 = GitFile(foo, 'wb')
         f1.write(b'new')
         f1.write(b'new')
         try:
         try:
@@ -169,8 +171,8 @@ class GitFileTests(TestCase):
         f.close()
         f.close()
 
 
     def test_abort(self):
     def test_abort(self):
-        foo = self.path('foo')
-        foo_lock = '%s.lock' % foo
+        foo = self.path(b'foo')
+        foo_lock = foo + b'.lock'
 
 
         orig_f = open(foo, 'rb')
         orig_f = open(foo, 'rb')
         self.assertEqual(orig_f.read(), b'foo contents')
         self.assertEqual(orig_f.read(), b'foo contents')
@@ -187,7 +189,7 @@ class GitFileTests(TestCase):
         new_orig_f.close()
         new_orig_f.close()
 
 
     def test_abort_close(self):
     def test_abort_close(self):
-        foo = self.path('foo')
+        foo = self.path(b'foo')
         f = GitFile(foo, 'wb')
         f = GitFile(foo, 'wb')
         f.abort()
         f.abort()
         try:
         try:
@@ -203,11 +205,11 @@ class GitFileTests(TestCase):
             self.fail()
             self.fail()
 
 
     def test_abort_close_removed(self):
     def test_abort_close_removed(self):
-        foo = self.path('foo')
+        foo = self.path(b'foo')
         f = GitFile(foo, 'wb')
         f = GitFile(foo, 'wb')
 
 
         f._file.close()
         f._file.close()
-        os.remove(foo+".lock")
+        os.remove(foo + b'.lock')
 
 
         f.abort()
         f.abort()
         self.assertTrue(f._closed)
         self.assertTrue(f._closed)

+ 22 - 28
dulwich/tests/test_grafts.py

@@ -23,7 +23,6 @@ import shutil
 
 
 from dulwich.errors import ObjectFormatException
 from dulwich.errors import ObjectFormatException
 from dulwich.tests import TestCase
 from dulwich.tests import TestCase
-from dulwich.tests.utils import skipIfPY3
 from dulwich.objects import (
 from dulwich.objects import (
     Tree,
     Tree,
     )
     )
@@ -36,10 +35,9 @@ from dulwich.repo import (
 
 
 
 
 def makesha(digit):
 def makesha(digit):
-    return (str(digit) * 40)[:40]
+    return (str(digit).encode('ascii') * 40)[:40]
 
 
 
 
-@skipIfPY3
 class GraftParserTests(TestCase):
 class GraftParserTests(TestCase):
 
 
     def assertParse(self, expected, graftpoints):
     def assertParse(self, expected, graftpoints):
@@ -53,7 +51,7 @@ class GraftParserTests(TestCase):
 
 
     def test_parents(self):
     def test_parents(self):
         self.assertParse({makesha(0): [makesha(1), makesha(2)]},
         self.assertParse({makesha(0): [makesha(1), makesha(2)]},
-                         [' '.join([makesha(0), makesha(1), makesha(2)])])
+                         [b' '.join([makesha(0), makesha(1), makesha(2)])])
 
 
     def test_multiple_hybrid(self):
     def test_multiple_hybrid(self):
         self.assertParse(
         self.assertParse(
@@ -61,11 +59,10 @@ class GraftParserTests(TestCase):
              makesha(1): [makesha(2)],
              makesha(1): [makesha(2)],
              makesha(3): [makesha(4), makesha(5)]},
              makesha(3): [makesha(4), makesha(5)]},
             [makesha(0),
             [makesha(0),
-             ' '.join([makesha(1), makesha(2)]),
-             ' '.join([makesha(3), makesha(4), makesha(5)])])
+             b' '.join([makesha(1), makesha(2)]),
+             b' '.join([makesha(3), makesha(4), makesha(5)])])
 
 
 
 
-@skipIfPY3
 class GraftSerializerTests(TestCase):
 class GraftSerializerTests(TestCase):
 
 
     def assertSerialize(self, expected, graftpoints):
     def assertSerialize(self, expected, graftpoints):
@@ -74,27 +71,26 @@ class GraftSerializerTests(TestCase):
             sorted(serialize_graftpoints(graftpoints)))
             sorted(serialize_graftpoints(graftpoints)))
 
 
     def test_no_grafts(self):
     def test_no_grafts(self):
-        self.assertSerialize('', {})
+        self.assertSerialize(b'', {})
 
 
     def test_no_parents(self):
     def test_no_parents(self):
         self.assertSerialize(makesha(0), {makesha(0): []})
         self.assertSerialize(makesha(0), {makesha(0): []})
 
 
     def test_parents(self):
     def test_parents(self):
-        self.assertSerialize(' '.join([makesha(0), makesha(1), makesha(2)]),
+        self.assertSerialize(b' '.join([makesha(0), makesha(1), makesha(2)]),
                              {makesha(0): [makesha(1), makesha(2)]})
                              {makesha(0): [makesha(1), makesha(2)]})
 
 
     def test_multiple_hybrid(self):
     def test_multiple_hybrid(self):
         self.assertSerialize(
         self.assertSerialize(
-            '\n'.join([
+            b'\n'.join([
                 makesha(0),
                 makesha(0),
-                ' '.join([makesha(1), makesha(2)]),
-                ' '.join([makesha(3), makesha(4), makesha(5)])]),
+                b' '.join([makesha(1), makesha(2)]),
+                b' '.join([makesha(3), makesha(4), makesha(5)])]),
             {makesha(0): [],
             {makesha(0): [],
              makesha(1): [makesha(2)],
              makesha(1): [makesha(2)],
              makesha(3): [makesha(4), makesha(5)]})
              makesha(3): [makesha(4), makesha(5)]})
 
 
 
 
-@skipIfPY3
 class GraftsInRepositoryBase(object):
 class GraftsInRepositoryBase(object):
 
 
     def tearDown(self):
     def tearDown(self):
@@ -139,7 +135,6 @@ class GraftsInRepositoryBase(object):
             {self._shas[-1]: ['1']})
             {self._shas[-1]: ['1']})
 
 
 
 
-@skipIfPY3
 class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
 class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -151,8 +146,8 @@ class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
         self._shas = []
         self._shas = []
 
 
         commit_kwargs = {
         commit_kwargs = {
-            'committer': 'Test Committer <test@nodomain.com>',
-            'author': 'Test Author <test@nodomain.com>',
+            'committer': b'Test Committer <test@nodomain.com>',
+            'author': b'Test Author <test@nodomain.com>',
             'commit_timestamp': 12395,
             'commit_timestamp': 12395,
             'commit_timezone': 0,
             'commit_timezone': 0,
             'author_timestamp': 12395,
             'author_timestamp': 12395,
@@ -160,15 +155,15 @@ class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
         }
         }
 
 
         self._shas.append(r.do_commit(
         self._shas.append(r.do_commit(
-            'empty commit', **commit_kwargs))
+            b'empty commit', **commit_kwargs))
         self._shas.append(r.do_commit(
         self._shas.append(r.do_commit(
-            'empty commit', **commit_kwargs))
+            b'empty commit', **commit_kwargs))
         self._shas.append(r.do_commit(
         self._shas.append(r.do_commit(
-            'empty commit', **commit_kwargs))
+            b'empty commit', **commit_kwargs))
 
 
     def test_init_with_empty_info_grafts(self):
     def test_init_with_empty_info_grafts(self):
         r = self._repo
         r = self._repo
-        r._put_named_file(os.path.join('info', 'grafts'), '')
+        r._put_named_file(os.path.join(b'info', b'grafts'), b'')
 
 
         r = Repo(self._repo_dir)
         r = Repo(self._repo_dir)
         self.assertEqual({}, r._graftpoints)
         self.assertEqual({}, r._graftpoints)
@@ -176,14 +171,13 @@ class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
     def test_init_with_info_grafts(self):
     def test_init_with_info_grafts(self):
         r = self._repo
         r = self._repo
         r._put_named_file(
         r._put_named_file(
-            os.path.join('info', 'grafts'),
-            "%s %s" % (self._shas[-1], self._shas[0]))
+            os.path.join(b'info', b'grafts'),
+            self._shas[-1] + b' ' + self._shas[0])
 
 
         r = Repo(self._repo_dir)
         r = Repo(self._repo_dir)
         self.assertEqual({self._shas[-1]: [self._shas[0]]}, r._graftpoints)
         self.assertEqual({self._shas[-1]: [self._shas[0]]}, r._graftpoints)
 
 
 
 
-@skipIfPY3
 class GraftsInMemoryRepoTests(GraftsInRepositoryBase, TestCase):
 class GraftsInMemoryRepoTests(GraftsInRepositoryBase, TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -195,8 +189,8 @@ class GraftsInMemoryRepoTests(GraftsInRepositoryBase, TestCase):
         tree = Tree()
         tree = Tree()
 
 
         commit_kwargs = {
         commit_kwargs = {
-            'committer': 'Test Committer <test@nodomain.com>',
-            'author': 'Test Author <test@nodomain.com>',
+            'committer': b'Test Committer <test@nodomain.com>',
+            'author': b'Test Author <test@nodomain.com>',
             'commit_timestamp': 12395,
             'commit_timestamp': 12395,
             'commit_timezone': 0,
             'commit_timezone': 0,
             'author_timestamp': 12395,
             'author_timestamp': 12395,
@@ -205,8 +199,8 @@ class GraftsInMemoryRepoTests(GraftsInRepositoryBase, TestCase):
         }
         }
 
 
         self._shas.append(r.do_commit(
         self._shas.append(r.do_commit(
-            'empty commit', **commit_kwargs))
+            b'empty commit', **commit_kwargs))
         self._shas.append(r.do_commit(
         self._shas.append(r.do_commit(
-            'empty commit', **commit_kwargs))
+            b'empty commit', **commit_kwargs))
         self._shas.append(r.do_commit(
         self._shas.append(r.do_commit(
-            'empty commit', **commit_kwargs))
+            b'empty commit', **commit_kwargs))

+ 2 - 6
dulwich/tests/test_greenthreads.py

@@ -22,6 +22,7 @@
 import time
 import time
 
 
 from dulwich.tests import (
 from dulwich.tests import (
+    skipIf,
     TestCase,
     TestCase,
     )
     )
 from dulwich.object_store import (
 from dulwich.object_store import (
@@ -35,11 +36,6 @@ from dulwich.objects import (
     parse_timezone,
     parse_timezone,
     )
     )
 
 
-try:
-    from unittest import skipIf
-except ImportError:
-    from unittest2 import skipIf
-
 try:
 try:
     import gevent
     import gevent
     gevent_support = True
     gevent_support = True
@@ -70,7 +66,7 @@ def create_commit(marker=None):
 
 
 def init_store(store, count=1):
 def init_store(store, count=1):
     ret = []
     ret = []
-    for i in xrange(0, count):
+    for i in range(0, count):
         objs = create_commit(marker=i)
         objs = create_commit(marker=i)
         for obj in objs:
         for obj in objs:
             ret.append(obj)
             ret.append(obj)

+ 30 - 20
dulwich/tests/test_hooks.py

@@ -41,28 +41,31 @@ class ShellHookTests(TestCase):
             self.skipTest('shell hook tests requires POSIX shell')
             self.skipTest('shell hook tests requires POSIX shell')
 
 
     def test_hook_pre_commit(self):
     def test_hook_pre_commit(self):
-        pre_commit_fail = b"""#!/bin/sh
+        pre_commit_fail = """#!/bin/sh
 exit 1
 exit 1
 """
 """
 
 
-        pre_commit_success = b"""#!/bin/sh
+        pre_commit_success = """#!/bin/sh
 exit 0
 exit 0
 """
 """
 
 
-        repo_dir = os.path.join(tempfile.mkdtemp())
-        os.mkdir(os.path.join(repo_dir, 'hooks'))
+        repo_dir = tempfile.mkdtemp()
+        if not isinstance(repo_dir, bytes):
+            repo_dir = repo_dir.encode(sys.getfilesystemencoding())
+
+        os.mkdir(os.path.join(repo_dir, b'hooks'))
         self.addCleanup(shutil.rmtree, repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
 
 
-        pre_commit = os.path.join(repo_dir, 'hooks', 'pre-commit')
+        pre_commit = os.path.join(repo_dir, b'hooks', b'pre-commit')
         hook = PreCommitShellHook(repo_dir)
         hook = PreCommitShellHook(repo_dir)
 
 
-        with open(pre_commit, 'wb') as f:
+        with open(pre_commit, 'w') as f:
             f.write(pre_commit_fail)
             f.write(pre_commit_fail)
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
         self.assertRaises(errors.HookError, hook.execute)
         self.assertRaises(errors.HookError, hook.execute)
 
 
-        with open(pre_commit, 'wb') as f:
+        with open(pre_commit, 'w') as f:
             f.write(pre_commit_success)
             f.write(pre_commit_success)
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
@@ -70,28 +73,31 @@ exit 0
 
 
     def test_hook_commit_msg(self):
     def test_hook_commit_msg(self):
 
 
-        commit_msg_fail = b"""#!/bin/sh
+        commit_msg_fail = """#!/bin/sh
 exit 1
 exit 1
 """
 """
 
 
-        commit_msg_success = b"""#!/bin/sh
+        commit_msg_success = """#!/bin/sh
 exit 0
 exit 0
 """
 """
 
 
         repo_dir = os.path.join(tempfile.mkdtemp())
         repo_dir = os.path.join(tempfile.mkdtemp())
-        os.mkdir(os.path.join(repo_dir, 'hooks'))
+        if not isinstance(repo_dir, bytes):
+            repo_dir = repo_dir.encode(sys.getfilesystemencoding())
+
+        os.mkdir(os.path.join(repo_dir, b'hooks'))
         self.addCleanup(shutil.rmtree, repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
 
 
-        commit_msg = os.path.join(repo_dir, 'hooks', 'commit-msg')
+        commit_msg = os.path.join(repo_dir, b'hooks', b'commit-msg')
         hook = CommitMsgShellHook(repo_dir)
         hook = CommitMsgShellHook(repo_dir)
 
 
-        with open(commit_msg, 'wb') as f:
+        with open(commit_msg, 'w') as f:
             f.write(commit_msg_fail)
             f.write(commit_msg_fail)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
         self.assertRaises(errors.HookError, hook.execute, b'failed commit')
         self.assertRaises(errors.HookError, hook.execute, b'failed commit')
 
 
-        with open(commit_msg, 'wb') as f:
+        with open(commit_msg, 'w') as f:
             f.write(commit_msg_success)
             f.write(commit_msg_success)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
@@ -100,27 +106,31 @@ exit 0
     def test_hook_post_commit(self):
     def test_hook_post_commit(self):
 
 
         (fd, path) = tempfile.mkstemp()
         (fd, path) = tempfile.mkstemp()
-        post_commit_msg = b"""#!/bin/sh
-rm """ + path.encode(sys.getfilesystemencoding()) + b"\n"
 
 
-        post_commit_msg_fail = b"""#!/bin/sh
+        post_commit_msg = """#!/bin/sh
+rm """ + path + "\n"
+
+        post_commit_msg_fail = """#!/bin/sh
 exit 1
 exit 1
 """
 """
 
 
         repo_dir = os.path.join(tempfile.mkdtemp())
         repo_dir = os.path.join(tempfile.mkdtemp())
-        os.mkdir(os.path.join(repo_dir, 'hooks'))
+        if not isinstance(repo_dir, bytes):
+            repo_dir = repo_dir.encode(sys.getfilesystemencoding())
+
+        os.mkdir(os.path.join(repo_dir, b'hooks'))
         self.addCleanup(shutil.rmtree, repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
 
 
-        post_commit = os.path.join(repo_dir, 'hooks', 'post-commit')
+        post_commit = os.path.join(repo_dir, b'hooks', b'post-commit')
         hook = PostCommitShellHook(repo_dir)
         hook = PostCommitShellHook(repo_dir)
 
 
-        with open(post_commit, 'wb') as f:
+        with open(post_commit, 'w') as f:
             f.write(post_commit_msg_fail)
             f.write(post_commit_msg_fail)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
         self.assertRaises(errors.HookError, hook.execute)
         self.assertRaises(errors.HookError, hook.execute)
 
 
-        with open(post_commit, 'wb') as f:
+        with open(post_commit, 'w') as f:
             f.write(post_commit_msg)
             f.write(post_commit_msg)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 

+ 65 - 77
dulwich/tests/test_index.py

@@ -50,9 +50,7 @@ from dulwich.objects import (
     )
     )
 from dulwich.repo import Repo
 from dulwich.repo import Repo
 from dulwich.tests import TestCase
 from dulwich.tests import TestCase
-from dulwich.tests.utils import skipIfPY3
 
 
-@skipIfPY3
 class IndexTestCase(TestCase):
 class IndexTestCase(TestCase):
 
 
     datadir = os.path.join(os.path.dirname(__file__), 'data/indexes')
     datadir = os.path.join(os.path.dirname(__file__), 'data/indexes')
@@ -61,20 +59,19 @@ class IndexTestCase(TestCase):
         return Index(os.path.join(self.datadir, name))
         return Index(os.path.join(self.datadir, name))
 
 
 
 
-@skipIfPY3
 class SimpleIndexTestCase(IndexTestCase):
 class SimpleIndexTestCase(IndexTestCase):
 
 
     def test_len(self):
     def test_len(self):
         self.assertEqual(1, len(self.get_simple_index("index")))
         self.assertEqual(1, len(self.get_simple_index("index")))
 
 
     def test_iter(self):
     def test_iter(self):
-        self.assertEqual(['bla'], list(self.get_simple_index("index")))
+        self.assertEqual([b'bla'], list(self.get_simple_index("index")))
 
 
     def test_getitem(self):
     def test_getitem(self):
         self.assertEqual(((1230680220, 0), (1230680220, 0), 2050, 3761020,
         self.assertEqual(((1230680220, 0), (1230680220, 0), 2050, 3761020,
                            33188, 1000, 1000, 0,
                            33188, 1000, 1000, 0,
-                           'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', 0),
-                          self.get_simple_index("index")["bla"])
+                           b'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', 0),
+                          self.get_simple_index("index")[b"bla"])
 
 
     def test_empty(self):
     def test_empty(self):
         i = self.get_simple_index("notanindex")
         i = self.get_simple_index("notanindex")
@@ -86,11 +83,9 @@ class SimpleIndexTestCase(IndexTestCase):
         changes = list(i.changes_from_tree(MemoryObjectStore(), None))
         changes = list(i.changes_from_tree(MemoryObjectStore(), None))
         self.assertEqual(1, len(changes))
         self.assertEqual(1, len(changes))
         (oldname, newname), (oldmode, newmode), (oldsha, newsha) = changes[0]
         (oldname, newname), (oldmode, newmode), (oldsha, newsha) = changes[0]
-        self.assertEqual('bla', newname)
-        self.assertEqual('e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', newsha)
+        self.assertEqual(b'bla', newname)
+        self.assertEqual(b'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', newsha)
 
 
-
-@skipIfPY3
 class SimpleIndexWriterTestCase(IndexTestCase):
 class SimpleIndexWriterTestCase(IndexTestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -102,18 +97,17 @@ class SimpleIndexWriterTestCase(IndexTestCase):
         shutil.rmtree(self.tempdir)
         shutil.rmtree(self.tempdir)
 
 
     def test_simple_write(self):
     def test_simple_write(self):
-        entries = [('barbla', (1230680220, 0), (1230680220, 0), 2050, 3761020,
+        entries = [(b'barbla', (1230680220, 0), (1230680220, 0), 2050, 3761020,
                     33188, 1000, 1000, 0,
                     33188, 1000, 1000, 0,
-                    'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', 0)]
+                    b'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', 0)]
         filename = os.path.join(self.tempdir, 'test-simple-write-index')
         filename = os.path.join(self.tempdir, 'test-simple-write-index')
-        with open(filename, 'w+') as x:
+        with open(filename, 'wb+') as x:
             write_index(x, entries)
             write_index(x, entries)
 
 
-        with open(filename, 'r') as x:
+        with open(filename, 'rb') as x:
             self.assertEqual(entries, list(read_index(x)))
             self.assertEqual(entries, list(read_index(x)))
 
 
 
 
-@skipIfPY3
 class ReadIndexDictTests(IndexTestCase):
 class ReadIndexDictTests(IndexTestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -125,18 +119,17 @@ class ReadIndexDictTests(IndexTestCase):
         shutil.rmtree(self.tempdir)
         shutil.rmtree(self.tempdir)
 
 
     def test_simple_write(self):
     def test_simple_write(self):
-        entries = {'barbla': ((1230680220, 0), (1230680220, 0), 2050, 3761020,
+        entries = {b'barbla': ((1230680220, 0), (1230680220, 0), 2050, 3761020,
                     33188, 1000, 1000, 0,
                     33188, 1000, 1000, 0,
-                    'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', 0)}
+                    b'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', 0)}
         filename = os.path.join(self.tempdir, 'test-simple-write-index')
         filename = os.path.join(self.tempdir, 'test-simple-write-index')
-        with open(filename, 'w+') as x:
+        with open(filename, 'wb+') as x:
             write_index_dict(x, entries)
             write_index_dict(x, entries)
 
 
-        with open(filename, 'r') as x:
+        with open(filename, 'rb') as x:
             self.assertEqual(entries, read_index_dict(x))
             self.assertEqual(entries, read_index_dict(x))
 
 
 
 
-@skipIfPY3
 class CommitTreeTests(TestCase):
 class CommitTreeTests(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -145,30 +138,29 @@ class CommitTreeTests(TestCase):
 
 
     def test_single_blob(self):
     def test_single_blob(self):
         blob = Blob()
         blob = Blob()
-        blob.data = "foo"
+        blob.data = b"foo"
         self.store.add_object(blob)
         self.store.add_object(blob)
-        blobs = [("bla", blob.id, stat.S_IFREG)]
+        blobs = [(b"bla", blob.id, stat.S_IFREG)]
         rootid = commit_tree(self.store, blobs)
         rootid = commit_tree(self.store, blobs)
-        self.assertEqual(rootid, "1a1e80437220f9312e855c37ac4398b68e5c1d50")
-        self.assertEqual((stat.S_IFREG, blob.id), self.store[rootid]["bla"])
+        self.assertEqual(rootid, b"1a1e80437220f9312e855c37ac4398b68e5c1d50")
+        self.assertEqual((stat.S_IFREG, blob.id), self.store[rootid][b"bla"])
         self.assertEqual(set([rootid, blob.id]), set(self.store._data.keys()))
         self.assertEqual(set([rootid, blob.id]), set(self.store._data.keys()))
 
 
     def test_nested(self):
     def test_nested(self):
         blob = Blob()
         blob = Blob()
-        blob.data = "foo"
+        blob.data = b"foo"
         self.store.add_object(blob)
         self.store.add_object(blob)
-        blobs = [("bla/bar", blob.id, stat.S_IFREG)]
+        blobs = [(b"bla/bar", blob.id, stat.S_IFREG)]
         rootid = commit_tree(self.store, blobs)
         rootid = commit_tree(self.store, blobs)
-        self.assertEqual(rootid, "d92b959b216ad0d044671981196781b3258fa537")
-        dirid = self.store[rootid]["bla"][1]
-        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(rootid, b"d92b959b216ad0d044671981196781b3258fa537")
+        dirid = self.store[rootid][b"bla"][1]
+        self.assertEqual(dirid, b"c1a1deb9788150829579a8b4efa6311e7b638650")
+        self.assertEqual((stat.S_IFDIR, dirid), self.store[rootid][b"bla"])
+        self.assertEqual((stat.S_IFREG, blob.id), self.store[dirid][b"bar"])
         self.assertEqual(set([rootid, dirid, blob.id]),
         self.assertEqual(set([rootid, dirid, blob.id]),
                           set(self.store._data.keys()))
                           set(self.store._data.keys()))
 
 
 
 
-@skipIfPY3
 class CleanupModeTests(TestCase):
 class CleanupModeTests(TestCase):
 
 
     def test_file(self):
     def test_file(self):
@@ -187,7 +179,6 @@ class CleanupModeTests(TestCase):
         self.assertEqual(0o160000, cleanup_mode(0o160744))
         self.assertEqual(0o160000, cleanup_mode(0o160744))
 
 
 
 
-@skipIfPY3
 class WriteCacheTimeTests(TestCase):
 class WriteCacheTimeTests(TestCase):
 
 
     def test_write_string(self):
     def test_write_string(self):
@@ -210,7 +201,6 @@ class WriteCacheTimeTests(TestCase):
         self.assertEqual(struct.pack(">LL", 434343, 21), f.getvalue())
         self.assertEqual(struct.pack(">LL", 434343, 21), f.getvalue())
 
 
 
 
-@skipIfPY3
 class IndexEntryFromStatTests(TestCase):
 class IndexEntryFromStatTests(TestCase):
 
 
     def test_simple(self):
     def test_simple(self):
@@ -249,7 +239,6 @@ class IndexEntryFromStatTests(TestCase):
             0))
             0))
 
 
 
 
-@skipIfPY3
 class BuildIndexTests(TestCase):
 class BuildIndexTests(TestCase):
 
 
     def assertReasonableIndexEntry(self, index_entry, mode, filesize, sha):
     def assertReasonableIndexEntry(self, index_entry, mode, filesize, sha):
@@ -291,12 +280,12 @@ class BuildIndexTests(TestCase):
         self.addCleanup(shutil.rmtree, repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
 
 
         # Populate repo
         # Populate repo
-        filea = Blob.from_string('file a')
-        filee = Blob.from_string('d')
+        filea = Blob.from_string(b'file a')
+        filee = Blob.from_string(b'd')
 
 
         tree = Tree()
         tree = Tree()
-        tree['.git/a'] = (stat.S_IFREG | 0o644, filea.id)
-        tree['c/e'] = (stat.S_IFREG | 0o644, filee.id)
+        tree[b'.git/a'] = (stat.S_IFREG | 0o644, filea.id)
+        tree[b'c/e'] = (stat.S_IFREG | 0o644, filee.id)
 
 
         repo.object_store.add_objects([(o, None)
         repo.object_store.add_objects([(o, None)
             for o in [filea, filee, tree]])
             for o in [filea, filee, tree]])
@@ -315,9 +304,9 @@ class BuildIndexTests(TestCase):
         # filee
         # filee
         epath = os.path.join(repo.path, 'c', 'e')
         epath = os.path.join(repo.path, 'c', 'e')
         self.assertTrue(os.path.exists(epath))
         self.assertTrue(os.path.exists(epath))
-        self.assertReasonableIndexEntry(index['c/e'],
+        self.assertReasonableIndexEntry(index[b'c/e'],
             stat.S_IFREG | 0o644, 1, filee.id)
             stat.S_IFREG | 0o644, 1, filee.id)
-        self.assertFileContents(epath, 'd')
+        self.assertFileContents(epath, b'd')
 
 
     def test_nonempty(self):
     def test_nonempty(self):
         if os.name != 'posix':
         if os.name != 'posix':
@@ -328,16 +317,16 @@ class BuildIndexTests(TestCase):
         self.addCleanup(shutil.rmtree, repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
 
 
         # Populate repo
         # 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')
+        filea = Blob.from_string(b'file a')
+        fileb = Blob.from_string(b'file b')
+        filed = Blob.from_string(b'file d')
+        filee = Blob.from_string(b'd')
 
 
         tree = Tree()
         tree = Tree()
-        tree['a'] = (stat.S_IFREG | 0o644, filea.id)
-        tree['b'] = (stat.S_IFREG | 0o644, fileb.id)
-        tree['c/d'] = (stat.S_IFREG | 0o644, filed.id)
-        tree['c/e'] = (stat.S_IFLNK, filee.id)  # symlink
+        tree[b'a'] = (stat.S_IFREG | 0o644, filea.id)
+        tree[b'b'] = (stat.S_IFREG | 0o644, fileb.id)
+        tree[b'c/d'] = (stat.S_IFREG | 0o644, filed.id)
+        tree[b'c/e'] = (stat.S_IFLNK, filee.id)  # symlink
 
 
         repo.object_store.add_objects([(o, None)
         repo.object_store.add_objects([(o, None)
             for o in [filea, fileb, filed, filee, tree]])
             for o in [filea, fileb, filed, filee, tree]])
@@ -352,28 +341,28 @@ class BuildIndexTests(TestCase):
         # filea
         # filea
         apath = os.path.join(repo.path, 'a')
         apath = os.path.join(repo.path, 'a')
         self.assertTrue(os.path.exists(apath))
         self.assertTrue(os.path.exists(apath))
-        self.assertReasonableIndexEntry(index['a'],
+        self.assertReasonableIndexEntry(index[b'a'],
             stat.S_IFREG | 0o644, 6, filea.id)
             stat.S_IFREG | 0o644, 6, filea.id)
-        self.assertFileContents(apath, 'file a')
+        self.assertFileContents(apath, b'file a')
 
 
         # fileb
         # fileb
         bpath = os.path.join(repo.path, 'b')
         bpath = os.path.join(repo.path, 'b')
         self.assertTrue(os.path.exists(bpath))
         self.assertTrue(os.path.exists(bpath))
-        self.assertReasonableIndexEntry(index['b'],
+        self.assertReasonableIndexEntry(index[b'b'],
             stat.S_IFREG | 0o644, 6, fileb.id)
             stat.S_IFREG | 0o644, 6, fileb.id)
-        self.assertFileContents(bpath, 'file b')
+        self.assertFileContents(bpath, b'file b')
 
 
         # filed
         # filed
         dpath = os.path.join(repo.path, 'c', 'd')
         dpath = os.path.join(repo.path, 'c', 'd')
         self.assertTrue(os.path.exists(dpath))
         self.assertTrue(os.path.exists(dpath))
-        self.assertReasonableIndexEntry(index['c/d'],
+        self.assertReasonableIndexEntry(index[b'c/d'],
             stat.S_IFREG | 0o644, 6, filed.id)
             stat.S_IFREG | 0o644, 6, filed.id)
-        self.assertFileContents(dpath, 'file d')
+        self.assertFileContents(dpath, b'file d')
 
 
         # symlink to d
         # symlink to d
         epath = os.path.join(repo.path, 'c', 'e')
         epath = os.path.join(repo.path, 'c', 'e')
         self.assertTrue(os.path.exists(epath))
         self.assertTrue(os.path.exists(epath))
-        self.assertReasonableIndexEntry(index['c/e'],
+        self.assertReasonableIndexEntry(index[b'c/e'],
             stat.S_IFLNK, 1, filee.id)
             stat.S_IFLNK, 1, filee.id)
         self.assertFileContents(epath, 'd', symlink=True)
         self.assertFileContents(epath, 'd', symlink=True)
 
 
@@ -384,7 +373,6 @@ class BuildIndexTests(TestCase):
             sorted(os.listdir(os.path.join(repo.path, 'c'))))
             sorted(os.listdir(os.path.join(repo.path, 'c'))))
 
 
 
 
-@skipIfPY3
 class GetUnstagedChangesTests(TestCase):
 class GetUnstagedChangesTests(TestCase):
 
 
     def test_get_unstaged_changes(self):
     def test_get_unstaged_changes(self):
@@ -396,41 +384,41 @@ class GetUnstagedChangesTests(TestCase):
 
 
         # Commit a dummy file then modify it
         # Commit a dummy file then modify it
         foo1_fullpath = os.path.join(repo_dir, 'foo1')
         foo1_fullpath = os.path.join(repo_dir, 'foo1')
-        with open(foo1_fullpath, 'w') as f:
-            f.write('origstuff')
+        with open(foo1_fullpath, 'wb') as f:
+            f.write(b'origstuff')
 
 
         foo2_fullpath = os.path.join(repo_dir, 'foo2')
         foo2_fullpath = os.path.join(repo_dir, 'foo2')
-        with open(foo2_fullpath, 'w') as f:
-            f.write('origstuff')
+        with open(foo2_fullpath, 'wb') as f:
+            f.write(b'origstuff')
 
 
         repo.stage(['foo1', 'foo2'])
         repo.stage(['foo1', 'foo2'])
-        repo.do_commit('test status', author='', committer='')
+        repo.do_commit(b'test status', author=b'', committer=b'')
 
 
-        with open(foo1_fullpath, 'w') as f:
-            f.write('newstuff')
+        with open(foo1_fullpath, 'wb') as f:
+            f.write(b'newstuff')
 
 
         # modify access and modify time of path
         # modify access and modify time of path
         os.utime(foo1_fullpath, (0, 0))
         os.utime(foo1_fullpath, (0, 0))
 
 
         changes = get_unstaged_changes(repo.open_index(), repo_dir)
         changes = get_unstaged_changes(repo.open_index(), repo_dir)
 
 
-        self.assertEqual(list(changes), ['foo1'])
+        self.assertEqual(list(changes), [b'foo1'])
 
 
 
 
 class TestValidatePathElement(TestCase):
 class TestValidatePathElement(TestCase):
 
 
     def test_default(self):
     def test_default(self):
-        self.assertTrue(validate_path_element_default("bla"))
-        self.assertTrue(validate_path_element_default(".bla"))
-        self.assertFalse(validate_path_element_default(".git"))
-        self.assertFalse(validate_path_element_default(".giT"))
-        self.assertFalse(validate_path_element_default(".."))
-        self.assertTrue(validate_path_element_default("git~1"))
+        self.assertTrue(validate_path_element_default(b"bla"))
+        self.assertTrue(validate_path_element_default(b".bla"))
+        self.assertFalse(validate_path_element_default(b".git"))
+        self.assertFalse(validate_path_element_default(b".giT"))
+        self.assertFalse(validate_path_element_default(b".."))
+        self.assertTrue(validate_path_element_default(b"git~1"))
 
 
     def test_ntfs(self):
     def test_ntfs(self):
-        self.assertTrue(validate_path_element_ntfs("bla"))
-        self.assertTrue(validate_path_element_ntfs(".bla"))
-        self.assertFalse(validate_path_element_ntfs(".git"))
-        self.assertFalse(validate_path_element_ntfs(".giT"))
-        self.assertFalse(validate_path_element_ntfs(".."))
-        self.assertFalse(validate_path_element_ntfs("git~1"))
+        self.assertTrue(validate_path_element_ntfs(b"bla"))
+        self.assertTrue(validate_path_element_ntfs(b".bla"))
+        self.assertFalse(validate_path_element_ntfs(b".git"))
+        self.assertFalse(validate_path_element_ntfs(b".giT"))
+        self.assertFalse(validate_path_element_ntfs(b".."))
+        self.assertFalse(validate_path_element_ntfs(b"git~1"))

+ 26 - 29
dulwich/tests/test_missing_obj_finder.py

@@ -27,11 +27,9 @@ from dulwich.tests.utils import (
     make_object,
     make_object,
     make_tag,
     make_tag,
     build_commit_graph,
     build_commit_graph,
-    skipIfPY3,
     )
     )
 
 
 
 
-@skipIfPY3
 class MissingObjectFinderTest(TestCase):
 class MissingObjectFinderTest(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -52,21 +50,20 @@ class MissingObjectFinderTest(TestCase):
             "some objects are not reported as missing: %s" % (expected, ))
             "some objects are not reported as missing: %s" % (expected, ))
 
 
 
 
-@skipIfPY3
 class MOFLinearRepoTest(MissingObjectFinderTest):
 class MOFLinearRepoTest(MissingObjectFinderTest):
 
 
     def setUp(self):
     def setUp(self):
         super(MOFLinearRepoTest, self).setUp()
         super(MOFLinearRepoTest, self).setUp()
-        f1_1 = make_object(Blob, data='f1') # present in 1, removed in 3
-        f2_1 = make_object(Blob, data='f2') # present in all revisions, changed in 2 and 3
-        f2_2 = make_object(Blob, data='f2-changed')
-        f2_3 = make_object(Blob, data='f2-changed-again')
-        f3_2 = make_object(Blob, data='f3') # added in 2, left unmodified in 3
+        f1_1 = make_object(Blob, data=b'f1') # present in 1, removed in 3
+        f2_1 = make_object(Blob, data=b'f2') # present in all revisions, changed in 2 and 3
+        f2_2 = make_object(Blob, data=b'f2-changed')
+        f2_3 = make_object(Blob, data=b'f2-changed-again')
+        f3_2 = make_object(Blob, data=b'f3') # added in 2, left unmodified in 3
 
 
         commit_spec = [[1], [2, 1], [3, 2]]
         commit_spec = [[1], [2, 1], [3, 2]]
-        trees = {1: [('f1', f1_1), ('f2', f2_1)],
-                2: [('f1', f1_1), ('f2', f2_2), ('f3', f3_2)],
-                3: [('f2', f2_3), ('f3', f3_2)] }
+        trees = {1: [(b'f1', f1_1), (b'f2', f2_1)],
+                 2: [(b'f1', f1_1), (b'f2', f2_2), (b'f3', f3_2)],
+                 3: [(b'f2', f2_3), (b'f3', f3_2)] }
         # commit 1: f1 and f2
         # commit 1: f1 and f2
         # commit 2: f3 added, f2 changed. Missing shall report commit id and a
         # commit 2: f3 added, f2 changed. Missing shall report commit id and a
         # tree referenced by commit
         # tree referenced by commit
@@ -111,7 +108,6 @@ class MOFLinearRepoTest(MissingObjectFinderTest):
         self.assertMissingMatch([self.cmt(3).id], [self.cmt(3).id], [])
         self.assertMissingMatch([self.cmt(3).id], [self.cmt(3).id], [])
 
 
 
 
-@skipIfPY3
 class MOFMergeForkRepoTest(MissingObjectFinderTest):
 class MOFMergeForkRepoTest(MissingObjectFinderTest):
     # 1 --- 2 --- 4 --- 6 --- 7
     # 1 --- 2 --- 4 --- 6 --- 7
     #          \        /
     #          \        /
@@ -121,24 +117,24 @@ class MOFMergeForkRepoTest(MissingObjectFinderTest):
 
 
     def setUp(self):
     def setUp(self):
         super(MOFMergeForkRepoTest, self).setUp()
         super(MOFMergeForkRepoTest, self).setUp()
-        f1_1 = make_object(Blob, data='f1')
-        f1_2 = make_object(Blob, data='f1-2')
-        f1_4 = make_object(Blob, data='f1-4')
-        f1_7 = make_object(Blob, data='f1-2') # same data as in rev 2
-        f2_1 = make_object(Blob, data='f2')
-        f2_3 = make_object(Blob, data='f2-3')
-        f3_3 = make_object(Blob, data='f3')
-        f3_5 = make_object(Blob, data='f3-5')
+        f1_1 = make_object(Blob, data=b'f1')
+        f1_2 = make_object(Blob, data=b'f1-2')
+        f1_4 = make_object(Blob, data=b'f1-4')
+        f1_7 = make_object(Blob, data=b'f1-2') # same data as in rev 2
+        f2_1 = make_object(Blob, data=b'f2')
+        f2_3 = make_object(Blob, data=b'f2-3')
+        f3_3 = make_object(Blob, data=b'f3')
+        f3_5 = make_object(Blob, data=b'f3-5')
         commit_spec = [[1], [2, 1], [3, 2], [4, 2], [5, 3], [6, 3, 4], [7, 6]]
         commit_spec = [[1], [2, 1], [3, 2], [4, 2], [5, 3], [6, 3, 4], [7, 6]]
-        trees = {1: [('f1', f1_1), ('f2', f2_1)],
-                2: [('f1', f1_2), ('f2', f2_1)], # f1 changed
+        trees = {1: [(b'f1', f1_1), (b'f2', f2_1)],
+                2: [(b'f1', f1_2), (b'f2', f2_1)], # f1 changed
                 # f3 added, f2 changed
                 # f3 added, f2 changed
-                3: [('f1', f1_2), ('f2', f2_3), ('f3', f3_3)],
-                4: [('f1', f1_4), ('f2', f2_1)],  # f1 changed
-                5: [('f1', f1_2), ('f3', f3_5)], # f2 removed, f3 changed
-                6: [('f1', f1_4), ('f2', f2_3), ('f3', f3_3)], # merged 3 and 4
+                3: [(b'f1', f1_2), (b'f2', f2_3), (b'f3', f3_3)],
+                4: [(b'f1', f1_4), (b'f2', f2_1)],  # f1 changed
+                5: [(b'f1', f1_2), (b'f3', f3_5)], # f2 removed, f3 changed
+                6: [(b'f1', f1_4), (b'f2', f2_3), (b'f3', f3_3)], # merged 3 and 4
                 # f1 changed to match rev2. f3 removed
                 # f1 changed to match rev2. f3 removed
-                7: [('f1', f1_7), ('f2', f2_3)]}
+                7: [(b'f1', f1_7), (b'f2', f2_3)]}
         self.commits = build_commit_graph(self.store, commit_spec, trees)
         self.commits = build_commit_graph(self.store, commit_spec, trees)
 
 
         self.f1_2_id = f1_2.id
         self.f1_2_id = f1_2.id
@@ -198,11 +194,12 @@ class MOFMergeForkRepoTest(MissingObjectFinderTest):
 
 
 
 
 class MOFTagsTest(MissingObjectFinderTest):
 class MOFTagsTest(MissingObjectFinderTest):
+
     def setUp(self):
     def setUp(self):
         super(MOFTagsTest, self).setUp()
         super(MOFTagsTest, self).setUp()
-        f1_1 = make_object(Blob, data='f1')
+        f1_1 = make_object(Blob, data=b'f1')
         commit_spec = [[1]]
         commit_spec = [[1]]
-        trees = {1: [('f1', f1_1)]}
+        trees = {1: [(b'f1', f1_1)]}
         self.commits = build_commit_graph(self.store, commit_spec, trees)
         self.commits = build_commit_graph(self.store, commit_spec, trees)
 
 
         self._normal_tag = make_tag(self.cmt(1))
         self._normal_tag = make_tag(self.cmt(1))

+ 112 - 111
dulwich/tests/test_object_store.py

@@ -22,6 +22,7 @@
 from io import BytesIO
 from io import BytesIO
 import os
 import os
 import shutil
 import shutil
+import sys
 import tempfile
 import tempfile
 
 
 from dulwich.index import (
 from dulwich.index import (
@@ -53,7 +54,6 @@ from dulwich.tests.utils import (
     make_object,
     make_object,
     make_tag,
     make_tag,
     build_pack,
     build_pack,
-    skipIfPY3,
     )
     )
 
 
 
 
@@ -63,21 +63,21 @@ testobject = make_object(Blob, data=b"yummy data")
 class ObjectStoreTests(object):
 class ObjectStoreTests(object):
 
 
     def test_determine_wants_all(self):
     def test_determine_wants_all(self):
-        self.assertEqual(["1" * 40],
-            self.store.determine_wants_all({"refs/heads/foo": "1" * 40}))
+        self.assertEqual([b"1" * 40],
+            self.store.determine_wants_all({b"refs/heads/foo": b"1" * 40}))
 
 
     def test_determine_wants_all_zero(self):
     def test_determine_wants_all_zero(self):
         self.assertEqual([],
         self.assertEqual([],
-            self.store.determine_wants_all({"refs/heads/foo": "0" * 40}))
+            self.store.determine_wants_all({b"refs/heads/foo": b"0" * 40}))
 
 
     def test_iter(self):
     def test_iter(self):
         self.assertEqual([], list(self.store))
         self.assertEqual([], list(self.store))
 
 
     def test_get_nonexistant(self):
     def test_get_nonexistant(self):
-        self.assertRaises(KeyError, lambda: self.store["a" * 40])
+        self.assertRaises(KeyError, lambda: self.store[b"a" * 40])
 
 
     def test_contains_nonexistant(self):
     def test_contains_nonexistant(self):
-        self.assertFalse(("a" * 40) in self.store)
+        self.assertFalse((b"a" * 40) in self.store)
 
 
     def test_add_objects_empty(self):
     def test_add_objects_empty(self):
         self.store.add_objects([])
         self.store.add_objects([])
@@ -103,66 +103,66 @@ class ObjectStoreTests(object):
         self.assertEqual(r, testobject)
         self.assertEqual(r, testobject)
 
 
     def test_tree_changes(self):
     def test_tree_changes(self):
-        blob_a1 = make_object(Blob, data='a1')
-        blob_a2 = make_object(Blob, data='a2')
-        blob_b = make_object(Blob, data='b')
+        blob_a1 = make_object(Blob, data=b'a1')
+        blob_a2 = make_object(Blob, data=b'a2')
+        blob_b = make_object(Blob, data=b'b')
         for blob in [blob_a1, blob_a2, blob_b]:
         for blob in [blob_a1, blob_a2, blob_b]:
             self.store.add_object(blob)
             self.store.add_object(blob)
 
 
-        blobs_1 = [('a', blob_a1.id, 0o100644), ('b', blob_b.id, 0o100644)]
+        blobs_1 = [(b'a', blob_a1.id, 0o100644), (b'b', blob_b.id, 0o100644)]
         tree1_id = commit_tree(self.store, blobs_1)
         tree1_id = commit_tree(self.store, blobs_1)
-        blobs_2 = [('a', blob_a2.id, 0o100644), ('b', blob_b.id, 0o100644)]
+        blobs_2 = [(b'a', blob_a2.id, 0o100644), (b'b', blob_b.id, 0o100644)]
         tree2_id = commit_tree(self.store, blobs_2)
         tree2_id = commit_tree(self.store, blobs_2)
-        change_a = (('a', 'a'), (0o100644, 0o100644), (blob_a1.id, blob_a2.id))
+        change_a = ((b'a', b'a'), (0o100644, 0o100644), (blob_a1.id, blob_a2.id))
         self.assertEqual([change_a],
         self.assertEqual([change_a],
                           list(self.store.tree_changes(tree1_id, tree2_id)))
                           list(self.store.tree_changes(tree1_id, tree2_id)))
         self.assertEqual(
         self.assertEqual(
-          [change_a, (('b', 'b'), (0o100644, 0o100644), (blob_b.id, blob_b.id))],
-          list(self.store.tree_changes(tree1_id, tree2_id,
-                                       want_unchanged=True)))
+            [change_a, ((b'b', b'b'), (0o100644, 0o100644), (blob_b.id, blob_b.id))],
+            list(self.store.tree_changes(tree1_id, tree2_id,
+                                         want_unchanged=True)))
 
 
     def test_iter_tree_contents(self):
     def test_iter_tree_contents(self):
-        blob_a = make_object(Blob, data='a')
-        blob_b = make_object(Blob, data='b')
-        blob_c = make_object(Blob, data='c')
+        blob_a = make_object(Blob, data=b'a')
+        blob_b = make_object(Blob, data=b'b')
+        blob_c = make_object(Blob, data=b'c')
         for blob in [blob_a, blob_b, blob_c]:
         for blob in [blob_a, blob_b, blob_c]:
             self.store.add_object(blob)
             self.store.add_object(blob)
 
 
         blobs = [
         blobs = [
-          ('a', blob_a.id, 0o100644),
-          ('ad/b', blob_b.id, 0o100644),
-          ('ad/bd/c', blob_c.id, 0o100755),
-          ('ad/c', blob_c.id, 0o100644),
-          ('c', blob_c.id, 0o100644),
-          ]
+            (b'a', blob_a.id, 0o100644),
+            (b'ad/b', blob_b.id, 0o100644),
+            (b'ad/bd/c', blob_c.id, 0o100755),
+            (b'ad/c', blob_c.id, 0o100644),
+            (b'c', blob_c.id, 0o100644),
+        ]
         tree_id = commit_tree(self.store, blobs)
         tree_id = commit_tree(self.store, blobs)
         self.assertEqual([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)))
                           list(self.store.iter_tree_contents(tree_id)))
 
 
     def test_iter_tree_contents_include_trees(self):
     def test_iter_tree_contents_include_trees(self):
-        blob_a = make_object(Blob, data='a')
-        blob_b = make_object(Blob, data='b')
-        blob_c = make_object(Blob, data='c')
+        blob_a = make_object(Blob, data=b'a')
+        blob_b = make_object(Blob, data=b'b')
+        blob_c = make_object(Blob, data=b'c')
         for blob in [blob_a, blob_b, blob_c]:
         for blob in [blob_a, blob_b, blob_c]:
             self.store.add_object(blob)
             self.store.add_object(blob)
 
 
         blobs = [
         blobs = [
-          ('a', blob_a.id, 0o100644),
-          ('ad/b', blob_b.id, 0o100644),
-          ('ad/bd/c', blob_c.id, 0o100755),
+          (b'a', blob_a.id, 0o100644),
+          (b'ad/b', blob_b.id, 0o100644),
+          (b'ad/bd/c', blob_c.id, 0o100755),
           ]
           ]
         tree_id = commit_tree(self.store, blobs)
         tree_id = commit_tree(self.store, blobs)
         tree = self.store[tree_id]
         tree = self.store[tree_id]
-        tree_ad = self.store[tree['ad'][1]]
-        tree_bd = self.store[tree_ad['bd'][1]]
+        tree_ad = self.store[tree[b'ad'][1]]
+        tree_bd = self.store[tree_ad[b'bd'][1]]
 
 
         expected = [
         expected = [
-          TreeEntry('', 0o040000, tree_id),
-          TreeEntry('a', 0o100644, blob_a.id),
-          TreeEntry('ad', 0o040000, tree_ad.id),
-          TreeEntry('ad/b', 0o100644, blob_b.id),
-          TreeEntry('ad/bd', 0o040000, tree_bd.id),
-          TreeEntry('ad/bd/c', 0o100755, blob_c.id),
+          TreeEntry(b'', 0o040000, tree_id),
+          TreeEntry(b'a', 0o100644, blob_a.id),
+          TreeEntry(b'ad', 0o040000, tree_ad.id),
+          TreeEntry(b'ad/b', 0o100644, blob_b.id),
+          TreeEntry(b'ad/bd', 0o040000, tree_bd.id),
+          TreeEntry(b'ad/bd/c', 0o100755, blob_c.id),
           ]
           ]
         actual = self.store.iter_tree_contents(tree_id, include_trees=True)
         actual = self.store.iter_tree_contents(tree_id, include_trees=True)
         self.assertEqual(expected, list(actual))
         self.assertEqual(expected, list(actual))
@@ -174,15 +174,15 @@ class ObjectStoreTests(object):
 
 
     def test_peel_sha(self):
     def test_peel_sha(self):
         self.store.add_object(testobject)
         self.store.add_object(testobject)
-        tag1 = self.make_tag('1', testobject)
-        tag2 = self.make_tag('2', testobject)
-        tag3 = self.make_tag('3', testobject)
+        tag1 = self.make_tag(b'1', testobject)
+        tag2 = self.make_tag(b'2', testobject)
+        tag3 = self.make_tag(b'3', testobject)
         for obj in [testobject, tag1, tag2, tag3]:
         for obj in [testobject, tag1, tag2, tag3]:
             self.assertEqual(testobject, self.store.peel_sha(obj.id))
             self.assertEqual(testobject, self.store.peel_sha(obj.id))
 
 
     def test_get_raw(self):
     def test_get_raw(self):
         self.store.add_object(testobject)
         self.store.add_object(testobject)
-        self.assertEqual((Blob.type_num, 'yummy data'),
+        self.assertEqual((Blob.type_num, b'yummy data'),
                          self.store.get_raw(testobject.id))
                          self.store.get_raw(testobject.id))
 
 
     def test_close(self):
     def test_close(self):
@@ -191,7 +191,6 @@ class ObjectStoreTests(object):
         self.store.close()
         self.store.close()
 
 
 
 
-@skipIfPY3
 class MemoryObjectStoreTests(ObjectStoreTests, TestCase):
 class MemoryObjectStoreTests(ObjectStoreTests, TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -202,7 +201,7 @@ class MemoryObjectStoreTests(ObjectStoreTests, TestCase):
         o = MemoryObjectStore()
         o = MemoryObjectStore()
         f, commit, abort = o.add_pack()
         f, commit, abort = o.add_pack()
         try:
         try:
-            b = make_object(Blob, data="more yummy data")
+            b = make_object(Blob, data=b"more yummy data")
             write_pack_objects(f, [(b, None)])
             write_pack_objects(f, [(b, None)])
         except:
         except:
             abort()
             abort()
@@ -217,16 +216,16 @@ class MemoryObjectStoreTests(ObjectStoreTests, TestCase):
 
 
     def test_add_thin_pack(self):
     def test_add_thin_pack(self):
         o = MemoryObjectStore()
         o = MemoryObjectStore()
-        blob = make_object(Blob, data='yummy data')
+        blob = make_object(Blob, data=b'yummy data')
         o.add_object(blob)
         o.add_object(blob)
 
 
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (REF_DELTA, (blob.id, 'more yummy data')),
-          ], store=o)
+            (REF_DELTA, (blob.id, b'more yummy data')),
+            ], store=o)
         o.add_thin_pack(f.read, None)
         o.add_thin_pack(f.read, None)
         packed_blob_sha = sha_to_hex(entries[0][3])
         packed_blob_sha = sha_to_hex(entries[0][3])
-        self.assertEqual((Blob.type_num, 'more yummy data'),
+        self.assertEqual((Blob.type_num, b'more yummy data'),
                          o.get_raw(packed_blob_sha))
                          o.get_raw(packed_blob_sha))
 
 
 
 
@@ -239,7 +238,6 @@ class MemoryObjectStoreTests(ObjectStoreTests, TestCase):
         o.add_thin_pack(f.read, None)
         o.add_thin_pack(f.read, None)
 
 
 
 
-@skipIfPY3
 class PackBasedObjectStoreTests(ObjectStoreTests):
 class PackBasedObjectStoreTests(ObjectStoreTests):
 
 
     def tearDown(self):
     def tearDown(self):
@@ -247,25 +245,26 @@ class PackBasedObjectStoreTests(ObjectStoreTests):
             pack.close()
             pack.close()
 
 
     def test_empty_packs(self):
     def test_empty_packs(self):
-        self.assertEqual([], self.store.packs)
+        self.assertEqual([], list(self.store.packs))
 
 
     def test_pack_loose_objects(self):
     def test_pack_loose_objects(self):
-        b1 = make_object(Blob, data="yummy data")
+        b1 = make_object(Blob, data=b"yummy data")
         self.store.add_object(b1)
         self.store.add_object(b1)
-        b2 = make_object(Blob, data="more yummy data")
+        b2 = make_object(Blob, data=b"more yummy data")
         self.store.add_object(b2)
         self.store.add_object(b2)
-        self.assertEqual([], self.store.packs)
+        self.assertEqual([], list(self.store.packs))
         self.assertEqual(2, self.store.pack_loose_objects())
         self.assertEqual(2, self.store.pack_loose_objects())
-        self.assertNotEqual([], self.store.packs)
+        self.assertNotEqual([], list(self.store.packs))
         self.assertEqual(0, self.store.pack_loose_objects())
         self.assertEqual(0, self.store.pack_loose_objects())
 
 
 
 
-@skipIfPY3
 class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
 class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
 
 
     def setUp(self):
     def setUp(self):
         TestCase.setUp(self)
         TestCase.setUp(self)
         self.store_dir = tempfile.mkdtemp()
         self.store_dir = tempfile.mkdtemp()
+        if not isinstance(self.store_dir, bytes):
+            self.store_dir = self.store_dir.encode(sys.getfilesystemencoding())
         self.addCleanup(shutil.rmtree, self.store_dir)
         self.addCleanup(shutil.rmtree, self.store_dir)
         self.store = DiskObjectStore.init(self.store_dir)
         self.store = DiskObjectStore.init(self.store_dir)
 
 
@@ -275,9 +274,11 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
 
 
     def test_alternates(self):
     def test_alternates(self):
         alternate_dir = tempfile.mkdtemp()
         alternate_dir = tempfile.mkdtemp()
+        if not isinstance(alternate_dir, bytes):
+            alternate_dir = alternate_dir.encode(sys.getfilesystemencoding())
         self.addCleanup(shutil.rmtree, alternate_dir)
         self.addCleanup(shutil.rmtree, alternate_dir)
         alternate_store = DiskObjectStore(alternate_dir)
         alternate_store = DiskObjectStore(alternate_dir)
-        b2 = make_object(Blob, data="yummy data")
+        b2 = make_object(Blob, data=b"yummy data")
         alternate_store.add_object(b2)
         alternate_store.add_object(b2)
         store = DiskObjectStore(self.store_dir)
         store = DiskObjectStore(self.store_dir)
         self.assertRaises(KeyError, store.__getitem__, b2.id)
         self.assertRaises(KeyError, store.__getitem__, b2.id)
@@ -287,19 +288,21 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
 
 
     def test_add_alternate_path(self):
     def test_add_alternate_path(self):
         store = DiskObjectStore(self.store_dir)
         store = DiskObjectStore(self.store_dir)
-        self.assertEqual([], store._read_alternate_paths())
-        store.add_alternate_path("/foo/path")
-        self.assertEqual(["/foo/path"], store._read_alternate_paths())
-        store.add_alternate_path("/bar/path")
+        self.assertEqual([], list(store._read_alternate_paths()))
+        store.add_alternate_path(b'/foo/path')
+        self.assertEqual([b'/foo/path'], list(store._read_alternate_paths()))
+        store.add_alternate_path(b'/bar/path')
         self.assertEqual(
         self.assertEqual(
-            ["/foo/path", "/bar/path"],
-            store._read_alternate_paths())
+            [b'/foo/path', b'/bar/path'],
+            list(store._read_alternate_paths()))
 
 
     def test_rel_alternative_path(self):
     def test_rel_alternative_path(self):
         alternate_dir = tempfile.mkdtemp()
         alternate_dir = tempfile.mkdtemp()
+        if not isinstance(alternate_dir, bytes):
+            alternate_dir = alternate_dir.encode(sys.getfilesystemencoding())
         self.addCleanup(shutil.rmtree, alternate_dir)
         self.addCleanup(shutil.rmtree, alternate_dir)
         alternate_store = DiskObjectStore(alternate_dir)
         alternate_store = DiskObjectStore(alternate_dir)
-        b2 = make_object(Blob, data="yummy data")
+        b2 = make_object(Blob, data=b"yummy data")
         alternate_store.add_object(b2)
         alternate_store.add_object(b2)
         store = DiskObjectStore(self.store_dir)
         store = DiskObjectStore(self.store_dir)
         self.assertRaises(KeyError, store.__getitem__, b2.id)
         self.assertRaises(KeyError, store.__getitem__, b2.id)
@@ -310,13 +313,13 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
 
 
     def test_pack_dir(self):
     def test_pack_dir(self):
         o = DiskObjectStore(self.store_dir)
         o = DiskObjectStore(self.store_dir)
-        self.assertEqual(os.path.join(self.store_dir, "pack"), o.pack_dir)
+        self.assertEqual(os.path.join(self.store_dir, b'pack'), o.pack_dir)
 
 
     def test_add_pack(self):
     def test_add_pack(self):
         o = DiskObjectStore(self.store_dir)
         o = DiskObjectStore(self.store_dir)
         f, commit, abort = o.add_pack()
         f, commit, abort = o.add_pack()
         try:
         try:
-            b = make_object(Blob, data="more yummy data")
+            b = make_object(Blob, data=b"more yummy data")
             write_pack_objects(f, [(b, None)])
             write_pack_objects(f, [(b, None)])
         except:
         except:
             abort()
             abort()
@@ -327,12 +330,12 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
     def test_add_thin_pack(self):
     def test_add_thin_pack(self):
         o = DiskObjectStore(self.store_dir)
         o = DiskObjectStore(self.store_dir)
         try:
         try:
-            blob = make_object(Blob, data='yummy data')
+            blob = make_object(Blob, data=b'yummy data')
             o.add_object(blob)
             o.add_object(blob)
 
 
             f = BytesIO()
             f = BytesIO()
             entries = build_pack(f, [
             entries = build_pack(f, [
-              (REF_DELTA, (blob.id, 'more yummy data')),
+              (REF_DELTA, (blob.id, b'more yummy data')),
               ], store=o)
               ], store=o)
 
 
             with o.add_thin_pack(f.read, None) as pack:
             with o.add_thin_pack(f.read, None) as pack:
@@ -341,7 +344,7 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
                 self.assertEqual(sorted([blob.id, packed_blob_sha]), list(pack))
                 self.assertEqual(sorted([blob.id, packed_blob_sha]), list(pack))
                 self.assertTrue(o.contains_packed(packed_blob_sha))
                 self.assertTrue(o.contains_packed(packed_blob_sha))
                 self.assertTrue(o.contains_packed(blob.id))
                 self.assertTrue(o.contains_packed(blob.id))
-                self.assertEqual((Blob.type_num, 'more yummy data'),
+                self.assertEqual((Blob.type_num, b'more yummy data'),
                                  o.get_raw(packed_blob_sha))
                                  o.get_raw(packed_blob_sha))
         finally:
         finally:
             o.close()
             o.close()
@@ -355,24 +358,23 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
         o.add_thin_pack(f.read, None)
         o.add_thin_pack(f.read, None)
 
 
 
 
-@skipIfPY3
 class TreeLookupPathTests(TestCase):
 class TreeLookupPathTests(TestCase):
 
 
     def setUp(self):
     def setUp(self):
         TestCase.setUp(self)
         TestCase.setUp(self)
         self.store = MemoryObjectStore()
         self.store = MemoryObjectStore()
-        blob_a = make_object(Blob, data='a')
-        blob_b = make_object(Blob, data='b')
-        blob_c = make_object(Blob, data='c')
+        blob_a = make_object(Blob, data=b'a')
+        blob_b = make_object(Blob, data=b'b')
+        blob_c = make_object(Blob, data=b'c')
         for blob in [blob_a, blob_b, blob_c]:
         for blob in [blob_a, blob_b, blob_c]:
             self.store.add_object(blob)
             self.store.add_object(blob)
 
 
         blobs = [
         blobs = [
-          ('a', blob_a.id, 0o100644),
-          ('ad/b', blob_b.id, 0o100644),
-          ('ad/bd/c', blob_c.id, 0o100755),
-          ('ad/c', blob_c.id, 0o100644),
-          ('c', blob_c.id, 0o100644),
+          (b'a', blob_a.id, 0o100644),
+          (b'ad/b', blob_b.id, 0o100644),
+          (b'ad/bd/c', blob_c.id, 0o100755),
+          (b'ad/c', blob_c.id, 0o100644),
+          (b'c', blob_c.id, 0o100644),
           ]
           ]
         self.tree_id = commit_tree(self.store, blobs)
         self.tree_id = commit_tree(self.store, blobs)
 
 
@@ -380,24 +382,23 @@ class TreeLookupPathTests(TestCase):
         return self.store[sha]
         return self.store[sha]
 
 
     def test_lookup_blob(self):
     def test_lookup_blob(self):
-        o_id = tree_lookup_path(self.get_object, self.tree_id, 'a')[1]
+        o_id = tree_lookup_path(self.get_object, self.tree_id, b'a')[1]
         self.assertTrue(isinstance(self.store[o_id], Blob))
         self.assertTrue(isinstance(self.store[o_id], Blob))
 
 
     def test_lookup_tree(self):
     def test_lookup_tree(self):
-        o_id = tree_lookup_path(self.get_object, self.tree_id, 'ad')[1]
+        o_id = tree_lookup_path(self.get_object, self.tree_id, b'ad')[1]
         self.assertTrue(isinstance(self.store[o_id], Tree))
         self.assertTrue(isinstance(self.store[o_id], Tree))
-        o_id = tree_lookup_path(self.get_object, self.tree_id, 'ad/bd')[1]
+        o_id = tree_lookup_path(self.get_object, self.tree_id, b'ad/bd')[1]
         self.assertTrue(isinstance(self.store[o_id], Tree))
         self.assertTrue(isinstance(self.store[o_id], Tree))
-        o_id = tree_lookup_path(self.get_object, self.tree_id, 'ad/bd/')[1]
+        o_id = tree_lookup_path(self.get_object, self.tree_id, b'ad/bd/')[1]
         self.assertTrue(isinstance(self.store[o_id], Tree))
         self.assertTrue(isinstance(self.store[o_id], Tree))
 
 
     def test_lookup_nonexistent(self):
     def test_lookup_nonexistent(self):
-        self.assertRaises(KeyError, tree_lookup_path, self.get_object, self.tree_id, 'j')
+        self.assertRaises(KeyError, tree_lookup_path, self.get_object, self.tree_id, b'j')
 
 
     def test_lookup_not_tree(self):
     def test_lookup_not_tree(self):
-        self.assertRaises(NotTreeError, tree_lookup_path, self.get_object, self.tree_id, 'ad/b/j')
+        self.assertRaises(NotTreeError, tree_lookup_path, self.get_object, self.tree_id, b'ad/b/j')
 
 
-@skipIfPY3
 class ObjectStoreGraphWalkerTests(TestCase):
 class ObjectStoreGraphWalkerTests(TestCase):
 
 
     def get_walker(self, heads, parent_map):
     def get_walker(self, heads, parent_map):
@@ -413,30 +414,30 @@ class ObjectStoreGraphWalkerTests(TestCase):
     def test_empty(self):
     def test_empty(self):
         gw = self.get_walker([], {})
         gw = self.get_walker([], {})
         self.assertIs(None, next(gw))
         self.assertIs(None, next(gw))
-        gw.ack("a" * 40)
+        gw.ack(b"a" * 40)
         self.assertIs(None, next(gw))
         self.assertIs(None, next(gw))
 
 
     def test_descends(self):
     def test_descends(self):
-        gw = self.get_walker(["a"], {"a": ["b"], "b": []})
-        self.assertEqual("a" * 40, next(gw))
-        self.assertEqual("b" * 40, next(gw))
+        gw = self.get_walker([b"a"], {b"a": [b"b"], b"b": []})
+        self.assertEqual(b"a" * 40, next(gw))
+        self.assertEqual(b"b" * 40, next(gw))
 
 
     def test_present(self):
     def test_present(self):
-        gw = self.get_walker(["a"], {"a": ["b"], "b": []})
-        gw.ack("a" * 40)
+        gw = self.get_walker([b"a"], {b"a": [b"b"], b"b": []})
+        gw.ack(b"a" * 40)
         self.assertIs(None, next(gw))
         self.assertIs(None, next(gw))
 
 
     def test_parent_present(self):
     def test_parent_present(self):
-        gw = self.get_walker(["a"], {"a": ["b"], "b": []})
-        self.assertEqual("a" * 40, next(gw))
-        gw.ack("a" * 40)
+        gw = self.get_walker([b"a"], {b"a": [b"b"], b"b": []})
+        self.assertEqual(b"a" * 40, next(gw))
+        gw.ack(b"a" * 40)
         self.assertIs(None, next(gw))
         self.assertIs(None, next(gw))
 
 
     def test_child_ack_later(self):
     def test_child_ack_later(self):
-        gw = self.get_walker(["a"], {"a": ["b"], "b": ["c"], "c": []})
-        self.assertEqual("a" * 40, next(gw))
-        self.assertEqual("b" * 40, next(gw))
-        gw.ack("a" * 40)
+        gw = self.get_walker([b"a"], {b"a": [b"b"], b"b": [b"c"], b"c": []})
+        self.assertEqual(b"a" * 40, next(gw))
+        self.assertEqual(b"b" * 40, next(gw))
+        gw.ack(b"a" * 40)
         self.assertIs(None, next(gw))
         self.assertIs(None, next(gw))
 
 
     def test_only_once(self):
     def test_only_once(self):
@@ -445,12 +446,12 @@ class ObjectStoreGraphWalkerTests(TestCase):
         # c  d
         # c  d
         # \ /
         # \ /
         #  e
         #  e
-        gw = self.get_walker(["a", "b"], {
-                "a": ["c"],
-                "b": ["d"],
-                "c": ["e"],
-                "d": ["e"],
-                "e": [],
+        gw = self.get_walker([b"a", b"b"], {
+                b"a": [b"c"],
+                b"b": [b"d"],
+                b"c": [b"e"],
+                b"d": [b"e"],
+                b"e": [],
                 })
                 })
         walk = []
         walk = []
         acked = False
         acked = False
@@ -458,18 +459,18 @@ class ObjectStoreGraphWalkerTests(TestCase):
         walk.append(next(gw))
         walk.append(next(gw))
         # A branch (a, c) or (b, d) may be done after 2 steps or 3 depending on
         # A branch (a, c) or (b, d) may be done after 2 steps or 3 depending on
         # the order walked: 3-step walks include (a, b, c) and (b, a, d), etc.
         # the order walked: 3-step walks include (a, b, c) and (b, a, d), etc.
-        if walk == ["a" * 40, "c" * 40] or walk == ["b" * 40, "d" * 40]:
+        if walk == [b"a" * 40, b"c" * 40] or walk == [b"b" * 40, b"d" * 40]:
           gw.ack(walk[0])
           gw.ack(walk[0])
           acked = True
           acked = True
 
 
         walk.append(next(gw))
         walk.append(next(gw))
-        if not acked and walk[2] == "c" * 40:
-          gw.ack("a" * 40)
-        elif not acked and walk[2] == "d" * 40:
-          gw.ack("b" * 40)
+        if not acked and walk[2] == b"c" * 40:
+          gw.ack(b"a" * 40)
+        elif not acked and walk[2] == b"d" * 40:
+          gw.ack(b"b" * 40)
         walk.append(next(gw))
         walk.append(next(gw))
         self.assertIs(None, next(gw))
         self.assertIs(None, next(gw))
 
 
-        self.assertEqual(["a" * 40, "b" * 40, "c" * 40, "d" * 40], sorted(walk))
-        self.assertLess(walk.index("a" * 40), walk.index("c" * 40))
-        self.assertLess(walk.index("b" * 40), walk.index("d" * 40))
+        self.assertEqual([b"a" * 40, b"b" * 40, b"c" * 40, b"d" * 40], sorted(walk))
+        self.assertLess(walk.index(b"a" * 40), walk.index(b"c" * 40))
+        self.assertLess(walk.index(b"b" * 40), walk.index(b"d" * 40))

+ 73 - 6
dulwich/tests/test_objects.py

@@ -29,7 +29,9 @@ from itertools import (
     )
     )
 import os
 import os
 import stat
 import stat
+import sys
 import warnings
 import warnings
+from contextlib import contextmanager
 
 
 from dulwich.errors import (
 from dulwich.errors import (
     ObjectFormatException,
     ObjectFormatException,
@@ -84,21 +86,23 @@ class BlobReadTests(TestCase):
     """Test decompression of blobs"""
     """Test decompression of blobs"""
 
 
     def get_sha_file(self, cls, base, sha):
     def get_sha_file(self, cls, base, sha):
-        dir = os.path.join(os.path.dirname(__file__), 'data', base)
+        dir = os.path.join(
+            os.path.dirname(__file__.encode(sys.getfilesystemencoding())),
+            b'data', base)
         return cls.from_path(hex_to_filename(dir, sha))
         return cls.from_path(hex_to_filename(dir, sha))
 
 
     def get_blob(self, sha):
     def get_blob(self, sha):
         """Return the blob named sha from the test data dir"""
         """Return the blob named sha from the test data dir"""
-        return self.get_sha_file(Blob, 'blobs', sha)
+        return self.get_sha_file(Blob, b'blobs', sha)
 
 
     def get_tree(self, sha):
     def get_tree(self, sha):
-        return self.get_sha_file(Tree, 'trees', sha)
+        return self.get_sha_file(Tree, b'trees', sha)
 
 
     def get_tag(self, sha):
     def get_tag(self, sha):
-        return self.get_sha_file(Tag, 'tags', sha)
+        return self.get_sha_file(Tag, b'tags', sha)
 
 
     def commit(self, sha):
     def commit(self, sha):
-        return self.get_sha_file(Commit, 'commits', sha)
+        return self.get_sha_file(Commit, b'commits', sha)
 
 
     def test_decompress_simple_blob(self):
     def test_decompress_simple_blob(self):
         b = self.get_blob(a_sha)
         b = self.get_blob(a_sha)
@@ -701,7 +705,9 @@ class TreeTests(ShaFileCheckTests):
         self.assertEqual(_SORTED_TREE_ITEMS, x.items())
         self.assertEqual(_SORTED_TREE_ITEMS, x.items())
 
 
     def _do_test_parse_tree(self, parse_tree):
     def _do_test_parse_tree(self, parse_tree):
-        dir = os.path.join(os.path.dirname(__file__), 'data', 'trees')
+        dir = os.path.join(
+            os.path.dirname(__file__.encode(sys.getfilesystemencoding())),
+            b'data', b'trees')
         o = Tree.from_path(hex_to_filename(dir, tree_sha))
         o = Tree.from_path(hex_to_filename(dir, tree_sha))
         self.assertEqual([(b'a', 0o100644, a_sha), (b'b', 0o100644, b_sha)],
         self.assertEqual([(b'a', 0o100644, a_sha), (b'b', 0o100644, b_sha)],
                          list(parse_tree(o.as_raw_string())))
                          list(parse_tree(o.as_raw_string())))
@@ -1029,3 +1035,64 @@ class ShaFileCopyTests(TestCase):
             tag_time=12345, tag_timezone=0,
             tag_time=12345, tag_timezone=0,
             object=(Commit, b'0' * 40))
             object=(Commit, b'0' * 40))
         self.assert_copy(tag)
         self.assert_copy(tag)
+
+
+class ShaFileSerializeTests(TestCase):
+    """
+    Test that `ShaFile` objects only gets serialized once if they haven't changed.
+    """
+
+    @contextmanager
+    def assert_serialization_on_change(self, obj, needs_serialization_after_change=True):
+        old_id = obj.id
+        self.assertFalse(obj._needs_serialization)
+
+        yield obj
+
+        if needs_serialization_after_change:
+            self.assertTrue(obj._needs_serialization)
+        else:
+            self.assertFalse(obj._needs_serialization)
+        new_id = obj.id
+        self.assertFalse(obj._needs_serialization)
+        self.assertNotEqual(old_id, new_id)
+
+    def test_commit_serialize(self):
+        attrs = {'tree': b'd80c186a03f423a81b39df39dc87fd269736ca86',
+                 'parents': [b'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
+                             b'4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'],
+                 'author': b'James Westby <jw+debian@jameswestby.net>',
+                 'committer': b'James Westby <jw+debian@jameswestby.net>',
+                 'commit_time': 1174773719,
+                 'author_time': 1174773719,
+                 'commit_timezone': 0,
+                 'author_timezone': 0,
+                 'message':  b'Merge ../b\n'}
+        commit = make_commit(**attrs)
+
+        with self.assert_serialization_on_change(commit):
+            commit.parents = [b'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd']
+
+    def test_blob_serialize(self):
+        blob = make_object(Blob, data=b'i am a blob')
+
+        with self.assert_serialization_on_change(blob, needs_serialization_after_change=False):
+            blob.data = b'i am another blob'
+
+    def test_tree_serialize(self):
+        blob = make_object(Blob, data=b'i am a blob')
+        tree = Tree()
+        tree[b'blob'] = (stat.S_IFREG, blob.id)
+
+        with self.assert_serialization_on_change(tree):
+            tree[b'blob2'] = (stat.S_IFREG, blob.id)
+
+    def test_tag_serialize(self):
+        tag = make_object(
+            Tag, name=b'tag', message=b'',
+            tagger=b'Tagger <test@example.com>',
+            tag_time=12345, tag_timezone=0,
+            object=(Commit, b'0' * 40))
+
+        with self.assert_serialization_on_change(tag):
+            tag.message = b'new message'

+ 1 - 4
dulwich/tests/test_objectspec.py

@@ -35,11 +35,9 @@ from dulwich.tests import (
     )
     )
 from dulwich.tests.utils import (
 from dulwich.tests.utils import (
     build_commit_graph,
     build_commit_graph,
-    skipIfPY3,
     )
     )
 
 
 
 
-@skipIfPY3
 class ParseObjectTests(TestCase):
 class ParseObjectTests(TestCase):
     """Test parse_object."""
     """Test parse_object."""
 
 
@@ -49,12 +47,11 @@ class ParseObjectTests(TestCase):
 
 
     def test_blob_by_sha(self):
     def test_blob_by_sha(self):
         r = MemoryRepo()
         r = MemoryRepo()
-        b = Blob.from_string("Blah")
+        b = Blob.from_string(b"Blah")
         r.object_store.add_object(b)
         r.object_store.add_object(b)
         self.assertEqual(b, parse_object(r, b.id))
         self.assertEqual(b, parse_object(r, b.id))
 
 
 
 
-@skipIfPY3
 class ParseCommitRangeTests(TestCase):
 class ParseCommitRangeTests(TestCase):
     """Test parse_commit_range."""
     """Test parse_commit_range."""
 
 

+ 197 - 204
dulwich/tests/test_pack.py

@@ -24,6 +24,7 @@ from io import BytesIO
 from hashlib import sha1
 from hashlib import sha1
 import os
 import os
 import shutil
 import shutil
+import sys
 import tempfile
 import tempfile
 import zlib
 import zlib
 
 
@@ -74,38 +75,39 @@ from dulwich.tests import (
 from dulwich.tests.utils import (
 from dulwich.tests.utils import (
     make_object,
     make_object,
     build_pack,
     build_pack,
-    skipIfPY3,
     )
     )
 
 
-pack1_sha = 'bc63ddad95e7321ee734ea11a7a62d314e0d7481'
+pack1_sha = b'bc63ddad95e7321ee734ea11a7a62d314e0d7481'
 
 
-a_sha = '6f670c0fb53f9463760b7295fbb814e965fb20c8'
-tree_sha = 'b2a2766a2879c209ab1176e7e778b81ae422eeaa'
-commit_sha = 'f18faa16531ac570a3fdc8c7ca16682548dafd12'
+a_sha = b'6f670c0fb53f9463760b7295fbb814e965fb20c8'
+tree_sha = b'b2a2766a2879c209ab1176e7e778b81ae422eeaa'
+commit_sha = b'f18faa16531ac570a3fdc8c7ca16682548dafd12'
 
 
 
 
-@skipIfPY3
 class PackTests(TestCase):
 class PackTests(TestCase):
     """Base class for testing packs"""
     """Base class for testing packs"""
 
 
     def setUp(self):
     def setUp(self):
         super(PackTests, self).setUp()
         super(PackTests, self).setUp()
         self.tempdir = tempfile.mkdtemp()
         self.tempdir = tempfile.mkdtemp()
+        if not isinstance(self.tempdir, bytes):
+            self.tempdir = self.tempdir.encode(sys.getfilesystemencoding())
         self.addCleanup(shutil.rmtree, self.tempdir)
         self.addCleanup(shutil.rmtree, self.tempdir)
 
 
-    datadir = os.path.abspath(os.path.join(os.path.dirname(__file__),
-        'data/packs'))
+    datadir = os.path.abspath(
+        os.path.join(os.path.dirname(__file__.encode(sys.getfilesystemencoding())),
+        b'data/packs'))
 
 
     def get_pack_index(self, sha):
     def get_pack_index(self, sha):
         """Returns a PackIndex from the datadir with the given sha"""
         """Returns a PackIndex from the datadir with the given sha"""
-        return load_pack_index(os.path.join(self.datadir, 'pack-%s.idx' % sha))
+        return load_pack_index(os.path.join(self.datadir, b'pack-' + sha + b'.idx'))
 
 
     def get_pack_data(self, sha):
     def get_pack_data(self, sha):
         """Returns a PackData object from the datadir with the given sha"""
         """Returns a PackData object from the datadir with the given sha"""
-        return PackData(os.path.join(self.datadir, 'pack-%s.pack' % sha))
+        return PackData(os.path.join(self.datadir, b'pack-' + sha + b'.pack'))
 
 
     def get_pack(self, sha):
     def get_pack(self, sha):
-        return Pack(os.path.join(self.datadir, 'pack-%s' % sha))
+        return Pack(os.path.join(self.datadir, b'pack-' + sha))
 
 
     def assertSucceeds(self, func, *args, **kwargs):
     def assertSucceeds(self, func, *args, **kwargs):
         try:
         try:
@@ -114,7 +116,6 @@ class PackTests(TestCase):
             self.fail(e)
             self.fail(e)
 
 
 
 
-@skipIfPY3
 class PackIndexTests(PackTests):
 class PackIndexTests(PackTests):
     """Class that tests the index of packfiles"""
     """Class that tests the index of packfiles"""
 
 
@@ -132,10 +133,10 @@ class PackIndexTests(PackTests):
 
 
     def test_get_stored_checksum(self):
     def test_get_stored_checksum(self):
         p = self.get_pack_index(pack1_sha)
         p = self.get_pack_index(pack1_sha)
-        self.assertEqual('f2848e2ad16f329ae1c92e3b95e91888daa5bd01',
-                          sha_to_hex(p.get_stored_checksum()))
-        self.assertEqual('721980e866af9a5f93ad674144e1459b8ba3e7b7',
-                          sha_to_hex(p.get_pack_checksum()))
+        self.assertEqual(b'f2848e2ad16f329ae1c92e3b95e91888daa5bd01',
+                         sha_to_hex(p.get_stored_checksum()))
+        self.assertEqual(b'721980e866af9a5f93ad674144e1459b8ba3e7b7',
+                         sha_to_hex(p.get_pack_checksum()))
 
 
     def test_index_check(self):
     def test_index_check(self):
         p = self.get_pack_index(pack1_sha)
         p = self.get_pack_index(pack1_sha)
@@ -145,30 +146,29 @@ class PackIndexTests(PackTests):
         p = self.get_pack_index(pack1_sha)
         p = self.get_pack_index(pack1_sha)
         entries = [(sha_to_hex(s), o, c) for s, o, c in p.iterentries()]
         entries = [(sha_to_hex(s), o, c) for s, o, c in p.iterentries()]
         self.assertEqual([
         self.assertEqual([
-          ('6f670c0fb53f9463760b7295fbb814e965fb20c8', 178, None),
-          ('b2a2766a2879c209ab1176e7e778b81ae422eeaa', 138, None),
-          ('f18faa16531ac570a3fdc8c7ca16682548dafd12', 12, None)
-          ], entries)
+            (b'6f670c0fb53f9463760b7295fbb814e965fb20c8', 178, None),
+            (b'b2a2766a2879c209ab1176e7e778b81ae422eeaa', 138, None),
+            (b'f18faa16531ac570a3fdc8c7ca16682548dafd12', 12, None)
+        ], entries)
 
 
     def test_iter(self):
     def test_iter(self):
         p = self.get_pack_index(pack1_sha)
         p = self.get_pack_index(pack1_sha)
         self.assertEqual(set([tree_sha, commit_sha, a_sha]), set(p))
         self.assertEqual(set([tree_sha, commit_sha, a_sha]), set(p))
 
 
 
 
-@skipIfPY3
 class TestPackDeltas(TestCase):
 class TestPackDeltas(TestCase):
 
 
-    test_string1 = 'The answer was flailing in the wind'
-    test_string2 = 'The answer was falling down the pipe'
-    test_string3 = 'zzzzz'
+    test_string1 = b'The answer was flailing in the wind'
+    test_string2 = b'The answer was falling down the pipe'
+    test_string3 = b'zzzzz'
 
 
-    test_string_empty = ''
-    test_string_big = 'Z' * 8192
-    test_string_huge = 'Z' * 100000
+    test_string_empty = b''
+    test_string_big = b'Z' * 8192
+    test_string_huge = b'Z' * 100000
 
 
     def _test_roundtrip(self, base, target):
     def _test_roundtrip(self, base, target):
         self.assertEqual(target,
         self.assertEqual(target,
-                          ''.join(apply_delta(base, create_delta(base, target))))
+                          b''.join(apply_delta(base, create_delta(base, target))))
 
 
     def test_nochange(self):
     def test_nochange(self):
         self._test_roundtrip(self.test_string1, self.test_string1)
         self._test_roundtrip(self.test_string1, self.test_string1)
@@ -195,13 +195,12 @@ class TestPackDeltas(TestCase):
     def test_dest_overflow(self):
     def test_dest_overflow(self):
         self.assertRaises(
         self.assertRaises(
             ApplyDeltaError,
             ApplyDeltaError,
-            apply_delta, 'a'*0x10000, '\x80\x80\x04\x80\x80\x04\x80' + 'a'*0x10000)
+            apply_delta, b'a'*0x10000, b'\x80\x80\x04\x80\x80\x04\x80' + b'a'*0x10000)
         self.assertRaises(
         self.assertRaises(
             ApplyDeltaError,
             ApplyDeltaError,
-            apply_delta, '', '\x00\x80\x02\xb0\x11\x11')
+            apply_delta, b'', b'\x00\x80\x02\xb0\x11\x11')
 
 
 
 
-@skipIfPY3
 class TestPackData(PackTests):
 class TestPackData(PackTests):
     """Tests getting the data from the packfile."""
     """Tests getting the data from the packfile."""
 
 
@@ -209,8 +208,9 @@ class TestPackData(PackTests):
         self.get_pack_data(pack1_sha).close()
         self.get_pack_data(pack1_sha).close()
 
 
     def test_from_file(self):
     def test_from_file(self):
-        path = os.path.join(self.datadir, 'pack-%s.pack' % pack1_sha)
-        PackData.from_file(open(path), os.path.getsize(path))
+        path = os.path.join(self.datadir, b'pack-' + pack1_sha + b'.pack')
+        with open(path, 'rb') as f:
+            PackData.from_file(f, os.path.getsize(path))
 
 
     def test_pack_len(self):
     def test_pack_len(self):
         with self.get_pack_data(pack1_sha) as p:
         with self.get_pack_data(pack1_sha) as p:
@@ -222,36 +222,36 @@ class TestPackData(PackTests):
 
 
     def test_iterobjects(self):
     def test_iterobjects(self):
         with self.get_pack_data(pack1_sha) as p:
         with self.get_pack_data(pack1_sha) as p:
-            commit_data = ('tree b2a2766a2879c209ab1176e7e778b81ae422eeaa\n'
-                           'author James Westby <jw+debian@jameswestby.net> '
-                           '1174945067 +0100\n'
-                           'committer James Westby <jw+debian@jameswestby.net> '
-                           '1174945067 +0100\n'
-                           '\n'
-                           'Test commit\n')
-            blob_sha = '6f670c0fb53f9463760b7295fbb814e965fb20c8'
-            tree_data = '100644 a\0%s' % hex_to_sha(blob_sha)
+            commit_data = (b'tree b2a2766a2879c209ab1176e7e778b81ae422eeaa\n'
+                           b'author James Westby <jw+debian@jameswestby.net> '
+                           b'1174945067 +0100\n'
+                           b'committer James Westby <jw+debian@jameswestby.net> '
+                           b'1174945067 +0100\n'
+                           b'\n'
+                           b'Test commit\n')
+            blob_sha = b'6f670c0fb53f9463760b7295fbb814e965fb20c8'
+            tree_data = b'100644 a\0' + hex_to_sha(blob_sha)
             actual = []
             actual = []
             for offset, type_num, chunks, crc32 in p.iterobjects():
             for offset, type_num, chunks, crc32 in p.iterobjects():
-                actual.append((offset, type_num, ''.join(chunks), crc32))
+                actual.append((offset, type_num, b''.join(chunks), crc32))
             self.assertEqual([
             self.assertEqual([
-              (12, 1, commit_data, 3775879613),
-              (138, 2, tree_data, 912998690),
-              (178, 3, 'test 1\n', 1373561701)
-              ], actual)
+                (12, 1, commit_data, 3775879613),
+                (138, 2, tree_data, 912998690),
+                (178, 3, b'test 1\n', 1373561701)
+                ], actual)
 
 
     def test_iterentries(self):
     def test_iterentries(self):
         with self.get_pack_data(pack1_sha) as p:
         with self.get_pack_data(pack1_sha) as p:
             entries = set((sha_to_hex(s), o, c) for s, o, c in p.iterentries())
             entries = set((sha_to_hex(s), o, c) for s, o, c in p.iterentries())
             self.assertEqual(set([
             self.assertEqual(set([
-              ('6f670c0fb53f9463760b7295fbb814e965fb20c8', 178, 1373561701),
-              ('b2a2766a2879c209ab1176e7e778b81ae422eeaa', 138, 912998690),
-              ('f18faa16531ac570a3fdc8c7ca16682548dafd12', 12, 3775879613),
+              (b'6f670c0fb53f9463760b7295fbb814e965fb20c8', 178, 1373561701),
+              (b'b2a2766a2879c209ab1176e7e778b81ae422eeaa', 138, 912998690),
+              (b'f18faa16531ac570a3fdc8c7ca16682548dafd12', 12, 3775879613),
               ]), entries)
               ]), entries)
 
 
     def test_create_index_v1(self):
     def test_create_index_v1(self):
         with self.get_pack_data(pack1_sha) as p:
         with self.get_pack_data(pack1_sha) as p:
-            filename = os.path.join(self.tempdir, 'v1test.idx')
+            filename = os.path.join(self.tempdir, b'v1test.idx')
             p.create_index_v1(filename)
             p.create_index_v1(filename)
             idx1 = load_pack_index(filename)
             idx1 = load_pack_index(filename)
             idx2 = self.get_pack_index(pack1_sha)
             idx2 = self.get_pack_index(pack1_sha)
@@ -259,35 +259,34 @@ class TestPackData(PackTests):
 
 
     def test_create_index_v2(self):
     def test_create_index_v2(self):
         with self.get_pack_data(pack1_sha) as p:
         with self.get_pack_data(pack1_sha) as p:
-            filename = os.path.join(self.tempdir, 'v2test.idx')
+            filename = os.path.join(self.tempdir, b'v2test.idx')
             p.create_index_v2(filename)
             p.create_index_v2(filename)
             idx1 = load_pack_index(filename)
             idx1 = load_pack_index(filename)
             idx2 = self.get_pack_index(pack1_sha)
             idx2 = self.get_pack_index(pack1_sha)
             self.assertEqual(idx1, idx2)
             self.assertEqual(idx1, idx2)
 
 
     def test_compute_file_sha(self):
     def test_compute_file_sha(self):
-        f = BytesIO('abcd1234wxyz')
-        self.assertEqual(sha1('abcd1234wxyz').hexdigest(),
+        f = BytesIO(b'abcd1234wxyz')
+        self.assertEqual(sha1(b'abcd1234wxyz').hexdigest(),
                          compute_file_sha(f).hexdigest())
                          compute_file_sha(f).hexdigest())
-        self.assertEqual(sha1('abcd1234wxyz').hexdigest(),
+        self.assertEqual(sha1(b'abcd1234wxyz').hexdigest(),
                          compute_file_sha(f, buffer_size=5).hexdigest())
                          compute_file_sha(f, buffer_size=5).hexdigest())
-        self.assertEqual(sha1('abcd1234').hexdigest(),
+        self.assertEqual(sha1(b'abcd1234').hexdigest(),
                          compute_file_sha(f, end_ofs=-4).hexdigest())
                          compute_file_sha(f, end_ofs=-4).hexdigest())
-        self.assertEqual(sha1('1234wxyz').hexdigest(),
+        self.assertEqual(sha1(b'1234wxyz').hexdigest(),
                          compute_file_sha(f, start_ofs=4).hexdigest())
                          compute_file_sha(f, start_ofs=4).hexdigest())
         self.assertEqual(
         self.assertEqual(
-          sha1('1234').hexdigest(),
-          compute_file_sha(f, start_ofs=4, end_ofs=-4).hexdigest())
+            sha1(b'1234').hexdigest(),
+            compute_file_sha(f, start_ofs=4, end_ofs=-4).hexdigest())
 
 
     def test_compute_file_sha_short_file(self):
     def test_compute_file_sha_short_file(self):
-        f = BytesIO('abcd1234wxyz')
+        f = BytesIO(b'abcd1234wxyz')
         self.assertRaises(AssertionError, compute_file_sha, f, end_ofs=-20)
         self.assertRaises(AssertionError, compute_file_sha, f, end_ofs=-20)
         self.assertRaises(AssertionError, compute_file_sha, f, end_ofs=20)
         self.assertRaises(AssertionError, compute_file_sha, f, end_ofs=20)
         self.assertRaises(AssertionError, compute_file_sha, f, start_ofs=10,
         self.assertRaises(AssertionError, compute_file_sha, f, start_ofs=10,
             end_ofs=-12)
             end_ofs=-12)
 
 
 
 
-@skipIfPY3
 class TestPack(PackTests):
 class TestPack(PackTests):
 
 
     def test_len(self):
     def test_len(self):
@@ -323,19 +322,19 @@ class TestPack(PackTests):
         """Tests random access for non-delta objects"""
         """Tests random access for non-delta objects"""
         with self.get_pack(pack1_sha) as p:
         with self.get_pack(pack1_sha) as p:
             obj = p[a_sha]
             obj = p[a_sha]
-            self.assertEqual(obj.type_name, 'blob')
-            self.assertEqual(obj.sha().hexdigest(), a_sha)
+            self.assertEqual(obj.type_name, b'blob')
+            self.assertEqual(obj.sha().hexdigest().encode('ascii'), a_sha)
             obj = p[tree_sha]
             obj = p[tree_sha]
-            self.assertEqual(obj.type_name, 'tree')
-            self.assertEqual(obj.sha().hexdigest(), tree_sha)
+            self.assertEqual(obj.type_name, b'tree')
+            self.assertEqual(obj.sha().hexdigest().encode('ascii'), tree_sha)
             obj = p[commit_sha]
             obj = p[commit_sha]
-            self.assertEqual(obj.type_name, 'commit')
-            self.assertEqual(obj.sha().hexdigest(), commit_sha)
+            self.assertEqual(obj.type_name, b'commit')
+            self.assertEqual(obj.sha().hexdigest().encode('ascii'), commit_sha)
 
 
     def test_copy(self):
     def test_copy(self):
         with self.get_pack(pack1_sha) as origpack:
         with self.get_pack(pack1_sha) as origpack:
             self.assertSucceeds(origpack.index.check)
             self.assertSucceeds(origpack.index.check)
-            basename = os.path.join(self.tempdir, 'Elch')
+            basename = os.path.join(self.tempdir, b'Elch')
             write_pack(basename, origpack.pack_tuples())
             write_pack(basename, origpack.pack_tuples())
 
 
             with Pack(basename) as newpack:
             with Pack(basename) as newpack:
@@ -353,12 +352,12 @@ class TestPack(PackTests):
     def test_commit_obj(self):
     def test_commit_obj(self):
         with self.get_pack(pack1_sha) as p:
         with self.get_pack(pack1_sha) as p:
             commit = p[commit_sha]
             commit = p[commit_sha]
-            self.assertEqual('James Westby <jw+debian@jameswestby.net>',
+            self.assertEqual(b'James Westby <jw+debian@jameswestby.net>',
                              commit.author)
                              commit.author)
             self.assertEqual([], commit.parents)
             self.assertEqual([], commit.parents)
 
 
     def _copy_pack(self, origpack):
     def _copy_pack(self, origpack):
-        basename = os.path.join(self.tempdir, 'somepack')
+        basename = os.path.join(self.tempdir, b'somepack')
         write_pack(basename, origpack.pack_tuples())
         write_pack(basename, origpack.pack_tuples())
         return Pack(basename)
         return Pack(basename)
 
 
@@ -380,7 +379,7 @@ class TestPack(PackTests):
         with self.get_pack(pack1_sha) as p:
         with self.get_pack(pack1_sha) as p:
             p = self._copy_pack(p)
             p = self._copy_pack(p)
 
 
-        msg = 'some message'
+        msg = b'some message'
         with p:
         with p:
             keepfile_name = p.keep(msg)
             keepfile_name = p.keep(msg)
 
 
@@ -388,9 +387,9 @@ class TestPack(PackTests):
         self.assertTrue(os.path.exists(keepfile_name))
         self.assertTrue(os.path.exists(keepfile_name))
 
 
         # and contain the right message, with a linefeed
         # and contain the right message, with a linefeed
-        with open(keepfile_name, 'r') as f:
+        with open(keepfile_name, 'rb') as f:
             buf = f.read()
             buf = f.read()
-            self.assertEqual(msg + '\n', buf)
+            self.assertEqual(msg + b'\n', buf)
 
 
     def test_name(self):
     def test_name(self):
         with self.get_pack(pack1_sha) as p:
         with self.get_pack(pack1_sha) as p:
@@ -406,7 +405,7 @@ class TestPack(PackTests):
             write_pack_header(bad_file, 9999)
             write_pack_header(bad_file, 9999)
             bad_file.write(data._file.read())
             bad_file.write(data._file.read())
             bad_file = BytesIO(bad_file.getvalue())
             bad_file = BytesIO(bad_file.getvalue())
-            bad_data = PackData('', file=bad_file)
+            bad_data = PackData(b'', file=bad_file)
             bad_pack = Pack.from_lazy_objects(lambda: bad_data, lambda: index)
             bad_pack = Pack.from_lazy_objects(lambda: bad_data, lambda: index)
             self.assertRaises(AssertionError, lambda: bad_pack.data)
             self.assertRaises(AssertionError, lambda: bad_pack.data)
             self.assertRaises(AssertionError,
             self.assertRaises(AssertionError,
@@ -418,8 +417,8 @@ class TestPack(PackTests):
             Pack.from_objects(data, index).check_length_and_checksum()
             Pack.from_objects(data, index).check_length_and_checksum()
 
 
             data._file.seek(0)
             data._file.seek(0)
-            bad_file = BytesIO(data._file.read()[:-20] + ('\xff' * 20))
-            bad_data = PackData('', file=bad_file)
+            bad_file = BytesIO(data._file.read()[:-20] + (b'\xff' * 20))
+            bad_data = PackData(b'', file=bad_file)
             bad_pack = Pack.from_lazy_objects(lambda: bad_data, lambda: index)
             bad_pack = Pack.from_lazy_objects(lambda: bad_data, lambda: index)
             self.assertRaises(ChecksumMismatch, lambda: bad_pack.data)
             self.assertRaises(ChecksumMismatch, lambda: bad_pack.data)
             self.assertRaises(ChecksumMismatch, lambda:
             self.assertRaises(ChecksumMismatch, lambda:
@@ -435,38 +434,39 @@ class TestPack(PackTests):
             self.assertTrue(isinstance(objs[commit_sha], Commit))
             self.assertTrue(isinstance(objs[commit_sha], Commit))
 
 
 
 
-@skipIfPY3
 class TestThinPack(PackTests):
 class TestThinPack(PackTests):
 
 
     def setUp(self):
     def setUp(self):
         super(TestThinPack, self).setUp()
         super(TestThinPack, self).setUp()
         self.store = MemoryObjectStore()
         self.store = MemoryObjectStore()
         self.blobs = {}
         self.blobs = {}
-        for blob in ('foo', 'bar', 'foo1234', 'bar2468'):
+        for blob in (b'foo', b'bar', b'foo1234', b'bar2468'):
             self.blobs[blob] = make_object(Blob, data=blob)
             self.blobs[blob] = make_object(Blob, data=blob)
-        self.store.add_object(self.blobs['foo'])
-        self.store.add_object(self.blobs['bar'])
+        self.store.add_object(self.blobs[b'foo'])
+        self.store.add_object(self.blobs[b'bar'])
 
 
         # Build a thin pack. 'foo' is as an external reference, 'bar' an
         # Build a thin pack. 'foo' is as an external reference, 'bar' an
         # internal reference.
         # internal reference.
         self.pack_dir = tempfile.mkdtemp()
         self.pack_dir = tempfile.mkdtemp()
+        if not isinstance(self.pack_dir, bytes):
+            self.pack_dir = self.pack_dir.encode(sys.getfilesystemencoding())
         self.addCleanup(shutil.rmtree, self.pack_dir)
         self.addCleanup(shutil.rmtree, self.pack_dir)
-        self.pack_prefix = os.path.join(self.pack_dir, 'pack')
+        self.pack_prefix = os.path.join(self.pack_dir, b'pack')
 
 
-        with open(self.pack_prefix + '.pack', 'wb') as f:
+        with open(self.pack_prefix + b'.pack', 'wb') as f:
             build_pack(f, [
             build_pack(f, [
-                (REF_DELTA, (self.blobs['foo'].id, 'foo1234')),
-                (Blob.type_num, 'bar'),
-                (REF_DELTA, (self.blobs['bar'].id, 'bar2468'))],
+                (REF_DELTA, (self.blobs[b'foo'].id, b'foo1234')),
+                (Blob.type_num, b'bar'),
+                (REF_DELTA, (self.blobs[b'bar'].id, b'bar2468'))],
                 store=self.store)
                 store=self.store)
 
 
         # Index the new pack.
         # Index the new pack.
         with self.make_pack(True) as pack:
         with self.make_pack(True) as pack:
             with PackData(pack._data_path) as data:
             with PackData(pack._data_path) as data:
                 data.pack = pack
                 data.pack = pack
-                data.create_index(self.pack_prefix + '.idx')
+                data.create_index(self.pack_prefix + b'.idx')
 
 
-        del self.store[self.blobs['bar'].id]
+        del self.store[self.blobs[b'bar'].id]
 
 
     def make_pack(self, resolve_ext_ref):
     def make_pack(self, resolve_ext_ref):
         return Pack(
         return Pack(
@@ -476,54 +476,53 @@ class TestThinPack(PackTests):
     def test_get_raw(self):
     def test_get_raw(self):
         with self.make_pack(False) as p:
         with self.make_pack(False) as p:
             self.assertRaises(
             self.assertRaises(
-                KeyError, p.get_raw, self.blobs['foo1234'].id)
+                KeyError, p.get_raw, self.blobs[b'foo1234'].id)
         with self.make_pack(True) as p:
         with self.make_pack(True) as p:
             self.assertEqual(
             self.assertEqual(
-                (3, 'foo1234'),
-                p.get_raw(self.blobs['foo1234'].id))
+                (3, b'foo1234'),
+                p.get_raw(self.blobs[b'foo1234'].id))
 
 
     def test_iterobjects(self):
     def test_iterobjects(self):
         with self.make_pack(False) as p:
         with self.make_pack(False) as p:
             self.assertRaises(KeyError, list, p.iterobjects())
             self.assertRaises(KeyError, list, p.iterobjects())
         with self.make_pack(True) as p:
         with self.make_pack(True) as p:
             self.assertEqual(
             self.assertEqual(
-                sorted([self.blobs['foo1234'].id, self.blobs[b'bar'].id,
-                        self.blobs['bar2468'].id]),
+                sorted([self.blobs[b'foo1234'].id, self.blobs[b'bar'].id,
+                        self.blobs[b'bar2468'].id]),
                 sorted(o.id for o in p.iterobjects()))
                 sorted(o.id for o in p.iterobjects()))
 
 
 
 
-@skipIfPY3
 class WritePackTests(TestCase):
 class WritePackTests(TestCase):
 
 
     def test_write_pack_header(self):
     def test_write_pack_header(self):
         f = BytesIO()
         f = BytesIO()
         write_pack_header(f, 42)
         write_pack_header(f, 42)
-        self.assertEqual('PACK\x00\x00\x00\x02\x00\x00\x00*',
-                f.getvalue())
+        self.assertEqual(b'PACK\x00\x00\x00\x02\x00\x00\x00*',
+                         f.getvalue())
 
 
     def test_write_pack_object(self):
     def test_write_pack_object(self):
         f = BytesIO()
         f = BytesIO()
-        f.write('header')
+        f.write(b'header')
         offset = f.tell()
         offset = f.tell()
-        crc32 = write_pack_object(f, Blob.type_num, 'blob')
+        crc32 = write_pack_object(f, Blob.type_num, b'blob')
         self.assertEqual(crc32, zlib.crc32(f.getvalue()[6:]) & 0xffffffff)
         self.assertEqual(crc32, zlib.crc32(f.getvalue()[6:]) & 0xffffffff)
 
 
-        f.write('x')  # unpack_object needs extra trailing data.
+        f.write(b'x')  # unpack_object needs extra trailing data.
         f.seek(offset)
         f.seek(offset)
         unpacked, unused = unpack_object(f.read, compute_crc32=True)
         unpacked, unused = unpack_object(f.read, compute_crc32=True)
         self.assertEqual(Blob.type_num, unpacked.pack_type_num)
         self.assertEqual(Blob.type_num, unpacked.pack_type_num)
         self.assertEqual(Blob.type_num, unpacked.obj_type_num)
         self.assertEqual(Blob.type_num, unpacked.obj_type_num)
-        self.assertEqual(['blob'], unpacked.decomp_chunks)
+        self.assertEqual([b'blob'], unpacked.decomp_chunks)
         self.assertEqual(crc32, unpacked.crc32)
         self.assertEqual(crc32, unpacked.crc32)
-        self.assertEqual('x', unused)
+        self.assertEqual(b'x', unused)
 
 
     def test_write_pack_object_sha(self):
     def test_write_pack_object_sha(self):
         f = BytesIO()
         f = BytesIO()
-        f.write('header')
+        f.write(b'header')
         offset = f.tell()
         offset = f.tell()
-        sha_a = sha1('foo')
+        sha_a = sha1(b'foo')
         sha_b = sha_a.copy()
         sha_b = sha_a.copy()
-        write_pack_object(f, Blob.type_num, 'blob', sha=sha_a)
+        write_pack_object(f, Blob.type_num, b'blob', sha=sha_a)
         self.assertNotEqual(sha_a.digest(), sha_b.digest())
         self.assertNotEqual(sha_a.digest(), sha_b.digest())
         sha_b.update(f.getvalue()[offset:])
         sha_b.update(f.getvalue()[offset:])
         self.assertEqual(sha_a.digest(), sha_b.digest())
         self.assertEqual(sha_a.digest(), sha_b.digest())
@@ -544,7 +543,7 @@ class BaseTestPackIndexWriting(object):
         raise NotImplementedError(self.index)
         raise NotImplementedError(self.index)
 
 
     def test_empty(self):
     def test_empty(self):
-        idx = self.index('empty.idx', [], pack_checksum)
+        idx = self.index(b'empty.idx', [], pack_checksum)
         self.assertEqual(idx.get_pack_checksum(), pack_checksum)
         self.assertEqual(idx.get_pack_checksum(), pack_checksum)
         self.assertEqual(0, len(idx))
         self.assertEqual(0, len(idx))
 
 
@@ -557,7 +556,7 @@ class BaseTestPackIndexWriting(object):
             self.assertRaises(TypeError, self.index, 'single.idx',
             self.assertRaises(TypeError, self.index, 'single.idx',
                 entries, pack_checksum)
                 entries, pack_checksum)
             return
             return
-        idx = self.index('single.idx', entries, pack_checksum)
+        idx = self.index(b'single.idx', entries, pack_checksum)
         self.assertEqual(idx.get_pack_checksum(), pack_checksum)
         self.assertEqual(idx.get_pack_checksum(), pack_checksum)
         self.assertEqual(2, len(idx))
         self.assertEqual(2, len(idx))
         actual_entries = list(idx.iterentries())
         actual_entries = list(idx.iterentries())
@@ -575,7 +574,7 @@ class BaseTestPackIndexWriting(object):
     def test_single(self):
     def test_single(self):
         entry_sha = hex_to_sha('6f670c0fb53f9463760b7295fbb814e965fb20c8')
         entry_sha = hex_to_sha('6f670c0fb53f9463760b7295fbb814e965fb20c8')
         my_entries = [(entry_sha, 178, 42)]
         my_entries = [(entry_sha, 178, 42)]
-        idx = self.index('single.idx', my_entries, pack_checksum)
+        idx = self.index(b'single.idx', my_entries, pack_checksum)
         self.assertEqual(idx.get_pack_checksum(), pack_checksum)
         self.assertEqual(idx.get_pack_checksum(), pack_checksum)
         self.assertEqual(1, len(idx))
         self.assertEqual(1, len(idx))
         actual_entries = list(idx.iterentries())
         actual_entries = list(idx.iterentries())
@@ -595,6 +594,8 @@ class BaseTestFilePackIndexWriting(BaseTestPackIndexWriting):
 
 
     def setUp(self):
     def setUp(self):
         self.tempdir = tempfile.mkdtemp()
         self.tempdir = tempfile.mkdtemp()
+        if not isinstance(self.tempdir, bytes):
+            self.tempdir = self.tempdir.encode(sys.getfilesystemencoding())
 
 
     def tearDown(self):
     def tearDown(self):
         shutil.rmtree(self.tempdir)
         shutil.rmtree(self.tempdir)
@@ -613,7 +614,6 @@ class BaseTestFilePackIndexWriting(BaseTestPackIndexWriting):
             self._write_fn(f, entries, pack_checksum)
             self._write_fn(f, entries, pack_checksum)
 
 
 
 
-@skipIfPY3
 class TestMemoryIndexWriting(TestCase, BaseTestPackIndexWriting):
 class TestMemoryIndexWriting(TestCase, BaseTestPackIndexWriting):
 
 
     def setUp(self):
     def setUp(self):
@@ -628,7 +628,6 @@ class TestMemoryIndexWriting(TestCase, BaseTestPackIndexWriting):
         TestCase.tearDown(self)
         TestCase.tearDown(self)
 
 
 
 
-@skipIfPY3
 class TestPackIndexWritingv1(TestCase, BaseTestFilePackIndexWriting):
 class TestPackIndexWritingv1(TestCase, BaseTestFilePackIndexWriting):
 
 
     def setUp(self):
     def setUp(self):
@@ -644,7 +643,6 @@ class TestPackIndexWritingv1(TestCase, BaseTestFilePackIndexWriting):
         BaseTestFilePackIndexWriting.tearDown(self)
         BaseTestFilePackIndexWriting.tearDown(self)
 
 
 
 
-@skipIfPY3
 class TestPackIndexWritingv2(TestCase, BaseTestFilePackIndexWriting):
 class TestPackIndexWritingv2(TestCase, BaseTestFilePackIndexWriting):
 
 
     def setUp(self):
     def setUp(self):
@@ -660,7 +658,6 @@ class TestPackIndexWritingv2(TestCase, BaseTestFilePackIndexWriting):
         BaseTestFilePackIndexWriting.tearDown(self)
         BaseTestFilePackIndexWriting.tearDown(self)
 
 
 
 
-@skipIfPY3
 class ReadZlibTests(TestCase):
 class ReadZlibTests(TestCase):
 
 
     decomp = (
     decomp = (
@@ -671,7 +668,7 @@ class ReadZlibTests(TestCase):
       b'\n'
       b'\n'
       b"Provide replacement for mmap()'s offset argument.")
       b"Provide replacement for mmap()'s offset argument.")
     comp = zlib.compress(decomp)
     comp = zlib.compress(decomp)
-    extra = 'nextobject'
+    extra = b'nextobject'
 
 
     def setUp(self):
     def setUp(self):
         super(ReadZlibTests, self).setUp()
         super(ReadZlibTests, self).setUp()
@@ -699,11 +696,11 @@ class ReadZlibTests(TestCase):
 
 
     def test_decompress_empty(self):
     def test_decompress_empty(self):
         unpacked = UnpackedObject(Tree.type_num, None, 0, None)
         unpacked = UnpackedObject(Tree.type_num, None, 0, None)
-        comp = zlib.compress('')
+        comp = zlib.compress(b'')
         read = BytesIO(comp + self.extra).read
         read = BytesIO(comp + self.extra).read
         unused = read_zlib_chunks(read, unpacked)
         unused = read_zlib_chunks(read, unpacked)
-        self.assertEqual('', ''.join(unpacked.decomp_chunks))
-        self.assertNotEqual('', unused)
+        self.assertEqual(b'', b''.join(unpacked.decomp_chunks))
+        self.assertNotEqual(b'', unused)
         self.assertEqual(self.extra, unused + read())
         self.assertEqual(self.extra, unused + read())
 
 
     def test_decompress_no_crc32(self):
     def test_decompress_no_crc32(self):
@@ -714,9 +711,9 @@ class ReadZlibTests(TestCase):
     def _do_decompress_test(self, buffer_size, **kwargs):
     def _do_decompress_test(self, buffer_size, **kwargs):
         unused = read_zlib_chunks(self.read, self.unpacked,
         unused = read_zlib_chunks(self.read, self.unpacked,
                                   buffer_size=buffer_size, **kwargs)
                                   buffer_size=buffer_size, **kwargs)
-        self.assertEqual(self.decomp, ''.join(self.unpacked.decomp_chunks))
+        self.assertEqual(self.decomp, b''.join(self.unpacked.decomp_chunks))
         self.assertEqual(zlib.crc32(self.comp), self.unpacked.crc32)
         self.assertEqual(zlib.crc32(self.comp), self.unpacked.crc32)
-        self.assertNotEqual('', unused)
+        self.assertNotEqual(b'', unused)
         self.assertEqual(self.extra, unused + self.read())
         self.assertEqual(self.extra, unused + self.read())
 
 
     def test_simple_decompress(self):
     def test_simple_decompress(self):
@@ -739,33 +736,31 @@ class ReadZlibTests(TestCase):
 
 
     def test_decompress_include_comp(self):
     def test_decompress_include_comp(self):
         self._do_decompress_test(4096, include_comp=True)
         self._do_decompress_test(4096, include_comp=True)
-        self.assertEqual(self.comp, ''.join(self.unpacked.comp_chunks))
+        self.assertEqual(self.comp, b''.join(self.unpacked.comp_chunks))
 
 
 
 
-@skipIfPY3
 class DeltifyTests(TestCase):
 class DeltifyTests(TestCase):
 
 
     def test_empty(self):
     def test_empty(self):
         self.assertEqual([], list(deltify_pack_objects([])))
         self.assertEqual([], list(deltify_pack_objects([])))
 
 
     def test_single(self):
     def test_single(self):
-        b = Blob.from_string("foo")
+        b = Blob.from_string(b"foo")
         self.assertEqual(
         self.assertEqual(
             [(b.type_num, b.sha().digest(), None, b.as_raw_string())],
             [(b.type_num, b.sha().digest(), None, b.as_raw_string())],
-            list(deltify_pack_objects([(b, "")])))
+            list(deltify_pack_objects([(b, b"")])))
 
 
     def test_simple_delta(self):
     def test_simple_delta(self):
-        b1 = Blob.from_string("a" * 101)
-        b2 = Blob.from_string("a" * 100)
+        b1 = Blob.from_string(b"a" * 101)
+        b2 = Blob.from_string(b"a" * 100)
         delta = create_delta(b1.as_raw_string(), b2.as_raw_string())
         delta = create_delta(b1.as_raw_string(), b2.as_raw_string())
         self.assertEqual([
         self.assertEqual([
             (b1.type_num, b1.sha().digest(), None, b1.as_raw_string()),
             (b1.type_num, b1.sha().digest(), None, b1.as_raw_string()),
             (b2.type_num, b2.sha().digest(), b1.sha().digest(), delta)
             (b2.type_num, b2.sha().digest(), b1.sha().digest(), delta)
             ],
             ],
-            list(deltify_pack_objects([(b1, ""), (b2, "")])))
+            list(deltify_pack_objects([(b1, b""), (b2, b"")])))
 
 
 
 
-@skipIfPY3
 class TestPackStreamReader(TestCase):
 class TestPackStreamReader(TestCase):
 
 
     def test_read_objects_emtpy(self):
     def test_read_objects_emtpy(self):
@@ -777,9 +772,9 @@ class TestPackStreamReader(TestCase):
     def test_read_objects(self):
     def test_read_objects(self):
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (Blob.type_num, 'blob'),
-          (OFS_DELTA, (0, 'blob1')),
-          ])
+            (Blob.type_num, b'blob'),
+            (OFS_DELTA, (0, b'blob1')),
+        ])
         reader = PackStreamReader(f.read)
         reader = PackStreamReader(f.read)
         objects = list(reader.read_objects(compute_crc32=True))
         objects = list(reader.read_objects(compute_crc32=True))
         self.assertEqual(2, len(objects))
         self.assertEqual(2, len(objects))
@@ -790,7 +785,7 @@ class TestPackStreamReader(TestCase):
         self.assertEqual(Blob.type_num, unpacked_blob.pack_type_num)
         self.assertEqual(Blob.type_num, unpacked_blob.pack_type_num)
         self.assertEqual(Blob.type_num, unpacked_blob.obj_type_num)
         self.assertEqual(Blob.type_num, unpacked_blob.obj_type_num)
         self.assertEqual(None, unpacked_blob.delta_base)
         self.assertEqual(None, unpacked_blob.delta_base)
-        self.assertEqual('blob', ''.join(unpacked_blob.decomp_chunks))
+        self.assertEqual(b'blob', b''.join(unpacked_blob.decomp_chunks))
         self.assertEqual(entries[0][4], unpacked_blob.crc32)
         self.assertEqual(entries[0][4], unpacked_blob.crc32)
 
 
         self.assertEqual(entries[1][0], unpacked_delta.offset)
         self.assertEqual(entries[1][0], unpacked_delta.offset)
@@ -798,16 +793,16 @@ class TestPackStreamReader(TestCase):
         self.assertEqual(None, unpacked_delta.obj_type_num)
         self.assertEqual(None, unpacked_delta.obj_type_num)
         self.assertEqual(unpacked_delta.offset - unpacked_blob.offset,
         self.assertEqual(unpacked_delta.offset - unpacked_blob.offset,
                          unpacked_delta.delta_base)
                          unpacked_delta.delta_base)
-        delta = create_delta('blob', 'blob1')
-        self.assertEqual(delta, ''.join(unpacked_delta.decomp_chunks))
+        delta = create_delta(b'blob', b'blob1')
+        self.assertEqual(delta, b''.join(unpacked_delta.decomp_chunks))
         self.assertEqual(entries[1][4], unpacked_delta.crc32)
         self.assertEqual(entries[1][4], unpacked_delta.crc32)
 
 
     def test_read_objects_buffered(self):
     def test_read_objects_buffered(self):
         f = BytesIO()
         f = BytesIO()
         build_pack(f, [
         build_pack(f, [
-          (Blob.type_num, 'blob'),
-          (OFS_DELTA, (0, 'blob1')),
-          ])
+            (Blob.type_num, b'blob'),
+            (OFS_DELTA, (0, b'blob1')),
+        ])
         reader = PackStreamReader(f.read, zlib_bufsize=4)
         reader = PackStreamReader(f.read, zlib_bufsize=4)
         self.assertEqual(2, len(list(reader.read_objects())))
         self.assertEqual(2, len(list(reader.read_objects())))
 
 
@@ -816,7 +811,6 @@ class TestPackStreamReader(TestCase):
         self.assertEqual([], list(reader.read_objects()))
         self.assertEqual([], list(reader.read_objects()))
 
 
 
 
-@skipIfPY3
 class TestPackIterator(DeltaChainIterator):
 class TestPackIterator(DeltaChainIterator):
 
 
     _compute_crc32 = True
     _compute_crc32 = True
@@ -828,7 +822,7 @@ class TestPackIterator(DeltaChainIterator):
     def _result(self, unpacked):
     def _result(self, unpacked):
         """Return entries in the same format as build_pack."""
         """Return entries in the same format as build_pack."""
         return (unpacked.offset, unpacked.obj_type_num,
         return (unpacked.offset, unpacked.obj_type_num,
-                ''.join(unpacked.obj_chunks), unpacked.sha(), unpacked.crc32)
+                b''.join(unpacked.obj_chunks), unpacked.sha(), unpacked.crc32)
 
 
     def _resolve_object(self, offset, pack_type_num, base_chunks):
     def _resolve_object(self, offset, pack_type_num, base_chunks):
         assert offset not in self._unpacked_offsets, (
         assert offset not in self._unpacked_offsets, (
@@ -838,7 +832,6 @@ class TestPackIterator(DeltaChainIterator):
           offset, pack_type_num, base_chunks)
           offset, pack_type_num, base_chunks)
 
 
 
 
-@skipIfPY3
 class DeltaChainIteratorTests(TestCase):
 class DeltaChainIteratorTests(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -877,46 +870,46 @@ class DeltaChainIteratorTests(TestCase):
     def test_no_deltas(self):
     def test_no_deltas(self):
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (Commit.type_num, 'commit'),
-          (Blob.type_num, 'blob'),
-          (Tree.type_num, 'tree'),
-          ])
+            (Commit.type_num, b'commit'),
+            (Blob.type_num, b'blob'),
+            (Tree.type_num, b'tree'),
+        ])
         self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f))
         self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f))
 
 
     def test_ofs_deltas(self):
     def test_ofs_deltas(self):
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (Blob.type_num, 'blob'),
-          (OFS_DELTA, (0, 'blob1')),
-          (OFS_DELTA, (0, 'blob2')),
-          ])
+            (Blob.type_num, b'blob'),
+            (OFS_DELTA, (0, b'blob1')),
+            (OFS_DELTA, (0, b'blob2')),
+        ])
         self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f))
         self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f))
 
 
     def test_ofs_deltas_chain(self):
     def test_ofs_deltas_chain(self):
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (Blob.type_num, 'blob'),
-          (OFS_DELTA, (0, 'blob1')),
-          (OFS_DELTA, (1, 'blob2')),
-          ])
+            (Blob.type_num, b'blob'),
+            (OFS_DELTA, (0, b'blob1')),
+            (OFS_DELTA, (1, b'blob2')),
+        ])
         self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f))
         self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f))
 
 
     def test_ref_deltas(self):
     def test_ref_deltas(self):
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (REF_DELTA, (1, 'blob1')),
-          (Blob.type_num, ('blob')),
-          (REF_DELTA, (1, 'blob2')),
-          ])
+            (REF_DELTA, (1, b'blob1')),
+            (Blob.type_num, (b'blob')),
+            (REF_DELTA, (1, b'blob2')),
+        ])
         self.assertEntriesMatch([1, 0, 2], entries, self.make_pack_iter(f))
         self.assertEntriesMatch([1, 0, 2], entries, self.make_pack_iter(f))
 
 
     def test_ref_deltas_chain(self):
     def test_ref_deltas_chain(self):
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (REF_DELTA, (2, 'blob1')),
-          (Blob.type_num, ('blob')),
-          (REF_DELTA, (1, 'blob2')),
-          ])
+            (REF_DELTA, (2, b'blob1')),
+            (Blob.type_num, (b'blob')),
+            (REF_DELTA, (1, b'blob2')),
+        ])
         self.assertEntriesMatch([1, 2, 0], entries, self.make_pack_iter(f))
         self.assertEntriesMatch([1, 2, 0], entries, self.make_pack_iter(f))
 
 
     def test_ofs_and_ref_deltas(self):
     def test_ofs_and_ref_deltas(self):
@@ -924,58 +917,58 @@ class DeltaChainIteratorTests(TestCase):
         # this ref.
         # this ref.
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (REF_DELTA, (1, 'blob1')),
-          (Blob.type_num, ('blob')),
-          (OFS_DELTA, (1, 'blob2')),
-          ])
+            (REF_DELTA, (1, b'blob1')),
+            (Blob.type_num, (b'blob')),
+            (OFS_DELTA, (1, b'blob2')),
+        ])
         self.assertEntriesMatch([1, 2, 0], entries, self.make_pack_iter(f))
         self.assertEntriesMatch([1, 2, 0], entries, self.make_pack_iter(f))
 
 
     def test_mixed_chain(self):
     def test_mixed_chain(self):
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (Blob.type_num, 'blob'),
-          (REF_DELTA, (2, 'blob2')),
-          (OFS_DELTA, (0, 'blob1')),
-          (OFS_DELTA, (1, 'blob3')),
-          (OFS_DELTA, (0, 'bob')),
-          ])
+            (Blob.type_num, b'blob'),
+            (REF_DELTA, (2, b'blob2')),
+            (OFS_DELTA, (0, b'blob1')),
+            (OFS_DELTA, (1, b'blob3')),
+            (OFS_DELTA, (0, b'bob')),
+        ])
         self.assertEntriesMatch([0, 2, 1, 3, 4], entries,
         self.assertEntriesMatch([0, 2, 1, 3, 4], entries,
                                 self.make_pack_iter(f))
                                 self.make_pack_iter(f))
 
 
     def test_long_chain(self):
     def test_long_chain(self):
         n = 100
         n = 100
-        objects_spec = [(Blob.type_num, 'blob')]
+        objects_spec = [(Blob.type_num, b'blob')]
         for i in range(n):
         for i in range(n):
-            objects_spec.append((OFS_DELTA, (i, 'blob%i' % i)))
+            objects_spec.append((OFS_DELTA, (i, b'blob' + str(i).encode('ascii'))))
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, objects_spec)
         entries = build_pack(f, objects_spec)
         self.assertEntriesMatch(range(n + 1), entries, self.make_pack_iter(f))
         self.assertEntriesMatch(range(n + 1), entries, self.make_pack_iter(f))
 
 
     def test_branchy_chain(self):
     def test_branchy_chain(self):
         n = 100
         n = 100
-        objects_spec = [(Blob.type_num, 'blob')]
+        objects_spec = [(Blob.type_num, b'blob')]
         for i in range(n):
         for i in range(n):
-            objects_spec.append((OFS_DELTA, (0, 'blob%i' % i)))
+            objects_spec.append((OFS_DELTA, (0, b'blob' + str(i).encode('ascii'))))
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, objects_spec)
         entries = build_pack(f, objects_spec)
         self.assertEntriesMatch(range(n + 1), entries, self.make_pack_iter(f))
         self.assertEntriesMatch(range(n + 1), entries, self.make_pack_iter(f))
 
 
     def test_ext_ref(self):
     def test_ext_ref(self):
-        blob, = self.store_blobs(['blob'])
+        blob, = self.store_blobs([b'blob'])
         f = BytesIO()
         f = BytesIO()
-        entries = build_pack(f, [(REF_DELTA, (blob.id, 'blob1'))],
+        entries = build_pack(f, [(REF_DELTA, (blob.id, b'blob1'))],
                              store=self.store)
                              store=self.store)
         pack_iter = self.make_pack_iter(f)
         pack_iter = self.make_pack_iter(f)
         self.assertEntriesMatch([0], entries, pack_iter)
         self.assertEntriesMatch([0], entries, pack_iter)
         self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
         self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
 
 
     def test_ext_ref_chain(self):
     def test_ext_ref_chain(self):
-        blob, = self.store_blobs(['blob'])
+        blob, = self.store_blobs([b'blob'])
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (REF_DELTA, (1, 'blob2')),
-          (REF_DELTA, (blob.id, 'blob1')),
-          ], store=self.store)
+            (REF_DELTA, (1, b'blob2')),
+            (REF_DELTA, (blob.id, b'blob1')),
+        ], store=self.store)
         pack_iter = self.make_pack_iter(f)
         pack_iter = self.make_pack_iter(f)
         self.assertEntriesMatch([1, 0], entries, pack_iter)
         self.assertEntriesMatch([1, 0], entries, pack_iter)
         self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
         self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
@@ -983,46 +976,46 @@ class DeltaChainIteratorTests(TestCase):
     def test_ext_ref_chain_degenerate(self):
     def test_ext_ref_chain_degenerate(self):
         # Test a degenerate case where the sender is sending a REF_DELTA
         # Test a degenerate case where the sender is sending a REF_DELTA
         # object that expands to an object already in the repository.
         # object that expands to an object already in the repository.
-        blob, = self.store_blobs(['blob'])
-        blob2, = self.store_blobs(['blob2'])
+        blob, = self.store_blobs([b'blob'])
+        blob2, = self.store_blobs([b'blob2'])
         assert blob.id < blob2.id
         assert blob.id < blob2.id
 
 
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (REF_DELTA, (blob.id, 'blob2')),
-          (REF_DELTA, (0, 'blob3')),
+          (REF_DELTA, (blob.id, b'blob2')),
+          (REF_DELTA, (0, b'blob3')),
           ], store=self.store)
           ], store=self.store)
         pack_iter = self.make_pack_iter(f)
         pack_iter = self.make_pack_iter(f)
         self.assertEntriesMatch([0, 1], entries, pack_iter)
         self.assertEntriesMatch([0, 1], entries, pack_iter)
         self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
         self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
 
 
     def test_ext_ref_multiple_times(self):
     def test_ext_ref_multiple_times(self):
-        blob, = self.store_blobs(['blob'])
+        blob, = self.store_blobs([b'blob'])
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (REF_DELTA, (blob.id, 'blob1')),
-          (REF_DELTA, (blob.id, 'blob2')),
-          ], store=self.store)
+            (REF_DELTA, (blob.id, b'blob1')),
+            (REF_DELTA, (blob.id, b'blob2')),
+        ], store=self.store)
         pack_iter = self.make_pack_iter(f)
         pack_iter = self.make_pack_iter(f)
         self.assertEntriesMatch([0, 1], entries, pack_iter)
         self.assertEntriesMatch([0, 1], entries, pack_iter)
         self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
         self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
 
 
     def test_multiple_ext_refs(self):
     def test_multiple_ext_refs(self):
-        b1, b2 = self.store_blobs(['foo', 'bar'])
+        b1, b2 = self.store_blobs([b'foo', b'bar'])
         f = BytesIO()
         f = BytesIO()
         entries = build_pack(f, [
         entries = build_pack(f, [
-          (REF_DELTA, (b1.id, 'foo1')),
-          (REF_DELTA, (b2.id, 'bar2')),
-          ], store=self.store)
+            (REF_DELTA, (b1.id, b'foo1')),
+            (REF_DELTA, (b2.id, b'bar2')),
+        ], store=self.store)
         pack_iter = self.make_pack_iter(f)
         pack_iter = self.make_pack_iter(f)
         self.assertEntriesMatch([0, 1], entries, pack_iter)
         self.assertEntriesMatch([0, 1], entries, pack_iter)
         self.assertEqual([hex_to_sha(b1.id), hex_to_sha(b2.id)],
         self.assertEqual([hex_to_sha(b1.id), hex_to_sha(b2.id)],
                          pack_iter.ext_refs())
                          pack_iter.ext_refs())
 
 
     def test_bad_ext_ref_non_thin_pack(self):
     def test_bad_ext_ref_non_thin_pack(self):
-        blob, = self.store_blobs(['blob'])
+        blob, = self.store_blobs([b'blob'])
         f = BytesIO()
         f = BytesIO()
-        build_pack(f, [(REF_DELTA, (blob.id, 'blob1'))],
+        entries = build_pack(f, [(REF_DELTA, (blob.id, b'blob1'))],
                              store=self.store)
                              store=self.store)
         pack_iter = self.make_pack_iter(f, thin=False)
         pack_iter = self.make_pack_iter(f, thin=False)
         try:
         try:
@@ -1032,13 +1025,13 @@ class DeltaChainIteratorTests(TestCase):
             self.assertEqual(([blob.id],), e.args)
             self.assertEqual(([blob.id],), e.args)
 
 
     def test_bad_ext_ref_thin_pack(self):
     def test_bad_ext_ref_thin_pack(self):
-        b1, b2, b3 = self.store_blobs(['foo', 'bar', 'baz'])
+        b1, b2, b3 = self.store_blobs([b'foo', b'bar', b'baz'])
         f = BytesIO()
         f = BytesIO()
         build_pack(f, [
         build_pack(f, [
-          (REF_DELTA, (1, 'foo99')),
-          (REF_DELTA, (b1.id, 'foo1')),
-          (REF_DELTA, (b2.id, 'bar2')),
-          (REF_DELTA, (b3.id, 'baz3')),
+          (REF_DELTA, (1, b'foo99')),
+          (REF_DELTA, (b1.id, b'foo1')),
+          (REF_DELTA, (b2.id, b'bar2')),
+          (REF_DELTA, (b3.id, b'baz3')),
           ], store=self.store)
           ], store=self.store)
         del self.store[b2.id]
         del self.store[b2.id]
         del self.store[b3.id]
         del self.store[b3.id]
@@ -1053,17 +1046,17 @@ class DeltaChainIteratorTests(TestCase):
 class DeltaEncodeSizeTests(TestCase):
 class DeltaEncodeSizeTests(TestCase):
 
 
     def test_basic(self):
     def test_basic(self):
-        self.assertEqual('\x00', _delta_encode_size(0))
-        self.assertEqual('\x01', _delta_encode_size(1))
-        self.assertEqual('\xfa\x01', _delta_encode_size(250))
-        self.assertEqual('\xe8\x07', _delta_encode_size(1000))
-        self.assertEqual('\xa0\x8d\x06', _delta_encode_size(100000))
+        self.assertEqual(b'\x00', _delta_encode_size(0))
+        self.assertEqual(b'\x01', _delta_encode_size(1))
+        self.assertEqual(b'\xfa\x01', _delta_encode_size(250))
+        self.assertEqual(b'\xe8\x07', _delta_encode_size(1000))
+        self.assertEqual(b'\xa0\x8d\x06', _delta_encode_size(100000))
 
 
 
 
 class EncodeCopyOperationTests(TestCase):
 class EncodeCopyOperationTests(TestCase):
 
 
     def test_basic(self):
     def test_basic(self):
-        self.assertEqual('\x80', _encode_copy_operation(0, 0))
-        self.assertEqual('\x91\x01\x0a', _encode_copy_operation(1, 10))
-        self.assertEqual('\xb1\x64\xe8\x03', _encode_copy_operation(100, 1000))
-        self.assertEqual('\x93\xe8\x03\x01', _encode_copy_operation(1000, 1))
+        self.assertEqual(b'\x80', _encode_copy_operation(0, 0))
+        self.assertEqual(b'\x91\x01\x0a', _encode_copy_operation(1, 10))
+        self.assertEqual(b'\xb1\x64\xe8\x03', _encode_copy_operation(100, 1000))
+        self.assertEqual(b'\x93\xe8\x03\x01', _encode_copy_operation(1000, 1))

+ 167 - 192
dulwich/tests/test_porcelain.py

@@ -39,7 +39,6 @@ from dulwich.tests.compat.utils import require_git_version
 from dulwich.tests.utils import (
 from dulwich.tests.utils import (
     build_commit_graph,
     build_commit_graph,
     make_object,
     make_object,
-    skipIfPY3,
     )
     )
 
 
 
 
@@ -52,7 +51,6 @@ class PorcelainTestCase(TestCase):
         self.repo = Repo.init(repo_dir)
         self.repo = Repo.init(repo_dir)
 
 
 
 
-@skipIfPY3
 class ArchiveTests(PorcelainTestCase):
 class ArchiveTests(PorcelainTestCase):
     """Tests for the archive command."""
     """Tests for the archive command."""
 
 
@@ -60,120 +58,116 @@ class ArchiveTests(PorcelainTestCase):
         # TODO(jelmer): Remove this once dulwich has its own implementation of archive.
         # TODO(jelmer): Remove this once dulwich has its own implementation of archive.
         require_git_version((1, 5, 0))
         require_git_version((1, 5, 0))
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
-        self.repo.refs["refs/heads/master"] = c3.id
+        self.repo.refs[b"refs/heads/master"] = c3.id
         out = BytesIO()
         out = BytesIO()
         err = BytesIO()
         err = BytesIO()
-        porcelain.archive(self.repo.path, "refs/heads/master", outstream=out,
+        porcelain.archive(self.repo.path, b"refs/heads/master", outstream=out,
             errstream=err)
             errstream=err)
-        self.assertEqual("", err.getvalue())
+        self.assertEqual(b"", err.getvalue())
         tf = tarfile.TarFile(fileobj=out)
         tf = tarfile.TarFile(fileobj=out)
         self.addCleanup(tf.close)
         self.addCleanup(tf.close)
         self.assertEqual([], tf.getnames())
         self.assertEqual([], tf.getnames())
 
 
 
 
-@skipIfPY3
 class UpdateServerInfoTests(PorcelainTestCase):
 class UpdateServerInfoTests(PorcelainTestCase):
 
 
     def test_simple(self):
     def test_simple(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["refs/heads/foo"] = c3.id
+        self.repo.refs[b"refs/heads/foo"] = c3.id
         porcelain.update_server_info(self.repo.path)
         porcelain.update_server_info(self.repo.path)
         self.assertTrue(os.path.exists(os.path.join(self.repo.controldir(),
         self.assertTrue(os.path.exists(os.path.join(self.repo.controldir(),
-            'info', 'refs')))
+            b'info', b'refs')))
 
 
 
 
-@skipIfPY3
 class CommitTests(PorcelainTestCase):
 class CommitTests(PorcelainTestCase):
 
 
     def test_custom_author(self):
     def test_custom_author(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["refs/heads/foo"] = c3.id
-        sha = porcelain.commit(self.repo.path, message="Some message",
-                author="Joe <joe@example.com>", committer="Bob <bob@example.com>")
-        self.assertTrue(isinstance(sha, str))
+        self.repo.refs[b"refs/heads/foo"] = c3.id
+        sha = porcelain.commit(self.repo.path, message=b"Some message",
+                author=b"Joe <joe@example.com>", committer=b"Bob <bob@example.com>")
+        self.assertTrue(isinstance(sha, bytes))
         self.assertEqual(len(sha), 40)
         self.assertEqual(len(sha), 40)
 
 
 
 
-@skipIfPY3
 class CloneTests(PorcelainTestCase):
 class CloneTests(PorcelainTestCase):
 
 
     def test_simple_local(self):
     def test_simple_local(self):
-        f1_1 = make_object(Blob, data='f1')
+        f1_1 = make_object(Blob, data=b'f1')
         commit_spec = [[1], [2, 1], [3, 1, 2]]
         commit_spec = [[1], [2, 1], [3, 1, 2]]
-        trees = {1: [('f1', f1_1), ('f2', f1_1)],
-                 2: [('f1', f1_1), ('f2', f1_1)],
-                 3: [('f1', f1_1), ('f2', f1_1)], }
+        trees = {1: [(b'f1', f1_1), (b'f2', f1_1)],
+                 2: [(b'f1', f1_1), (b'f2', f1_1)],
+                 3: [(b'f1', f1_1), (b'f2', f1_1)], }
 
 
         c1, c2, c3 = build_commit_graph(self.repo.object_store,
         c1, c2, c3 = build_commit_graph(self.repo.object_store,
                                         commit_spec, trees)
                                         commit_spec, trees)
-        self.repo.refs["refs/heads/master"] = c3.id
+        self.repo.refs[b"refs/heads/master"] = c3.id
         target_path = tempfile.mkdtemp()
         target_path = tempfile.mkdtemp()
-        outstream = BytesIO()
+        errstream = BytesIO()
         self.addCleanup(shutil.rmtree, target_path)
         self.addCleanup(shutil.rmtree, target_path)
         r = porcelain.clone(self.repo.path, target_path,
         r = porcelain.clone(self.repo.path, target_path,
-                            checkout=False, outstream=outstream)
+                            checkout=False, errstream=errstream)
         self.assertEqual(r.path, target_path)
         self.assertEqual(r.path, target_path)
         self.assertEqual(Repo(target_path).head(), c3.id)
         self.assertEqual(Repo(target_path).head(), c3.id)
-        self.assertTrue('f1' not in os.listdir(target_path))
-        self.assertTrue('f2' not in os.listdir(target_path))
+        self.assertTrue(b'f1' not in os.listdir(target_path))
+        self.assertTrue(b'f2' not in os.listdir(target_path))
 
 
     def test_simple_local_with_checkout(self):
     def test_simple_local_with_checkout(self):
-        f1_1 = make_object(Blob, data='f1')
+        f1_1 = make_object(Blob, data=b'f1')
         commit_spec = [[1], [2, 1], [3, 1, 2]]
         commit_spec = [[1], [2, 1], [3, 1, 2]]
-        trees = {1: [('f1', f1_1), ('f2', f1_1)],
-                 2: [('f1', f1_1), ('f2', f1_1)],
-                 3: [('f1', f1_1), ('f2', f1_1)], }
+        trees = {1: [(b'f1', f1_1), (b'f2', f1_1)],
+                 2: [(b'f1', f1_1), (b'f2', f1_1)],
+                 3: [(b'f1', f1_1), (b'f2', f1_1)], }
 
 
         c1, c2, c3 = build_commit_graph(self.repo.object_store,
         c1, c2, c3 = build_commit_graph(self.repo.object_store,
                                         commit_spec, trees)
                                         commit_spec, trees)
-        self.repo.refs["refs/heads/master"] = c3.id
+        self.repo.refs[b"refs/heads/master"] = c3.id
         target_path = tempfile.mkdtemp()
         target_path = tempfile.mkdtemp()
-        outstream = BytesIO()
+        errstream = BytesIO()
         self.addCleanup(shutil.rmtree, target_path)
         self.addCleanup(shutil.rmtree, target_path)
         r = porcelain.clone(self.repo.path, target_path,
         r = porcelain.clone(self.repo.path, target_path,
-                            checkout=True, outstream=outstream)
+                            checkout=True, errstream=errstream)
         self.assertEqual(r.path, target_path)
         self.assertEqual(r.path, target_path)
         self.assertEqual(Repo(target_path).head(), c3.id)
         self.assertEqual(Repo(target_path).head(), c3.id)
         self.assertTrue('f1' in os.listdir(target_path))
         self.assertTrue('f1' in os.listdir(target_path))
         self.assertTrue('f2' in os.listdir(target_path))
         self.assertTrue('f2' in os.listdir(target_path))
 
 
     def test_bare_local_with_checkout(self):
     def test_bare_local_with_checkout(self):
-        f1_1 = make_object(Blob, data='f1')
+        f1_1 = make_object(Blob, data=b'f1')
         commit_spec = [[1], [2, 1], [3, 1, 2]]
         commit_spec = [[1], [2, 1], [3, 1, 2]]
-        trees = {1: [('f1', f1_1), ('f2', f1_1)],
-                 2: [('f1', f1_1), ('f2', f1_1)],
-                 3: [('f1', f1_1), ('f2', f1_1)], }
+        trees = {1: [(b'f1', f1_1), (b'f2', f1_1)],
+                 2: [(b'f1', f1_1), (b'f2', f1_1)],
+                 3: [(b'f1', f1_1), (b'f2', f1_1)], }
 
 
         c1, c2, c3 = build_commit_graph(self.repo.object_store,
         c1, c2, c3 = build_commit_graph(self.repo.object_store,
                                         commit_spec, trees)
                                         commit_spec, trees)
-        self.repo.refs["refs/heads/master"] = c3.id
+        self.repo.refs[b"refs/heads/master"] = c3.id
         target_path = tempfile.mkdtemp()
         target_path = tempfile.mkdtemp()
-        outstream = BytesIO()
+        errstream = BytesIO()
         self.addCleanup(shutil.rmtree, target_path)
         self.addCleanup(shutil.rmtree, target_path)
         r = porcelain.clone(self.repo.path, target_path,
         r = porcelain.clone(self.repo.path, target_path,
-                            bare=True, outstream=outstream)
+                            bare=True, errstream=errstream)
         self.assertEqual(r.path, target_path)
         self.assertEqual(r.path, target_path)
         self.assertEqual(Repo(target_path).head(), c3.id)
         self.assertEqual(Repo(target_path).head(), c3.id)
-        self.assertFalse('f1' in os.listdir(target_path))
-        self.assertFalse('f2' in os.listdir(target_path))
+        self.assertFalse(b'f1' in os.listdir(target_path))
+        self.assertFalse(b'f2' in os.listdir(target_path))
 
 
     def test_no_checkout_with_bare(self):
     def test_no_checkout_with_bare(self):
-        f1_1 = make_object(Blob, data='f1')
+        f1_1 = make_object(Blob, data=b'f1')
         commit_spec = [[1]]
         commit_spec = [[1]]
-        trees = {1: [('f1', f1_1), ('f2', f1_1)]}
+        trees = {1: [(b'f1', f1_1), (b'f2', f1_1)]}
 
 
         (c1, ) = build_commit_graph(self.repo.object_store, commit_spec, trees)
         (c1, ) = build_commit_graph(self.repo.object_store, commit_spec, trees)
-        self.repo.refs["refs/heads/master"] = c1.id
+        self.repo.refs[b"refs/heads/master"] = c1.id
         target_path = tempfile.mkdtemp()
         target_path = tempfile.mkdtemp()
-        outstream = BytesIO()
+        errstream = BytesIO()
         self.addCleanup(shutil.rmtree, target_path)
         self.addCleanup(shutil.rmtree, target_path)
         self.assertRaises(ValueError, porcelain.clone, self.repo.path,
         self.assertRaises(ValueError, porcelain.clone, self.repo.path,
-            target_path, checkout=True, bare=True, outstream=outstream)
+            target_path, checkout=True, bare=True, errstream=errstream)
 
 
 
 
-@skipIfPY3
 class InitTests(TestCase):
 class InitTests(TestCase):
 
 
     def test_non_bare(self):
     def test_non_bare(self):
@@ -187,7 +181,6 @@ class InitTests(TestCase):
         porcelain.init(repo_dir, bare=True)
         porcelain.init(repo_dir, bare=True)
 
 
 
 
-@skipIfPY3
 class AddTests(PorcelainTestCase):
 class AddTests(PorcelainTestCase):
 
 
     def test_add_default_paths(self):
     def test_add_default_paths(self):
@@ -196,8 +189,8 @@ class AddTests(PorcelainTestCase):
         with open(os.path.join(self.repo.path, 'blah'), 'w') as f:
         with open(os.path.join(self.repo.path, 'blah'), 'w') as f:
             f.write("\n")
             f.write("\n")
         porcelain.add(repo=self.repo.path, paths=['blah'])
         porcelain.add(repo=self.repo.path, paths=['blah'])
-        porcelain.commit(repo=self.repo.path, message='test',
-            author='test', committer='test')
+        porcelain.commit(repo=self.repo.path, message=b'test',
+            author=b'test', committer=b'test')
 
 
         # Add a second test file and a file in a directory
         # Add a second test file and a file in a directory
         with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
         with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
@@ -209,7 +202,7 @@ class AddTests(PorcelainTestCase):
 
 
         # Check that foo was added and nothing in .git was modified
         # Check that foo was added and nothing in .git was modified
         index = self.repo.open_index()
         index = self.repo.open_index()
-        self.assertEqual(sorted(index), ['adir/afile', 'blah', 'foo'])
+        self.assertEqual(sorted(index), [b'adir/afile', b'blah', b'foo'])
 
 
     def test_add_file(self):
     def test_add_file(self):
         with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
         with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
@@ -217,7 +210,6 @@ class AddTests(PorcelainTestCase):
         porcelain.add(self.repo.path, paths=["foo"])
         porcelain.add(self.repo.path, paths=["foo"])
 
 
 
 
-@skipIfPY3
 class RemoveTests(PorcelainTestCase):
 class RemoveTests(PorcelainTestCase):
 
 
     def test_remove_file(self):
     def test_remove_file(self):
@@ -227,129 +219,123 @@ class RemoveTests(PorcelainTestCase):
         porcelain.rm(self.repo.path, paths=["foo"])
         porcelain.rm(self.repo.path, paths=["foo"])
 
 
 
 
-@skipIfPY3
 class LogTests(PorcelainTestCase):
 class LogTests(PorcelainTestCase):
 
 
     def test_simple(self):
     def test_simple(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
         outstream = BytesIO()
         outstream = BytesIO()
         porcelain.log(self.repo.path, outstream=outstream)
         porcelain.log(self.repo.path, outstream=outstream)
-        self.assertEqual(3, outstream.getvalue().count("-" * 50))
+        self.assertEqual(3, outstream.getvalue().count(b"-" * 50))
 
 
     def test_max_entries(self):
     def test_max_entries(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
         outstream = BytesIO()
         outstream = BytesIO()
         porcelain.log(self.repo.path, outstream=outstream, max_entries=1)
         porcelain.log(self.repo.path, outstream=outstream, max_entries=1)
-        self.assertEqual(1, outstream.getvalue().count("-" * 50))
+        self.assertEqual(1, outstream.getvalue().count(b"-" * 50))
 
 
 
 
-@skipIfPY3
 class ShowTests(PorcelainTestCase):
 class ShowTests(PorcelainTestCase):
 
 
     def test_nolist(self):
     def test_nolist(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
         outstream = BytesIO()
         outstream = BytesIO()
         porcelain.show(self.repo.path, objects=c3.id, outstream=outstream)
         porcelain.show(self.repo.path, objects=c3.id, outstream=outstream)
-        self.assertTrue(outstream.getvalue().startswith("-" * 50))
+        self.assertTrue(outstream.getvalue().startswith(b"-" * 50))
 
 
     def test_simple(self):
     def test_simple(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
         outstream = BytesIO()
         outstream = BytesIO()
         porcelain.show(self.repo.path, objects=[c3.id], outstream=outstream)
         porcelain.show(self.repo.path, objects=[c3.id], outstream=outstream)
-        self.assertTrue(outstream.getvalue().startswith("-" * 50))
+        self.assertTrue(outstream.getvalue().startswith(b"-" * 50))
 
 
     def test_blob(self):
     def test_blob(self):
-        b = Blob.from_string("The Foo\n")
+        b = Blob.from_string(b"The Foo\n")
         self.repo.object_store.add_object(b)
         self.repo.object_store.add_object(b)
         outstream = BytesIO()
         outstream = BytesIO()
         porcelain.show(self.repo.path, objects=[b.id], outstream=outstream)
         porcelain.show(self.repo.path, objects=[b.id], outstream=outstream)
-        self.assertEqual(outstream.getvalue(), "The Foo\n")
+        self.assertEqual(outstream.getvalue(), b"The Foo\n")
 
 
 
 
-@skipIfPY3
 class SymbolicRefTests(PorcelainTestCase):
 class SymbolicRefTests(PorcelainTestCase):
 
 
     def test_set_wrong_symbolic_ref(self):
     def test_set_wrong_symbolic_ref(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
 
 
-        self.assertRaises(ValueError, porcelain.symbolic_ref, self.repo.path, 'foobar')
+        self.assertRaises(ValueError, porcelain.symbolic_ref, self.repo.path, b'foobar')
 
 
     def test_set_force_wrong_symbolic_ref(self):
     def test_set_force_wrong_symbolic_ref(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
 
 
-        porcelain.symbolic_ref(self.repo.path, 'force_foobar', force=True)
+        porcelain.symbolic_ref(self.repo.path, b'force_foobar', force=True)
 
 
         #test if we actually changed the file
         #test if we actually changed the file
-        with self.repo.get_named_file('HEAD') as f:
+        with self.repo.get_named_file(b'HEAD') as f:
             new_ref = f.read()
             new_ref = f.read()
         self.assertEqual(new_ref, b'ref: refs/heads/force_foobar\n')
         self.assertEqual(new_ref, b'ref: refs/heads/force_foobar\n')
 
 
     def test_set_symbolic_ref(self):
     def test_set_symbolic_ref(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
 
 
-        porcelain.symbolic_ref(self.repo.path, 'master')
+        porcelain.symbolic_ref(self.repo.path, b'master')
 
 
     def test_set_symbolic_ref_other_than_master(self):
     def test_set_symbolic_ref_other_than_master(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]], attrs=dict(refs='develop'))
             [3, 1, 2]], attrs=dict(refs='develop'))
-        self.repo.refs["HEAD"] = c3.id
-        self.repo.refs["refs/heads/develop"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
+        self.repo.refs[b"refs/heads/develop"] = c3.id
 
 
-        porcelain.symbolic_ref(self.repo.path, 'develop')
+        porcelain.symbolic_ref(self.repo.path, b'develop')
 
 
         #test if we actually changed the file
         #test if we actually changed the file
-        with self.repo.get_named_file('HEAD') as f:
+        with self.repo.get_named_file(b'HEAD') as f:
             new_ref = f.read()
             new_ref = f.read()
         self.assertEqual(new_ref, b'ref: refs/heads/develop\n')
         self.assertEqual(new_ref, b'ref: refs/heads/develop\n')
 
 
 
 
-@skipIfPY3
 class DiffTreeTests(PorcelainTestCase):
 class DiffTreeTests(PorcelainTestCase):
 
 
     def test_empty(self):
     def test_empty(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
         outstream = BytesIO()
         outstream = BytesIO()
         porcelain.diff_tree(self.repo.path, c2.tree, c3.tree, outstream=outstream)
         porcelain.diff_tree(self.repo.path, c2.tree, c3.tree, outstream=outstream)
-        self.assertEqual(outstream.getvalue(), "")
+        self.assertEqual(outstream.getvalue(), b"")
 
 
 
 
-@skipIfPY3
 class CommitTreeTests(PorcelainTestCase):
 class CommitTreeTests(PorcelainTestCase):
 
 
     def test_simple(self):
     def test_simple(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
         b = Blob()
         b = Blob()
-        b.data = "foo the bar"
+        b.data = b"foo the bar"
         t = Tree()
         t = Tree()
-        t.add("somename", 0o100644, b.id)
+        t.add(b"somename", 0o100644, b.id)
         self.repo.object_store.add_object(t)
         self.repo.object_store.add_object(t)
         self.repo.object_store.add_object(b)
         self.repo.object_store.add_object(b)
         sha = porcelain.commit_tree(
         sha = porcelain.commit_tree(
-            self.repo.path, t.id, message="Withcommit.",
-            author="Joe <joe@example.com>",
-            committer="Jane <jane@example.com>")
-        self.assertTrue(isinstance(sha, str))
+            self.repo.path, t.id, message=b"Withcommit.",
+            author=b"Joe <joe@example.com>",
+            committer=b"Jane <jane@example.com>")
+        self.assertTrue(isinstance(sha, bytes))
         self.assertEqual(len(sha), 40)
         self.assertEqual(len(sha), 40)
 
 
 
 
-@skipIfPY3
 class RevListTests(PorcelainTestCase):
 class RevListTests(PorcelainTestCase):
 
 
     def test_simple(self):
     def test_simple(self):
@@ -359,42 +345,42 @@ class RevListTests(PorcelainTestCase):
         porcelain.rev_list(
         porcelain.rev_list(
             self.repo.path, [c3.id], outstream=outstream)
             self.repo.path, [c3.id], outstream=outstream)
         self.assertEqual(
         self.assertEqual(
-            "%s\n%s\n%s\n" % (c3.id, c2.id, c1.id),
+            c3.id + b"\n" +
+            c2.id + b"\n" +
+            c1.id + b"\n",
             outstream.getvalue())
             outstream.getvalue())
 
 
 
 
-@skipIfPY3
 class TagCreateTests(PorcelainTestCase):
 class TagCreateTests(PorcelainTestCase):
 
 
     def test_annotated(self):
     def test_annotated(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
 
 
-        porcelain.tag_create(self.repo.path, "tryme", 'foo <foo@bar.com>',
-                'bar', annotated=True)
+        porcelain.tag_create(self.repo.path, b"tryme", b'foo <foo@bar.com>',
+                b'bar', annotated=True)
 
 
-        tags = self.repo.refs.as_dict("refs/tags")
-        self.assertEqual(tags.keys(), ["tryme"])
-        tag = self.repo['refs/tags/tryme']
+        tags = self.repo.refs.as_dict(b"refs/tags")
+        self.assertEqual(list(tags.keys()), [b"tryme"])
+        tag = self.repo[b'refs/tags/tryme']
         self.assertTrue(isinstance(tag, Tag))
         self.assertTrue(isinstance(tag, Tag))
-        self.assertEqual("foo <foo@bar.com>", tag.tagger)
-        self.assertEqual("bar", tag.message)
+        self.assertEqual(b"foo <foo@bar.com>", tag.tagger)
+        self.assertEqual(b"bar", tag.message)
 
 
     def test_unannotated(self):
     def test_unannotated(self):
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
         c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
             [3, 1, 2]])
             [3, 1, 2]])
-        self.repo.refs["HEAD"] = c3.id
+        self.repo.refs[b"HEAD"] = c3.id
 
 
-        porcelain.tag_create(self.repo.path, "tryme", annotated=False)
+        porcelain.tag_create(self.repo.path, b"tryme", annotated=False)
 
 
-        tags = self.repo.refs.as_dict("refs/tags")
-        self.assertEqual(tags.keys(), ["tryme"])
-        self.repo['refs/tags/tryme']
-        self.assertEqual(tags.values(), [self.repo.head()])
+        tags = self.repo.refs.as_dict(b"refs/tags")
+        self.assertEqual(list(tags.keys()), [b"tryme"])
+        self.repo[b'refs/tags/tryme']
+        self.assertEqual(list(tags.values()), [self.repo.head()])
 
 
 
 
-@skipIfPY3
 class TagListTests(PorcelainTestCase):
 class TagListTests(PorcelainTestCase):
 
 
     def test_empty(self):
     def test_empty(self):
@@ -402,50 +388,47 @@ class TagListTests(PorcelainTestCase):
         self.assertEqual([], tags)
         self.assertEqual([], tags)
 
 
     def test_simple(self):
     def test_simple(self):
-        self.repo.refs["refs/tags/foo"] = "aa" * 20
-        self.repo.refs["refs/tags/bar/bla"] = "bb" * 20
+        self.repo.refs[b"refs/tags/foo"] = b"aa" * 20
+        self.repo.refs[b"refs/tags/bar/bla"] = b"bb" * 20
         tags = porcelain.tag_list(self.repo.path)
         tags = porcelain.tag_list(self.repo.path)
 
 
-        self.assertEqual(["bar/bla", "foo"], tags)
+        self.assertEqual([b"bar/bla", b"foo"], tags)
 
 
 
 
-@skipIfPY3
 class TagDeleteTests(PorcelainTestCase):
 class TagDeleteTests(PorcelainTestCase):
 
 
     def test_simple(self):
     def test_simple(self):
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
-        self.repo["HEAD"] = c1.id
-        porcelain.tag_create(self.repo, 'foo')
-        self.assertTrue("foo" in porcelain.tag_list(self.repo))
-        porcelain.tag_delete(self.repo, 'foo')
-        self.assertFalse("foo" in porcelain.tag_list(self.repo))
+        self.repo[b"HEAD"] = c1.id
+        porcelain.tag_create(self.repo, b'foo')
+        self.assertTrue(b"foo" in porcelain.tag_list(self.repo))
+        porcelain.tag_delete(self.repo, b'foo')
+        self.assertFalse(b"foo" in porcelain.tag_list(self.repo))
 
 
 
 
-@skipIfPY3
 class ResetTests(PorcelainTestCase):
 class ResetTests(PorcelainTestCase):
 
 
     def test_hard_head(self):
     def test_hard_head(self):
         with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
         with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
             f.write("BAR")
             f.write("BAR")
         porcelain.add(self.repo.path, paths=["foo"])
         porcelain.add(self.repo.path, paths=["foo"])
-        porcelain.commit(self.repo.path, message="Some message",
-                committer="Jane <jane@example.com>",
-                author="John <john@example.com>")
+        porcelain.commit(self.repo.path, message=b"Some message",
+                committer=b"Jane <jane@example.com>",
+                author=b"John <john@example.com>")
 
 
-        with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
-            f.write("OOH")
+        with open(os.path.join(self.repo.path, 'foo'), 'wb') as f:
+            f.write(b"OOH")
 
 
-        porcelain.reset(self.repo, "hard", "HEAD")
+        porcelain.reset(self.repo, "hard", b"HEAD")
 
 
         index = self.repo.open_index()
         index = self.repo.open_index()
         changes = list(tree_changes(self.repo,
         changes = list(tree_changes(self.repo,
                        index.commit(self.repo.object_store),
                        index.commit(self.repo.object_store),
-                       self.repo['HEAD'].tree))
+                       self.repo[b'HEAD'].tree))
 
 
         self.assertEqual([], changes)
         self.assertEqual([], changes)
 
 
 
 
-@skipIfPY3
 class PushTests(PorcelainTestCase):
 class PushTests(PorcelainTestCase):
 
 
     def test_simple(self):
     def test_simple(self):
@@ -457,22 +440,22 @@ class PushTests(PorcelainTestCase):
         outstream = BytesIO()
         outstream = BytesIO()
         errstream = BytesIO()
         errstream = BytesIO()
 
 
-        porcelain.commit(repo=self.repo.path, message='init',
-            author='', committer='')
+        porcelain.commit(repo=self.repo.path, message=b'init',
+            author=b'', committer=b'')
 
 
         # Setup target repo cloned from temp test repo
         # Setup target repo cloned from temp test repo
         clone_path = tempfile.mkdtemp()
         clone_path = tempfile.mkdtemp()
-        porcelain.clone(self.repo.path, target=clone_path, outstream=outstream)
+        porcelain.clone(self.repo.path, target=clone_path, errstream=errstream)
 
 
         # create a second file to be pushed back to origin
         # create a second file to be pushed back to origin
         handle, fullpath = tempfile.mkstemp(dir=clone_path)
         handle, fullpath = tempfile.mkstemp(dir=clone_path)
         porcelain.add(repo=clone_path, paths=[os.path.basename(fullpath)])
         porcelain.add(repo=clone_path, paths=[os.path.basename(fullpath)])
-        porcelain.commit(repo=clone_path, message='push',
-            author='', committer='')
+        porcelain.commit(repo=clone_path, message=b'push',
+            author=b'', committer=b'')
 
 
         # Setup a non-checked out branch in the remote
         # Setup a non-checked out branch in the remote
-        refs_path = os.path.join('refs', 'heads', 'foo')
-        self.repo[refs_path] = self.repo['HEAD']
+        refs_path = b"refs/heads/foo"
+        self.repo[refs_path] = self.repo[b'HEAD']
 
 
         # Push to the remote
         # Push to the remote
         porcelain.push(clone_path, self.repo.path, refs_path, outstream=outstream,
         porcelain.push(clone_path, self.repo.path, refs_path, outstream=outstream,
@@ -483,14 +466,13 @@ class PushTests(PorcelainTestCase):
 
 
         # Get the change in the target repo corresponding to the add
         # Get the change in the target repo corresponding to the add
         # this will be in the foo branch.
         # this will be in the foo branch.
-        change = list(tree_changes(self.repo, self.repo['HEAD'].tree,
-                                   self.repo['refs/heads/foo'].tree))[0]
+        change = list(tree_changes(self.repo, self.repo[b'HEAD'].tree,
+                                   self.repo[b'refs/heads/foo'].tree))[0]
 
 
-        self.assertEqual(r_clone['HEAD'].id, self.repo[refs_path].id)
-        self.assertEqual(os.path.basename(fullpath), change.new.path)
+        self.assertEqual(r_clone[b'HEAD'].id, self.repo[refs_path].id)
+        self.assertEqual(os.path.basename(fullpath), change.new.path.decode('ascii'))
 
 
 
 
-@skipIfPY3
 class PullTests(PorcelainTestCase):
 class PullTests(PorcelainTestCase):
 
 
     def test_simple(self):
     def test_simple(self):
@@ -501,30 +483,29 @@ class PullTests(PorcelainTestCase):
         handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
         handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
         filename = os.path.basename(fullpath)
         filename = os.path.basename(fullpath)
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
-        porcelain.commit(repo=self.repo.path, message='test',
-                         author='test', committer='test')
+        porcelain.commit(repo=self.repo.path, message=b'test',
+                         author=b'test', committer=b'test')
 
 
         # Setup target repo
         # Setup target repo
         target_path = tempfile.mkdtemp()
         target_path = tempfile.mkdtemp()
-        porcelain.clone(self.repo.path, target=target_path, outstream=outstream)
+        porcelain.clone(self.repo.path, target=target_path, errstream=errstream)
 
 
         # create a second file to be pushed
         # create a second file to be pushed
         handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
         handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
         filename = os.path.basename(fullpath)
         filename = os.path.basename(fullpath)
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
-        porcelain.commit(repo=self.repo.path, message='test2',
-            author='test2', committer='test2')
+        porcelain.commit(repo=self.repo.path, message=b'test2',
+            author=b'test2', committer=b'test2')
 
 
         # Pull changes into the cloned repo
         # Pull changes into the cloned repo
-        porcelain.pull(target_path, self.repo.path, 'refs/heads/master',
+        porcelain.pull(target_path, self.repo.path, b'refs/heads/master',
             outstream=outstream, errstream=errstream)
             outstream=outstream, errstream=errstream)
 
 
         # Check the target repo for pushed changes
         # Check the target repo for pushed changes
         r = Repo(target_path)
         r = Repo(target_path)
-        self.assertEqual(r['HEAD'].id, self.repo['HEAD'].id)
+        self.assertEqual(r[b'HEAD'].id, self.repo[b'HEAD'].id)
 
 
 
 
-@skipIfPY3
 class StatusTests(PorcelainTestCase):
 class StatusTests(PorcelainTestCase):
 
 
     def test_status(self):
     def test_status(self):
@@ -536,14 +517,14 @@ class StatusTests(PorcelainTestCase):
             f.write('origstuff')
             f.write('origstuff')
 
 
         porcelain.add(repo=self.repo.path, paths=['foo'])
         porcelain.add(repo=self.repo.path, paths=['foo'])
-        porcelain.commit(repo=self.repo.path, message='test status',
-            author='', committer='')
+        porcelain.commit(repo=self.repo.path, message=b'test status',
+            author=b'', committer=b'')
 
 
         # modify access and modify time of path
         # modify access and modify time of path
         os.utime(fullpath, (0, 0))
         os.utime(fullpath, (0, 0))
 
 
-        with open(fullpath, 'w') as f:
-            f.write('stuff')
+        with open(fullpath, 'wb') as f:
+            f.write(b'stuff')
 
 
         # Make a dummy file and stage it
         # Make a dummy file and stage it
         filename_add = 'bar'
         filename_add = 'bar'
@@ -554,8 +535,8 @@ class StatusTests(PorcelainTestCase):
 
 
         results = porcelain.status(self.repo)
         results = porcelain.status(self.repo)
 
 
-        self.assertEqual(results.staged['add'][0], filename_add)
-        self.assertEqual(results.unstaged, ['foo'])
+        self.assertEqual(results.staged['add'][0], filename_add.encode('ascii'))
+        self.assertEqual(results.unstaged, [b'foo'])
 
 
     def test_get_tree_changes_add(self):
     def test_get_tree_changes_add(self):
         """Unit test for get_tree_changes add."""
         """Unit test for get_tree_changes add."""
@@ -565,8 +546,8 @@ class StatusTests(PorcelainTestCase):
         with open(os.path.join(self.repo.path, filename), 'w') as f:
         with open(os.path.join(self.repo.path, filename), 'w') as f:
             f.write('stuff')
             f.write('stuff')
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
-        porcelain.commit(repo=self.repo.path, message='test status',
-            author='', committer='')
+        porcelain.commit(repo=self.repo.path, message=b'test status',
+            author=b'', committer=b'')
 
 
         filename = 'foo'
         filename = 'foo'
         with open(os.path.join(self.repo.path, filename), 'w') as f:
         with open(os.path.join(self.repo.path, filename), 'w') as f:
@@ -574,7 +555,7 @@ class StatusTests(PorcelainTestCase):
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
         changes = porcelain.get_tree_changes(self.repo.path)
         changes = porcelain.get_tree_changes(self.repo.path)
 
 
-        self.assertEqual(changes['add'][0], filename)
+        self.assertEqual(changes['add'][0], filename.encode('ascii'))
         self.assertEqual(len(changes['add']), 1)
         self.assertEqual(len(changes['add']), 1)
         self.assertEqual(len(changes['modify']), 0)
         self.assertEqual(len(changes['modify']), 0)
         self.assertEqual(len(changes['delete']), 0)
         self.assertEqual(len(changes['delete']), 0)
@@ -588,14 +569,14 @@ class StatusTests(PorcelainTestCase):
         with open(fullpath, 'w') as f:
         with open(fullpath, 'w') as f:
             f.write('stuff')
             f.write('stuff')
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
-        porcelain.commit(repo=self.repo.path, message='test status',
-            author='', committer='')
+        porcelain.commit(repo=self.repo.path, message=b'test status',
+            author=b'', committer=b'')
         with open(fullpath, 'w') as f:
         with open(fullpath, 'w') as f:
             f.write('otherstuff')
             f.write('otherstuff')
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
         changes = porcelain.get_tree_changes(self.repo.path)
         changes = porcelain.get_tree_changes(self.repo.path)
 
 
-        self.assertEqual(changes['modify'][0], filename)
+        self.assertEqual(changes['modify'][0], filename.encode('ascii'))
         self.assertEqual(len(changes['add']), 0)
         self.assertEqual(len(changes['add']), 0)
         self.assertEqual(len(changes['modify']), 1)
         self.assertEqual(len(changes['modify']), 1)
         self.assertEqual(len(changes['delete']), 0)
         self.assertEqual(len(changes['delete']), 0)
@@ -608,12 +589,12 @@ class StatusTests(PorcelainTestCase):
         with open(os.path.join(self.repo.path, filename), 'w') as f:
         with open(os.path.join(self.repo.path, filename), 'w') as f:
             f.write('stuff')
             f.write('stuff')
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
-        porcelain.commit(repo=self.repo.path, message='test status',
-            author='', committer='')
+        porcelain.commit(repo=self.repo.path, message=b'test status',
+            author=b'', committer=b'')
         porcelain.rm(repo=self.repo.path, paths=[filename])
         porcelain.rm(repo=self.repo.path, paths=[filename])
         changes = porcelain.get_tree_changes(self.repo.path)
         changes = porcelain.get_tree_changes(self.repo.path)
 
 
-        self.assertEqual(changes['delete'][0], filename)
+        self.assertEqual(changes['delete'][0], filename.encode('ascii'))
         self.assertEqual(len(changes['add']), 0)
         self.assertEqual(len(changes['add']), 0)
         self.assertEqual(len(changes['modify']), 0)
         self.assertEqual(len(changes['modify']), 0)
         self.assertEqual(len(changes['delete']), 1)
         self.assertEqual(len(changes['delete']), 1)
@@ -622,19 +603,17 @@ class StatusTests(PorcelainTestCase):
 # TODO(jelmer): Add test for dulwich.porcelain.daemon
 # TODO(jelmer): Add test for dulwich.porcelain.daemon
 
 
 
 
-@skipIfPY3
 class UploadPackTests(PorcelainTestCase):
 class UploadPackTests(PorcelainTestCase):
     """Tests for upload_pack."""
     """Tests for upload_pack."""
 
 
     def test_upload_pack(self):
     def test_upload_pack(self):
         outf = BytesIO()
         outf = BytesIO()
-        exitcode = porcelain.upload_pack(self.repo.path, BytesIO("0000"), outf)
+        exitcode = porcelain.upload_pack(self.repo.path, BytesIO(b"0000"), outf)
         outlines = outf.getvalue().splitlines()
         outlines = outf.getvalue().splitlines()
-        self.assertEqual(["0000"], outlines)
+        self.assertEqual([b"0000"], outlines)
         self.assertEqual(0, exitcode)
         self.assertEqual(0, exitcode)
 
 
 
 
-@skipIfPY3
 class ReceivePackTests(PorcelainTestCase):
 class ReceivePackTests(PorcelainTestCase):
     """Tests for receive_pack."""
     """Tests for receive_pack."""
 
 
@@ -643,21 +622,20 @@ class ReceivePackTests(PorcelainTestCase):
         with open(os.path.join(self.repo.path, filename), 'w') as f:
         with open(os.path.join(self.repo.path, filename), 'w') as f:
             f.write('stuff')
             f.write('stuff')
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
-        self.repo.do_commit(message='test status',
-            author='', committer='', author_timestamp=1402354300,
+        self.repo.do_commit(message=b'test status',
+            author=b'', committer=b'', author_timestamp=1402354300,
             commit_timestamp=1402354300, author_timezone=0, commit_timezone=0)
             commit_timestamp=1402354300, author_timezone=0, commit_timezone=0)
         outf = BytesIO()
         outf = BytesIO()
-        exitcode = porcelain.receive_pack(self.repo.path, BytesIO("0000"), outf)
+        exitcode = porcelain.receive_pack(self.repo.path, BytesIO(b"0000"), outf)
         outlines = outf.getvalue().splitlines()
         outlines = outf.getvalue().splitlines()
         self.assertEqual([
         self.assertEqual([
-            '005a9e65bdcf4a22cdd4f3700604a275cd2aaf146b23 HEAD\x00report-status '
-            'delete-refs side-band-64k',
-            '003f9e65bdcf4a22cdd4f3700604a275cd2aaf146b23 refs/heads/master',
-            '0000'], outlines)
+            b'005a9e65bdcf4a22cdd4f3700604a275cd2aaf146b23 HEAD\x00report-status '
+            b'delete-refs side-band-64k',
+            b'003f9e65bdcf4a22cdd4f3700604a275cd2aaf146b23 refs/heads/master',
+            b'0000'], outlines)
         self.assertEqual(0, exitcode)
         self.assertEqual(0, exitcode)
 
 
 
 
-@skipIfPY3
 class BranchListTests(PorcelainTestCase):
 class BranchListTests(PorcelainTestCase):
 
 
     def test_standard(self):
     def test_standard(self):
@@ -665,45 +643,42 @@ class BranchListTests(PorcelainTestCase):
 
 
     def test_new_branch(self):
     def test_new_branch(self):
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
-        self.repo["HEAD"] = c1.id
-        porcelain.branch_create(self.repo, "foo")
+        self.repo[b"HEAD"] = c1.id
+        porcelain.branch_create(self.repo, b"foo")
         self.assertEqual(
         self.assertEqual(
-            set(["master", "foo"]),
+            set([b"master", b"foo"]),
             set(porcelain.branch_list(self.repo)))
             set(porcelain.branch_list(self.repo)))
 
 
 
 
-@skipIfPY3
 class BranchCreateTests(PorcelainTestCase):
 class BranchCreateTests(PorcelainTestCase):
 
 
     def test_branch_exists(self):
     def test_branch_exists(self):
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
-        self.repo["HEAD"] = c1.id
-        porcelain.branch_create(self.repo, "foo")
-        self.assertRaises(KeyError, porcelain.branch_create, self.repo, "foo")
-        porcelain.branch_create(self.repo, "foo", force=True)
+        self.repo[b"HEAD"] = c1.id
+        porcelain.branch_create(self.repo, b"foo")
+        self.assertRaises(KeyError, porcelain.branch_create, self.repo, b"foo")
+        porcelain.branch_create(self.repo, b"foo", force=True)
 
 
     def test_new_branch(self):
     def test_new_branch(self):
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
-        self.repo["HEAD"] = c1.id
-        porcelain.branch_create(self.repo, "foo")
+        self.repo[b"HEAD"] = c1.id
+        porcelain.branch_create(self.repo, b"foo")
         self.assertEqual(
         self.assertEqual(
-            set(["master", "foo"]),
+            set([b"master", b"foo"]),
             set(porcelain.branch_list(self.repo)))
             set(porcelain.branch_list(self.repo)))
 
 
 
 
-@skipIfPY3
 class BranchDeleteTests(PorcelainTestCase):
 class BranchDeleteTests(PorcelainTestCase):
 
 
     def test_simple(self):
     def test_simple(self):
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
         [c1] = build_commit_graph(self.repo.object_store, [[1]])
-        self.repo["HEAD"] = c1.id
-        porcelain.branch_create(self.repo, 'foo')
-        self.assertTrue("foo" in porcelain.branch_list(self.repo))
-        porcelain.branch_delete(self.repo, 'foo')
-        self.assertFalse("foo" in porcelain.branch_list(self.repo))
+        self.repo[b"HEAD"] = c1.id
+        porcelain.branch_create(self.repo, b'foo')
+        self.assertTrue(b"foo" in porcelain.branch_list(self.repo))
+        porcelain.branch_delete(self.repo, b'foo')
+        self.assertFalse(b"foo" in porcelain.branch_list(self.repo))
 
 
 
 
-@skipIfPY3
 class FetchTests(PorcelainTestCase):
 class FetchTests(PorcelainTestCase):
 
 
     def test_simple(self):
     def test_simple(self):
@@ -714,22 +689,22 @@ class FetchTests(PorcelainTestCase):
         handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
         handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
         filename = os.path.basename(fullpath)
         filename = os.path.basename(fullpath)
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
-        porcelain.commit(repo=self.repo.path, message='test',
-                         author='test', committer='test')
+        porcelain.commit(repo=self.repo.path, message=b'test',
+                         author=b'test', committer=b'test')
 
 
         # Setup target repo
         # Setup target repo
         target_path = tempfile.mkdtemp()
         target_path = tempfile.mkdtemp()
         target_repo = porcelain.clone(self.repo.path, target=target_path,
         target_repo = porcelain.clone(self.repo.path, target=target_path,
-            outstream=outstream)
+            errstream=errstream)
 
 
         # create a second file to be pushed
         # create a second file to be pushed
         handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
         handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
         filename = os.path.basename(fullpath)
         filename = os.path.basename(fullpath)
         porcelain.add(repo=self.repo.path, paths=filename)
         porcelain.add(repo=self.repo.path, paths=filename)
-        porcelain.commit(repo=self.repo.path, message='test2',
-            author='test2', committer='test2')
+        porcelain.commit(repo=self.repo.path, message=b'test2',
+            author=b'test2', committer=b'test2')
 
 
-        self.assertFalse(self.repo['HEAD'].id in target_repo)
+        self.assertFalse(self.repo[b'HEAD'].id in target_repo)
 
 
         # Fetch changes into the cloned repo
         # Fetch changes into the cloned repo
         porcelain.fetch(target_path, self.repo.path, outstream=outstream,
         porcelain.fetch(target_path, self.repo.path, outstream=outstream,
@@ -737,4 +712,4 @@ class FetchTests(PorcelainTestCase):
 
 
         # Check the target repo for pushed changes
         # Check the target repo for pushed changes
         r = Repo(target_path)
         r = Repo(target_path)
-        self.assertTrue(self.repo['HEAD'].id in r)
+        self.assertTrue(self.repo[b'HEAD'].id in r)

+ 85 - 87
dulwich/tests/test_protocol.py

@@ -25,6 +25,7 @@ from dulwich.errors import (
     HangupException,
     HangupException,
     )
     )
 from dulwich.protocol import (
 from dulwich.protocol import (
+    GitProtocolError,
     PktLineParser,
     PktLineParser,
     Protocol,
     Protocol,
     ReceivableProtocol,
     ReceivableProtocol,
@@ -37,26 +38,25 @@ from dulwich.protocol import (
     BufferedPktLineWriter,
     BufferedPktLineWriter,
     )
     )
 from dulwich.tests import TestCase
 from dulwich.tests import TestCase
-from dulwich.tests.utils import skipIfPY3
 
 
 
 
 class BaseProtocolTests(object):
 class BaseProtocolTests(object):
 
 
     def test_write_pkt_line_none(self):
     def test_write_pkt_line_none(self):
         self.proto.write_pkt_line(None)
         self.proto.write_pkt_line(None)
-        self.assertEqual(self.rout.getvalue(), '0000')
+        self.assertEqual(self.rout.getvalue(), b'0000')
 
 
     def test_write_pkt_line(self):
     def test_write_pkt_line(self):
-        self.proto.write_pkt_line('bla')
-        self.assertEqual(self.rout.getvalue(), '0007bla')
+        self.proto.write_pkt_line(b'bla')
+        self.assertEqual(self.rout.getvalue(), b'0007bla')
 
 
     def test_read_pkt_line(self):
     def test_read_pkt_line(self):
-        self.rin.write('0008cmd ')
+        self.rin.write(b'0008cmd ')
         self.rin.seek(0)
         self.rin.seek(0)
-        self.assertEqual('cmd ', self.proto.read_pkt_line())
+        self.assertEqual(b'cmd ', self.proto.read_pkt_line())
 
 
     def test_eof(self):
     def test_eof(self):
-        self.rin.write('0000')
+        self.rin.write(b'0000')
         self.rin.seek(0)
         self.rin.seek(0)
         self.assertFalse(self.proto.eof())
         self.assertFalse(self.proto.eof())
         self.assertEqual(None, self.proto.read_pkt_line())
         self.assertEqual(None, self.proto.read_pkt_line())
@@ -64,50 +64,49 @@ class BaseProtocolTests(object):
         self.assertRaises(HangupException, self.proto.read_pkt_line)
         self.assertRaises(HangupException, self.proto.read_pkt_line)
 
 
     def test_unread_pkt_line(self):
     def test_unread_pkt_line(self):
-        self.rin.write('0007foo0000')
+        self.rin.write(b'0007foo0000')
         self.rin.seek(0)
         self.rin.seek(0)
-        self.assertEqual('foo', self.proto.read_pkt_line())
-        self.proto.unread_pkt_line('bar')
-        self.assertEqual('bar', self.proto.read_pkt_line())
+        self.assertEqual(b'foo', self.proto.read_pkt_line())
+        self.proto.unread_pkt_line(b'bar')
+        self.assertEqual(b'bar', self.proto.read_pkt_line())
         self.assertEqual(None, 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')
+        self.proto.unread_pkt_line(b'baz1')
+        self.assertRaises(ValueError, self.proto.unread_pkt_line, b'baz2')
 
 
     def test_read_pkt_seq(self):
     def test_read_pkt_seq(self):
-        self.rin.write('0008cmd 0005l0000')
+        self.rin.write(b'0008cmd 0005l0000')
         self.rin.seek(0)
         self.rin.seek(0)
-        self.assertEqual(['cmd ', 'l'], list(self.proto.read_pkt_seq()))
+        self.assertEqual([b'cmd ', b'l'], list(self.proto.read_pkt_seq()))
 
 
     def test_read_pkt_line_none(self):
     def test_read_pkt_line_none(self):
-        self.rin.write('0000')
+        self.rin.write(b'0000')
         self.rin.seek(0)
         self.rin.seek(0)
         self.assertEqual(None, self.proto.read_pkt_line())
         self.assertEqual(None, self.proto.read_pkt_line())
 
 
     def test_read_pkt_line_wrong_size(self):
     def test_read_pkt_line_wrong_size(self):
-        self.rin.write('0100too short')
+        self.rin.write(b'0100too short')
         self.rin.seek(0)
         self.rin.seek(0)
-        self.assertRaises(AssertionError, self.proto.read_pkt_line)
+        self.assertRaises(GitProtocolError, self.proto.read_pkt_line)
 
 
     def test_write_sideband(self):
     def test_write_sideband(self):
-        self.proto.write_sideband(3, 'bloe')
-        self.assertEqual(self.rout.getvalue(), '0009\x03bloe')
+        self.proto.write_sideband(3, b'bloe')
+        self.assertEqual(self.rout.getvalue(), b'0009\x03bloe')
 
 
     def test_send_cmd(self):
     def test_send_cmd(self):
-        self.proto.send_cmd('fetch', 'a', 'b')
-        self.assertEqual(self.rout.getvalue(), '000efetch a\x00b\x00')
+        self.proto.send_cmd(b'fetch', b'a', b'b')
+        self.assertEqual(self.rout.getvalue(), b'000efetch a\x00b\x00')
 
 
     def test_read_cmd(self):
     def test_read_cmd(self):
-        self.rin.write('0012cmd arg1\x00arg2\x00')
+        self.rin.write(b'0012cmd arg1\x00arg2\x00')
         self.rin.seek(0)
         self.rin.seek(0)
-        self.assertEqual(('cmd', ['arg1', 'arg2']), self.proto.read_cmd())
+        self.assertEqual((b'cmd', [b'arg1', b'arg2']), self.proto.read_cmd())
 
 
     def test_read_cmd_noend0(self):
     def test_read_cmd_noend0(self):
-        self.rin.write('0011cmd arg1\x00arg2')
+        self.rin.write(b'0011cmd arg1\x00arg2')
         self.rin.seek(0)
         self.rin.seek(0)
         self.assertRaises(AssertionError, self.proto.read_cmd)
         self.assertRaises(AssertionError, self.proto.read_cmd)
 
 
 
 
-@skipIfPY3
 class ProtocolTests(BaseProtocolTests, TestCase):
 class ProtocolTests(BaseProtocolTests, TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -128,14 +127,13 @@ class ReceivableBytesIO(BytesIO):
         # fail fast if no bytes are available; in a real socket, this would
         # fail fast if no bytes are available; in a real socket, this would
         # block forever
         # block forever
         if self.tell() == len(self.getvalue()) and not self.allow_read_past_eof:
         if self.tell() == len(self.getvalue()) and not self.allow_read_past_eof:
-            raise AssertionError('Blocking read past end of socket')
+            raise GitProtocolError('Blocking read past end of socket')
         if size == 1:
         if size == 1:
             return self.read(1)
             return self.read(1)
         # calls shouldn't return quite as much as asked for
         # calls shouldn't return quite as much as asked for
         return self.read(size - 1)
         return self.read(size - 1)
 
 
 
 
-@skipIfPY3
 class ReceivableProtocolTests(BaseProtocolTests, TestCase):
 class ReceivableProtocolTests(BaseProtocolTests, TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -153,41 +151,41 @@ class ReceivableProtocolTests(BaseProtocolTests, TestCase):
         BaseProtocolTests.test_eof(self)
         BaseProtocolTests.test_eof(self)
 
 
     def test_recv(self):
     def test_recv(self):
-        all_data = '1234567' * 10  # not a multiple of bufsize
+        all_data = b'1234567' * 10  # not a multiple of bufsize
         self.rin.write(all_data)
         self.rin.write(all_data)
         self.rin.seek(0)
         self.rin.seek(0)
-        data = ''
+        data = b''
         # We ask for 8 bytes each time and actually read 7, so it should take
         # We ask for 8 bytes each time and actually read 7, so it should take
         # exactly 10 iterations.
         # exactly 10 iterations.
         for _ in range(10):
         for _ in range(10):
             data += self.proto.recv(10)
             data += self.proto.recv(10)
         # any more reads would block
         # any more reads would block
-        self.assertRaises(AssertionError, self.proto.recv, 10)
+        self.assertRaises(GitProtocolError, self.proto.recv, 10)
         self.assertEqual(all_data, data)
         self.assertEqual(all_data, data)
 
 
     def test_recv_read(self):
     def test_recv_read(self):
-        all_data = '1234567'  # recv exactly in one call
+        all_data = b'1234567'  # recv exactly in one call
         self.rin.write(all_data)
         self.rin.write(all_data)
         self.rin.seek(0)
         self.rin.seek(0)
-        self.assertEqual('1234', self.proto.recv(4))
-        self.assertEqual('567', self.proto.read(3))
-        self.assertRaises(AssertionError, self.proto.recv, 10)
+        self.assertEqual(b'1234', self.proto.recv(4))
+        self.assertEqual(b'567', self.proto.read(3))
+        self.assertRaises(GitProtocolError, self.proto.recv, 10)
 
 
     def test_read_recv(self):
     def test_read_recv(self):
-        all_data = '12345678abcdefg'
+        all_data = b'12345678abcdefg'
         self.rin.write(all_data)
         self.rin.write(all_data)
         self.rin.seek(0)
         self.rin.seek(0)
-        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)
+        self.assertEqual(b'1234', self.proto.read(4))
+        self.assertEqual(b'5678abc', self.proto.recv(8))
+        self.assertEqual(b'defg', self.proto.read(4))
+        self.assertRaises(GitProtocolError, self.proto.recv, 10)
 
 
     def test_mixed(self):
     def test_mixed(self):
         # arbitrary non-repeating string
         # arbitrary non-repeating string
-        all_data = ','.join(str(i) for i in range(100))
+        all_data = b','.join(str(i).encode('ascii') for i in range(100))
         self.rin.write(all_data)
         self.rin.write(all_data)
         self.rin.seek(0)
         self.rin.seek(0)
-        data = ''
+        data = b''
 
 
         for i in range(1, 100):
         for i in range(1, 100):
             data += self.proto.recv(i)
             data += self.proto.recv(i)
@@ -207,37 +205,38 @@ class ReceivableProtocolTests(BaseProtocolTests, TestCase):
         self.assertEqual(all_data, data)
         self.assertEqual(all_data, data)
 
 
 
 
-@skipIfPY3
 class CapabilitiesTestCase(TestCase):
 class CapabilitiesTestCase(TestCase):
 
 
     def test_plain(self):
     def test_plain(self):
-        self.assertEqual(('bla', []), extract_capabilities('bla'))
+        self.assertEqual((b'bla', []), extract_capabilities(b'bla'))
 
 
     def test_caps(self):
     def test_caps(self):
-        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'))
+        self.assertEqual((b'bla', [b'la']), extract_capabilities(b'bla\0la'))
+        self.assertEqual((b'bla', [b'la']), extract_capabilities(b'bla\0la\n'))
+        self.assertEqual((b'bla', [b'la', b'la']), extract_capabilities(b'bla\0la la'))
 
 
     def test_plain_want_line(self):
     def test_plain_want_line(self):
-        self.assertEqual(('want bla', []), extract_want_line_capabilities('want bla'))
+        self.assertEqual((b'want bla', []), extract_want_line_capabilities(b'want bla'))
 
 
     def test_caps_want_line(self):
     def test_caps_want_line(self):
-        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'))
+        self.assertEqual((b'want bla', [b'la']),
+                extract_want_line_capabilities(b'want bla la'))
+        self.assertEqual((b'want bla', [b'la']),
+                extract_want_line_capabilities(b'want bla la\n'))
+        self.assertEqual((b'want bla', [b'la', b'la']),
+                extract_want_line_capabilities(b'want bla la la'))
 
 
     def test_ack_type(self):
     def test_ack_type(self):
-        self.assertEqual(SINGLE_ACK, ack_type(['foo', 'bar']))
-        self.assertEqual(MULTI_ACK, ack_type(['foo', 'bar', 'multi_ack']))
+        self.assertEqual(SINGLE_ACK, ack_type([b'foo', b'bar']))
+        self.assertEqual(MULTI_ACK, ack_type([b'foo', b'bar', b'multi_ack']))
         self.assertEqual(MULTI_ACK_DETAILED,
         self.assertEqual(MULTI_ACK_DETAILED,
-                          ack_type(['foo', 'bar', 'multi_ack_detailed']))
+                          ack_type([b'foo', b'bar', b'multi_ack_detailed']))
         # choose detailed when both present
         # choose detailed when both present
         self.assertEqual(MULTI_ACK_DETAILED,
         self.assertEqual(MULTI_ACK_DETAILED,
-                          ack_type(['foo', 'bar', 'multi_ack',
-                                    'multi_ack_detailed']))
+                          ack_type([b'foo', b'bar', b'multi_ack',
+                                    b'multi_ack_detailed']))
 
 
 
 
-@skipIfPY3
 class BufferedPktLineWriterTests(TestCase):
 class BufferedPktLineWriterTests(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -253,68 +252,67 @@ class BufferedPktLineWriterTests(TestCase):
         self._output.truncate()
         self._output.truncate()
 
 
     def test_write(self):
     def test_write(self):
-        self._writer.write('foo')
-        self.assertOutputEquals('')
+        self._writer.write(b'foo')
+        self.assertOutputEquals(b'')
         self._writer.flush()
         self._writer.flush()
-        self.assertOutputEquals('0007foo')
+        self.assertOutputEquals(b'0007foo')
 
 
     def test_write_none(self):
     def test_write_none(self):
         self._writer.write(None)
         self._writer.write(None)
-        self.assertOutputEquals('')
+        self.assertOutputEquals(b'')
         self._writer.flush()
         self._writer.flush()
-        self.assertOutputEquals('0000')
+        self.assertOutputEquals(b'0000')
 
 
     def test_flush_empty(self):
     def test_flush_empty(self):
         self._writer.flush()
         self._writer.flush()
-        self.assertOutputEquals('')
+        self.assertOutputEquals(b'')
 
 
     def test_write_multiple(self):
     def test_write_multiple(self):
-        self._writer.write('foo')
-        self._writer.write('bar')
-        self.assertOutputEquals('')
+        self._writer.write(b'foo')
+        self._writer.write(b'bar')
+        self.assertOutputEquals(b'')
         self._writer.flush()
         self._writer.flush()
-        self.assertOutputEquals('0007foo0007bar')
+        self.assertOutputEquals(b'0007foo0007bar')
 
 
     def test_write_across_boundary(self):
     def test_write_across_boundary(self):
-        self._writer.write('foo')
-        self._writer.write('barbaz')
-        self.assertOutputEquals('0007foo000abarba')
+        self._writer.write(b'foo')
+        self._writer.write(b'barbaz')
+        self.assertOutputEquals(b'0007foo000abarba')
         self._truncate()
         self._truncate()
         self._writer.flush()
         self._writer.flush()
-        self.assertOutputEquals('z')
+        self.assertOutputEquals(b'z')
 
 
     def test_write_to_boundary(self):
     def test_write_to_boundary(self):
-        self._writer.write('foo')
-        self._writer.write('barba')
-        self.assertOutputEquals('0007foo0009barba')
+        self._writer.write(b'foo')
+        self._writer.write(b'barba')
+        self.assertOutputEquals(b'0007foo0009barba')
         self._truncate()
         self._truncate()
-        self._writer.write('z')
+        self._writer.write(b'z')
         self._writer.flush()
         self._writer.flush()
-        self.assertOutputEquals('0005z')
+        self.assertOutputEquals(b'0005z')
 
 
 
 
-@skipIfPY3
 class PktLineParserTests(TestCase):
 class PktLineParserTests(TestCase):
 
 
     def test_none(self):
     def test_none(self):
         pktlines = []
         pktlines = []
         parser = PktLineParser(pktlines.append)
         parser = PktLineParser(pktlines.append)
-        parser.parse("0000")
+        parser.parse(b"0000")
         self.assertEqual(pktlines, [None])
         self.assertEqual(pktlines, [None])
-        self.assertEqual("", parser.get_tail())
+        self.assertEqual(b"", parser.get_tail())
 
 
     def test_small_fragments(self):
     def test_small_fragments(self):
         pktlines = []
         pktlines = []
         parser = PktLineParser(pktlines.append)
         parser = PktLineParser(pktlines.append)
-        parser.parse("00")
-        parser.parse("05")
-        parser.parse("z0000")
-        self.assertEqual(pktlines, ["z", None])
-        self.assertEqual("", parser.get_tail())
+        parser.parse(b"00")
+        parser.parse(b"05")
+        parser.parse(b"z0000")
+        self.assertEqual(pktlines, [b"z", None])
+        self.assertEqual(b"", parser.get_tail())
 
 
     def test_multiple_packets(self):
     def test_multiple_packets(self):
         pktlines = []
         pktlines = []
         parser = PktLineParser(pktlines.append)
         parser = PktLineParser(pktlines.append)
-        parser.parse("0005z0006aba")
-        self.assertEqual(pktlines, ["z", "ab"])
-        self.assertEqual("a", parser.get_tail())
+        parser.parse(b"0005z0006aba")
+        self.assertEqual(pktlines, [b"z", b"ab"])
+        self.assertEqual(b"a", parser.get_tail())

+ 10 - 12
dulwich/tests/test_refs.py

@@ -45,7 +45,6 @@ from dulwich.tests import (
 from dulwich.tests.utils import (
 from dulwich.tests.utils import (
     open_repo,
     open_repo,
     tear_down_repo,
     tear_down_repo,
-    skipIfPY3,
     )
     )
 
 
 
 
@@ -309,7 +308,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
 
 
     def test_setitem(self):
     def test_setitem(self):
         RefsContainerTests.test_setitem(self)
         RefsContainerTests.test_setitem(self)
-        f = open(os.path.join(self._refs.path, 'refs', 'some', 'ref'), 'rb')
+        f = open(os.path.join(self._refs.path, b'refs', b'some', b'ref'), 'rb')
         self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
         self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                          f.read()[:40])
                          f.read()[:40])
         f.close()
         f.close()
@@ -320,12 +319,12 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
         self.assertEqual(ones, self._refs[b'HEAD'])
         self.assertEqual(ones, self._refs[b'HEAD'])
 
 
         # ensure HEAD was not modified
         # ensure HEAD was not modified
-        f = open(os.path.join(self._refs.path, 'HEAD'), 'rb')
+        f = open(os.path.join(self._refs.path, b'HEAD'), 'rb')
         self.assertEqual(b'ref: refs/heads/master', next(iter(f)).rstrip(b'\n'))
         self.assertEqual(b'ref: refs/heads/master', next(iter(f)).rstrip(b'\n'))
         f.close()
         f.close()
 
 
         # ensure the symbolic link was written through
         # ensure the symbolic link was written through
-        f = open(os.path.join(self._refs.path, 'refs', 'heads', 'master'), 'rb')
+        f = open(os.path.join(self._refs.path, b'refs', b'heads', b'master'), 'rb')
         self.assertEqual(ones, f.read()[:40])
         self.assertEqual(ones, f.read()[:40])
         f.close()
         f.close()
 
 
@@ -337,9 +336,9 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
 
 
         # ensure lockfile was deleted
         # ensure lockfile was deleted
         self.assertFalse(os.path.exists(
         self.assertFalse(os.path.exists(
-            os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
+            os.path.join(self._refs.path, b'refs', b'heads', b'master.lock')))
         self.assertFalse(os.path.exists(
         self.assertFalse(os.path.exists(
-            os.path.join(self._refs.path, 'HEAD.lock')))
+            os.path.join(self._refs.path, b'HEAD.lock')))
 
 
     def test_add_if_new_packed(self):
     def test_add_if_new_packed(self):
         # don't overwrite packed ref
         # don't overwrite packed ref
@@ -348,7 +347,6 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
         self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
         self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
                          self._refs[b'refs/tags/refs-0.1'])
                          self._refs[b'refs/tags/refs-0.1'])
 
 
-    @skipIfPY3
     def test_add_if_new_symbolic(self):
     def test_add_if_new_symbolic(self):
         # Use an empty repo instead of the default.
         # Use an empty repo instead of the default.
         tear_down_repo(self._repo)
         tear_down_repo(self._repo)
@@ -379,7 +377,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
 
 
     def test_delitem(self):
     def test_delitem(self):
         RefsContainerTests.test_delitem(self)
         RefsContainerTests.test_delitem(self)
-        ref_file = os.path.join(self._refs.path, 'refs', 'heads', 'master')
+        ref_file = os.path.join(self._refs.path, b'refs', b'heads', b'master')
         self.assertFalse(os.path.exists(ref_file))
         self.assertFalse(os.path.exists(ref_file))
         self.assertFalse(b'refs/heads/master' in self._refs.get_packed_refs())
         self.assertFalse(b'refs/heads/master' in self._refs.get_packed_refs())
 
 
@@ -390,7 +388,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
         self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
         self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
         self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
         self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                          self._refs[b'refs/heads/master'])
                          self._refs[b'refs/heads/master'])
-        self.assertFalse(os.path.exists(os.path.join(self._refs.path, 'HEAD')))
+        self.assertFalse(os.path.exists(os.path.join(self._refs.path, b'HEAD')))
 
 
     def test_remove_if_equals_symref(self):
     def test_remove_if_equals_symref(self):
         # HEAD is a symref, so shouldn't equal its dereferenced value
         # HEAD is a symref, so shouldn't equal its dereferenced value
@@ -406,12 +404,12 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
                          self._refs.read_loose_ref(b'HEAD'))
                          self._refs.read_loose_ref(b'HEAD'))
 
 
         self.assertFalse(os.path.exists(
         self.assertFalse(os.path.exists(
-            os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
+            os.path.join(self._refs.path, b'refs', b'heads', b'master.lock')))
         self.assertFalse(os.path.exists(
         self.assertFalse(os.path.exists(
-            os.path.join(self._refs.path, 'HEAD.lock')))
+            os.path.join(self._refs.path, b'HEAD.lock')))
 
 
     def test_remove_packed_without_peeled(self):
     def test_remove_packed_without_peeled(self):
-        refs_file = os.path.join(self._repo.path, 'packed-refs')
+        refs_file = os.path.join(self._repo._controldir, b'packed-refs')
         f = GitFile(refs_file)
         f = GitFile(refs_file)
         refs_data = f.read()
         refs_data = f.read()
         f.close()
         f.close()

+ 312 - 235
dulwich/tests/test_repository.py

@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # test_repository.py -- tests for repository.py
 # test_repository.py -- tests for repository.py
 # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
 # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
 #
 #
@@ -22,8 +23,10 @@
 import os
 import os
 import stat
 import stat
 import shutil
 import shutil
+import sys
 import tempfile
 import tempfile
 import warnings
 import warnings
+import sys
 
 
 from dulwich import errors
 from dulwich import errors
 from dulwich.object_store import (
 from dulwich.object_store import (
@@ -42,13 +45,28 @@ from dulwich.tests.utils import (
     open_repo,
     open_repo,
     tear_down_repo,
     tear_down_repo,
     setup_warning_catcher,
     setup_warning_catcher,
-    skipIfPY3,
     )
     )
 
 
-missing_sha = 'b91fa4d900e17e99b433218e988c4eb4a3e9a097'
+missing_sha = b'b91fa4d900e17e99b433218e988c4eb4a3e9a097'
+
+
+def mkdtemp_bytes():
+    tmp_dir = tempfile.mkdtemp()
+    if sys.version_info[0] > 2:
+        tmp_dir = tmp_dir.encode(sys.getfilesystemencoding())
+    return tmp_dir
+
+def mkdtemp_unicode():
+    suffix = u'déłwíçh'
+    if sys.version_info[0] == 2:
+        suffix = suffix.encode(sys.getfilesystemencoding())
+    tmp_dir = tempfile.mkdtemp(suffix=suffix)
+    if sys.version_info[0] == 2:
+        tmp_dir = tmp_dir.decode(sys.getfilesystemencoding())
+    return tmp_dir
+
 
 
 
 
-@skipIfPY3
 class CreateRepositoryTests(TestCase):
 class CreateRepositoryTests(TestCase):
 
 
     def assertFileContentsEqual(self, expected, repo, path):
     def assertFileContentsEqual(self, expected, repo, path):
@@ -61,61 +79,86 @@ class CreateRepositoryTests(TestCase):
 
 
     def _check_repo_contents(self, repo, expect_bare):
     def _check_repo_contents(self, repo, expect_bare):
         self.assertEqual(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')
-        barestr = 'bare = %s' % str(expect_bare).lower()
-        config_text = repo.get_named_file('config').read()
-        self.assertTrue(barestr in config_text, "%r" % config_text)
+        self.assertFileContentsEqual(b'Unnamed repository', repo, b'description')
+        self.assertFileContentsEqual(b'', repo, os.path.join(b'info', b'exclude'))
+        self.assertFileContentsEqual(None, repo, b'nonexistent file')
+        barestr = b'bare = ' + str(expect_bare).lower().encode('ascii')
+        with repo.get_named_file(b'config') as f:
+            config_text = f.read()
+            self.assertTrue(barestr in config_text, "%r" % config_text)
+
+
+class CreateMemoryRepositoryTests(CreateRepositoryTests):
+
+    def test_create_memory(self):
+        repo = MemoryRepo.init_bare([], {})
+        self._check_repo_contents(repo, True)
+
+
+class CreateRepositoryBytesRootTests(CreateRepositoryTests):
+
+    def mkdtemp(self):
+        tmp_dir = mkdtemp_bytes()
+        return tmp_dir, tmp_dir
 
 
     def test_create_disk_bare(self):
     def test_create_disk_bare(self):
-        tmp_dir = tempfile.mkdtemp()
+        tmp_dir, tmp_dir_bytes = self.mkdtemp()
         self.addCleanup(shutil.rmtree, tmp_dir)
         self.addCleanup(shutil.rmtree, tmp_dir)
         repo = Repo.init_bare(tmp_dir)
         repo = Repo.init_bare(tmp_dir)
-        self.assertEqual(tmp_dir, repo._controldir)
+        self.assertEqual(tmp_dir_bytes, repo._controldir)
         self._check_repo_contents(repo, True)
         self._check_repo_contents(repo, True)
 
 
     def test_create_disk_non_bare(self):
     def test_create_disk_non_bare(self):
-        tmp_dir = tempfile.mkdtemp()
+        tmp_dir, tmp_dir_bytes = self.mkdtemp()
         self.addCleanup(shutil.rmtree, tmp_dir)
         self.addCleanup(shutil.rmtree, tmp_dir)
         repo = Repo.init(tmp_dir)
         repo = Repo.init(tmp_dir)
-        self.assertEqual(os.path.join(tmp_dir, '.git'), repo._controldir)
+        self.assertEqual(os.path.join(tmp_dir_bytes, b'.git'), repo._controldir)
         self._check_repo_contents(repo, False)
         self._check_repo_contents(repo, False)
 
 
-    def test_create_memory(self):
-        repo = MemoryRepo.init_bare([], {})
-        self._check_repo_contents(repo, True)
 
 
+class CreateRepositoryUnicodeRootTests(CreateRepositoryBytesRootTests):
+
+    def mktemp(self):
+        tmp_dir = mkdtemp_unicode()
+        tmp_dir_bytes = tmp_dir.encode(sys.getfilesystemencoding())
+        return tmp_dir, tmp_dir_bytes
 
 
-@skipIfPY3
-class RepositoryTests(TestCase):
+
+class RepositoryBytesRootTests(TestCase):
 
 
     def setUp(self):
     def setUp(self):
-        super(RepositoryTests, self).setUp()
+        super(RepositoryBytesRootTests, self).setUp()
         self._repo = None
         self._repo = None
 
 
     def tearDown(self):
     def tearDown(self):
         if self._repo is not None:
         if self._repo is not None:
             tear_down_repo(self._repo)
             tear_down_repo(self._repo)
-        super(RepositoryTests, self).tearDown()
+        super(RepositoryBytesRootTests, self).tearDown()
+
+    def mkdtemp(self):
+        return mkdtemp_bytes()
+
+    def open_repo(self, name):
+        temp_dir = self.mkdtemp()
+        return open_repo(name, temp_dir)
 
 
     def test_simple_props(self):
     def test_simple_props(self):
-        r = self._repo = open_repo('a.git')
-        self.assertEqual(r.controldir(), r.path)
+        r = self._repo = self.open_repo('a.git')
+        self.assertEqual(r.controldir(), r._path_bytes)
 
 
     def test_setitem(self):
     def test_setitem(self):
-        r = self._repo = open_repo('a.git')
-        r["refs/tags/foo"] = 'a90fa2d900a17e99b433217e988c4eb4a2e9a097'
-        self.assertEqual('a90fa2d900a17e99b433217e988c4eb4a2e9a097',
-                          r["refs/tags/foo"].id)
+        r = self._repo = self.open_repo('a.git')
+        r[b"refs/tags/foo"] = b'a90fa2d900a17e99b433217e988c4eb4a2e9a097'
+        self.assertEqual(b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
+                          r[b"refs/tags/foo"].id)
 
 
     def test_getitem_unicode(self):
     def test_getitem_unicode(self):
-        r = self._repo = open_repo('a.git')
+        r = self._repo = self.open_repo('a.git')
 
 
         test_keys = [
         test_keys = [
-            ('refs/heads/master', True),
-            ('a90fa2d900a17e99b433217e988c4eb4a2e9a097', True),
-            ('11' * 19 + '--', False),
+            (b'refs/heads/master', True),
+            (b'a90fa2d900a17e99b433217e988c4eb4a2e9a097', True),
+            (b'11' * 19 + b'--', False),
         ]
         ]
 
 
         for k, contained in test_keys:
         for k, contained in test_keys:
@@ -123,135 +166,139 @@ class RepositoryTests(TestCase):
 
 
         for k, _ in test_keys:
         for k, _ in test_keys:
             self.assertRaisesRegexp(
             self.assertRaisesRegexp(
-                TypeError, "'name' must be bytestring, not unicode",
-                r.__getitem__, unicode(k)
+                TypeError, "'name' must be bytestring, not int",
+                r.__getitem__, 12
             )
             )
 
 
     def test_delitem(self):
     def test_delitem(self):
-        r = self._repo = open_repo('a.git')
+        r = self._repo = self.open_repo('a.git')
 
 
-        del r['refs/heads/master']
-        self.assertRaises(KeyError, lambda: r['refs/heads/master'])
+        del r[b'refs/heads/master']
+        self.assertRaises(KeyError, lambda: r[b'refs/heads/master'])
 
 
-        del r['HEAD']
-        self.assertRaises(KeyError, lambda: r['HEAD'])
+        del r[b'HEAD']
+        self.assertRaises(KeyError, lambda: r[b'HEAD'])
 
 
-        self.assertRaises(ValueError, r.__delitem__, 'notrefs/foo')
+        self.assertRaises(ValueError, r.__delitem__, b'notrefs/foo')
 
 
     def test_get_refs(self):
     def test_get_refs(self):
-        r = self._repo = open_repo('a.git')
+        r = self._repo = self.open_repo('a.git')
         self.assertEqual({
         self.assertEqual({
-            'HEAD': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
-            'refs/heads/master': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
-            'refs/tags/mytag': '28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
-            'refs/tags/mytag-packed': 'b0931cadc54336e78a1d980420e3268903b57a50',
+            b'HEAD': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
+            b'refs/heads/master': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
+            b'refs/tags/mytag': b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
+            b'refs/tags/mytag-packed': b'b0931cadc54336e78a1d980420e3268903b57a50',
             }, r.get_refs())
             }, r.get_refs())
 
 
     def test_head(self):
     def test_head(self):
-        r = self._repo = open_repo('a.git')
-        self.assertEqual(r.head(), 'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
+        r = self._repo = self.open_repo('a.git')
+        self.assertEqual(r.head(), b'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
 
 
     def test_get_object(self):
     def test_get_object(self):
-        r = self._repo = open_repo('a.git')
+        r = self._repo = self.open_repo('a.git')
         obj = r.get_object(r.head())
         obj = r.get_object(r.head())
-        self.assertEqual(obj.type_name, 'commit')
+        self.assertEqual(obj.type_name, b'commit')
 
 
     def test_get_object_non_existant(self):
     def test_get_object_non_existant(self):
-        r = self._repo = open_repo('a.git')
+        r = self._repo = self.open_repo('a.git')
         self.assertRaises(KeyError, r.get_object, missing_sha)
         self.assertRaises(KeyError, r.get_object, missing_sha)
 
 
     def test_contains_object(self):
     def test_contains_object(self):
-        r = self._repo = open_repo('a.git')
+        r = self._repo = self.open_repo('a.git')
         self.assertTrue(r.head() in r)
         self.assertTrue(r.head() in r)
 
 
     def test_contains_ref(self):
     def test_contains_ref(self):
-        r = self._repo = open_repo('a.git')
-        self.assertTrue("HEAD" in r)
+        r = self._repo = self.open_repo('a.git')
+        self.assertTrue(b"HEAD" in r)
 
 
     def test_get_no_description(self):
     def test_get_no_description(self):
-        r = self._repo = open_repo('a.git')
+        r = self._repo = self.open_repo('a.git')
         self.assertIs(None, r.get_description())
         self.assertIs(None, r.get_description())
 
 
     def test_get_description(self):
     def test_get_description(self):
-        r = self._repo = open_repo('a.git')
-        with open(os.path.join(r.path, 'description'), 'w') as f:
-            f.write("Some description")
-        self.assertEqual("Some description", r.get_description())
+        r = self._repo = self.open_repo('a.git')
+        with open(os.path.join(r.path, 'description'), 'wb') as f:
+            f.write(b"Some description")
+        self.assertEqual(b"Some description", r.get_description())
 
 
     def test_set_description(self):
     def test_set_description(self):
-        r = self._repo = open_repo('a.git')
-        description = "Some description"
+        r = self._repo = self.open_repo('a.git')
+        description = b"Some description"
         r.set_description(description)
         r.set_description(description)
         self.assertEqual(description, r.get_description())
         self.assertEqual(description, r.get_description())
 
 
     def test_contains_missing(self):
     def test_contains_missing(self):
-        r = self._repo = open_repo('a.git')
-        self.assertFalse("bar" in r)
+        r = self._repo = self.open_repo('a.git')
+        self.assertFalse(b"bar" in r)
 
 
     def test_get_peeled(self):
     def test_get_peeled(self):
         # unpacked ref
         # unpacked ref
-        r = self._repo = open_repo('a.git')
-        tag_sha = '28237f4dc30d0d462658d6b937b08a0f0b6ef55a'
+        r = self._repo = self.open_repo('a.git')
+        tag_sha = b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a'
         self.assertNotEqual(r[tag_sha].sha().hexdigest(), r.head())
         self.assertNotEqual(r[tag_sha].sha().hexdigest(), r.head())
-        self.assertEqual(r.get_peeled('refs/tags/mytag'), r.head())
+        self.assertEqual(r.get_peeled(b'refs/tags/mytag'), r.head())
 
 
         # packed ref with cached peeled value
         # packed ref with cached peeled value
-        packed_tag_sha = 'b0931cadc54336e78a1d980420e3268903b57a50'
+        packed_tag_sha = b'b0931cadc54336e78a1d980420e3268903b57a50'
         parent_sha = r[r.head()].parents[0]
         parent_sha = r[r.head()].parents[0]
         self.assertNotEqual(r[packed_tag_sha].sha().hexdigest(), parent_sha)
         self.assertNotEqual(r[packed_tag_sha].sha().hexdigest(), parent_sha)
-        self.assertEqual(r.get_peeled('refs/tags/mytag-packed'), parent_sha)
+        self.assertEqual(r.get_peeled(b'refs/tags/mytag-packed'), parent_sha)
 
 
         # TODO: add more corner cases to test repo
         # TODO: add more corner cases to test repo
 
 
     def test_get_peeled_not_tag(self):
     def test_get_peeled_not_tag(self):
-        r = self._repo = open_repo('a.git')
-        self.assertEqual(r.get_peeled('HEAD'), r.head())
+        r = self._repo = self.open_repo('a.git')
+        self.assertEqual(r.get_peeled(b'HEAD'), r.head())
 
 
     def test_get_walker(self):
     def test_get_walker(self):
-        r = self._repo = open_repo('a.git')
+        r = self._repo = self.open_repo('a.git')
         # include defaults to [r.head()]
         # include defaults to [r.head()]
         self.assertEqual([e.commit.id for e in r.get_walker()],
         self.assertEqual([e.commit.id for e in r.get_walker()],
-                         [r.head(), '2a72d929692c41d8554c07f6301757ba18a65d91'])
+                         [r.head(), b'2a72d929692c41d8554c07f6301757ba18a65d91'])
         self.assertEqual(
         self.assertEqual(
-            [e.commit.id for e in r.get_walker(['2a72d929692c41d8554c07f6301757ba18a65d91'])],
-            ['2a72d929692c41d8554c07f6301757ba18a65d91'])
+            [e.commit.id for e in r.get_walker([b'2a72d929692c41d8554c07f6301757ba18a65d91'])],
+            [b'2a72d929692c41d8554c07f6301757ba18a65d91'])
         self.assertEqual(
         self.assertEqual(
-            [e.commit.id for e in r.get_walker('2a72d929692c41d8554c07f6301757ba18a65d91')],
-            ['2a72d929692c41d8554c07f6301757ba18a65d91'])
+            [e.commit.id for e in r.get_walker(b'2a72d929692c41d8554c07f6301757ba18a65d91')],
+            [b'2a72d929692c41d8554c07f6301757ba18a65d91'])
 
 
     def test_clone(self):
     def test_clone(self):
-        r = self._repo = open_repo('a.git')
-        tmp_dir = tempfile.mkdtemp()
+        r = self._repo = self.open_repo('a.git')
+        tmp_dir = self.mkdtemp()
         self.addCleanup(shutil.rmtree, tmp_dir)
         self.addCleanup(shutil.rmtree, tmp_dir)
         t = r.clone(tmp_dir, mkdir=False)
         t = r.clone(tmp_dir, mkdir=False)
         self.assertEqual({
         self.assertEqual({
-            'HEAD': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
-            'refs/remotes/origin/master':
-                'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
-            'refs/heads/master': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
-            'refs/tags/mytag': '28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
-            'refs/tags/mytag-packed':
-                'b0931cadc54336e78a1d980420e3268903b57a50',
+            b'HEAD': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
+            b'refs/remotes/origin/master':
+                b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
+            b'refs/heads/master': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
+            b'refs/tags/mytag': b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
+            b'refs/tags/mytag-packed':
+                b'b0931cadc54336e78a1d980420e3268903b57a50',
             }, t.refs.as_dict())
             }, t.refs.as_dict())
         shas = [e.commit.id for e in r.get_walker()]
         shas = [e.commit.id for e in r.get_walker()]
         self.assertEqual(shas, [t.head(),
         self.assertEqual(shas, [t.head(),
-                         '2a72d929692c41d8554c07f6301757ba18a65d91'])
+                         b'2a72d929692c41d8554c07f6301757ba18a65d91'])
 
 
     def test_clone_no_head(self):
     def test_clone_no_head(self):
-        temp_dir = tempfile.mkdtemp()
+        temp_dir = self.mkdtemp()
+        if isinstance(temp_dir, bytes):
+            temp_dir_str = temp_dir.decode(sys.getfilesystemencoding())
+        else:
+            temp_dir_str = temp_dir
         self.addCleanup(shutil.rmtree, temp_dir)
         self.addCleanup(shutil.rmtree, temp_dir)
         repo_dir = os.path.join(os.path.dirname(__file__), 'data', 'repos')
         repo_dir = os.path.join(os.path.dirname(__file__), 'data', 'repos')
-        dest_dir = os.path.join(temp_dir, 'a.git')
+        dest_dir = os.path.join(temp_dir_str, 'a.git')
         shutil.copytree(os.path.join(repo_dir, 'a.git'),
         shutil.copytree(os.path.join(repo_dir, 'a.git'),
                         dest_dir, symlinks=True)
                         dest_dir, symlinks=True)
         r = Repo(dest_dir)
         r = Repo(dest_dir)
-        del r.refs["refs/heads/master"]
-        del r.refs["HEAD"]
-        t = r.clone(os.path.join(temp_dir, 'b.git'), mkdir=True)
+        del r.refs[b"refs/heads/master"]
+        del r.refs[b"HEAD"]
+        t = r.clone(os.path.join(temp_dir_str, 'b.git'), mkdir=True)
         self.assertEqual({
         self.assertEqual({
-            'refs/tags/mytag': '28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
-            'refs/tags/mytag-packed':
-                'b0931cadc54336e78a1d980420e3268903b57a50',
+            b'refs/tags/mytag': b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
+            b'refs/tags/mytag-packed':
+                b'b0931cadc54336e78a1d980420e3268903b57a50',
             }, t.refs.as_dict())
             }, t.refs.as_dict())
 
 
     def test_clone_empty(self):
     def test_clone_empty(self):
@@ -262,50 +309,54 @@ class RepositoryTests(TestCase):
         to the server.
         to the server.
         Non-bare repo HEAD always points to an existing ref.
         Non-bare repo HEAD always points to an existing ref.
         """
         """
-        r = self._repo = open_repo('empty.git')
-        tmp_dir = tempfile.mkdtemp()
+        r = self._repo = self.open_repo('empty.git')
+        tmp_dir = self.mkdtemp()
         self.addCleanup(shutil.rmtree, tmp_dir)
         self.addCleanup(shutil.rmtree, tmp_dir)
         r.clone(tmp_dir, mkdir=False, bare=True)
         r.clone(tmp_dir, mkdir=False, bare=True)
 
 
     def test_merge_history(self):
     def test_merge_history(self):
-        r = self._repo = open_repo('simple_merge.git')
+        r = self._repo = self.open_repo('simple_merge.git')
         shas = [e.commit.id for e in r.get_walker()]
         shas = [e.commit.id for e in r.get_walker()]
-        self.assertEqual(shas, ['5dac377bdded4c9aeb8dff595f0faeebcc8498cc',
-                                'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
-                                '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6',
-                                '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
-                                '0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
+        self.assertEqual(shas, [b'5dac377bdded4c9aeb8dff595f0faeebcc8498cc',
+                                b'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
+                                b'4cffe90e0a41ad3f5190079d7c8f036bde29cbe6',
+                                b'60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
+                                b'0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
 
 
     def test_out_of_order_merge(self):
     def test_out_of_order_merge(self):
         """Test that revision history is ordered by date, not parent order."""
         """Test that revision history is ordered by date, not parent order."""
-        r = self._repo = open_repo('ooo_merge.git')
+        r = self._repo = self.open_repo('ooo_merge.git')
         shas = [e.commit.id for e in r.get_walker()]
         shas = [e.commit.id for e in r.get_walker()]
-        self.assertEqual(shas, ['7601d7f6231db6a57f7bbb79ee52e4d462fd44d1',
-                                'f507291b64138b875c28e03469025b1ea20bc614',
-                                'fb5b0425c7ce46959bec94d54b9a157645e114f5',
-                                'f9e39b120c68182a4ba35349f832d0e4e61f485c'])
+        self.assertEqual(shas, [b'7601d7f6231db6a57f7bbb79ee52e4d462fd44d1',
+                                b'f507291b64138b875c28e03469025b1ea20bc614',
+                                b'fb5b0425c7ce46959bec94d54b9a157645e114f5',
+                                b'f9e39b120c68182a4ba35349f832d0e4e61f485c'])
 
 
     def test_get_tags_empty(self):
     def test_get_tags_empty(self):
-        r = self._repo = open_repo('ooo_merge.git')
-        self.assertEqual({}, r.refs.as_dict('refs/tags'))
+        r = self._repo = self.open_repo('ooo_merge.git')
+        self.assertEqual({}, r.refs.as_dict(b'refs/tags'))
 
 
     def test_get_config(self):
     def test_get_config(self):
-        r = self._repo = open_repo('ooo_merge.git')
+        r = self._repo = self.open_repo('ooo_merge.git')
         self.assertIsInstance(r.get_config(), Config)
         self.assertIsInstance(r.get_config(), Config)
 
 
     def test_get_config_stack(self):
     def test_get_config_stack(self):
-        r = self._repo = open_repo('ooo_merge.git')
+        r = self._repo = self.open_repo('ooo_merge.git')
         self.assertIsInstance(r.get_config_stack(), Config)
         self.assertIsInstance(r.get_config_stack(), Config)
 
 
     def test_submodule(self):
     def test_submodule(self):
-        temp_dir = tempfile.mkdtemp()
+        temp_dir = self.mkdtemp()
         repo_dir = os.path.join(os.path.dirname(__file__), 'data', 'repos')
         repo_dir = os.path.join(os.path.dirname(__file__), 'data', 'repos')
+        if isinstance(temp_dir, bytes):
+            temp_dir_str = temp_dir.decode(sys.getfilesystemencoding())
+        else:
+            temp_dir_str = temp_dir
         shutil.copytree(os.path.join(repo_dir, 'a.git'),
         shutil.copytree(os.path.join(repo_dir, 'a.git'),
-                        os.path.join(temp_dir, 'a.git'), symlinks=True)
-        rel = os.path.relpath(os.path.join(repo_dir, 'submodule'), temp_dir)
-        os.symlink(os.path.join(rel, 'dotgit'), os.path.join(temp_dir, '.git'))
+                        os.path.join(temp_dir_str, 'a.git'), symlinks=True)
+        rel = os.path.relpath(os.path.join(repo_dir, 'submodule'), temp_dir_str)
+        os.symlink(os.path.join(rel, 'dotgit'), os.path.join(temp_dir_str, '.git'))
         r = Repo(temp_dir)
         r = Repo(temp_dir)
-        self.assertEqual(r.head(), 'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
+        self.assertEqual(r.head(), b'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
 
 
     def test_common_revisions(self):
     def test_common_revisions(self):
         """
         """
@@ -315,36 +366,36 @@ class RepositoryTests(TestCase):
         ``Repo.fetch_objects()``).
         ``Repo.fetch_objects()``).
         """
         """
 
 
-        expected_shas = set(['60dacdc733de308bb77bb76ce0fb0f9b44c9769e'])
+        expected_shas = set([b'60dacdc733de308bb77bb76ce0fb0f9b44c9769e'])
 
 
         # Source for objects.
         # Source for objects.
-        r_base = open_repo('simple_merge.git')
+        r_base = self.open_repo('simple_merge.git')
 
 
         # Re-create each-side of the merge in simple_merge.git.
         # Re-create each-side of the merge in simple_merge.git.
         #
         #
         # Since the trees and blobs are missing, the repository created is
         # Since the trees and blobs are missing, the repository created is
         # corrupted, but we're only checking for commits for the purpose of this
         # corrupted, but we're only checking for commits for the purpose of this
         # test, so it's immaterial.
         # test, so it's immaterial.
-        r1_dir = tempfile.mkdtemp()
-        r1_commits = ['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd', # HEAD
-                      '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
-                      '0d89f20333fbb1d2f3a94da77f4981373d8f4310']
+        r1_dir = self.mkdtemp()
+        r1_commits = [b'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd', # HEAD
+                      b'60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
+                      b'0d89f20333fbb1d2f3a94da77f4981373d8f4310']
 
 
-        r2_dir = tempfile.mkdtemp()
-        r2_commits = ['4cffe90e0a41ad3f5190079d7c8f036bde29cbe6', # HEAD
-                      '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
-                      '0d89f20333fbb1d2f3a94da77f4981373d8f4310']
+        r2_dir = self.mkdtemp()
+        r2_commits = [b'4cffe90e0a41ad3f5190079d7c8f036bde29cbe6', # HEAD
+                      b'60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
+                      b'0d89f20333fbb1d2f3a94da77f4981373d8f4310']
 
 
         try:
         try:
             r1 = Repo.init_bare(r1_dir)
             r1 = Repo.init_bare(r1_dir)
             for c in r1_commits:
             for c in r1_commits:
                 r1.object_store.add_object(r_base.get_object(c))
                 r1.object_store.add_object(r_base.get_object(c))
-            r1.refs['HEAD'] = r1_commits[0]
+            r1.refs[b'HEAD'] = r1_commits[0]
 
 
             r2 = Repo.init_bare(r2_dir)
             r2 = Repo.init_bare(r2_dir)
             for c in r2_commits:
             for c in r2_commits:
                 r2.object_store.add_object(r_base.get_object(c))
                 r2.object_store.add_object(r_base.get_object(c))
-            r2.refs['HEAD'] = r2_commits[0]
+            r2.refs[b'HEAD'] = r2_commits[0]
 
 
             # Finally, the 'real' testing!
             # Finally, the 'real' testing!
             shas = r2.object_store.find_common_revisions(r1.get_graph_walker())
             shas = r2.object_store.find_common_revisions(r1.get_graph_walker())
@@ -360,19 +411,19 @@ class RepositoryTests(TestCase):
         if os.name != 'posix':
         if os.name != 'posix':
             self.skipTest('shell hook tests requires POSIX shell')
             self.skipTest('shell hook tests requires POSIX shell')
 
 
-        pre_commit_fail = """#!/bin/sh
+        pre_commit_fail = b"""#!/bin/sh
 exit 1
 exit 1
 """
 """
 
 
-        pre_commit_success = """#!/bin/sh
+        pre_commit_success = b"""#!/bin/sh
 exit 0
 exit 0
 """
 """
 
 
-        repo_dir = os.path.join(tempfile.mkdtemp())
+        repo_dir = os.path.join(self.mkdtemp())
         r = Repo.init(repo_dir)
         r = Repo.init(repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
 
 
-        pre_commit = os.path.join(r.controldir(), 'hooks', 'pre-commit')
+        pre_commit = os.path.join(r.controldir(), b'hooks', b'pre-commit')
 
 
         with open(pre_commit, 'wb') as f:
         with open(pre_commit, 'wb') as f:
             f.write(pre_commit_fail)
             f.write(pre_commit_fail)
@@ -389,9 +440,9 @@ exit 0
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
         commit_sha = r.do_commit(
         commit_sha = r.do_commit(
-            'empty commit',
-            committer='Test Committer <test@nodomain.com>',
-            author='Test Author <test@nodomain.com>',
+            b'empty commit',
+            committer=b'Test Committer <test@nodomain.com>',
+            author=b'Test Author <test@nodomain.com>',
             commit_timestamp=12395, commit_timezone=0,
             commit_timestamp=12395, commit_timezone=0,
             author_timestamp=12395, author_timezone=0)
             author_timestamp=12395, author_timezone=0)
         self.assertEqual([], r[commit_sha].parents)
         self.assertEqual([], r[commit_sha].parents)
@@ -400,27 +451,27 @@ exit 0
         if os.name != 'posix':
         if os.name != 'posix':
             self.skipTest('shell hook tests requires POSIX shell')
             self.skipTest('shell hook tests requires POSIX shell')
 
 
-        commit_msg_fail = """#!/bin/sh
+        commit_msg_fail = b"""#!/bin/sh
 exit 1
 exit 1
 """
 """
 
 
-        commit_msg_success = """#!/bin/sh
+        commit_msg_success = b"""#!/bin/sh
 exit 0
 exit 0
 """
 """
 
 
-        repo_dir = os.path.join(tempfile.mkdtemp())
+        repo_dir = os.path.join(self.mkdtemp())
         r = Repo.init(repo_dir)
         r = Repo.init(repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
 
 
-        commit_msg = os.path.join(r.controldir(), 'hooks', 'commit-msg')
+        commit_msg = os.path.join(r.controldir(), b'hooks', b'commit-msg')
 
 
         with open(commit_msg, 'wb') as f:
         with open(commit_msg, 'wb') as f:
             f.write(commit_msg_fail)
             f.write(commit_msg_fail)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
-        self.assertRaises(errors.CommitError, r.do_commit, 'failed commit',
-                          committer='Test Committer <test@nodomain.com>',
-                          author='Test Author <test@nodomain.com>',
+        self.assertRaises(errors.CommitError, r.do_commit, b'failed commit',
+                          committer=b'Test Committer <test@nodomain.com>',
+                          author=b'Test Author <test@nodomain.com>',
                           commit_timestamp=12345, commit_timezone=0,
                           commit_timestamp=12345, commit_timezone=0,
                           author_timestamp=12345, author_timezone=0)
                           author_timestamp=12345, author_timezone=0)
 
 
@@ -429,9 +480,9 @@ exit 0
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
         commit_sha = r.do_commit(
         commit_sha = r.do_commit(
-            'empty commit',
-            committer='Test Committer <test@nodomain.com>',
-            author='Test Author <test@nodomain.com>',
+            b'empty commit',
+            committer=b'Test Committer <test@nodomain.com>',
+            author=b'Test Author <test@nodomain.com>',
             commit_timestamp=12395, commit_timezone=0,
             commit_timestamp=12395, commit_timezone=0,
             author_timestamp=12395, author_timezone=0)
             author_timestamp=12395, author_timezone=0)
         self.assertEqual([], r[commit_sha].parents)
         self.assertEqual([], r[commit_sha].parents)
@@ -440,33 +491,38 @@ exit 0
         if os.name != 'posix':
         if os.name != 'posix':
             self.skipTest('shell hook tests requires POSIX shell')
             self.skipTest('shell hook tests requires POSIX shell')
 
 
-        repo_dir = os.path.join(tempfile.mkdtemp())
+        repo_dir = self.mkdtemp()
+        if isinstance(repo_dir, bytes):
+            repo_dir_str = repo_dir.decode(sys.getfilesystemencoding())
+        else:
+            repo_dir_str = repo_dir
+
         r = Repo.init(repo_dir)
         r = Repo.init(repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
         self.addCleanup(shutil.rmtree, repo_dir)
 
 
-        (fd, path) = tempfile.mkstemp(dir=repo_dir)
+        (fd, path) = tempfile.mkstemp(dir=repo_dir_str)
         post_commit_msg = """#!/bin/sh
         post_commit_msg = """#!/bin/sh
-rm %(file)s
-""" % {'file': path}
+rm """ + path + """
+"""
 
 
         root_sha = r.do_commit(
         root_sha = r.do_commit(
-            'empty commit',
-            committer='Test Committer <test@nodomain.com>',
-            author='Test Author <test@nodomain.com>',
+            b'empty commit',
+            committer=b'Test Committer <test@nodomain.com>',
+            author=b'Test Author <test@nodomain.com>',
             commit_timestamp=12345, commit_timezone=0,
             commit_timestamp=12345, commit_timezone=0,
             author_timestamp=12345, author_timezone=0)
             author_timestamp=12345, author_timezone=0)
         self.assertEqual([], r[root_sha].parents)
         self.assertEqual([], r[root_sha].parents)
 
 
-        post_commit = os.path.join(r.controldir(), 'hooks', 'post-commit')
+        post_commit = os.path.join(r.controldir(), b'hooks', b'post-commit')
 
 
-        with open(post_commit, 'wb') as f:
+        with open(post_commit, 'w') as f:
             f.write(post_commit_msg)
             f.write(post_commit_msg)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
         commit_sha = r.do_commit(
         commit_sha = r.do_commit(
-            'empty commit',
-            committer='Test Committer <test@nodomain.com>',
-            author='Test Author <test@nodomain.com>',
+            b'empty commit',
+            committer=b'Test Committer <test@nodomain.com>',
+            author=b'Test Author <test@nodomain.com>',
             commit_timestamp=12345, commit_timezone=0,
             commit_timestamp=12345, commit_timezone=0,
             author_timestamp=12345, author_timezone=0)
             author_timestamp=12345, author_timezone=0)
         self.assertEqual([root_sha], r[commit_sha].parents)
         self.assertEqual([root_sha], r[commit_sha].parents)
@@ -476,7 +532,7 @@ rm %(file)s
         post_commit_msg_fail = """#!/bin/sh
         post_commit_msg_fail = """#!/bin/sh
 exit 1
 exit 1
 """
 """
-        with open(post_commit, 'wb') as f:
+        with open(post_commit, 'w') as f:
             f.write(post_commit_msg_fail)
             f.write(post_commit_msg_fail)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
 
@@ -486,40 +542,51 @@ exit 1
         self.addCleanup(restore_warnings)
         self.addCleanup(restore_warnings)
 
 
         commit_sha2 = r.do_commit(
         commit_sha2 = r.do_commit(
-            'empty commit',
-            committer='Test Committer <test@nodomain.com>',
-            author='Test Author <test@nodomain.com>',
+            b'empty commit',
+            committer=b'Test Committer <test@nodomain.com>',
+            author=b'Test Author <test@nodomain.com>',
             commit_timestamp=12345, commit_timezone=0,
             commit_timestamp=12345, commit_timezone=0,
             author_timestamp=12345, author_timezone=0)
             author_timestamp=12345, author_timezone=0)
-        self.assertEqual(len(warnings_list), 1)
+        self.assertEqual(len(warnings_list), 1, warnings_list)
         self.assertIsInstance(warnings_list[-1], UserWarning)
         self.assertIsInstance(warnings_list[-1], UserWarning)
         self.assertTrue("post-commit hook failed: " in str(warnings_list[-1]))
         self.assertTrue("post-commit hook failed: " in str(warnings_list[-1]))
         self.assertEqual([commit_sha], r[commit_sha2].parents)
         self.assertEqual([commit_sha], r[commit_sha2].parents)
 
 
 
 
-@skipIfPY3
-class BuildRepoTests(TestCase):
+class RepositoryUnicodeRootTests(RepositoryBytesRootTests):
+
+    def mktemp(self):
+        return mkdtemp_unicode()
+
+
+class BuildRepoBytesRootTests(TestCase):
     """Tests that build on-disk repos from scratch.
     """Tests that build on-disk repos from scratch.
 
 
     Repos live in a temp dir and are torn down after each test. They start with
     Repos live in a temp dir and are torn down after each test. They start with
     a single commit in master having single file named 'a'.
     a single commit in master having single file named 'a'.
     """
     """
 
 
+    def get_repo_dir(self):
+        return os.path.join(mkdtemp_bytes(), b'test')
+
+    def get_a_filename(self):
+        return b'a'
+
     def setUp(self):
     def setUp(self):
-        super(BuildRepoTests, self).setUp()
-        self._repo_dir = os.path.join(tempfile.mkdtemp(), 'test')
+        super(BuildRepoBytesRootTests, self).setUp()
+        self._repo_dir = self.get_repo_dir()
         os.makedirs(self._repo_dir)
         os.makedirs(self._repo_dir)
         r = self._repo = Repo.init(self._repo_dir)
         r = self._repo = Repo.init(self._repo_dir)
         self.assertFalse(r.bare)
         self.assertFalse(r.bare)
-        self.assertEqual('ref: refs/heads/master', r.refs.read_ref('HEAD'))
-        self.assertRaises(KeyError, lambda: r.refs['refs/heads/master'])
+        self.assertEqual(b'ref: refs/heads/master', r.refs.read_ref(b'HEAD'))
+        self.assertRaises(KeyError, lambda: r.refs[b'refs/heads/master'])
 
 
-        with open(os.path.join(r.path, 'a'), 'wb') as f:
-            f.write('file contents')
+        with open(os.path.join(r._path_bytes, b'a'), 'wb') as f:
+            f.write(b'file contents')
         r.stage(['a'])
         r.stage(['a'])
-        commit_sha = r.do_commit('msg',
-                                 committer='Test Committer <test@nodomain.com>',
-                                 author='Test Author <test@nodomain.com>',
+        commit_sha = r.do_commit(b'msg',
+                                 committer=b'Test Committer <test@nodomain.com>',
+                                 author=b'Test Author <test@nodomain.com>',
                                  commit_timestamp=12345, commit_timezone=0,
                                  commit_timestamp=12345, commit_timezone=0,
                                  author_timestamp=12345, author_timezone=0)
                                  author_timestamp=12345, author_timezone=0)
         self.assertEqual([], r[commit_sha].parents)
         self.assertEqual([], r[commit_sha].parents)
@@ -527,43 +594,43 @@ class BuildRepoTests(TestCase):
 
 
     def tearDown(self):
     def tearDown(self):
         tear_down_repo(self._repo)
         tear_down_repo(self._repo)
-        super(BuildRepoTests, self).tearDown()
+        super(BuildRepoBytesRootTests, self).tearDown()
 
 
     def test_build_repo(self):
     def test_build_repo(self):
         r = self._repo
         r = self._repo
-        self.assertEqual('ref: refs/heads/master', r.refs.read_ref('HEAD'))
-        self.assertEqual(self._root_commit, r.refs['refs/heads/master'])
-        expected_blob = objects.Blob.from_string('file contents')
+        self.assertEqual(b'ref: refs/heads/master', r.refs.read_ref(b'HEAD'))
+        self.assertEqual(self._root_commit, r.refs[b'refs/heads/master'])
+        expected_blob = objects.Blob.from_string(b'file contents')
         self.assertEqual(expected_blob.data, r[expected_blob.id].data)
         self.assertEqual(expected_blob.data, r[expected_blob.id].data)
         actual_commit = r[self._root_commit]
         actual_commit = r[self._root_commit]
-        self.assertEqual('msg', actual_commit.message)
+        self.assertEqual(b'msg', actual_commit.message)
 
 
     def test_commit_modified(self):
     def test_commit_modified(self):
         r = self._repo
         r = self._repo
-        with open(os.path.join(r.path, 'a'), 'wb') as f:
-            f.write('new contents')
-        os.symlink('a', os.path.join(self._repo_dir, 'b'))
+        with open(os.path.join(r._path_bytes, b'a'), 'wb') as f:
+            f.write(b'new contents')
+        os.symlink('a', os.path.join(r._path_bytes, b'b'))
         r.stage(['a', 'b'])
         r.stage(['a', 'b'])
-        commit_sha = r.do_commit('modified a',
-                                 committer='Test Committer <test@nodomain.com>',
-                                 author='Test Author <test@nodomain.com>',
+        commit_sha = r.do_commit(b'modified a',
+                                 committer=b'Test Committer <test@nodomain.com>',
+                                 author=b'Test Author <test@nodomain.com>',
                                  commit_timestamp=12395, commit_timezone=0,
                                  commit_timestamp=12395, commit_timezone=0,
                                  author_timestamp=12395, author_timezone=0)
                                  author_timestamp=12395, author_timezone=0)
         self.assertEqual([self._root_commit], r[commit_sha].parents)
         self.assertEqual([self._root_commit], r[commit_sha].parents)
-        a_mode, a_id = tree_lookup_path(r.get_object, r[commit_sha].tree, 'a')
+        a_mode, a_id = tree_lookup_path(r.get_object, r[commit_sha].tree, b'a')
         self.assertEqual(stat.S_IFREG | 0o644, a_mode)
         self.assertEqual(stat.S_IFREG | 0o644, a_mode)
-        self.assertEqual('new contents', r[a_id].data)
-        b_mode, b_id = tree_lookup_path(r.get_object, r[commit_sha].tree, 'b')
+        self.assertEqual(b'new contents', r[a_id].data)
+        b_mode, b_id = tree_lookup_path(r.get_object, r[commit_sha].tree, b'b')
         self.assertTrue(stat.S_ISLNK(b_mode))
         self.assertTrue(stat.S_ISLNK(b_mode))
-        self.assertEqual('a', r[b_id].data)
+        self.assertEqual(b'a', r[b_id].data)
 
 
     def test_commit_deleted(self):
     def test_commit_deleted(self):
         r = self._repo
         r = self._repo
-        os.remove(os.path.join(r.path, 'a'))
+        os.remove(os.path.join(r._path_bytes, b'a'))
         r.stage(['a'])
         r.stage(['a'])
-        commit_sha = r.do_commit('deleted a',
-                                 committer='Test Committer <test@nodomain.com>',
-                                 author='Test Author <test@nodomain.com>',
+        commit_sha = r.do_commit(b'deleted a',
+                                 committer=b'Test Committer <test@nodomain.com>',
+                                 author=b'Test Author <test@nodomain.com>',
                                  commit_timestamp=12395, commit_timezone=0,
                                  commit_timestamp=12395, commit_timezone=0,
                                  author_timestamp=12395, author_timezone=0)
                                  author_timestamp=12395, author_timezone=0)
         self.assertEqual([self._root_commit], r[commit_sha].parents)
         self.assertEqual([self._root_commit], r[commit_sha].parents)
@@ -573,42 +640,42 @@ class BuildRepoTests(TestCase):
 
 
     def test_commit_encoding(self):
     def test_commit_encoding(self):
         r = self._repo
         r = self._repo
-        commit_sha = r.do_commit('commit with strange character \xee',
-             committer='Test Committer <test@nodomain.com>',
-             author='Test Author <test@nodomain.com>',
+        commit_sha = r.do_commit(b'commit with strange character \xee',
+             committer=b'Test Committer <test@nodomain.com>',
+             author=b'Test Author <test@nodomain.com>',
              commit_timestamp=12395, commit_timezone=0,
              commit_timestamp=12395, commit_timezone=0,
              author_timestamp=12395, author_timezone=0,
              author_timestamp=12395, author_timezone=0,
-             encoding="iso8859-1")
-        self.assertEqual("iso8859-1", r[commit_sha].encoding)
+             encoding=b"iso8859-1")
+        self.assertEqual(b"iso8859-1", r[commit_sha].encoding)
 
 
     def test_commit_config_identity(self):
     def test_commit_config_identity(self):
         # commit falls back to the users' identity if it wasn't specified
         # commit falls back to the users' identity if it wasn't specified
         r = self._repo
         r = self._repo
         c = r.get_config()
         c = r.get_config()
-        c.set(("user", ), "name", "Jelmer")
-        c.set(("user", ), "email", "jelmer@apache.org")
+        c.set((b"user", ), b"name", b"Jelmer")
+        c.set((b"user", ), b"email", b"jelmer@apache.org")
         c.write_to_path()
         c.write_to_path()
-        commit_sha = r.do_commit('message')
+        commit_sha = r.do_commit(b'message')
         self.assertEqual(
         self.assertEqual(
-            "Jelmer <jelmer@apache.org>",
+            b"Jelmer <jelmer@apache.org>",
             r[commit_sha].author)
             r[commit_sha].author)
         self.assertEqual(
         self.assertEqual(
-            "Jelmer <jelmer@apache.org>",
+            b"Jelmer <jelmer@apache.org>",
             r[commit_sha].committer)
             r[commit_sha].committer)
 
 
     def test_commit_config_identity_in_memoryrepo(self):
     def test_commit_config_identity_in_memoryrepo(self):
         # commit falls back to the users' identity if it wasn't specified
         # commit falls back to the users' identity if it wasn't specified
         r = MemoryRepo.init_bare([], {})
         r = MemoryRepo.init_bare([], {})
         c = r.get_config()
         c = r.get_config()
-        c.set(("user", ), "name", "Jelmer")
-        c.set(("user", ), "email", "jelmer@apache.org")
+        c.set((b"user", ), b"name", b"Jelmer")
+        c.set((b"user", ), b"email", b"jelmer@apache.org")
 
 
-        commit_sha = r.do_commit('message', tree=objects.Tree().id)
+        commit_sha = r.do_commit(b'message', tree=objects.Tree().id)
         self.assertEqual(
         self.assertEqual(
-            "Jelmer <jelmer@apache.org>",
+            b"Jelmer <jelmer@apache.org>",
             r[commit_sha].author)
             r[commit_sha].author)
         self.assertEqual(
         self.assertEqual(
-            "Jelmer <jelmer@apache.org>",
+            b"Jelmer <jelmer@apache.org>",
             r[commit_sha].committer)
             r[commit_sha].committer)
 
 
     def test_commit_fail_ref(self):
     def test_commit_fail_ref(self):
@@ -623,9 +690,9 @@ class BuildRepoTests(TestCase):
         r.refs.add_if_new = add_if_new
         r.refs.add_if_new = add_if_new
 
 
         old_shas = set(r.object_store)
         old_shas = set(r.object_store)
-        self.assertRaises(errors.CommitError, r.do_commit, 'failed commit',
-                          committer='Test Committer <test@nodomain.com>',
-                          author='Test Author <test@nodomain.com>',
+        self.assertRaises(errors.CommitError, r.do_commit, b'failed commit',
+                          committer=b'Test Committer <test@nodomain.com>',
+                          author=b'Test Author <test@nodomain.com>',
                           commit_timestamp=12345, commit_timezone=0,
                           commit_timestamp=12345, commit_timezone=0,
                           author_timestamp=12345, author_timezone=0)
                           author_timestamp=12345, author_timezone=0)
         new_shas = set(r.object_store) - old_shas
         new_shas = set(r.object_store) - old_shas
@@ -633,45 +700,45 @@ class BuildRepoTests(TestCase):
         # Check that the new commit (now garbage) was added.
         # Check that the new commit (now garbage) was added.
         new_commit = r[new_shas.pop()]
         new_commit = r[new_shas.pop()]
         self.assertEqual(r[self._root_commit].tree, new_commit.tree)
         self.assertEqual(r[self._root_commit].tree, new_commit.tree)
-        self.assertEqual('failed commit', new_commit.message)
+        self.assertEqual(b'failed commit', new_commit.message)
 
 
     def test_commit_branch(self):
     def test_commit_branch(self):
         r = self._repo
         r = self._repo
 
 
-        commit_sha = r.do_commit('commit to branch',
-             committer='Test Committer <test@nodomain.com>',
-             author='Test Author <test@nodomain.com>',
+        commit_sha = r.do_commit(b'commit to branch',
+             committer=b'Test Committer <test@nodomain.com>',
+             author=b'Test Author <test@nodomain.com>',
              commit_timestamp=12395, commit_timezone=0,
              commit_timestamp=12395, commit_timezone=0,
              author_timestamp=12395, author_timezone=0,
              author_timestamp=12395, author_timezone=0,
-             ref="refs/heads/new_branch")
-        self.assertEqual(self._root_commit, r["HEAD"].id)
-        self.assertEqual(commit_sha, r["refs/heads/new_branch"].id)
+             ref=b"refs/heads/new_branch")
+        self.assertEqual(self._root_commit, r[b"HEAD"].id)
+        self.assertEqual(commit_sha, r[b"refs/heads/new_branch"].id)
         self.assertEqual([], r[commit_sha].parents)
         self.assertEqual([], r[commit_sha].parents)
-        self.assertTrue("refs/heads/new_branch" in r)
+        self.assertTrue(b"refs/heads/new_branch" in r)
 
 
         new_branch_head = commit_sha
         new_branch_head = commit_sha
 
 
-        commit_sha = r.do_commit('commit to branch 2',
-             committer='Test Committer <test@nodomain.com>',
-             author='Test Author <test@nodomain.com>',
+        commit_sha = r.do_commit(b'commit to branch 2',
+             committer=b'Test Committer <test@nodomain.com>',
+             author=b'Test Author <test@nodomain.com>',
              commit_timestamp=12395, commit_timezone=0,
              commit_timestamp=12395, commit_timezone=0,
              author_timestamp=12395, author_timezone=0,
              author_timestamp=12395, author_timezone=0,
-             ref="refs/heads/new_branch")
-        self.assertEqual(self._root_commit, r["HEAD"].id)
-        self.assertEqual(commit_sha, r["refs/heads/new_branch"].id)
+             ref=b"refs/heads/new_branch")
+        self.assertEqual(self._root_commit, r[b"HEAD"].id)
+        self.assertEqual(commit_sha, r[b"refs/heads/new_branch"].id)
         self.assertEqual([new_branch_head], r[commit_sha].parents)
         self.assertEqual([new_branch_head], r[commit_sha].parents)
 
 
     def test_commit_merge_heads(self):
     def test_commit_merge_heads(self):
         r = self._repo
         r = self._repo
-        merge_1 = r.do_commit('commit to branch 2',
-             committer='Test Committer <test@nodomain.com>',
-             author='Test Author <test@nodomain.com>',
+        merge_1 = r.do_commit(b'commit to branch 2',
+             committer=b'Test Committer <test@nodomain.com>',
+             author=b'Test Author <test@nodomain.com>',
              commit_timestamp=12395, commit_timezone=0,
              commit_timestamp=12395, commit_timezone=0,
              author_timestamp=12395, author_timezone=0,
              author_timestamp=12395, author_timezone=0,
-             ref="refs/heads/new_branch")
-        commit_sha = r.do_commit('commit with merge',
-             committer='Test Committer <test@nodomain.com>',
-             author='Test Author <test@nodomain.com>',
+             ref=b"refs/heads/new_branch")
+        commit_sha = r.do_commit(b'commit with merge',
+             committer=b'Test Committer <test@nodomain.com>',
+             author=b'Test Author <test@nodomain.com>',
              commit_timestamp=12395, commit_timezone=0,
              commit_timestamp=12395, commit_timezone=0,
              author_timestamp=12395, author_timezone=0,
              author_timestamp=12395, author_timezone=0,
              merge_heads=[merge_1])
              merge_heads=[merge_1])
@@ -684,9 +751,9 @@ class BuildRepoTests(TestCase):
 
 
         old_shas = set(r.object_store)
         old_shas = set(r.object_store)
         old_refs = r.get_refs()
         old_refs = r.get_refs()
-        commit_sha = r.do_commit('commit with no ref',
-             committer='Test Committer <test@nodomain.com>',
-             author='Test Author <test@nodomain.com>',
+        commit_sha = r.do_commit(b'commit with no ref',
+             committer=b'Test Committer <test@nodomain.com>',
+             author=b'Test Author <test@nodomain.com>',
              commit_timestamp=12395, commit_timezone=0,
              commit_timestamp=12395, commit_timezone=0,
              author_timestamp=12395, author_timezone=0,
              author_timestamp=12395, author_timezone=0,
              ref=None)
              ref=None)
@@ -704,9 +771,9 @@ class BuildRepoTests(TestCase):
 
 
         old_shas = set(r.object_store)
         old_shas = set(r.object_store)
         old_refs = r.get_refs()
         old_refs = r.get_refs()
-        commit_sha = r.do_commit('commit with no ref',
-             committer='Test Committer <test@nodomain.com>',
-             author='Test Author <test@nodomain.com>',
+        commit_sha = r.do_commit(b'commit with no ref',
+             committer=b'Test Committer <test@nodomain.com>',
+             author=b'Test Author <test@nodomain.com>',
              commit_timestamp=12395, commit_timezone=0,
              commit_timestamp=12395, commit_timezone=0,
              author_timestamp=12395, author_timezone=0,
              author_timestamp=12395, author_timezone=0,
              ref=None, merge_heads=[self._root_commit])
              ref=None, merge_heads=[self._root_commit])
@@ -721,7 +788,17 @@ class BuildRepoTests(TestCase):
 
 
     def test_stage_deleted(self):
     def test_stage_deleted(self):
         r = self._repo
         r = self._repo
-        os.remove(os.path.join(r.path, 'a'))
+        os.remove(os.path.join(r._path_bytes, b'a'))
         r.stage(['a'])
         r.stage(['a'])
         r.stage(['a'])  # double-stage a deleted path
         r.stage(['a'])  # double-stage a deleted path
 
 
+
+class BuildRepoUnicodeRootTests(TestCase):
+    """Tests that build on-disk repos from scratch.
+
+    Repos live in a temp dir and are torn down after each test. They start with
+    a single commit in master having single file named 'a'.
+    """
+
+    def get_repo_dir(self):
+        return os.path.join(mkdtemp_unicode(), 'test')

+ 138 - 158
dulwich/tests/test_server.py

@@ -28,10 +28,6 @@ from dulwich.errors import (
     UnexpectedCommandError,
     UnexpectedCommandError,
     HangupException,
     HangupException,
     )
     )
-from dulwich.objects import (
-    Commit,
-    Tag,
-    )
 from dulwich.object_store import (
 from dulwich.object_store import (
     MemoryObjectStore,
     MemoryObjectStore,
     )
     )
@@ -58,20 +54,18 @@ from dulwich.server import (
 from dulwich.tests import TestCase
 from dulwich.tests import TestCase
 from dulwich.tests.utils import (
 from dulwich.tests.utils import (
     make_commit,
     make_commit,
-    make_object,
     make_tag,
     make_tag,
-    skipIfPY3,
     )
     )
 from dulwich.protocol import (
 from dulwich.protocol import (
     ZERO_SHA,
     ZERO_SHA,
     )
     )
 
 
-ONE = '1' * 40
-TWO = '2' * 40
-THREE = '3' * 40
-FOUR = '4' * 40
-FIVE = '5' * 40
-SIX = '6' * 40
+ONE = b'1' * 40
+TWO = b'2' * 40
+THREE = b'3' * 40
+FOUR = b'4' * 40
+FIVE = b'5' * 40
+SIX = b'6' * 40
 
 
 
 
 class TestProto(object):
 class TestProto(object):
@@ -87,7 +81,7 @@ class TestProto(object):
         if self._output:
         if self._output:
             data = self._output.pop(0)
             data = self._output.pop(0)
             if data is not None:
             if data is not None:
-                return '%s\n' % data.rstrip()
+                return data.rstrip() + b'\n'
             else:
             else:
                 # flush-pkt ('0000').
                 # flush-pkt ('0000').
                 return None
                 return None
@@ -112,11 +106,11 @@ class TestGenericHandler(Handler):
 
 
     @classmethod
     @classmethod
     def capabilities(cls):
     def capabilities(cls):
-        return ('cap1', 'cap2', 'cap3')
+        return (b'cap1', b'cap2', b'cap3')
 
 
     @classmethod
     @classmethod
     def required_capabilities(cls):
     def required_capabilities(cls):
-        return ('cap2',)
+        return (b'cap2',)
 
 
 
 
 class HandlerTestCase(TestCase):
 class HandlerTestCase(TestCase):
@@ -132,81 +126,80 @@ class HandlerTestCase(TestCase):
             self.fail(e)
             self.fail(e)
 
 
     def test_capability_line(self):
     def test_capability_line(self):
-        self.assertEqual('cap1 cap2 cap3', self._handler.capability_line())
+        self.assertEqual(b'cap1 cap2 cap3', self._handler.capability_line())
 
 
     def test_set_client_capabilities(self):
     def test_set_client_capabilities(self):
         set_caps = self._handler.set_client_capabilities
         set_caps = self._handler.set_client_capabilities
-        self.assertSucceeds(set_caps, ['cap2'])
-        self.assertSucceeds(set_caps, ['cap1', 'cap2'])
+        self.assertSucceeds(set_caps, [b'cap2'])
+        self.assertSucceeds(set_caps, [b'cap1', b'cap2'])
 
 
         # different order
         # different order
-        self.assertSucceeds(set_caps, ['cap3', 'cap1', 'cap2'])
+        self.assertSucceeds(set_caps, [b'cap3', b'cap1', b'cap2'])
 
 
         # error cases
         # error cases
-        self.assertRaises(GitProtocolError, set_caps, ['capxxx', 'cap2'])
-        self.assertRaises(GitProtocolError, set_caps, ['cap1', 'cap3'])
+        self.assertRaises(GitProtocolError, set_caps, [b'capxxx', b'cap2'])
+        self.assertRaises(GitProtocolError, set_caps, [b'cap1', b'cap3'])
 
 
         # ignore innocuous but unknown capabilities
         # ignore innocuous but unknown capabilities
-        self.assertRaises(GitProtocolError, set_caps, ['cap2', 'ignoreme'])
-        self.assertFalse('ignoreme' in self._handler.capabilities())
-        self._handler.innocuous_capabilities = lambda: ('ignoreme',)
-        self.assertSucceeds(set_caps, ['cap2', 'ignoreme'])
+        self.assertRaises(GitProtocolError, set_caps, [b'cap2', b'ignoreme'])
+        self.assertFalse(b'ignoreme' in self._handler.capabilities())
+        self._handler.innocuous_capabilities = lambda: (b'ignoreme',)
+        self.assertSucceeds(set_caps, [b'cap2', b'ignoreme'])
 
 
     def test_has_capability(self):
     def test_has_capability(self):
-        self.assertRaises(GitProtocolError, self._handler.has_capability, 'cap')
+        self.assertRaises(GitProtocolError, self._handler.has_capability, b'cap')
         caps = self._handler.capabilities()
         caps = self._handler.capabilities()
         self._handler.set_client_capabilities(caps)
         self._handler.set_client_capabilities(caps)
         for cap in caps:
         for cap in caps:
             self.assertTrue(self._handler.has_capability(cap))
             self.assertTrue(self._handler.has_capability(cap))
-        self.assertFalse(self._handler.has_capability('capxxx'))
+        self.assertFalse(self._handler.has_capability(b'capxxx'))
 
 
 
 
-@skipIfPY3
 class UploadPackHandlerTestCase(TestCase):
 class UploadPackHandlerTestCase(TestCase):
 
 
     def setUp(self):
     def setUp(self):
         super(UploadPackHandlerTestCase, self).setUp()
         super(UploadPackHandlerTestCase, self).setUp()
         self._repo = MemoryRepo.init_bare([], {})
         self._repo = MemoryRepo.init_bare([], {})
-        backend = DictBackend({'/': self._repo})
+        backend = DictBackend({b'/': self._repo})
         self._handler = UploadPackHandler(
         self._handler = UploadPackHandler(
-          backend, ['/', 'host=lolcathost'], TestProto())
+          backend, [b'/', b'host=lolcathost'], TestProto())
 
 
     def test_progress(self):
     def test_progress(self):
         caps = self._handler.required_capabilities()
         caps = self._handler.required_capabilities()
         self._handler.set_client_capabilities(caps)
         self._handler.set_client_capabilities(caps)
-        self._handler.progress('first message')
-        self._handler.progress('second message')
-        self.assertEqual('first message',
+        self._handler.progress(b'first message')
+        self._handler.progress(b'second message')
+        self.assertEqual(b'first message',
                          self._handler.proto.get_received_line(2))
                          self._handler.proto.get_received_line(2))
-        self.assertEqual('second message',
+        self.assertEqual(b'second message',
                          self._handler.proto.get_received_line(2))
                          self._handler.proto.get_received_line(2))
         self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
         self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
 
 
     def test_no_progress(self):
     def test_no_progress(self):
-        caps = list(self._handler.required_capabilities()) + ['no-progress']
+        caps = list(self._handler.required_capabilities()) + [b'no-progress']
         self._handler.set_client_capabilities(caps)
         self._handler.set_client_capabilities(caps)
-        self._handler.progress('first message')
-        self._handler.progress('second message')
+        self._handler.progress(b'first message')
+        self._handler.progress(b'second message')
         self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
         self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
 
 
     def test_get_tagged(self):
     def test_get_tagged(self):
         refs = {
         refs = {
-            'refs/tags/tag1': ONE,
-            'refs/tags/tag2': TWO,
-            'refs/heads/master': FOUR,  # not a tag, no peeled value
+            b'refs/tags/tag1': ONE,
+            b'refs/tags/tag2': TWO,
+            b'refs/heads/master': FOUR,  # not a tag, no peeled value
             }
             }
         # repo needs to peel this object
         # repo needs to peel this object
         self._repo.object_store.add_object(make_commit(id=FOUR))
         self._repo.object_store.add_object(make_commit(id=FOUR))
         self._repo.refs._update(refs)
         self._repo.refs._update(refs)
         peeled = {
         peeled = {
-            'refs/tags/tag1': '1234' * 10,
-            'refs/tags/tag2': '5678' * 10,
+            b'refs/tags/tag1': b'1234' * 10,
+            b'refs/tags/tag2': b'5678' * 10,
             }
             }
         self._repo.refs._update_peeled(peeled)
         self._repo.refs._update_peeled(peeled)
 
 
-        caps = list(self._handler.required_capabilities()) + ['include-tag']
+        caps = list(self._handler.required_capabilities()) + [b'include-tag']
         self._handler.set_client_capabilities(caps)
         self._handler.set_client_capabilities(caps)
-        self.assertEqual({'1234' * 10: ONE, '5678' * 10: TWO},
+        self.assertEqual({b'1234' * 10: ONE, b'5678' * 10: TWO},
                           self._handler.get_tagged(refs, repo=self._repo))
                           self._handler.get_tagged(refs, repo=self._repo))
 
 
         # non-include-tag case
         # non-include-tag case
@@ -215,7 +208,6 @@ class UploadPackHandlerTestCase(TestCase):
         self.assertEqual({}, self._handler.get_tagged(refs, repo=self._repo))
         self.assertEqual({}, self._handler.get_tagged(refs, repo=self._repo))
 
 
 
 
-@skipIfPY3
 class FindShallowTests(TestCase):
 class FindShallowTests(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -226,7 +218,7 @@ class FindShallowTests(TestCase):
         self._store.add_object(commit)
         self._store.add_object(commit)
         return commit
         return commit
 
 
-    def make_linear_commits(self, n, message=''):
+    def make_linear_commits(self, n, message=b''):
         commits = []
         commits = []
         parents = []
         parents = []
         for _ in range(n):
         for _ in range(n):
@@ -250,9 +242,9 @@ class FindShallowTests(TestCase):
                          _find_shallow(self._store, [c3.id], 3))
                          _find_shallow(self._store, [c3.id], 3))
 
 
     def test_multiple_independent(self):
     def test_multiple_independent(self):
-        a = self.make_linear_commits(2, message='a')
-        b = self.make_linear_commits(2, message='b')
-        c = self.make_linear_commits(2, message='c')
+        a = self.make_linear_commits(2, message=b'a')
+        b = self.make_linear_commits(2, message=b'b')
+        c = self.make_linear_commits(2, message=b'c')
         heads = [a[1].id, b[1].id, c[1].id]
         heads = [a[1].id, b[1].id, c[1].id]
 
 
         self.assertEqual((set([a[0].id, b[0].id, c[0].id]), set(heads)),
         self.assertEqual((set([a[0].id, b[0].id, c[0].id]), set(heads)),
@@ -281,7 +273,7 @@ class FindShallowTests(TestCase):
 
 
     def test_tag(self):
     def test_tag(self):
         c1, c2 = self.make_linear_commits(2)
         c1, c2 = self.make_linear_commits(2)
-        tag = make_tag(c2, name='tag')
+        tag = make_tag(c2, name=b'tag')
         self._store.add_object(tag)
         self._store.add_object(tag)
 
 
         self.assertEqual((set([c1.id]), set([c2.id])),
         self.assertEqual((set([c1.id]), set([c2.id])),
@@ -293,37 +285,35 @@ class TestUploadPackHandler(UploadPackHandler):
     def required_capabilities(self):
     def required_capabilities(self):
         return ()
         return ()
 
 
-@skipIfPY3
 class ReceivePackHandlerTestCase(TestCase):
 class ReceivePackHandlerTestCase(TestCase):
 
 
     def setUp(self):
     def setUp(self):
         super(ReceivePackHandlerTestCase, self).setUp()
         super(ReceivePackHandlerTestCase, self).setUp()
         self._repo = MemoryRepo.init_bare([], {})
         self._repo = MemoryRepo.init_bare([], {})
-        backend = DictBackend({'/': self._repo})
+        backend = DictBackend({b'/': self._repo})
         self._handler = ReceivePackHandler(
         self._handler = ReceivePackHandler(
-          backend, ['/', 'host=lolcathost'], TestProto())
+          backend, [b'/', b'host=lolcathost'], TestProto())
 
 
     def test_apply_pack_del_ref(self):
     def test_apply_pack_del_ref(self):
         refs = {
         refs = {
-            'refs/heads/master': TWO,
-            'refs/heads/fake-branch': ONE}
+            b'refs/heads/master': TWO,
+            b'refs/heads/fake-branch': ONE}
         self._repo.refs._update(refs)
         self._repo.refs._update(refs)
-        update_refs = [[ONE, ZERO_SHA, 'refs/heads/fake-branch'], ]
+        update_refs = [[ONE, ZERO_SHA, b'refs/heads/fake-branch'], ]
         status = self._handler._apply_pack(update_refs)
         status = self._handler._apply_pack(update_refs)
-        self.assertEqual(status[0][0], 'unpack')
-        self.assertEqual(status[0][1], 'ok')
-        self.assertEqual(status[1][0], 'refs/heads/fake-branch')
-        self.assertEqual(status[1][1], 'ok')
+        self.assertEqual(status[0][0], b'unpack')
+        self.assertEqual(status[0][1], b'ok')
+        self.assertEqual(status[1][0], b'refs/heads/fake-branch')
+        self.assertEqual(status[1][1], b'ok')
 
 
 
 
-@skipIfPY3
 class ProtocolGraphWalkerEmptyTestCase(TestCase):
 class ProtocolGraphWalkerEmptyTestCase(TestCase):
     def setUp(self):
     def setUp(self):
         super(ProtocolGraphWalkerEmptyTestCase, self).setUp()
         super(ProtocolGraphWalkerEmptyTestCase, self).setUp()
         self._repo = MemoryRepo.init_bare([], {})
         self._repo = MemoryRepo.init_bare([], {})
-        backend = DictBackend({'/': self._repo})
+        backend = DictBackend({b'/': self._repo})
         self._walker = ProtocolGraphWalker(
         self._walker = ProtocolGraphWalker(
-            TestUploadPackHandler(backend, ['/', 'host=lolcats'], TestProto()),
+            TestUploadPackHandler(backend, [b'/', b'host=lolcats'], TestProto()),
             self._repo.object_store, self._repo.get_peeled)
             self._repo.object_store, self._repo.get_peeled)
 
 
     def test_empty_repository(self):
     def test_empty_repository(self):
@@ -338,7 +328,6 @@ class ProtocolGraphWalkerEmptyTestCase(TestCase):
 
 
 
 
 
 
-@skipIfPY3
 class ProtocolGraphWalkerTestCase(TestCase):
 class ProtocolGraphWalkerTestCase(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -355,9 +344,9 @@ class ProtocolGraphWalkerTestCase(TestCase):
           make_commit(id=FIVE, parents=[THREE], commit_time=555),
           make_commit(id=FIVE, parents=[THREE], commit_time=555),
           ]
           ]
         self._repo = MemoryRepo.init_bare(commits, {})
         self._repo = MemoryRepo.init_bare(commits, {})
-        backend = DictBackend({'/': self._repo})
+        backend = DictBackend({b'/': self._repo})
         self._walker = ProtocolGraphWalker(
         self._walker = ProtocolGraphWalker(
-            TestUploadPackHandler(backend, ['/', 'host=lolcats'], TestProto()),
+            TestUploadPackHandler(backend, [b'/', b'host=lolcats'], TestProto()),
             self._repo.object_store, self._repo.get_peeled)
             self._repo.object_store, self._repo.get_peeled)
 
 
     def test_all_wants_satisfied_no_haves(self):
     def test_all_wants_satisfied_no_haves(self):
@@ -394,20 +383,20 @@ class ProtocolGraphWalkerTestCase(TestCase):
         self.assertTrue(self._walker.all_wants_satisfied([TWO, THREE]))
         self.assertTrue(self._walker.all_wants_satisfied([TWO, THREE]))
 
 
     def test_split_proto_line(self):
     def test_split_proto_line(self):
-        allowed = ('want', 'done', None)
-        self.assertEqual(('want', ONE),
-                          _split_proto_line('want %s\n' % ONE, allowed))
-        self.assertEqual(('want', TWO),
-                          _split_proto_line('want %s\n' % TWO, allowed))
+        allowed = (b'want', b'done', None)
+        self.assertEqual((b'want', ONE),
+                          _split_proto_line(b'want ' + ONE + b'\n', allowed))
+        self.assertEqual((b'want', TWO),
+                          _split_proto_line(b'want ' + TWO + b'\n', allowed))
         self.assertRaises(GitProtocolError, _split_proto_line,
         self.assertRaises(GitProtocolError, _split_proto_line,
-                          'want xxxx\n', allowed)
+                          b'want xxxx\n', allowed)
         self.assertRaises(UnexpectedCommandError, _split_proto_line,
         self.assertRaises(UnexpectedCommandError, _split_proto_line,
-                          'have %s\n' % THREE, allowed)
+                          b'have ' + THREE + b'\n', allowed)
         self.assertRaises(GitProtocolError, _split_proto_line,
         self.assertRaises(GitProtocolError, _split_proto_line,
-                          'foo %s\n' % FOUR, allowed)
-        self.assertRaises(GitProtocolError, _split_proto_line, 'bar', allowed)
-        self.assertEqual(('done', None), _split_proto_line('done\n', allowed))
-        self.assertEqual((None, None), _split_proto_line('', allowed))
+                          b'foo ' + FOUR + b'\n', allowed)
+        self.assertRaises(GitProtocolError, _split_proto_line, b'bar', allowed)
+        self.assertEqual((b'done', None), _split_proto_line(b'done\n', allowed))
+        self.assertEqual((None, None), _split_proto_line(b'', allowed))
 
 
     def test_determine_wants(self):
     def test_determine_wants(self):
         self._walker.proto.set_output([None])
         self._walker.proto.set_output([None])
@@ -415,14 +404,14 @@ class ProtocolGraphWalkerTestCase(TestCase):
         self.assertEqual(None, self._walker.proto.get_received_line())
         self.assertEqual(None, self._walker.proto.get_received_line())
 
 
         self._walker.proto.set_output([
         self._walker.proto.set_output([
-          'want %s multi_ack' % ONE,
-          'want %s' % TWO,
+          b'want ' + ONE + b' multi_ack',
+          b'want ' + TWO,
           None,
           None,
           ])
           ])
         heads = {
         heads = {
-          'refs/heads/ref1': ONE,
-          'refs/heads/ref2': TWO,
-          'refs/heads/ref3': THREE,
+          b'refs/heads/ref1': ONE,
+          b'refs/heads/ref2': TWO,
+          b'refs/heads/ref3': THREE,
           }
           }
         self._repo.refs._update(heads)
         self._repo.refs._update(heads)
         self.assertEqual([ONE, TWO], self._walker.determine_wants(heads))
         self.assertEqual([ONE, TWO], self._walker.determine_wants(heads))
@@ -431,29 +420,29 @@ class ProtocolGraphWalkerTestCase(TestCase):
         self.assertEqual([], self._walker.determine_wants(heads))
         self.assertEqual([], self._walker.determine_wants(heads))
         self._walker.advertise_refs = False
         self._walker.advertise_refs = False
 
 
-        self._walker.proto.set_output(['want %s multi_ack' % FOUR, None])
+        self._walker.proto.set_output([b'want ' + FOUR + b' multi_ack', None])
         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
 
 
         self._walker.proto.set_output([None])
         self._walker.proto.set_output([None])
         self.assertEqual([], self._walker.determine_wants(heads))
         self.assertEqual([], self._walker.determine_wants(heads))
 
 
-        self._walker.proto.set_output(['want %s multi_ack' % ONE, 'foo', None])
+        self._walker.proto.set_output([b'want ' + ONE + b' multi_ack', b'foo', None])
         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
 
 
-        self._walker.proto.set_output(['want %s multi_ack' % FOUR, None])
+        self._walker.proto.set_output([b'want ' + FOUR + b' multi_ack', None])
         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
 
 
     def test_determine_wants_advertisement(self):
     def test_determine_wants_advertisement(self):
         self._walker.proto.set_output([None])
         self._walker.proto.set_output([None])
         # advertise branch tips plus tag
         # advertise branch tips plus tag
         heads = {
         heads = {
-          'refs/heads/ref4': FOUR,
-          'refs/heads/ref5': FIVE,
-          'refs/heads/tag6': SIX,
+          b'refs/heads/ref4': FOUR,
+          b'refs/heads/ref5': FIVE,
+          b'refs/heads/tag6': SIX,
           }
           }
         self._repo.refs._update(heads)
         self._repo.refs._update(heads)
         self._repo.refs._update_peeled(heads)
         self._repo.refs._update_peeled(heads)
-        self._repo.refs._update_peeled({'refs/heads/tag6': FIVE})
+        self._repo.refs._update_peeled({b'refs/heads/tag6': FIVE})
         self._walker.determine_wants(heads)
         self._walker.determine_wants(heads)
         lines = []
         lines = []
         while True:
         while True:
@@ -461,21 +450,21 @@ class ProtocolGraphWalkerTestCase(TestCase):
             if line is None:
             if line is None:
                 break
                 break
             # strip capabilities list if present
             # strip capabilities list if present
-            if '\x00' in line:
-                line = line[:line.index('\x00')]
+            if b'\x00' in line:
+                line = line[:line.index(b'\x00')]
             lines.append(line.rstrip())
             lines.append(line.rstrip())
 
 
         self.assertEqual([
         self.assertEqual([
-          '%s refs/heads/ref4' % FOUR,
-          '%s refs/heads/ref5' % FIVE,
-          '%s refs/heads/tag6^{}' % FIVE,
-          '%s refs/heads/tag6' % SIX,
+          FOUR + b' refs/heads/ref4',
+          FIVE + b' refs/heads/ref5',
+          FIVE + b' refs/heads/tag6^{}',
+          SIX + b' refs/heads/tag6',
           ], sorted(lines))
           ], sorted(lines))
 
 
         # ensure peeled tag was advertised immediately following tag
         # ensure peeled tag was advertised immediately following tag
         for i, line in enumerate(lines):
         for i, line in enumerate(lines):
-            if line.endswith(' refs/heads/tag6'):
-                self.assertEqual('%s refs/heads/tag6^{}' % FIVE, lines[i+1])
+            if line.endswith(b' refs/heads/tag6'):
+                self.assertEqual(FIVE + b' refs/heads/tag6^{}', lines[i+1])
 
 
     # TODO: test commit time cutoff
     # TODO: test commit time cutoff
 
 
@@ -488,18 +477,18 @@ class ProtocolGraphWalkerTestCase(TestCase):
           expected, list(iter(self._walker.proto.get_received_line, None)))
           expected, list(iter(self._walker.proto.get_received_line, None)))
 
 
     def test_handle_shallow_request_no_client_shallows(self):
     def test_handle_shallow_request_no_client_shallows(self):
-        self._handle_shallow_request(['deepen 1\n'], [FOUR, FIVE])
+        self._handle_shallow_request([b'deepen 1\n'], [FOUR, FIVE])
         self.assertEqual(set([TWO, THREE]), self._walker.shallow)
         self.assertEqual(set([TWO, THREE]), self._walker.shallow)
         self.assertReceived([
         self.assertReceived([
-          'shallow %s' % TWO,
-          'shallow %s' % THREE,
+          b'shallow ' + TWO,
+          b'shallow ' + THREE,
           ])
           ])
 
 
     def test_handle_shallow_request_no_new_shallows(self):
     def test_handle_shallow_request_no_new_shallows(self):
         lines = [
         lines = [
-          'shallow %s\n' % TWO,
-          'shallow %s\n' % THREE,
-          'deepen 1\n',
+          b'shallow ' + TWO + b'\n',
+          b'shallow ' + THREE + b'\n',
+          b'deepen 1\n',
           ]
           ]
         self._handle_shallow_request(lines, [FOUR, FIVE])
         self._handle_shallow_request(lines, [FOUR, FIVE])
         self.assertEqual(set([TWO, THREE]), self._walker.shallow)
         self.assertEqual(set([TWO, THREE]), self._walker.shallow)
@@ -507,19 +496,18 @@ class ProtocolGraphWalkerTestCase(TestCase):
 
 
     def test_handle_shallow_request_unshallows(self):
     def test_handle_shallow_request_unshallows(self):
         lines = [
         lines = [
-          'shallow %s\n' % TWO,
-          'deepen 2\n',
+          b'shallow ' + TWO + b'\n',
+          b'deepen 2\n',
           ]
           ]
         self._handle_shallow_request(lines, [FOUR, FIVE])
         self._handle_shallow_request(lines, [FOUR, FIVE])
         self.assertEqual(set([ONE]), self._walker.shallow)
         self.assertEqual(set([ONE]), self._walker.shallow)
         self.assertReceived([
         self.assertReceived([
-          'shallow %s' % ONE,
-          'unshallow %s' % TWO,
+          b'shallow ' + ONE,
+          b'unshallow ' + TWO,
           # THREE is unshallow but was is not shallow in the client
           # THREE is unshallow but was is not shallow in the client
           ])
           ])
 
 
 
 
-@skipIfPY3
 class TestProtocolGraphWalker(object):
 class TestProtocolGraphWalker(object):
 
 
     def __init__(self):
     def __init__(self):
@@ -535,11 +523,11 @@ class TestProtocolGraphWalker(object):
             assert command in allowed
             assert command in allowed
         return command, sha
         return command, sha
 
 
-    def send_ack(self, sha, ack_type=''):
+    def send_ack(self, sha, ack_type=b''):
         self.acks.append((sha, ack_type))
         self.acks.append((sha, ack_type))
 
 
     def send_nak(self):
     def send_nak(self):
-        self.acks.append((None, 'nak'))
+        self.acks.append((None, b'nak'))
 
 
     def all_wants_satisfied(self, haves):
     def all_wants_satisfied(self, haves):
         return self.done
         return self.done
@@ -550,7 +538,6 @@ class TestProtocolGraphWalker(object):
         return self.acks.pop(0)
         return self.acks.pop(0)
 
 
 
 
-@skipIfPY3
 class AckGraphWalkerImplTestCase(TestCase):
 class AckGraphWalkerImplTestCase(TestCase):
     """Base setup and asserts for AckGraphWalker tests."""
     """Base setup and asserts for AckGraphWalker tests."""
 
 
@@ -558,10 +545,10 @@ class AckGraphWalkerImplTestCase(TestCase):
         super(AckGraphWalkerImplTestCase, self).setUp()
         super(AckGraphWalkerImplTestCase, self).setUp()
         self._walker = TestProtocolGraphWalker()
         self._walker = TestProtocolGraphWalker()
         self._walker.lines = [
         self._walker.lines = [
-          ('have', TWO),
-          ('have', ONE),
-          ('have', THREE),
-          ('done', None),
+          (b'have', TWO),
+          (b'have', ONE),
+          (b'have', THREE),
+          (b'done', None),
           ]
           ]
         self._impl = self.impl_cls(self._walker)
         self._impl = self.impl_cls(self._walker)
 
 
@@ -573,17 +560,16 @@ class AckGraphWalkerImplTestCase(TestCase):
             self.assertEqual((sha, ack_type), self._walker.pop_ack())
             self.assertEqual((sha, ack_type), self._walker.pop_ack())
         self.assertNoAck()
         self.assertNoAck()
 
 
-    def assertAck(self, sha, ack_type=''):
+    def assertAck(self, sha, ack_type=b''):
         self.assertAcks([(sha, ack_type)])
         self.assertAcks([(sha, ack_type)])
 
 
     def assertNak(self):
     def assertNak(self):
-        self.assertAck(None, 'nak')
+        self.assertAck(None, b'nak')
 
 
     def assertNextEquals(self, sha):
     def assertNextEquals(self, sha):
         self.assertEqual(sha, next(self._impl))
         self.assertEqual(sha, next(self._impl))
 
 
 
 
-@skipIfPY3
 class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
 
     impl_cls = SingleAckGraphWalkerImpl
     impl_cls = SingleAckGraphWalkerImpl
@@ -652,7 +638,6 @@ class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
         self.assertNak()
         self.assertNak()
 
 
 
 
-@skipIfPY3
 class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
 
     impl_cls = MultiAckGraphWalkerImpl
     impl_cls = MultiAckGraphWalkerImpl
@@ -664,11 +649,11 @@ class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
         self.assertNextEquals(ONE)
         self.assertNextEquals(ONE)
         self._walker.done = True
         self._walker.done = True
         self._impl.ack(ONE)
         self._impl.ack(ONE)
-        self.assertAck(ONE, 'continue')
+        self.assertAck(ONE, b'continue')
 
 
         self.assertNextEquals(THREE)
         self.assertNextEquals(THREE)
         self._impl.ack(THREE)
         self._impl.ack(THREE)
-        self.assertAck(THREE, 'continue')
+        self.assertAck(THREE, b'continue')
 
 
         self.assertNextEquals(None)
         self.assertNextEquals(None)
         self.assertAck(THREE)
         self.assertAck(THREE)
@@ -679,7 +664,7 @@ class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
 
         self.assertNextEquals(ONE)
         self.assertNextEquals(ONE)
         self._impl.ack(ONE)
         self._impl.ack(ONE)
-        self.assertAck(ONE, 'continue')
+        self.assertAck(ONE, b'continue')
 
 
         self.assertNextEquals(THREE)
         self.assertNextEquals(THREE)
         self.assertNoAck()
         self.assertNoAck()
@@ -690,11 +675,11 @@ class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
 
     def test_multi_ack_flush(self):
     def test_multi_ack_flush(self):
         self._walker.lines = [
         self._walker.lines = [
-          ('have', TWO),
+          (b'have', TWO),
           (None, None),
           (None, None),
-          ('have', ONE),
-          ('have', THREE),
-          ('done', None),
+          (b'have', ONE),
+          (b'have', THREE),
+          (b'done', None),
           ]
           ]
         self.assertNextEquals(TWO)
         self.assertNextEquals(TWO)
         self.assertNoAck()
         self.assertNoAck()
@@ -704,11 +689,11 @@ class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
 
         self._walker.done = True
         self._walker.done = True
         self._impl.ack(ONE)
         self._impl.ack(ONE)
-        self.assertAck(ONE, 'continue')
+        self.assertAck(ONE, b'continue')
 
 
         self.assertNextEquals(THREE)
         self.assertNextEquals(THREE)
         self._impl.ack(THREE)
         self._impl.ack(THREE)
-        self.assertAck(THREE, 'continue')
+        self.assertAck(THREE, b'continue')
 
 
         self.assertNextEquals(None)
         self.assertNextEquals(None)
         self.assertAck(THREE)
         self.assertAck(THREE)
@@ -727,7 +712,6 @@ class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
         self.assertNak()
         self.assertNak()
 
 
 
 
-@skipIfPY3
 class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
 
     impl_cls = MultiAckDetailedGraphWalkerImpl
     impl_cls = MultiAckDetailedGraphWalkerImpl
@@ -739,11 +723,11 @@ class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
         self.assertNextEquals(ONE)
         self.assertNextEquals(ONE)
         self._walker.done = True
         self._walker.done = True
         self._impl.ack(ONE)
         self._impl.ack(ONE)
-        self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
+        self.assertAcks([(ONE, b'common'), (ONE, b'ready')])
 
 
         self.assertNextEquals(THREE)
         self.assertNextEquals(THREE)
         self._impl.ack(THREE)
         self._impl.ack(THREE)
-        self.assertAck(THREE, 'ready')
+        self.assertAck(THREE, b'ready')
 
 
         self.assertNextEquals(None)
         self.assertNextEquals(None)
         self.assertAck(THREE)
         self.assertAck(THREE)
@@ -754,7 +738,7 @@ class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
 
         self.assertNextEquals(ONE)
         self.assertNextEquals(ONE)
         self._impl.ack(ONE)
         self._impl.ack(ONE)
-        self.assertAck(ONE, 'common')
+        self.assertAck(ONE, b'common')
 
 
         self.assertNextEquals(THREE)
         self.assertNextEquals(THREE)
         self.assertNoAck()
         self.assertNoAck()
@@ -766,11 +750,11 @@ class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
     def test_multi_ack_flush(self):
     def test_multi_ack_flush(self):
         # same as ack test but contains a flush-pkt in the middle
         # same as ack test but contains a flush-pkt in the middle
         self._walker.lines = [
         self._walker.lines = [
-          ('have', TWO),
+          (b'have', TWO),
           (None, None),
           (None, None),
-          ('have', ONE),
-          ('have', THREE),
-          ('done', None),
+          (b'have', ONE),
+          (b'have', THREE),
+          (b'done', None),
           ]
           ]
         self.assertNextEquals(TWO)
         self.assertNextEquals(TWO)
         self.assertNoAck()
         self.assertNoAck()
@@ -780,11 +764,11 @@ class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
 
         self._walker.done = True
         self._walker.done = True
         self._impl.ack(ONE)
         self._impl.ack(ONE)
-        self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
+        self.assertAcks([(ONE, b'common'), (ONE, b'ready')])
 
 
         self.assertNextEquals(THREE)
         self.assertNextEquals(THREE)
         self._impl.ack(THREE)
         self._impl.ack(THREE)
-        self.assertAck(THREE, 'ready')
+        self.assertAck(THREE, b'ready')
 
 
         self.assertNextEquals(None)
         self.assertNextEquals(None)
         self.assertAck(THREE)
         self.assertAck(THREE)
@@ -805,11 +789,11 @@ class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
     def test_multi_ack_nak_flush(self):
     def test_multi_ack_nak_flush(self):
         # same as nak test but contains a flush-pkt in the middle
         # same as nak test but contains a flush-pkt in the middle
         self._walker.lines = [
         self._walker.lines = [
-          ('have', TWO),
+          (b'have', TWO),
           (None, None),
           (None, None),
-          ('have', ONE),
-          ('have', THREE),
-          ('done', None),
+          (b'have', ONE),
+          (b'have', THREE),
+          (b'done', None),
           ]
           ]
         self.assertNextEquals(TWO)
         self.assertNextEquals(TWO)
         self.assertNoAck()
         self.assertNoAck()
@@ -841,7 +825,6 @@ class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
         self.assertNak()
         self.assertNak()
 
 
 
 
-@skipIfPY3
 class FileSystemBackendTests(TestCase):
 class FileSystemBackendTests(TestCase):
     """Tests for FileSystemBackend."""
     """Tests for FileSystemBackend."""
 
 
@@ -870,25 +853,23 @@ class FileSystemBackendTests(TestCase):
                           lambda: backend.open_repository('/ups'))
                           lambda: backend.open_repository('/ups'))
 
 
 
 
-@skipIfPY3
 class DictBackendTests(TestCase):
 class DictBackendTests(TestCase):
     """Tests for DictBackend."""
     """Tests for DictBackend."""
 
 
     def test_nonexistant(self):
     def test_nonexistant(self):
         repo = MemoryRepo.init_bare([], {})
         repo = MemoryRepo.init_bare([], {})
-        backend = DictBackend({'/': repo})
+        backend = DictBackend({b'/': repo})
         self.assertRaises(NotGitRepository,
         self.assertRaises(NotGitRepository,
             backend.open_repository, "/does/not/exist/unless/foo")
             backend.open_repository, "/does/not/exist/unless/foo")
 
 
     def test_bad_repo_path(self):
     def test_bad_repo_path(self):
         repo = MemoryRepo.init_bare([], {})
         repo = MemoryRepo.init_bare([], {})
-        backend = DictBackend({'/': repo})
+        backend = DictBackend({b'/': repo})
 
 
         self.assertRaises(NotGitRepository,
         self.assertRaises(NotGitRepository,
                           lambda: backend.open_repository('/ups'))
                           lambda: backend.open_repository('/ups'))
 
 
 
 
-@skipIfPY3
 class ServeCommandTests(TestCase):
 class ServeCommandTests(TestCase):
     """Tests for serve_command."""
     """Tests for serve_command."""
 
 
@@ -897,24 +878,23 @@ class ServeCommandTests(TestCase):
         self.backend = DictBackend({})
         self.backend = DictBackend({})
 
 
     def serve_command(self, handler_cls, args, inf, outf):
     def serve_command(self, handler_cls, args, inf, outf):
-        return serve_command(handler_cls, ["test"] + args, backend=self.backend,
+        return serve_command(handler_cls, [b"test"] + args, backend=self.backend,
             inf=inf, outf=outf)
             inf=inf, outf=outf)
 
 
     def test_receive_pack(self):
     def test_receive_pack(self):
         commit = make_commit(id=ONE, parents=[], commit_time=111)
         commit = make_commit(id=ONE, parents=[], commit_time=111)
-        self.backend.repos["/"] = MemoryRepo.init_bare(
-            [commit], {"refs/heads/master": commit.id})
+        self.backend.repos[b"/"] = MemoryRepo.init_bare(
+            [commit], {b"refs/heads/master": commit.id})
         outf = BytesIO()
         outf = BytesIO()
-        exitcode = self.serve_command(ReceivePackHandler, ["/"], BytesIO("0000"), outf)
+        exitcode = self.serve_command(ReceivePackHandler, [b"/"], BytesIO(b"0000"), outf)
         outlines = outf.getvalue().splitlines()
         outlines = outf.getvalue().splitlines()
         self.assertEqual(2, len(outlines))
         self.assertEqual(2, len(outlines))
-        self.assertEqual("1111111111111111111111111111111111111111 refs/heads/master",
-            outlines[0][4:].split("\x00")[0])
-        self.assertEqual("0000", outlines[-1])
+        self.assertEqual(b"1111111111111111111111111111111111111111 refs/heads/master",
+            outlines[0][4:].split(b"\x00")[0])
+        self.assertEqual(b"0000", outlines[-1])
         self.assertEqual(0, exitcode)
         self.assertEqual(0, exitcode)
 
 
 
 
-@skipIfPY3
 class UpdateServerInfoTests(TestCase):
 class UpdateServerInfoTests(TestCase):
     """Tests for update_server_info."""
     """Tests for update_server_info."""
 
 
@@ -932,9 +912,9 @@ class UpdateServerInfoTests(TestCase):
 
 
     def test_simple(self):
     def test_simple(self):
         commit_id = self.repo.do_commit(
         commit_id = self.repo.do_commit(
-            message="foo",
-            committer="Joe Example <joe@example.com>",
-            ref="refs/heads/foo")
+            message=b"foo",
+            committer=b"Joe Example <joe@example.com>",
+            ref=b"refs/heads/foo")
         update_server_info(self.repo)
         update_server_info(self.repo)
         with open(os.path.join(self.path, ".git", "info", "refs"), 'rb') as f:
         with open(os.path.join(self.path, ".git", "info", "refs"), 'rb') as f:
             self.assertEqual(f.read(), commit_id + b'\trefs/heads/foo\n')
             self.assertEqual(f.read(), commit_id + b'\trefs/heads/foo\n')

+ 10 - 10
dulwich/tests/test_utils.py

@@ -47,7 +47,7 @@ class BuildCommitGraphTest(TestCase):
         self.assertEqual([], c1.parents)
         self.assertEqual([], c1.parents)
         self.assertEqual([c1.id], c2.parents)
         self.assertEqual([c1.id], c2.parents)
         self.assertEqual(c1.tree, c2.tree)
         self.assertEqual(c1.tree, c2.tree)
-        self.assertEqual([], list(self.store[c1.tree].iteritems()))
+        self.assertEqual([], self.store[c1.tree].items())
         self.assertTrue(c2.commit_time > c1.commit_time)
         self.assertTrue(c2.commit_time > c1.commit_time)
 
 
     def test_merge(self):
     def test_merge(self):
@@ -62,19 +62,19 @@ class BuildCommitGraphTest(TestCase):
                           [[1], [3, 2], [2, 1]])
                           [[1], [3, 2], [2, 1]])
 
 
     def test_trees(self):
     def test_trees(self):
-        a1 = make_object(Blob, data='aaa1')
-        a2 = make_object(Blob, data='aaa2')
+        a1 = make_object(Blob, data=b'aaa1')
+        a2 = make_object(Blob, data=b'aaa2')
         c1, c2 = build_commit_graph(self.store, [[1], [2, 1]],
         c1, c2 = build_commit_graph(self.store, [[1], [2, 1]],
-                                    trees={1: [('a', a1)],
-                                           2: [('a', a2, 0o100644)]})
-        self.assertEqual((0o100644, a1.id), self.store[c1.tree]['a'])
-        self.assertEqual((0o100644, a2.id), self.store[c2.tree]['a'])
+                                    trees={1: [(b'a', a1)],
+                                           2: [(b'a', a2, 0o100644)]})
+        self.assertEqual((0o100644, a1.id), self.store[c1.tree][b'a'])
+        self.assertEqual((0o100644, a2.id), self.store[c2.tree][b'a'])
 
 
     def test_attrs(self):
     def test_attrs(self):
         c1, c2 = build_commit_graph(self.store, [[1], [2, 1]],
         c1, c2 = build_commit_graph(self.store, [[1], [2, 1]],
-                                    attrs={1: {'message': 'Hooray!'}})
-        self.assertEqual('Hooray!', c1.message)
-        self.assertEqual('Commit 2', c2.message)
+                                    attrs={1: {'message': b'Hooray!'}})
+        self.assertEqual(b'Hooray!', c1.message)
+        self.assertEqual(b'Commit 2', c2.message)
 
 
     def test_commit_time(self):
     def test_commit_time(self):
         c1, c2, c3 = build_commit_graph(self.store, [[1], [2, 1], [3, 2]],
         c1, c2, c3 = build_commit_graph(self.store, [[1], [2, 1], [3, 2]],

+ 0 - 2
dulwich/tests/test_walk.py

@@ -49,7 +49,6 @@ from dulwich.tests.utils import (
     F,
     F,
     make_object,
     make_object,
     build_commit_graph,
     build_commit_graph,
-    skipIfPY3,
     )
     )
 
 
 
 
@@ -71,7 +70,6 @@ class TestWalkEntry(object):
         return self.changes == other.changes()
         return self.changes == other.changes()
 
 
 
 
-@skipIfPY3
 class WalkerTest(TestCase):
 class WalkerTest(TestCase):
 
 
     def setUp(self):
     def setUp(self):

+ 54 - 60
dulwich/tests/test_web.py

@@ -28,7 +28,6 @@ from dulwich.object_store import (
     )
     )
 from dulwich.objects import (
 from dulwich.objects import (
     Blob,
     Blob,
-    Tag,
     )
     )
 from dulwich.repo import (
 from dulwich.repo import (
     BaseRepo,
     BaseRepo,
@@ -62,7 +61,6 @@ from dulwich.web import (
 from dulwich.tests.utils import (
 from dulwich.tests.utils import (
     make_object,
     make_object,
     make_tag,
     make_tag,
-    skipIfPY3,
     )
     )
 
 
 
 
@@ -112,12 +110,11 @@ def _test_backend(objects, refs=None, named_files=None):
     if not named_files:
     if not named_files:
         named_files = {}
         named_files = {}
     repo = MemoryRepo.init_bare(objects, refs)
     repo = MemoryRepo.init_bare(objects, refs)
-    for path, contents in named_files.iteritems():
+    for path, contents in named_files.items():
         repo._put_named_file(path, contents)
         repo._put_named_file(path, contents)
     return DictBackend({'/': repo})
     return DictBackend({'/': repo})
 
 
 
 
-@skipIfPY3
 class DumbHandlersTestCase(WebTestCase):
 class DumbHandlersTestCase(WebTestCase):
 
 
     def test_send_file_not_found(self):
     def test_send_file_not_found(self):
@@ -125,16 +122,16 @@ class DumbHandlersTestCase(WebTestCase):
         self.assertEqual(HTTP_NOT_FOUND, self._status)
         self.assertEqual(HTTP_NOT_FOUND, self._status)
 
 
     def test_send_file(self):
     def test_send_file(self):
-        f = BytesIO('foobar')
-        output = ''.join(send_file(self._req, f, 'some/thing'))
-        self.assertEqual('foobar', output)
+        f = BytesIO(b'foobar')
+        output = b''.join(send_file(self._req, f, 'some/thing'))
+        self.assertEqual(b'foobar', output)
         self.assertEqual(HTTP_OK, self._status)
         self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('some/thing')
         self.assertContentTypeEquals('some/thing')
         self.assertTrue(f.closed)
         self.assertTrue(f.closed)
 
 
     def test_send_file_buffered(self):
     def test_send_file_buffered(self):
         bufsize = 10240
         bufsize = 10240
-        xs = 'x' * bufsize
+        xs = b'x' * bufsize
         f = BytesIO(2 * xs)
         f = BytesIO(2 * xs)
         self.assertEqual([xs, xs],
         self.assertEqual([xs, xs],
                           list(send_file(self._req, f, 'some/thing')))
                           list(send_file(self._req, f, 'some/thing')))
@@ -168,19 +165,19 @@ class DumbHandlersTestCase(WebTestCase):
         self.assertFalse(self._req.cached)
         self.assertFalse(self._req.cached)
 
 
     def test_get_text_file(self):
     def test_get_text_file(self):
-        backend = _test_backend([], named_files={'description': 'foo'})
+        backend = _test_backend([], named_files={'description': b'foo'})
         mat = re.search('.*', 'description')
         mat = re.search('.*', 'description')
-        output = ''.join(get_text_file(self._req, backend, mat))
-        self.assertEqual('foo', output)
+        output = b''.join(get_text_file(self._req, backend, mat))
+        self.assertEqual(b'foo', output)
         self.assertEqual(HTTP_OK, self._status)
         self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('text/plain')
         self.assertContentTypeEquals('text/plain')
         self.assertFalse(self._req.cached)
         self.assertFalse(self._req.cached)
 
 
     def test_get_loose_object(self):
     def test_get_loose_object(self):
-        blob = make_object(Blob, data='foo')
+        blob = make_object(Blob, data=b'foo')
         backend = _test_backend([blob])
         backend = _test_backend([blob])
-        mat = re.search('^(..)(.{38})$', blob.id)
-        output = ''.join(get_loose_object(self._req, backend, mat))
+        mat = re.search('^(..)(.{38})$', blob.id.decode('ascii'))
+        output = b''.join(get_loose_object(self._req, backend, mat))
         self.assertEqual(blob.as_legacy_object(), output)
         self.assertEqual(blob.as_legacy_object(), output)
         self.assertEqual(HTTP_OK, self._status)
         self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('application/x-git-loose-object')
         self.assertContentTypeEquals('application/x-git-loose-object')
@@ -192,9 +189,9 @@ class DumbHandlersTestCase(WebTestCase):
         self.assertEqual(HTTP_NOT_FOUND, self._status)
         self.assertEqual(HTTP_NOT_FOUND, self._status)
 
 
     def test_get_loose_object_error(self):
     def test_get_loose_object_error(self):
-        blob = make_object(Blob, data='foo')
+        blob = make_object(Blob, data=b'foo')
         backend = _test_backend([blob])
         backend = _test_backend([blob])
-        mat = re.search('^(..)(.{38})$', blob.id)
+        mat = re.search('^(..)(.{38})$', blob.id.decode('ascii'))
 
 
         def as_legacy_object_error():
         def as_legacy_object_error():
             raise IOError
             raise IOError
@@ -205,20 +202,20 @@ class DumbHandlersTestCase(WebTestCase):
 
 
     def test_get_pack_file(self):
     def test_get_pack_file(self):
         pack_name = os.path.join('objects', 'pack', 'pack-%s.pack' % ('1' * 40))
         pack_name = os.path.join('objects', 'pack', 'pack-%s.pack' % ('1' * 40))
-        backend = _test_backend([], named_files={pack_name: 'pack contents'})
+        backend = _test_backend([], named_files={pack_name: b'pack contents'})
         mat = re.search('.*', pack_name)
         mat = re.search('.*', pack_name)
-        output = ''.join(get_pack_file(self._req, backend, mat))
-        self.assertEqual('pack contents', output)
+        output = b''.join(get_pack_file(self._req, backend, mat))
+        self.assertEqual(b'pack contents', output)
         self.assertEqual(HTTP_OK, self._status)
         self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('application/x-git-packed-objects')
         self.assertContentTypeEquals('application/x-git-packed-objects')
         self.assertTrue(self._req.cached)
         self.assertTrue(self._req.cached)
 
 
     def test_get_idx_file(self):
     def test_get_idx_file(self):
         idx_name = os.path.join('objects', 'pack', 'pack-%s.idx' % ('1' * 40))
         idx_name = os.path.join('objects', 'pack', 'pack-%s.idx' % ('1' * 40))
-        backend = _test_backend([], named_files={idx_name: 'idx contents'})
+        backend = _test_backend([], named_files={idx_name: b'idx contents'})
         mat = re.search('.*', idx_name)
         mat = re.search('.*', idx_name)
-        output = ''.join(get_idx_file(self._req, backend, mat))
-        self.assertEqual('idx contents', output)
+        output = b''.join(get_idx_file(self._req, backend, mat))
+        self.assertEqual(b'idx contents', output)
         self.assertEqual(HTTP_OK, self._status)
         self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('application/x-git-packed-objects-toc')
         self.assertContentTypeEquals('application/x-git-packed-objects-toc')
         self.assertTrue(self._req.cached)
         self.assertTrue(self._req.cached)
@@ -226,26 +223,26 @@ class DumbHandlersTestCase(WebTestCase):
     def test_get_info_refs(self):
     def test_get_info_refs(self):
         self._environ['QUERY_STRING'] = ''
         self._environ['QUERY_STRING'] = ''
 
 
-        blob1 = make_object(Blob, data='1')
-        blob2 = make_object(Blob, data='2')
-        blob3 = make_object(Blob, data='3')
+        blob1 = make_object(Blob, data=b'1')
+        blob2 = make_object(Blob, data=b'2')
+        blob3 = make_object(Blob, data=b'3')
 
 
-        tag1 = make_tag(blob2, name='tag-tag')
+        tag1 = make_tag(blob2, name=b'tag-tag')
 
 
         objects = [blob1, blob2, blob3, tag1]
         objects = [blob1, blob2, blob3, tag1]
         refs = {
         refs = {
-          'HEAD': '000',
-          'refs/heads/master': blob1.id,
-          'refs/tags/tag-tag': tag1.id,
-          'refs/tags/blob-tag': blob3.id,
+          b'HEAD': b'000',
+          b'refs/heads/master': blob1.id,
+          b'refs/tags/tag-tag': tag1.id,
+          b'refs/tags/blob-tag': blob3.id,
           }
           }
         backend = _test_backend(objects, refs=refs)
         backend = _test_backend(objects, refs=refs)
 
 
         mat = re.search('.*', '//info/refs')
         mat = re.search('.*', '//info/refs')
-        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],
+        self.assertEqual([blob1.id + b'\trefs/heads/master\n',
+                           blob3.id + b'\trefs/tags/blob-tag\n',
+                           tag1.id + b'\trefs/tags/tag-tag\n',
+                           blob2.id + b'\trefs/tags/tag-tag^{}\n'],
                           list(get_info_refs(self._req, backend, mat)))
                           list(get_info_refs(self._req, backend, mat)))
         self.assertEqual(HTTP_OK, self._status)
         self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('text/plain')
         self.assertContentTypeEquals('text/plain')
@@ -273,16 +270,15 @@ class DumbHandlersTestCase(WebTestCase):
         repo = BaseRepo(store, None)
         repo = BaseRepo(store, None)
         backend = DictBackend({'/': repo})
         backend = DictBackend({'/': repo})
         mat = re.search('.*', '//info/packs')
         mat = re.search('.*', '//info/packs')
-        output = ''.join(get_info_packs(self._req, backend, mat))
-        expected = 'P pack-%s.pack\n' * 3
-        expected %= ('1' * 40, '2' * 40, '3' * 40)
+        output = b''.join(get_info_packs(self._req, backend, mat))
+        expected = b''.join(
+            [(b'P pack-' + s + b'.pack\n') for s in [b'1' * 40, b'2' * 40, b'3' * 40]])
         self.assertEqual(expected, output)
         self.assertEqual(expected, output)
         self.assertEqual(HTTP_OK, self._status)
         self.assertEqual(HTTP_OK, self._status)
         self.assertContentTypeEquals('text/plain')
         self.assertContentTypeEquals('text/plain')
         self.assertFalse(self._req.cached)
         self.assertFalse(self._req.cached)
 
 
 
 
-@skipIfPY3
 class SmartHandlersTestCase(WebTestCase):
 class SmartHandlersTestCase(WebTestCase):
 
 
     class _TestUploadPackHandler(object):
     class _TestUploadPackHandler(object):
@@ -294,7 +290,7 @@ class SmartHandlersTestCase(WebTestCase):
             self.advertise_refs = advertise_refs
             self.advertise_refs = advertise_refs
 
 
         def handle(self):
         def handle(self):
-            self.proto.write('handled input: %s' % self.proto.recv(1024))
+            self.proto.write(b'handled input: ' + self.proto.recv(1024))
 
 
     def _make_handler(self, *args, **kwargs):
     def _make_handler(self, *args, **kwargs):
         self._handler = self._TestUploadPackHandler(*args, **kwargs)
         self._handler = self._TestUploadPackHandler(*args, **kwargs)
@@ -311,7 +307,7 @@ class SmartHandlersTestCase(WebTestCase):
         self.assertFalse(self._req.cached)
         self.assertFalse(self._req.cached)
 
 
     def _run_handle_service_request(self, content_length=None):
     def _run_handle_service_request(self, content_length=None):
-        self._environ['wsgi.input'] = BytesIO('foo')
+        self._environ['wsgi.input'] = BytesIO(b'foo')
         if content_length is not None:
         if content_length is not None:
             self._environ['CONTENT_LENGTH'] = content_length
             self._environ['CONTENT_LENGTH'] = content_length
         mat = re.search('.*', '/git-upload-pack')
         mat = re.search('.*', '/git-upload-pack')
@@ -320,7 +316,7 @@ class SmartHandlersTestCase(WebTestCase):
         write_output = self._output.getvalue()
         write_output = self._output.getvalue()
         # Ensure all output was written via the write callback.
         # Ensure all output was written via the write callback.
         self.assertEqual('', handler_output)
         self.assertEqual('', handler_output)
-        self.assertEqual('handled input: foo', write_output)
+        self.assertEqual(b'handled input: foo', write_output)
         self.assertContentTypeEquals('application/x-git-upload-pack-result')
         self.assertContentTypeEquals('application/x-git-upload-pack-result')
         self.assertFalse(self._handler.advertise_refs)
         self.assertFalse(self._handler.advertise_refs)
         self.assertTrue(self._handler.http_req)
         self.assertTrue(self._handler.http_req)
@@ -337,45 +333,44 @@ class SmartHandlersTestCase(WebTestCase):
 
 
     def test_get_info_refs_unknown(self):
     def test_get_info_refs_unknown(self):
         self._environ['QUERY_STRING'] = 'service=git-evil-handler'
         self._environ['QUERY_STRING'] = 'service=git-evil-handler'
-        content = list(get_info_refs(self._req, 'backend', None))
+        content = list(get_info_refs(self._req, b'backend', None))
         self.assertFalse('git-evil-handler' in "".join(content))
         self.assertFalse('git-evil-handler' in "".join(content))
         self.assertEqual(HTTP_FORBIDDEN, self._status)
         self.assertEqual(HTTP_FORBIDDEN, self._status)
         self.assertFalse(self._req.cached)
         self.assertFalse(self._req.cached)
 
 
     def test_get_info_refs(self):
     def test_get_info_refs(self):
-        self._environ['wsgi.input'] = BytesIO('foo')
+        self._environ['wsgi.input'] = BytesIO(b'foo')
         self._environ['QUERY_STRING'] = 'service=git-upload-pack'
         self._environ['QUERY_STRING'] = 'service=git-upload-pack'
 
 
         mat = re.search('.*', '/git-upload-pack')
         mat = re.search('.*', '/git-upload-pack')
-        handler_output = ''.join(get_info_refs(self._req, 'backend', mat))
+        handler_output = b''.join(get_info_refs(self._req, b'backend', mat))
         write_output = self._output.getvalue()
         write_output = self._output.getvalue()
-        self.assertEqual(('001e# service=git-upload-pack\n'
-                           '0000'
+        self.assertEqual((b'001e# service=git-upload-pack\n'
+                           b'0000'
                            # input is ignored by the handler
                            # input is ignored by the handler
-                           'handled input: '), write_output)
+                           b'handled input: '), write_output)
         # Ensure all output was written via the write callback.
         # Ensure all output was written via the write callback.
-        self.assertEqual('', handler_output)
+        self.assertEqual(b'', handler_output)
         self.assertTrue(self._handler.advertise_refs)
         self.assertTrue(self._handler.advertise_refs)
         self.assertTrue(self._handler.http_req)
         self.assertTrue(self._handler.http_req)
         self.assertFalse(self._req.cached)
         self.assertFalse(self._req.cached)
 
 
 
 
-@skipIfPY3
 class LengthLimitedFileTestCase(TestCase):
 class LengthLimitedFileTestCase(TestCase):
     def test_no_cutoff(self):
     def test_no_cutoff(self):
-        f = _LengthLimitedFile(BytesIO('foobar'), 1024)
-        self.assertEqual('foobar', f.read())
+        f = _LengthLimitedFile(BytesIO(b'foobar'), 1024)
+        self.assertEqual(b'foobar', f.read())
 
 
     def test_cutoff(self):
     def test_cutoff(self):
-        f = _LengthLimitedFile(BytesIO('foobar'), 3)
-        self.assertEqual('foo', f.read())
-        self.assertEqual('', f.read())
+        f = _LengthLimitedFile(BytesIO(b'foobar'), 3)
+        self.assertEqual(b'foo', f.read())
+        self.assertEqual(b'', f.read())
 
 
     def test_multiple_reads(self):
     def test_multiple_reads(self):
-        f = _LengthLimitedFile(BytesIO('foobar'), 3)
-        self.assertEqual('fo', f.read(2))
-        self.assertEqual('o', f.read(2))
-        self.assertEqual('', f.read())
+        f = _LengthLimitedFile(BytesIO(b'foobar'), 3)
+        self.assertEqual(b'fo', f.read(2))
+        self.assertEqual(b'o', f.read(2))
+        self.assertEqual(b'', f.read())
 
 
 
 
 class HTTPGitRequestTestCase(WebTestCase):
 class HTTPGitRequestTestCase(WebTestCase):
@@ -419,7 +414,6 @@ class HTTPGitRequestTestCase(WebTestCase):
         self.assertEqual(402, self._status)
         self.assertEqual(402, self._status)
 
 
 
 
-@skipIfPY3
 class HTTPGitApplicationTestCase(TestCase):
 class HTTPGitApplicationTestCase(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -460,7 +454,7 @@ class GunzipTestCase(HTTPGitApplicationTestCase):
     __doc__ = """TestCase for testing the GunzipFilter, ensuring the wsgi.input
     __doc__ = """TestCase for testing the GunzipFilter, ensuring the wsgi.input
     is correctly decompressed and headers are corrected.
     is correctly decompressed and headers are corrected.
     """
     """
-    example_text = __doc__
+    example_text = __doc__.encode('ascii')
 
 
     def setUp(self):
     def setUp(self):
         super(GunzipTestCase, self).setUp()
         super(GunzipTestCase, self).setUp()

+ 8 - 6
dulwich/tests/utils.py

@@ -60,7 +60,7 @@ from dulwich.tests import (
 F = 0o100644  # Shorthand mode for Files.
 F = 0o100644  # Shorthand mode for Files.
 
 
 
 
-def open_repo(name):
+def open_repo(name, temp_dir=None):
     """Open a copy of a repo in a temporary directory.
     """Open a copy of a repo in a temporary directory.
 
 
     Use this function for accessing repos in dulwich/tests/data/repos to avoid
     Use this function for accessing repos in dulwich/tests/data/repos to avoid
@@ -69,6 +69,8 @@ def open_repo(name):
 
 
     :param name: The name of the repository, relative to
     :param name: The name of the repository, relative to
         dulwich/tests/data/repos
         dulwich/tests/data/repos
+    :param temp_dir: temporary directory to initialize to. If not provided, a
+        temporary directory will be created.
     :returns: An initialized Repo object that lives in a temporary directory.
     :returns: An initialized Repo object that lives in a temporary directory.
     """
     """
     temp_dir = tempfile.mkdtemp()
     temp_dir = tempfile.mkdtemp()
@@ -80,7 +82,7 @@ def open_repo(name):
 
 
 def tear_down_repo(repo):
 def tear_down_repo(repo):
     """Tear down a test repository."""
     """Tear down a test repository."""
-    temp_dir = os.path.dirname(repo.path.rstrip(os.sep))
+    temp_dir = os.path.dirname(repo._path_bytes.rstrip(os.sep.encode(sys.getfilesystemencoding())))
     shutil.rmtree(temp_dir)
     shutil.rmtree(temp_dir)
 
 
 
 
@@ -145,12 +147,12 @@ def make_tag(target, **attrs):
     target_id = target.id
     target_id = target.id
     target_type = object_class(target.type_name)
     target_type = object_class(target.type_name)
     default_time = int(time.mktime(datetime.datetime(2010, 1, 1).timetuple()))
     default_time = int(time.mktime(datetime.datetime(2010, 1, 1).timetuple()))
-    all_attrs = {'tagger': 'Test Author <test@nodomain.com>',
+    all_attrs = {'tagger': b'Test Author <test@nodomain.com>',
                  'tag_time': default_time,
                  'tag_time': default_time,
                  'tag_timezone': 0,
                  'tag_timezone': 0,
-                 'message': 'Test message.',
+                 'message': b'Test message.',
                  'object': (target_type, target_id),
                  'object': (target_type, target_id),
-                 'name': 'Test Tag',
+                 'name': b'Test Tag',
                  }
                  }
     all_attrs.update(attrs)
     all_attrs.update(attrs)
     return make_object(Tag, **all_attrs)
     return make_object(Tag, **all_attrs)
@@ -323,7 +325,7 @@ def build_commit_graph(object_store, commit_spec, trees=None, attrs=None):
         tree_id = commit_tree(object_store, blobs)
         tree_id = commit_tree(object_store, blobs)
 
 
         commit_attrs = {
         commit_attrs = {
-            'message': 'Commit %i' % commit_num,
+            'message': ('Commit %i' % commit_num).encode('ascii'),
             'parents': parent_ids,
             'parents': parent_ids,
             'tree': tree_id,
             'tree': tree_id,
             'commit_time': commit_time,
             'commit_time': commit_time,

+ 2 - 0
dulwich/walk.py

@@ -226,6 +226,8 @@ class Walker(object):
         if order not in ALL_ORDERS:
         if order not in ALL_ORDERS:
             raise ValueError('Unknown walk order %s' % order)
             raise ValueError('Unknown walk order %s' % order)
         self.store = store
         self.store = store
+        if not isinstance(include, list):
+            include = [include]
         self.include = include
         self.include = include
         self.excluded = set(exclude or [])
         self.excluded = set(exclude or [])
         self.order = order
         self.order = order

+ 4 - 4
dulwich/web.py

@@ -139,7 +139,7 @@ def get_text_file(req, backend, mat):
 
 
 
 
 def get_loose_object(req, backend, mat):
 def get_loose_object(req, backend, mat):
-    sha = mat.group(1) + mat.group(2)
+    sha = (mat.group(1) + mat.group(2)).encode('ascii')
     logger.info('Sending loose object %s', sha)
     logger.info('Sending loose object %s', sha)
     object_store = get_repo(backend, mat).object_store
     object_store = get_repo(backend, mat).object_store
     if not object_store.contains_loose(sha):
     if not object_store.contains_loose(sha):
@@ -184,7 +184,7 @@ def get_info_refs(req, backend, mat):
         proto = ReceivableProtocol(BytesIO().read, write)
         proto = ReceivableProtocol(BytesIO().read, write)
         handler = handler_cls(backend, [url_prefix(mat)], proto,
         handler = handler_cls(backend, [url_prefix(mat)], proto,
                               http_req=req, advertise_refs=True)
                               http_req=req, advertise_refs=True)
-        handler.proto.write_pkt_line('# service=%s\n' % service)
+        handler.proto.write_pkt_line(b'# service=' + service.encode('ascii') + b'\n')
         handler.proto.write_pkt_line(None)
         handler.proto.write_pkt_line(None)
         handler.handle()
         handler.handle()
     else:
     else:
@@ -219,7 +219,7 @@ class _LengthLimitedFile(object):
 
 
     def read(self, size=-1):
     def read(self, size=-1):
         if self._bytes_avail <= 0:
         if self._bytes_avail <= 0:
-            return ''
+            return b''
         if size == -1 or size > self._bytes_avail:
         if size == -1 or size > self._bytes_avail:
             size = self._bytes_avail
             size = self._bytes_avail
         self._bytes_avail -= size
         self._bytes_avail -= size
@@ -344,7 +344,7 @@ class HTTPGitApplication(object):
                              handlers=self.handlers)
                              handlers=self.handlers)
         # environ['QUERY_STRING'] has qs args
         # environ['QUERY_STRING'] has qs args
         handler = None
         handler = None
-        for smethod, spath in self.services.iterkeys():
+        for smethod, spath in self.services.keys():
             if smethod != method:
             if smethod != method:
                 continue
                 continue
             mat = spath.search(path)
             mat = spath.search(path)

+ 0 - 5
setup.cfg

@@ -1,5 +0,0 @@
-[egg_info]
-tag_build = 
-tag_date = 0
-tag_svn_revision = 0
-

+ 17 - 10
setup.py

@@ -8,7 +8,7 @@ except ImportError:
     from distutils.core import setup, Extension
     from distutils.core import setup, Extension
 from distutils.core import Distribution
 from distutils.core import Distribution
 
 
-dulwich_version_string = '0.10.1a'
+dulwich_version_string = '0.10.2'
 
 
 include_dirs = []
 include_dirs = []
 # Windows MSVC support
 # Windows MSVC support
@@ -48,7 +48,7 @@ if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
 
 
 if sys.version_info[0] == 2:
 if sys.version_info[0] == 2:
     tests_require = ['fastimport', 'mock']
     tests_require = ['fastimport', 'mock']
-    if not '__pypy__' in sys.modules:
+    if not '__pypy__' in sys.modules and not sys.platform == 'win32':
         tests_require.extend(['gevent', 'geventhttpclient'])
         tests_require.extend(['gevent', 'geventhttpclient'])
 else:
 else:
     # fastimport, gevent, geventhttpclient are not available for PY3
     # fastimport, gevent, geventhttpclient are not available for PY3
@@ -57,6 +57,20 @@ else:
 if sys.version_info < (2, 7):
 if sys.version_info < (2, 7):
     tests_require.append('unittest2')
     tests_require.append('unittest2')
 
 
+if sys.version_info[0] > 2 and sys.platform == 'win32':
+    # C Modules don't build for python3 windows, and prevent tests from running
+    ext_modules = []
+else:
+    ext_modules = [
+        Extension('dulwich._objects', ['dulwich/_objects.c'],
+                  include_dirs=include_dirs),
+        Extension('dulwich._pack', ['dulwich/_pack.c'],
+                  include_dirs=include_dirs),
+        Extension('dulwich._diff_tree', ['dulwich/_diff_tree.c'],
+                  include_dirs=include_dirs),
+    ]
+
+
 setup(name='dulwich',
 setup(name='dulwich',
       description='Python Git Library',
       description='Python Git Library',
       keywords='git',
       keywords='git',
@@ -86,14 +100,7 @@ setup(name='dulwich',
           'Operating System :: POSIX',
           'Operating System :: POSIX',
           'Topic :: Software Development :: Version Control',
           'Topic :: Software Development :: Version Control',
       ],
       ],
-      ext_modules=[
-          Extension('dulwich._objects', ['dulwich/_objects.c'],
-                    include_dirs=include_dirs),
-          Extension('dulwich._pack', ['dulwich/_pack.c'],
-              include_dirs=include_dirs),
-          Extension('dulwich._diff_tree', ['dulwich/_diff_tree.c'],
-              include_dirs=include_dirs),
-      ],
+      ext_modules=ext_modules,
       test_suite='dulwich.tests.test_suite',
       test_suite='dulwich.tests.test_suite',
       tests_require=tests_require,
       tests_require=tests_require,
       distclass=DulwichDistribution,
       distclass=DulwichDistribution,