瀏覽代碼

Change the way methods that take a path argument behave.

For a number of user facing methods, make it possible to pass in paths in as
either a unicode string, or bytes. These include:

* Repo.__init__
* Repo.init
* Repo.init_bare
* index.build_index_from_tree
* index.get_unstaged_changes

The repo.Repo.path attribute will remain unchanged. The Repo._controldir
attribute and a new Repo._path_bytes attribute are ensured to be bytes.

For a number of internal methods, it now requires a bytes path rather than
a unicode string. These include:

* objects.hex_to_filename
* objects.filename_to_hex
* _GitFile.__init__
* ShellHook.__init__ (and subclassed of ShellHook)
* DiskObjectStore.__init__
* DiskRefsContainer.__init__
* Repo._put_named_file
* Repo.get_named_file
* pack.write_pack
* Pack.__init__
* etc...
Gary van der Merwé 10 年之前
父節點
當前提交
7a02943889

+ 1 - 1
dulwich/file.py

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

+ 3 - 3
dulwich/hooks.py

@@ -100,7 +100,7 @@ class PreCommitShellHook(ShellHook):
     """pre-commit shell hook"""
 
     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)
 
@@ -109,7 +109,7 @@ class PostCommitShellHook(ShellHook):
     """post-commit shell hook"""
 
     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)
 
@@ -122,7 +122,7 @@ class CommitMsgShellHook(ShellHook):
     """
 
     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):
             (fd, path) = tempfile.mkstemp()

+ 13 - 3
dulwich/index.py

@@ -476,12 +476,15 @@ def build_index_from_tree(prefix, index_path, object_store, tree_id,
         in a working dir. Suiteable only for fresh clones.
     """
 
+    if not isinstance(prefix, bytes):
+        prefix = prefix.encode(sys.getfilesystemencoding())
+
     index = Index(index_path)
 
     for entry in object_store.iter_tree_contents(tree_id):
         if not validate_path(entry.path):
             continue
-        full_path = os.path.join(prefix, entry.path.decode(sys.getfilesystemencoding()))
+        full_path = os.path.join(prefix, entry.path)
 
         if not os.path.exists(os.path.dirname(full_path)):
             os.makedirs(os.path.dirname(full_path))
@@ -509,7 +512,11 @@ def blob_from_path_and_stat(path, st):
         with open(path, 'rb') as f:
             blob.data = f.read()
     else:
-        blob.data = os.readlink(path).encode(sys.getfilesystemencoding())
+        if not isinstance(path, bytes):
+            blob.data = os.readlink(path.encode(sys.getfilesystemencoding()))
+        else:
+            blob.data = os.readlink(path)
+
     return blob
 
 
@@ -521,8 +528,11 @@ def get_unstaged_changes(index, path):
     :return: iterator over paths with unstaged changes
     """
     # 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():
-        fp = os.path.join(path, name.decode(sys.getfilesystemencoding()))
+        fp = os.path.join(path, name)
         blob = blob_from_path_and_stat(fp, os.lstat(fp))
         if blob.id != entry.sha:
             yield name

+ 24 - 21
dulwich/object_store.py

@@ -63,8 +63,8 @@ from dulwich.pack import (
     PackStreamCopier,
     )
 
-INFODIR = 'info'
-PACKDIR = 'pack'
+INFODIR = b'info'
+PACKDIR = b'pack'
 
 
 class BaseObjectStore(object):
@@ -425,7 +425,7 @@ class DiskObjectStore(PackBasedObjectStore):
 
     def _read_alternate_paths(self):
         try:
-            f = GitFile(os.path.join(self.path, "info", "alternates"),
+            f = GitFile(os.path.join(self.path, INFODIR, b'alternates'),
                     'rb')
         except (OSError, IOError) as e:
             if e.errno == errno.ENOENT:
@@ -437,19 +437,19 @@ class DiskObjectStore(PackBasedObjectStore):
                 if l[0] == b"#":
                     continue
                 if os.path.isabs(l):
-                    yield l.decode(sys.getfilesystemencoding())
+                    yield l
                 else:
-                    yield os.path.join(self.path, l).decode(sys.getfilesystemencoding())
+                    yield os.path.join(self.path, l)
 
     def add_alternate_path(self, path):
         """Add an alternate path to this object store.
         """
         try:
-            os.mkdir(os.path.join(self.path, "info"))
+            os.mkdir(os.path.join(self.path, INFODIR))
         except OSError as e:
             if e.errno != errno.EEXIST:
                 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:
             try:
                 orig_f = open(alternates_path, 'rb')
@@ -459,7 +459,7 @@ class DiskObjectStore(PackBasedObjectStore):
             else:
                 with orig_f:
                     f.write(orig_f.read())
-            f.write(path.encode(sys.getfilesystemencoding()) + b"\n")
+            f.write(path + b"\n")
 
         if not os.path.isabs(path):
             path = os.path.join(self.path, path)
@@ -477,10 +477,10 @@ class DiskObjectStore(PackBasedObjectStore):
         self._pack_cache_time = os.stat(self.pack_dir).st_mtime
         pack_files = set()
         for name in pack_dir_contents:
-            assert type(name) is str
+            assert type(name) is bytes
             # 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
         for f in pack_files:
@@ -507,7 +507,7 @@ class DiskObjectStore(PackBasedObjectStore):
             if len(base) != 2:
                 continue
             for rest in os.listdir(os.path.join(self.path, base)):
-                yield (base+rest).encode(sys.getfilesystemencoding())
+                yield (base+rest)
 
     def _get_loose_object(self, sha):
         path = self._get_shafile_path(sha)
@@ -524,8 +524,7 @@ class DiskObjectStore(PackBasedObjectStore):
     def _get_pack_basepath(self, entries):
         suffix = iter_sha1(entry[0] for entry in entries)
         # TODO: Handle self.pack_dir being bytes
-        suffix = suffix.decode('ascii')
-        return os.path.join(self.pack_dir, "pack-" + suffix)
+        return os.path.join(self.pack_dir, b"pack-" + suffix)
 
     def _complete_thin_pack(self, f, path, copier, indexer):
         """Move a specific file containing a pack into the pack directory.
@@ -567,10 +566,10 @@ class DiskObjectStore(PackBasedObjectStore):
         # Move the pack in.
         entries.sort()
         pack_base_name = self._get_pack_basepath(entries)
-        os.rename(path, pack_base_name + '.pack')
+        os.rename(path, pack_base_name + b'.pack')
 
         # Write the index.
-        index_file = GitFile(pack_base_name + '.idx', 'wb')
+        index_file = GitFile(pack_base_name + b'.idx', 'wb')
         try:
             write_pack_index_v2(index_file, entries, pack_sha)
             index_file.close()
@@ -597,7 +596,10 @@ class DiskObjectStore(PackBasedObjectStore):
         :return: A Pack object pointing at the now-completed thin pack in the
             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:
             indexer = PackIndexer(f, resolve_ext_ref=self.get_raw)
             copier = PackStreamCopier(read_all, read_some, f,
@@ -616,9 +618,9 @@ class DiskObjectStore(PackBasedObjectStore):
         with PackData(path) as p:
             entries = p.sorted_entries()
             basename = self._get_pack_basepath(entries)
-            with GitFile(basename+".idx", "wb") as f:
+            with GitFile(basename+b'.idx', "wb") as f:
                 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)
         self._add_known_pack(basename, final_pack)
         return final_pack
@@ -630,7 +632,8 @@ class DiskObjectStore(PackBasedObjectStore):
             call when the pack is finished and an abort
             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')
         def commit():
             os.fsync(fd)
@@ -669,7 +672,7 @@ class DiskObjectStore(PackBasedObjectStore):
         except OSError as e:
             if e.errno != errno.EEXIST:
                 raise
-        os.mkdir(os.path.join(path, "info"))
+        os.mkdir(os.path.join(path, INFODIR))
         os.mkdir(os.path.join(path, PACKDIR))
         return cls(path)
 

+ 2 - 4
dulwich/objects.py

@@ -112,12 +112,10 @@ def hex_to_filename(path, hex):
     # 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
     # as path.
-    if getattr(path, 'encode', None) is not None:
-        hex = hex.decode('ascii')
-    dir = hex[:2]
+    directory = hex[:2]
     file = hex[2:]
     # Check from object dir
-    return os.path.join(path, dir, file)
+    return os.path.join(path, directory, file)
 
 
 def filename_to_hex(filename):

+ 7 - 7
dulwich/pack.py

@@ -1474,12 +1474,12 @@ def write_pack(filename, objects, deltify=None, delta_window_size=None):
     :param deltify: Whether to deltify pack objects
     :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,
             delta_window_size=delta_window_size, deltify=deltify)
     entries = [(k, v[0], v[1]) for (k, v) in entries.items()]
     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)
 
 
@@ -1785,8 +1785,8 @@ class Pack(object):
         self._basename = basename
         self._data = 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._idx_load = lambda: load_pack_index(self._idx_path)
         self.resolve_ext_ref = resolve_ext_ref
@@ -1795,7 +1795,7 @@ class Pack(object):
     def from_lazy_objects(self, data_fn, idx_fn):
         """Create a new pack object from callables to load pack data and
         index objects."""
-        ret = Pack('')
+        ret = Pack(b'')
         ret._data_load = data_fn
         ret._idx_load = idx_fn
         return ret
@@ -1803,7 +1803,7 @@ class Pack(object):
     @classmethod
     def from_objects(self, data, idx):
         """Create a new pack object from pack data and index objects."""
-        ret = Pack('')
+        ret = Pack(b'')
         ret._data_load = lambda: data
         ret._idx_load = lambda: idx
         return ret
@@ -1930,7 +1930,7 @@ class Pack(object):
                     determine whether or not a .keep file is obsolete.
         :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:
             if msg:
                 keepfile.write(msg)

+ 6 - 2
dulwich/porcelain.py

@@ -515,12 +515,16 @@ def push(repo, remote_location, refs_path,
         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:
         client.send_pack(path, update_refs,
             r.object_store.generate_pack_contents, progress=errstream.write)
-        errstream.write(b"Push to " + remote_location.encode(err_encoding) + b" successful.\n")
+        errstream.write(b"Push to " + remote_location_bytes + b" successful.\n")
     except (UpdateRefsError, SendPackError) as e:
-        errstream.write(b"Push to " + remote_location.encode(err_encoding) + b" failed -> " + e.message.encode(err_encoding) + b"\n")
+        errstream.write(b"Push to " + remote_location_bytes + b" failed -> " + e.message.encode(err_encoding) + b"\n")
 
 
 def pull(repo, remote_location, refs_path,

+ 11 - 10
dulwich/refs.py

@@ -23,6 +23,7 @@
 """
 import errno
 import os
+import sys
 
 from dulwich.errors import (
     PackedRefsException,
@@ -43,6 +44,8 @@ SYMREF = b'ref: '
 LOCAL_BRANCH_PREFIX = b'refs/heads/'
 BAD_REF_CHARS = set(b'\177 ~^:?*[')
 
+path_sep_bytes = os.path.sep.encode(sys.getfilesystemencoding())
+
 
 def check_ref_format(refname):
     """Check if a refname is correctly formatted.
@@ -395,10 +398,9 @@ class DiskRefsContainer(RefsContainer):
         subkeys = set()
         path = self.refpath(base)
         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:
-                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
                 # base before calling it.
                 if check_ref_format(base + b'/' + refname):
@@ -414,9 +416,9 @@ class DiskRefsContainer(RefsContainer):
             allkeys.add(b'HEAD')
         path = self.refpath(b'')
         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:
-                refname = ("%s/%s" % (dir, filename)).strip("/").encode('ascii')
+                refname = (dir + b'/' + filename).strip(b'/')
                 if check_ref_format(refname):
                     allkeys.add(refname)
         allkeys.update(self.get_packed_refs())
@@ -426,9 +428,8 @@ class DiskRefsContainer(RefsContainer):
         """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)
 
     def get_packed_refs(self):
@@ -445,7 +446,7 @@ class DiskRefsContainer(RefsContainer):
             # None if and only if _packed_refs is also None.
             self._packed_refs = {}
             self._peeled_refs = {}
-            path = os.path.join(self.path, 'packed-refs')
+            path = os.path.join(self.path, b'packed-refs')
             try:
                 f = GitFile(path, 'rb')
             except IOError as e:
@@ -513,7 +514,7 @@ class DiskRefsContainer(RefsContainer):
     def _remove_packed_ref(self, name):
         if self._packed_refs is None:
             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
         f = GitFile(filename, 'wb')
         try:

+ 56 - 38
dulwich/repo.py

@@ -82,19 +82,19 @@ from dulwich.refs import (
 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 = [
-    ["branches"],
+    [b'branches'],
     [REFSDIR],
     [REFSDIR, REFSDIR_TAGS],
     [REFSDIR, REFSDIR_HEADS],
-    ["hooks"],
-    ["info"]
+    [b'hooks'],
+    [b'info']
     ]
 
 
@@ -176,7 +176,7 @@ class BaseRepo(object):
     def _init_files(self, bare):
         """Initialize a default set of named files."""
         from dulwich.config import ConfigFile
-        self._put_named_file('description', b"Unnamed repository")
+        self._put_named_file(b'description', b"Unnamed repository")
         f = BytesIO()
         cf = ConfigFile()
         cf.set(b"core", b"repositoryformatversion", b"0")
@@ -184,8 +184,8 @@ class BaseRepo(object):
         cf.set(b"core", b"bare", bare)
         cf.set(b"core", b"logallrefupdates", True)
         cf.write_to_file(f)
-        self._put_named_file('config', f.getvalue())
-        self._put_named_file(os.path.join('info', 'exclude'), b'')
+        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):
         """Get a file from the control dir with a specific name.
@@ -627,6 +627,7 @@ class BaseRepo(object):
 
         return c.id
 
+path_sep_bytes = os.path.sep.encode(sys.getfilesystemencoding())
 
 class Repo(BaseRepo):
     """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.
     """
 
-    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._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._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
-            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._controldir = os.path.join(root, path)
+            self._controldir = os.path.join(self._path_bytes, gitdir)
         else:
             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(),
                                                     OBJECTDIR))
         refs = DiskRefsContainer(self.controldir())
         BaseRepo.__init__(self, object_store, refs)
 
         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:
             with 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:
             with 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 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:
             f.write(contents)
 
@@ -701,7 +706,7 @@ class Repo(BaseRepo):
         """
         # TODO(dborowitz): sanitize filenames, since this is used directly by
         # the dumb web serving code.
-        path = path.lstrip(os.path.sep)
+        path = path.lstrip(path_sep_bytes)
         try:
             return open(os.path.join(self.controldir(), path), 'rb')
         except (IOError, OSError) as e:
@@ -743,19 +748,24 @@ class Repo(BaseRepo):
             )
         index = self.open_index()
         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:
                 st = os.lstat(full_path)
             except OSError:
                 # File no longer exists
                 try:
-                    del index[path.encode(fsencoding)]
+                    del index[repo_path_bytes]
                 except KeyError:
                     pass  # already removed
             else:
                 blob = blob_from_path_and_stat(full_path, st)
                 self.object_store.add_object(blob)
-                index[path.encode(fsencoding)] = index_entry_from_stat(st, blob.id, 0)
+                index[repo_path_bytes] = index_entry_from_stat(st, blob.id, 0)
         index.write()
 
     def clone(self, target_path, mkdir=True, bare=False,
@@ -815,7 +825,7 @@ class Repo(BaseRepo):
             validate_path_element = validate_path_element_ntfs
         else:
             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,
                 validate_path_element=validate_path_element)
 
@@ -825,7 +835,7 @@ class Repo(BaseRepo):
         :return: `ConfigFile` object for the ``.git/config`` file.
         """
         from dulwich.config import ConfigFile
-        path = os.path.join(self._controldir, 'config')
+        path = os.path.join(self._controldir, b'config')
         try:
             return ConfigFile.from_path(path)
         except (IOError, OSError) as e:
@@ -840,7 +850,7 @@ class Repo(BaseRepo):
 
         :return: A string describing the repository or None.
         """
-        path = os.path.join(self._controldir, 'description')
+        path = os.path.join(self._controldir, b'description')
         try:
             with GitFile(path, 'rb') as f:
                 return f.read()
@@ -858,13 +868,17 @@ class Repo(BaseRepo):
         :param description: Text to set as description for this repository.
         """
 
-        self._put_named_file('description', description)
+        self._put_named_file(b'description', description)
 
     @classmethod
     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:
-            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.refs.set_symbolic_ref(b'HEAD', b"refs/heads/master")
         ret._init_files(bare)
@@ -878,9 +892,13 @@ class Repo(BaseRepo):
         :param mkdir: Whether to create the directory
         :return: `Repo` instance
         """
+        if not isinstance(path, bytes):
+            path_bytes = path.encode(sys.getfilesystemencoding())
+        else:
+            path_bytes = path
         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)
         cls._init_maybe_bare(controldir, False)
         return cls(path)

+ 2 - 2
dulwich/server.py

@@ -989,10 +989,10 @@ def update_server_info(repo):
     This generates info/refs and objects/info/packs,
     similar to "git update-server-info".
     """
-    repo._put_named_file(os.path.join('info', 'refs'),
+    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'),
+    repo._put_named_file(os.path.join(b'objects', b'info', b'packs'),
         b"".join(generate_objects_info_packs(repo)))
 
 

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

@@ -23,6 +23,7 @@ import errno
 import os
 import shutil
 import socket
+import sys
 import tempfile
 
 from dulwich.repo import Repo
@@ -47,6 +48,11 @@ class _StubRepo(object):
     def __init__(self, name):
         temp_dir = tempfile.mkdtemp()
         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)
 
 

+ 6 - 3
dulwich/tests/compat/test_pack.py

@@ -24,6 +24,7 @@ import binascii
 import os
 import re
 import shutil
+import sys
 import tempfile
 
 from dulwich.pack import (
@@ -66,12 +67,14 @@ class TestPack(PackTests):
         require_git_version((1, 5, 0))
         super(TestPack, self).setUp()
         self._tempdir = tempfile.mkdtemp()
+        if not isinstance(self._tempdir, bytes):
+            self._tempdir = self._tempdir.encode(sys.getfilesystemencoding())
         self.addCleanup(shutil.rmtree, self._tempdir)
 
     def test_copy(self):
         with self.get_pack(pack1_sha) as origpack:
             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())
             output = run_git_or_fail(['verify-pack', '-v', pack_path])
             orig_shas = set(o.id for o in origpack.iterobjects())
@@ -83,7 +86,7 @@ class TestPack(PackTests):
         new_blob = Blob()
         new_blob.data = orig_blob.data + b'x'
         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)
         output = run_git_or_fail(['verify-pack', '-v', pack_path])
         self.assertEqual(set(x[0].id for x in all_to_pack),
@@ -107,7 +110,7 @@ class TestPack(PackTests):
         new_blob_2.data = new_blob.data + b'y'
         all_to_pack = list(orig_pack.pack_tuples()) + [(new_blob, 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)
         output = run_git_or_fail(['verify-pack', '-v', pack_path])
         self.assertEqual(set(x[0].id for x in all_to_pack),

+ 14 - 12
dulwich/tests/test_file.py

@@ -92,7 +92,9 @@ class GitFileTests(TestCase):
     def setUp(self):
         super(GitFileTests, self).setUp()
         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.close()
 
@@ -104,7 +106,7 @@ class GitFileTests(TestCase):
         return os.path.join(self._tempdir, filename)
 
     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='ab')
         self.assertRaises(IOError, GitFile, foo, mode='r+b')
@@ -112,7 +114,7 @@ class GitFileTests(TestCase):
         self.assertRaises(IOError, GitFile, foo, mode='a+bU')
 
     def test_readonly(self):
-        f = GitFile(self.path('foo'), 'rb')
+        f = GitFile(self.path(b'foo'), 'rb')
         self.assertTrue(isinstance(f, io.IOBase))
         self.assertEqual(b'foo contents', f.read())
         self.assertEqual(b'', f.read())
@@ -121,13 +123,13 @@ class GitFileTests(TestCase):
         f.close()
 
     def test_default_mode(self):
-        f = GitFile(self.path('foo'))
+        f = GitFile(self.path(b'foo'))
         self.assertEqual(b'foo contents', f.read())
         f.close()
 
     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')
         self.assertEqual(orig_f.read(), b'foo contents')
@@ -150,7 +152,7 @@ class GitFileTests(TestCase):
         new_f.close()
 
     def test_open_twice(self):
-        foo = self.path('foo')
+        foo = self.path(b'foo')
         f1 = GitFile(foo, 'wb')
         f1.write(b'new')
         try:
@@ -169,8 +171,8 @@ class GitFileTests(TestCase):
         f.close()
 
     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')
         self.assertEqual(orig_f.read(), b'foo contents')
@@ -187,7 +189,7 @@ class GitFileTests(TestCase):
         new_orig_f.close()
 
     def test_abort_close(self):
-        foo = self.path('foo')
+        foo = self.path(b'foo')
         f = GitFile(foo, 'wb')
         f.abort()
         try:
@@ -203,11 +205,11 @@ class GitFileTests(TestCase):
             self.fail()
 
     def test_abort_close_removed(self):
-        foo = self.path('foo')
+        foo = self.path(b'foo')
         f = GitFile(foo, 'wb')
 
         f._file.close()
-        os.remove(foo+".lock")
+        os.remove(foo + b'.lock')
 
         f.abort()
         self.assertTrue(f._closed)

+ 2 - 2
dulwich/tests/test_grafts.py

@@ -163,7 +163,7 @@ class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
 
     def test_init_with_empty_info_grafts(self):
         r = self._repo
-        r._put_named_file(os.path.join('info', 'grafts'), b'')
+        r._put_named_file(os.path.join(b'info', b'grafts'), b'')
 
         r = Repo(self._repo_dir)
         self.assertEqual({}, r._graftpoints)
@@ -171,7 +171,7 @@ class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
     def test_init_with_info_grafts(self):
         r = self._repo
         r._put_named_file(
-            os.path.join('info', 'grafts'),
+            os.path.join(b'info', b'grafts'),
             self._shas[-1] + b' ' + self._shas[0])
 
         r = Repo(self._repo_dir)

+ 30 - 20
dulwich/tests/test_hooks.py

@@ -41,28 +41,31 @@ class ShellHookTests(TestCase):
             self.skipTest('shell hook tests requires POSIX shell')
 
     def test_hook_pre_commit(self):
-        pre_commit_fail = b"""#!/bin/sh
+        pre_commit_fail = """#!/bin/sh
 exit 1
 """
 
-        pre_commit_success = b"""#!/bin/sh
+        pre_commit_success = """#!/bin/sh
 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)
 
-        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)
 
-        with open(pre_commit, 'wb') as f:
+        with open(pre_commit, 'w') as f:
             f.write(pre_commit_fail)
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
         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)
         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):
 
-        commit_msg_fail = b"""#!/bin/sh
+        commit_msg_fail = """#!/bin/sh
 exit 1
 """
 
-        commit_msg_success = b"""#!/bin/sh
+        commit_msg_success = """#!/bin/sh
 exit 0
 """
 
         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)
 
-        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)
 
-        with open(commit_msg, 'wb') as f:
+        with open(commit_msg, 'w') as f:
             f.write(commit_msg_fail)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
         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)
         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):
 
         (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
 """
 
         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)
 
-        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)
 
-        with open(post_commit, 'wb') as f:
+        with open(post_commit, 'w') as f:
             f.write(post_commit_msg_fail)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
         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)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 

+ 12 - 5
dulwich/tests/test_object_store.py

@@ -22,6 +22,7 @@
 from io import BytesIO
 import os
 import shutil
+import sys
 import tempfile
 
 from dulwich.index import (
@@ -262,6 +263,8 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
     def setUp(self):
         TestCase.setUp(self)
         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.store = DiskObjectStore.init(self.store_dir)
 
@@ -271,6 +274,8 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
 
     def test_alternates(self):
         alternate_dir = tempfile.mkdtemp()
+        if not isinstance(alternate_dir, bytes):
+            alternate_dir = alternate_dir.encode(sys.getfilesystemencoding())
         self.addCleanup(shutil.rmtree, alternate_dir)
         alternate_store = DiskObjectStore(alternate_dir)
         b2 = make_object(Blob, data=b"yummy data")
@@ -284,15 +289,17 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
     def test_add_alternate_path(self):
         store = DiskObjectStore(self.store_dir)
         self.assertEqual([], list(store._read_alternate_paths()))
-        store.add_alternate_path("/foo/path")
-        self.assertEqual(["/foo/path"], list(store._read_alternate_paths()))
-        store.add_alternate_path("/bar/path")
+        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(
-            ["/foo/path", "/bar/path"],
+            [b'/foo/path', b'/bar/path'],
             list(store._read_alternate_paths()))
 
     def test_rel_alternative_path(self):
         alternate_dir = tempfile.mkdtemp()
+        if not isinstance(alternate_dir, bytes):
+            alternate_dir = alternate_dir.encode(sys.getfilesystemencoding())
         self.addCleanup(shutil.rmtree, alternate_dir)
         alternate_store = DiskObjectStore(alternate_dir)
         b2 = make_object(Blob, data=b"yummy data")
@@ -306,7 +313,7 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
 
     def test_pack_dir(self):
         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):
         o = DiskObjectStore(self.store_dir)

+ 11 - 6
dulwich/tests/test_objects.py

@@ -29,6 +29,7 @@ from itertools import (
     )
 import os
 import stat
+import sys
 import warnings
 from contextlib import contextmanager
 
@@ -85,21 +86,23 @@ class BlobReadTests(TestCase):
     """Test decompression of blobs"""
 
     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))
 
     def get_blob(self, sha):
         """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):
-        return self.get_sha_file(Tree, 'trees', sha)
+        return self.get_sha_file(Tree, b'trees', 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):
-        return self.get_sha_file(Commit, 'commits', sha)
+        return self.get_sha_file(Commit, b'commits', sha)
 
     def test_decompress_simple_blob(self):
         b = self.get_blob(a_sha)
@@ -702,7 +705,9 @@ class TreeTests(ShaFileCheckTests):
         self.assertEqual(_SORTED_TREE_ITEMS, x.items())
 
     def _do_test_parse_tree(self, parse_tree):
-        dir = os.path.join(os.path.dirname(__file__), 'data', 'trees')
+        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))
         self.assertEqual([(b'a', 0o100644, a_sha), (b'b', 0o100644, b_sha)],
                          list(parse_tree(o.as_raw_string())))

+ 26 - 18
dulwich/tests/test_pack.py

@@ -24,6 +24,7 @@ from io import BytesIO
 from hashlib import sha1
 import os
 import shutil
+import sys
 import tempfile
 import zlib
 
@@ -89,21 +90,24 @@ class PackTests(TestCase):
     def setUp(self):
         super(PackTests, self).setUp()
         self.tempdir = tempfile.mkdtemp()
+        if not isinstance(self.tempdir, bytes):
+            self.tempdir = self.tempdir.encode(sys.getfilesystemencoding())
         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):
         """Returns a PackIndex from the datadir with the given sha"""
-        return load_pack_index(os.path.join(self.datadir, 'pack-%s.idx' % sha.decode('ascii')))
+        return load_pack_index(os.path.join(self.datadir, b'pack-' + sha + b'.idx'))
 
     def get_pack_data(self, sha):
         """Returns a PackData object from the datadir with the given sha"""
-        return PackData(os.path.join(self.datadir, 'pack-%s.pack' % sha.decode('ascii')))
+        return PackData(os.path.join(self.datadir, b'pack-' + sha + b'.pack'))
 
     def get_pack(self, sha):
-        return Pack(os.path.join(self.datadir, 'pack-%s' % sha.decode('ascii')))
+        return Pack(os.path.join(self.datadir, b'pack-' + sha))
 
     def assertSucceeds(self, func, *args, **kwargs):
         try:
@@ -204,7 +208,7 @@ class TestPackData(PackTests):
         self.get_pack_data(pack1_sha).close()
 
     def test_from_file(self):
-        path = os.path.join(self.datadir, 'pack-%s.pack' % pack1_sha.decode('ascii'))
+        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))
 
@@ -247,7 +251,7 @@ class TestPackData(PackTests):
 
     def test_create_index_v1(self):
         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)
             idx1 = load_pack_index(filename)
             idx2 = self.get_pack_index(pack1_sha)
@@ -255,7 +259,7 @@ class TestPackData(PackTests):
 
     def test_create_index_v2(self):
         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)
             idx1 = load_pack_index(filename)
             idx2 = self.get_pack_index(pack1_sha)
@@ -330,7 +334,7 @@ class TestPack(PackTests):
     def test_copy(self):
         with self.get_pack(pack1_sha) as origpack:
             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())
 
             with Pack(basename) as newpack:
@@ -353,7 +357,7 @@ class TestPack(PackTests):
             self.assertEqual([], commit.parents)
 
     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())
         return Pack(basename)
 
@@ -401,7 +405,7 @@ class TestPack(PackTests):
             write_pack_header(bad_file, 9999)
             bad_file.write(data._file.read())
             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)
             self.assertRaises(AssertionError, lambda: bad_pack.data)
             self.assertRaises(AssertionError,
@@ -414,7 +418,7 @@ class TestPack(PackTests):
 
             data._file.seek(0)
             bad_file = BytesIO(data._file.read()[:-20] + (b'\xff' * 20))
-            bad_data = PackData('', file=bad_file)
+            bad_data = PackData(b'', file=bad_file)
             bad_pack = Pack.from_lazy_objects(lambda: bad_data, lambda: index)
             self.assertRaises(ChecksumMismatch, lambda: bad_pack.data)
             self.assertRaises(ChecksumMismatch, lambda:
@@ -444,10 +448,12 @@ class TestThinPack(PackTests):
         # Build a thin pack. 'foo' is as an external reference, 'bar' an
         # internal reference.
         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.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, [
                 (REF_DELTA, (self.blobs[b'foo'].id, b'foo1234')),
                 (Blob.type_num, b'bar'),
@@ -458,7 +464,7 @@ class TestThinPack(PackTests):
         with self.make_pack(True) as pack:
             with PackData(pack._data_path) as data:
                 data.pack = pack
-                data.create_index(self.pack_prefix + '.idx')
+                data.create_index(self.pack_prefix + b'.idx')
 
         del self.store[self.blobs[b'bar'].id]
 
@@ -537,7 +543,7 @@ class BaseTestPackIndexWriting(object):
         raise NotImplementedError(self.index)
 
     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(0, len(idx))
 
@@ -550,7 +556,7 @@ class BaseTestPackIndexWriting(object):
             self.assertRaises(TypeError, self.index, 'single.idx',
                 entries, pack_checksum)
             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(2, len(idx))
         actual_entries = list(idx.iterentries())
@@ -568,7 +574,7 @@ class BaseTestPackIndexWriting(object):
     def test_single(self):
         entry_sha = hex_to_sha('6f670c0fb53f9463760b7295fbb814e965fb20c8')
         my_entries = [(entry_sha, 178, 42)]
-        idx = self.index('single.idx', my_entries, pack_checksum)
+        idx = self.index(b'single.idx', my_entries, pack_checksum)
         self.assertEqual(idx.get_pack_checksum(), pack_checksum)
         self.assertEqual(1, len(idx))
         actual_entries = list(idx.iterentries())
@@ -588,6 +594,8 @@ class BaseTestFilePackIndexWriting(BaseTestPackIndexWriting):
 
     def setUp(self):
         self.tempdir = tempfile.mkdtemp()
+        if not isinstance(self.tempdir, bytes):
+            self.tempdir = self.tempdir.encode(sys.getfilesystemencoding())
 
     def tearDown(self):
         shutil.rmtree(self.tempdir)

+ 3 - 3
dulwich/tests/test_porcelain.py

@@ -77,7 +77,7 @@ class UpdateServerInfoTests(PorcelainTestCase):
         self.repo.refs[b"refs/heads/foo"] = c3.id
         porcelain.update_server_info(self.repo.path)
         self.assertTrue(os.path.exists(os.path.join(self.repo.controldir(),
-            'info', 'refs')))
+            b'info', b'refs')))
 
 
 class CommitTests(PorcelainTestCase):
@@ -281,7 +281,7 @@ class SymbolicRefTests(PorcelainTestCase):
         porcelain.symbolic_ref(self.repo.path, b'force_foobar', force=True)
 
         #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()
         self.assertEqual(new_ref, b'ref: refs/heads/force_foobar\n')
 
@@ -301,7 +301,7 @@ class SymbolicRefTests(PorcelainTestCase):
         porcelain.symbolic_ref(self.repo.path, b'develop')
 
         #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()
         self.assertEqual(new_ref, b'ref: refs/heads/develop\n')
 

+ 10 - 10
dulwich/tests/test_refs.py

@@ -308,7 +308,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
 
     def 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',
                          f.read()[:40])
         f.close()
@@ -319,12 +319,12 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
         self.assertEqual(ones, self._refs[b'HEAD'])
 
         # 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'))
         f.close()
 
         # 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])
         f.close()
 
@@ -336,9 +336,9 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
 
         # ensure lockfile was deleted
         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(
-            os.path.join(self._refs.path, 'HEAD.lock')))
+            os.path.join(self._refs.path, b'HEAD.lock')))
 
     def test_add_if_new_packed(self):
         # don't overwrite packed ref
@@ -377,7 +377,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
 
     def 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(b'refs/heads/master' in self._refs.get_packed_refs())
 
@@ -388,7 +388,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
         self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
         self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                          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):
         # HEAD is a symref, so shouldn't equal its dereferenced value
@@ -404,12 +404,12 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
                          self._refs.read_loose_ref(b'HEAD'))
 
         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(
-            os.path.join(self._refs.path, 'HEAD.lock')))
+            os.path.join(self._refs.path, b'HEAD.lock')))
 
     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)
         refs_data = f.read()
         f.close()

+ 20 - 17
dulwich/tests/test_repository.py

@@ -79,11 +79,11 @@ class CreateRepositoryTests(TestCase):
 
     def _check_repo_contents(self, repo, expect_bare):
         self.assertEqual(expect_bare, repo.bare)
-        self.assertFileContentsEqual(b'Unnamed repository', repo, 'description')
-        self.assertFileContentsEqual(b'', repo, os.path.join('info', 'exclude'))
-        self.assertFileContentsEqual(None, repo, 'nonexistent file')
+        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('config') as f:
+        with repo.get_named_file(b'config') as f:
             config_text = f.read()
             self.assertTrue(barestr in config_text, "%r" % config_text)
 
@@ -423,7 +423,7 @@ exit 0
         r = Repo.init(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:
             f.write(pre_commit_fail)
@@ -463,7 +463,7 @@ exit 0
         r = Repo.init(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:
             f.write(commit_msg_fail)
@@ -501,8 +501,8 @@ exit 0
         self.addCleanup(shutil.rmtree, repo_dir)
 
         (fd, path) = tempfile.mkstemp(dir=repo_dir_str)
-        post_commit_msg = b"""#!/bin/sh
-rm """ + path.encode(sys.getfilesystemencoding()) + b"""
+        post_commit_msg = """#!/bin/sh
+rm """ + path + """
 """
 
         root_sha = r.do_commit(
@@ -513,9 +513,9 @@ rm """ + path.encode(sys.getfilesystemencoding()) + b"""
             author_timestamp=12345, author_timezone=0)
         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)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
@@ -529,10 +529,10 @@ rm """ + path.encode(sys.getfilesystemencoding()) + b"""
 
         self.assertFalse(os.path.exists(path))
 
-        post_commit_msg_fail = b"""#!/bin/sh
+        post_commit_msg_fail = """#!/bin/sh
 exit 1
 """
-        with open(post_commit, 'wb') as f:
+        with open(post_commit, 'w') as f:
             f.write(post_commit_msg_fail)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
@@ -569,6 +569,9 @@ class BuildRepoBytesRootTests(TestCase):
     def get_repo_dir(self):
         return os.path.join(mkdtemp_bytes(), b'test')
 
+    def get_a_filename(self):
+        return b'a'
+
     def setUp(self):
         super(BuildRepoBytesRootTests, self).setUp()
         self._repo_dir = self.get_repo_dir()
@@ -578,7 +581,7 @@ class BuildRepoBytesRootTests(TestCase):
         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:
+        with open(os.path.join(r._path_bytes, b'a'), 'wb') as f:
             f.write(b'file contents')
         r.stage(['a'])
         commit_sha = r.do_commit(b'msg',
@@ -604,9 +607,9 @@ class BuildRepoBytesRootTests(TestCase):
 
     def test_commit_modified(self):
         r = self._repo
-        with open(os.path.join(r.path, 'a'), 'wb') as f:
+        with open(os.path.join(r._path_bytes, b'a'), 'wb') as f:
             f.write(b'new contents')
-        os.symlink('a', os.path.join(self._repo_dir, 'b'))
+        os.symlink('a', os.path.join(r._path_bytes, b'b'))
         r.stage(['a', 'b'])
         commit_sha = r.do_commit(b'modified a',
                                  committer=b'Test Committer <test@nodomain.com>',
@@ -623,7 +626,7 @@ class BuildRepoBytesRootTests(TestCase):
 
     def test_commit_deleted(self):
         r = self._repo
-        os.remove(os.path.join(r.path, 'a'))
+        os.remove(os.path.join(r._path_bytes, b'a'))
         r.stage(['a'])
         commit_sha = r.do_commit(b'deleted a',
                                  committer=b'Test Committer <test@nodomain.com>',
@@ -785,7 +788,7 @@ class BuildRepoBytesRootTests(TestCase):
 
     def test_stage_deleted(self):
         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'])  # double-stage a deleted path
 

+ 1 - 1
dulwich/tests/utils.py

@@ -82,7 +82,7 @@ def open_repo(name, temp_dir=None):
 
 def tear_down_repo(repo):
     """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)