فهرست منبع

Fix indentation to be 4 spaces everywhere, consistently.

Jelmer Vernooij 16 سال پیش
والد
کامیت
d8e1460beb
8فایلهای تغییر یافته به همراه1046 افزوده شده و 1049 حذف شده
  1. 28 28
      dulwich/errors.py
  2. 135 135
      dulwich/objects.py
  3. 325 325
      dulwich/pack.py
  4. 244 245
      dulwich/repo.py
  5. 9 9
      dulwich/tests/__init__.py
  6. 98 98
      dulwich/tests/test_objects.py
  7. 102 104
      dulwich/tests/test_pack.py
  8. 105 105
      dulwich/tests/test_repository.py

+ 28 - 28
dulwich/errors.py

@@ -17,45 +17,45 @@
 # MA  02110-1301, USA.
 
 class WrongObjectException(Exception):
-  """Baseclass for all the _ is not a _ exceptions on objects.
-
-  Do not instantiate directly.
-
-  Subclasses should define a _type attribute that indicates what
-  was expected if they were raised.
-  """
-
-  def __init__(self, sha, *args, **kwargs):
-    string = "%s is not a %s" % (sha, self._type)
-    Exception.__init__(self, string)
+    """Baseclass for all the _ is not a _ exceptions on objects.
+  
+    Do not instantiate directly.
+  
+    Subclasses should define a _type attribute that indicates what
+    was expected if they were raised.
+    """
+  
+    def __init__(self, sha, *args, **kwargs):
+        string = "%s is not a %s" % (sha, self._type)
+        Exception.__init__(self, string)
 
 class NotCommitError(WrongObjectException):
-  """Indicates that the sha requested does not point to a commit."""
-
-  _type = 'commit'
+    """Indicates that the sha requested does not point to a commit."""
+  
+    _type = 'commit'
 
 class NotTreeError(WrongObjectException):
-  """Indicates that the sha requested does not point to a tree."""
-
-  _type = 'tree'
+    """Indicates that the sha requested does not point to a tree."""
+  
+    _type = 'tree'
 
 class NotBlobError(WrongObjectException):
-  """Indicates that the sha requested does not point to a blob."""
-
-  _type = 'blob'
+    """Indicates that the sha requested does not point to a blob."""
+  
+    _type = 'blob'
 
 class MissingCommitError(Exception):
-  """Indicates that a commit was not found in the repository"""
-
-  def __init__(self, sha, *args, **kwargs):
-    Exception.__init__(self, "%s is not in the revision store" % sha)
+    """Indicates that a commit was not found in the repository"""
+  
+    def __init__(self, sha, *args, **kwargs):
+        Exception.__init__(self, "%s is not in the revision store" % sha)
 
 
 class ObjectMissing(Exception):
-  """Indicates that a requested object is missing."""
-
-  def __init__(self, sha, *args, **kwargs):
-    Exception.__init__(self, "%s is not in the pack" % sha)
+    """Indicates that a requested object is missing."""
+  
+    def __init__(self, sha, *args, **kwargs):
+        Exception.__init__(self, "%s is not in the pack" % sha)
 
 
 class ApplyDeltaError(Exception):

+ 135 - 135
dulwich/objects.py

@@ -48,145 +48,145 @@ def _decompress(string):
     return dcomped
 
 def sha_to_hex(sha):
-  """Takes a string and returns the hex of the sha within"""
-  hexsha = ''
-  for c in sha:
-    hexsha += "%02x" % ord(c)
-  assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % \
-         len(hexsha)
-  return hexsha
+    """Takes a string and returns the hex of the sha within"""
+    hexsha = ''
+    for c in sha:
+        hexsha += "%02x" % ord(c)
+    assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % \
+           len(hexsha)
+    return hexsha
 
 def hex_to_sha(hex):
-  """Takes a hex sha and returns a binary sha"""
-  sha = ''
-  for i in range(0, len(hex), 2):
-    sha += chr(int(hex[i:i+2], 16))
-  assert len(sha) == 20, "Incorrent length of sha1: %d" % len(sha)
-  return sha
+    """Takes a hex sha and returns a binary sha"""
+    sha = ''
+    for i in range(0, len(hex), 2):
+        sha += chr(int(hex[i:i+2], 16))
+    assert len(sha) == 20, "Incorrent length of sha1: %d" % len(sha)
+    return sha
 
 class ShaFile(object):
-  """A git SHA file."""
-
-  @classmethod
-  def _parse_legacy_object(cls, map):
-    """Parse a legacy object, creating it and setting object._text"""
-    text = _decompress(map)
-    object = None
-    for posstype in type_map.keys():
-      if text.startswith(posstype):
-        object = type_map[posstype]()
-        text = text[len(posstype):]
-        break
-    assert object is not None, "%s is not a known object type" % text[:9]
-    assert text[0] == ' ', "%s is not a space" % text[0]
-    text = text[1:]
-    size = 0
-    i = 0
-    while text[0] >= '0' and text[0] <= '9':
-      if i > 0 and size == 0:
-        assert False, "Size is not in canonical format"
-      size = (size * 10) + int(text[0])
-      text = text[1:]
-      i += 1
-    object._size = size
-    assert text[0] == "\0", "Size not followed by null"
-    text = text[1:]
-    object._text = text
-    return object
-
-  def as_raw_string(self):
-    return self._num_type, self._text
-
-  @classmethod
-  def _parse_object(cls, map):
-    """Parse a new style object , creating it and setting object._text"""
-    used = 0
-    byte = ord(map[used])
-    used += 1
-    num_type = (byte >> 4) & 7
-    try:
-      object = num_type_map[num_type]()
-    except KeyError:
-      assert False, "Not a known type: %d" % num_type
-    while((byte & 0x80) != 0):
-      byte = ord(map[used])
-      used += 1
-    raw = map[used:]
-    object._text = _decompress(raw)
-    return object
-
-  @classmethod
-  def _parse_file(cls, map):
-    word = (ord(map[0]) << 8) + ord(map[1])
-    if ord(map[0]) == 0x78 and (word % 31) == 0:
-      return cls._parse_legacy_object(map)
-    else:
-      return cls._parse_object(map)
-
-  def __init__(self):
-    """Don't call this directly"""
-
-  def _parse_text(self):
-    """For subclasses to do initialisation time parsing"""
-
-  @classmethod
-  def from_file(cls, filename):
-    """Get the contents of a SHA file on disk"""
-    size = os.path.getsize(filename)
-    f = open(filename, 'rb')
-    try:
-      map = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)
-      shafile = cls._parse_file(map)
-      shafile._parse_text()
-      return shafile
-    finally:
-      f.close()
-
-  @classmethod
-  def from_raw_string(cls, type, string):
-    """Creates an object of the indicated type from the raw string given.
-
-    Type is the numeric type of an object. String is the raw uncompressed
-    contents.
-    """
-    real_class = num_type_map[type]
-    obj = real_class()
-    obj._num_type = type
-    obj._text = string
-    obj._parse_text()
-    return obj
-
-  def _header(self):
-    return "%s %lu\0" % (self._type, len(self._text))
-
-  def crc32(self):
-    return zlib.crc32(self._text)
-
-  def sha(self):
-    """The SHA1 object that is the name of this object."""
-    ressha = sha.new()
-    ressha.update(self._header())
-    ressha.update(self._text)
-    return ressha
-
-  @property
-  def id(self):
-      return self.sha().hexdigest()
-
-  @property
-  def type(self):
-      return self._num_type
-
-  def __repr__(self):
-    return "<%s %s>" % (self.__class__.__name__, self.id)
-
-  def __eq__(self, other):
-    """Return true id the sha of the two objects match.
-
-    The __le__ etc methods aren't overriden as they make no sense,
-    certainly at this level.
-    """
-    return self.sha().digest() == other.sha().digest()
+    """A git SHA file."""
+  
+    @classmethod
+    def _parse_legacy_object(cls, map):
+        """Parse a legacy object, creating it and setting object._text"""
+        text = _decompress(map)
+        object = None
+        for posstype in type_map.keys():
+            if text.startswith(posstype):
+                object = type_map[posstype]()
+                text = text[len(posstype):]
+                break
+        assert object is not None, "%s is not a known object type" % text[:9]
+        assert text[0] == ' ', "%s is not a space" % text[0]
+        text = text[1:]
+        size = 0
+        i = 0
+        while text[0] >= '0' and text[0] <= '9':
+            if i > 0 and size == 0:
+                assert False, "Size is not in canonical format"
+            size = (size * 10) + int(text[0])
+            text = text[1:]
+            i += 1
+        object._size = size
+        assert text[0] == "\0", "Size not followed by null"
+        text = text[1:]
+        object._text = text
+        return object
+  
+    def as_raw_string(self):
+        return self._num_type, self._text
+  
+    @classmethod
+    def _parse_object(cls, map):
+        """Parse a new style object , creating it and setting object._text"""
+        used = 0
+        byte = ord(map[used])
+        used += 1
+        num_type = (byte >> 4) & 7
+        try:
+            object = num_type_map[num_type]()
+        except KeyError:
+            assert False, "Not a known type: %d" % num_type
+        while((byte & 0x80) != 0):
+            byte = ord(map[used])
+            used += 1
+        raw = map[used:]
+        object._text = _decompress(raw)
+        return object
+  
+    @classmethod
+    def _parse_file(cls, map):
+        word = (ord(map[0]) << 8) + ord(map[1])
+        if ord(map[0]) == 0x78 and (word % 31) == 0:
+            return cls._parse_legacy_object(map)
+        else:
+            return cls._parse_object(map)
+  
+    def __init__(self):
+        """Don't call this directly"""
+  
+    def _parse_text(self):
+        """For subclasses to do initialisation time parsing"""
+  
+    @classmethod
+    def from_file(cls, filename):
+        """Get the contents of a SHA file on disk"""
+        size = os.path.getsize(filename)
+        f = open(filename, 'rb')
+        try:
+            map = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)
+            shafile = cls._parse_file(map)
+            shafile._parse_text()
+            return shafile
+        finally:
+            f.close()
+  
+    @classmethod
+    def from_raw_string(cls, type, string):
+        """Creates an object of the indicated type from the raw string given.
+    
+        Type is the numeric type of an object. String is the raw uncompressed
+        contents.
+        """
+        real_class = num_type_map[type]
+        obj = real_class()
+        obj._num_type = type
+        obj._text = string
+        obj._parse_text()
+        return obj
+  
+    def _header(self):
+        return "%s %lu\0" % (self._type, len(self._text))
+  
+    def crc32(self):
+        return zlib.crc32(self._text)
+  
+    def sha(self):
+        """The SHA1 object that is the name of this object."""
+        ressha = sha.new()
+        ressha.update(self._header())
+        ressha.update(self._text)
+        return ressha
+  
+    @property
+    def id(self):
+        return self.sha().hexdigest()
+  
+    @property
+    def type(self):
+        return self._num_type
+  
+    def __repr__(self):
+        return "<%s %s>" % (self.__class__.__name__, self.id)
+  
+    def __eq__(self, other):
+        """Return true id the sha of the two objects match.
+  
+        The __le__ etc methods aren't overriden as they make no sense,
+        certainly at this level.
+        """
+        return self.sha().digest() == other.sha().digest()
 
 
 class Blob(ShaFile):

+ 325 - 325
dulwich/pack.py

@@ -125,190 +125,190 @@ def simple_mmap(f, offset, size, access=mmap.ACCESS_READ):
 
 
 def resolve_object(offset, type, obj, get_ref, get_offset):
-  """Resolve an object, possibly resolving deltas when necessary."""
-  if not type in (6, 7): # Not a delta
-     return type, obj
-
-  if type == 6: # offset delta
-     (delta_offset, delta) = obj
-     assert isinstance(delta_offset, int)
-     assert isinstance(delta, str)
-     offset = offset-delta_offset
-     type, base_obj = get_offset(offset)
-     assert isinstance(type, int)
-  elif type == 7: # ref delta
-     (basename, delta) = obj
-     assert isinstance(basename, str) and len(basename) == 20
-     assert isinstance(delta, str)
-     type, base_obj = get_ref(basename)
-     assert isinstance(type, int)
-  type, base_text = resolve_object(offset, type, base_obj, get_ref, get_offset)
-  return type, apply_delta(base_text, delta)
+    """Resolve an object, possibly resolving deltas when necessary."""
+    if not type in (6, 7): # Not a delta
+        return type, obj
+  
+    if type == 6: # offset delta
+        (delta_offset, delta) = obj
+        assert isinstance(delta_offset, int)
+        assert isinstance(delta, str)
+        offset = offset-delta_offset
+        type, base_obj = get_offset(offset)
+        assert isinstance(type, int)
+    elif type == 7: # ref delta
+        (basename, delta) = obj
+        assert isinstance(basename, str) and len(basename) == 20
+        assert isinstance(delta, str)
+        type, base_obj = get_ref(basename)
+        assert isinstance(type, int)
+    type, base_text = resolve_object(offset, type, base_obj, get_ref, get_offset)
+    return type, apply_delta(base_text, delta)
 
 
 class PackIndex(object):
-  """An index in to a packfile.
-
-  Given a sha id of an object a pack index can tell you the location in the
-  packfile of that object if it has it.
-
-  To do the loop it opens the file, and indexes first 256 4 byte groups
-  with the first byte of the sha id. The value in the four byte group indexed
-  is the end of the group that shares the same starting byte. Subtract one
-  from the starting byte and index again to find the start of the group.
-  The values are sorted by sha id within the group, so do the math to find
-  the start and end offset and then bisect in to find if the value is present.
-  """
-
-  def __init__(self, filename):
-    """Create a pack index object.
-
-    Provide it with the name of the index file to consider, and it will map
-    it whenever required.
+    """An index in to a packfile.
+  
+    Given a sha id of an object a pack index can tell you the location in the
+    packfile of that object if it has it.
+  
+    To do the loop it opens the file, and indexes first 256 4 byte groups
+    with the first byte of the sha id. The value in the four byte group indexed
+    is the end of the group that shares the same starting byte. Subtract one
+    from the starting byte and index again to find the start of the group.
+    The values are sorted by sha id within the group, so do the math to find
+    the start and end offset and then bisect in to find if the value is present.
     """
-    self._filename = filename
-    # Take the size now, so it can be checked each time we map the file to
-    # ensure that it hasn't changed.
-    self._size = os.path.getsize(filename)
-    self._file = open(filename, 'r')
-    self._contents = simple_mmap(self._file, 0, self._size)
-    if self._contents[:4] != '\377tOc':
-        self.version = 1
-        self._fan_out_table = self._read_fan_out_table(0)
-    else:
-        (self.version, ) = struct.unpack_from(">L", self._contents, 4)
-        assert self.version in (2,), "Version was %d" % self.version
-        self._fan_out_table = self._read_fan_out_table(8)
-        self._name_table_offset = 8 + 0x100 * 4
-        self._crc32_table_offset = self._name_table_offset + 20 * len(self)
-        self._pack_offset_table_offset = self._crc32_table_offset + 4 * len(self)
-
-  def __eq__(self, other):
-    if type(self) != type(other):
-        return False
-
-    if self._fan_out_table != other._fan_out_table:
-        return False
-
-    for (name1, _, _), (name2, _, _) in izip(self.iterentries(), other.iterentries()):
-        if name1 != name2:
+  
+    def __init__(self, filename):
+        """Create a pack index object.
+    
+        Provide it with the name of the index file to consider, and it will map
+        it whenever required.
+        """
+        self._filename = filename
+        # Take the size now, so it can be checked each time we map the file to
+        # ensure that it hasn't changed.
+        self._size = os.path.getsize(filename)
+        self._file = open(filename, 'r')
+        self._contents = simple_mmap(self._file, 0, self._size)
+        if self._contents[:4] != '\377tOc':
+            self.version = 1
+            self._fan_out_table = self._read_fan_out_table(0)
+        else:
+            (self.version, ) = struct.unpack_from(">L", self._contents, 4)
+            assert self.version in (2,), "Version was %d" % self.version
+            self._fan_out_table = self._read_fan_out_table(8)
+            self._name_table_offset = 8 + 0x100 * 4
+            self._crc32_table_offset = self._name_table_offset + 20 * len(self)
+            self._pack_offset_table_offset = self._crc32_table_offset + 4 * len(self)
+  
+    def __eq__(self, other):
+        if type(self) != type(other):
             return False
-    return True
-
-  def close(self):
-    self._file.close()
-
-  def __len__(self):
-    """Return the number of entries in this pack index."""
-    return self._fan_out_table[-1]
-
-  def _unpack_entry(self, i):
-    """Unpack the i-th entry in the index file.
-
-    :return: Tuple with object name (SHA), offset in pack file and 
-          CRC32 checksum (if known)."""
-    if self.version == 1:
-        (offset, name) = struct.unpack_from(">L20s", self._contents, 
-            (0x100 * 4) + (i * 24))
-        return (name, offset, None)
-    else:
-        return (self._unpack_name(i), self._unpack_offset(i), 
-                self._unpack_crc32_checksum(i))
-
-  def _unpack_name(self, i):
-    if self.version == 1:
-        return self._unpack_entry(i)[0]
-    else:
-        return struct.unpack_from("20s", self._contents, 
-                                  self._name_table_offset + i * 20)[0]
-
-  def _unpack_offset(self, i):
-    if self.version == 1:
-        return self._unpack_entry(i)[1]
-    else:
-        return struct.unpack_from(">L", self._contents, 
-                                  self._pack_offset_table_offset + i * 4)[0]
-
-  def _unpack_crc32_checksum(self, i):
-    if self.version == 1:
-        return None
-    else:
-        return struct.unpack_from(">L", self._contents, 
-                                  self._crc32_table_offset + i * 4)[0]
-
-  def __iter__(self):
-      return imap(sha_to_hex, self._itersha())
-
-  def _itersha(self):
-    for i in range(len(self)):
-        yield self._unpack_name(i)
-
-  def objects_sha1(self):
-    return iter_sha1(self._itersha())
-
-  def iterentries(self):
-    """Iterate over the entries in this pack index.
-   
-    Will yield tuples with object name, offset in packfile and crc32 checksum.
-    """
-    for i in range(len(self)):
-        yield self._unpack_entry(i)
-
-  def _read_fan_out_table(self, start_offset):
-    ret = []
-    for i in range(0x100):
-        ret.append(struct.unpack(">L", self._contents[start_offset+i*4:start_offset+(i+1)*4])[0])
-    return ret
-
-  def check(self):
-    """Check that the stored checksum matches the actual checksum."""
-    return self.calculate_checksum() == self.get_stored_checksums()[1]
-
-  def calculate_checksum(self):
-    f = open(self._filename, 'r')
-    try:
-        return hashlib.sha1(self._contents[:-20]).digest()
-    finally:
-        f.close()
-
-  def get_stored_checksums(self):
-    """Return the SHA1 checksums stored for the corresponding packfile and 
-    this header file itself."""
-    return str(self._contents[-40:-20]), str(self._contents[-20:])
-
-  def object_index(self, sha):
-    """Return the index in to the corresponding packfile for the object.
-
-    Given the name of an object it will return the offset that object lives
-    at within the corresponding pack file. If the pack file doesn't have the
-    object then None will be returned.
-    """
-    size = os.path.getsize(self._filename)
-    assert size == self._size, "Pack index %s has changed size, I don't " \
-         "like that" % self._filename
-    if len(sha) == 40:
-        sha = hex_to_sha(sha)
-    return self._object_index(sha)
-
-  def _object_index(self, sha):
-      """See object_index"""
-      idx = ord(sha[0])
-      if idx == 0:
-          start = 0
-      else:
-          start = self._fan_out_table[idx-1]
-      end = self._fan_out_table[idx]
-      assert start <= end
-      while start <= end:
-        i = (start + end)/2
-        file_sha = self._unpack_name(i)
-        if file_sha < sha:
-          start = i + 1
-        elif file_sha > sha:
-          end = i - 1
+    
+        if self._fan_out_table != other._fan_out_table:
+            return False
+    
+        for (name1, _, _), (name2, _, _) in izip(self.iterentries(), other.iterentries()):
+            if name1 != name2:
+                return False
+        return True
+  
+    def close(self):
+        self._file.close()
+  
+    def __len__(self):
+        """Return the number of entries in this pack index."""
+        return self._fan_out_table[-1]
+  
+    def _unpack_entry(self, i):
+        """Unpack the i-th entry in the index file.
+    
+        :return: Tuple with object name (SHA), offset in pack file and 
+              CRC32 checksum (if known)."""
+        if self.version == 1:
+            (offset, name) = struct.unpack_from(">L20s", self._contents, 
+                (0x100 * 4) + (i * 24))
+            return (name, offset, None)
         else:
-          return self._unpack_offset(i)
-      return None
+            return (self._unpack_name(i), self._unpack_offset(i), 
+                    self._unpack_crc32_checksum(i))
+  
+    def _unpack_name(self, i):
+        if self.version == 1:
+            return self._unpack_entry(i)[0]
+        else:
+            return struct.unpack_from("20s", self._contents, 
+                                      self._name_table_offset + i * 20)[0]
+  
+    def _unpack_offset(self, i):
+        if self.version == 1:
+            return self._unpack_entry(i)[1]
+        else:
+            return struct.unpack_from(">L", self._contents, 
+                                      self._pack_offset_table_offset + i * 4)[0]
+  
+    def _unpack_crc32_checksum(self, i):
+        if self.version == 1:
+            return None
+        else:
+            return struct.unpack_from(">L", self._contents, 
+                                      self._crc32_table_offset + i * 4)[0]
+  
+    def __iter__(self):
+        return imap(sha_to_hex, self._itersha())
+  
+    def _itersha(self):
+        for i in range(len(self)):
+            yield self._unpack_name(i)
+  
+    def objects_sha1(self):
+        return iter_sha1(self._itersha())
+  
+    def iterentries(self):
+        """Iterate over the entries in this pack index.
+       
+        Will yield tuples with object name, offset in packfile and crc32 checksum.
+        """
+        for i in range(len(self)):
+            yield self._unpack_entry(i)
+  
+    def _read_fan_out_table(self, start_offset):
+        ret = []
+        for i in range(0x100):
+            ret.append(struct.unpack(">L", self._contents[start_offset+i*4:start_offset+(i+1)*4])[0])
+        return ret
+  
+    def check(self):
+        """Check that the stored checksum matches the actual checksum."""
+        return self.calculate_checksum() == self.get_stored_checksums()[1]
+  
+    def calculate_checksum(self):
+        f = open(self._filename, 'r')
+        try:
+            return hashlib.sha1(self._contents[:-20]).digest()
+        finally:
+            f.close()
+  
+    def get_stored_checksums(self):
+        """Return the SHA1 checksums stored for the corresponding packfile and 
+        this header file itself."""
+        return str(self._contents[-40:-20]), str(self._contents[-20:])
+  
+    def object_index(self, sha):
+        """Return the index in to the corresponding packfile for the object.
+    
+        Given the name of an object it will return the offset that object lives
+        at within the corresponding pack file. If the pack file doesn't have the
+        object then None will be returned.
+        """
+        size = os.path.getsize(self._filename)
+        assert size == self._size, "Pack index %s has changed size, I don't " \
+             "like that" % self._filename
+        if len(sha) == 40:
+            sha = hex_to_sha(sha)
+        return self._object_index(sha)
+  
+    def _object_index(self, sha):
+        """See object_index"""
+        idx = ord(sha[0])
+        if idx == 0:
+            start = 0
+        else:
+            start = self._fan_out_table[idx-1]
+        end = self._fan_out_table[idx]
+        assert start <= end
+        while start <= end:
+            i = (start + end)/2
+            file_sha = self._unpack_name(i)
+            if file_sha < sha:
+                start = i + 1
+            elif file_sha > sha:
+                end = i - 1
+            else:
+                return self._unpack_offset(i)
+        return None
 
 
 def read_pack_header(f):
@@ -329,7 +329,7 @@ def unpack_object(map):
     type = (bytes[0] >> 4) & 0x07
     size = bytes[0] & 0x0f
     for i, byte in enumerate(bytes[1:]):
-      size += (byte & 0x7f) << ((i * 7) + 4)
+        size += (byte & 0x7f) << ((i * 7) + 4)
     raw_base = len(bytes)
     if type == 6: # offset delta
         bytes = take_msb_bytes(map, raw_base)
@@ -355,155 +355,155 @@ def unpack_object(map):
 
 
 class PackData(object):
-  """The data contained in a packfile.
-
-  Pack files can be accessed both sequentially for exploding a pack, and
-  directly with the help of an index to retrieve a specific object.
-
-  The objects within are either complete or a delta aginst another.
-
-  The header is variable length. If the MSB of each byte is set then it
-  indicates that the subsequent byte is still part of the header.
-  For the first byte the next MS bits are the type, which tells you the type
-  of object, and whether it is a delta. The LS byte is the lowest bits of the
-  size. For each subsequent byte the LS 7 bits are the next MS bits of the
-  size, i.e. the last byte of the header contains the MS bits of the size.
-
-  For the complete objects the data is stored as zlib deflated data.
-  The size in the header is the uncompressed object size, so to uncompress
-  you need to just keep feeding data to zlib until you get an object back,
-  or it errors on bad data. This is done here by just giving the complete
-  buffer from the start of the deflated object on. This is bad, but until I
-  get mmap sorted out it will have to do.
-
-  Currently there are no integrity checks done. Also no attempt is made to try
-  and detect the delta case, or a request for an object at the wrong position.
-  It will all just throw a zlib or KeyError.
-  """
-
-  def __init__(self, filename):
-    """Create a PackData object that represents the pack in the given filename.
-
-    The file must exist and stay readable until the object is disposed of. It
-    must also stay the same size. It will be mapped whenever needed.
-
-    Currently there is a restriction on the size of the pack as the python
-    mmap implementation is flawed.
+    """The data contained in a packfile.
+  
+    Pack files can be accessed both sequentially for exploding a pack, and
+    directly with the help of an index to retrieve a specific object.
+  
+    The objects within are either complete or a delta aginst another.
+  
+    The header is variable length. If the MSB of each byte is set then it
+    indicates that the subsequent byte is still part of the header.
+    For the first byte the next MS bits are the type, which tells you the type
+    of object, and whether it is a delta. The LS byte is the lowest bits of the
+    size. For each subsequent byte the LS 7 bits are the next MS bits of the
+    size, i.e. the last byte of the header contains the MS bits of the size.
+  
+    For the complete objects the data is stored as zlib deflated data.
+    The size in the header is the uncompressed object size, so to uncompress
+    you need to just keep feeding data to zlib until you get an object back,
+    or it errors on bad data. This is done here by just giving the complete
+    buffer from the start of the deflated object on. This is bad, but until I
+    get mmap sorted out it will have to do.
+  
+    Currently there are no integrity checks done. Also no attempt is made to try
+    and detect the delta case, or a request for an object at the wrong position.
+    It will all just throw a zlib or KeyError.
     """
-    self._filename = filename
-    assert os.path.exists(filename), "%s is not a packfile" % filename
-    self._size = os.path.getsize(filename)
-    self._header_size = 12
-    assert self._size >= self._header_size, "%s is too small for a packfile (%d < %d)" % (filename, self._size, self._header_size)
-    self._read_header()
-
-  def _read_header(self):
-    f = open(self._filename, 'rb')
-    try:
-        (version, self._num_objects) = \
-                read_pack_header(f)
-        f.seek(self._size-20)
-        (self._stored_checksum,) = read_pack_tail(f)
-    finally:
-        f.close()
-
-  def __len__(self):
-      """Returns the number of objects in this pack."""
-      return self._num_objects
-
-  def calculate_checksum(self):
-    f = open(self._filename, 'rb')
-    try:
-        map = simple_mmap(f, 0, self._size)
-        return hashlib.sha1(map[:-20]).digest()
-    finally:
+  
+    def __init__(self, filename):
+        """Create a PackData object that represents the pack in the given filename.
+    
+        The file must exist and stay readable until the object is disposed of. It
+        must also stay the same size. It will be mapped whenever needed.
+    
+        Currently there is a restriction on the size of the pack as the python
+        mmap implementation is flawed.
+        """
+        self._filename = filename
+        assert os.path.exists(filename), "%s is not a packfile" % filename
+        self._size = os.path.getsize(filename)
+        self._header_size = 12
+        assert self._size >= self._header_size, "%s is too small for a packfile (%d < %d)" % (filename, self._size, self._header_size)
+        self._read_header()
+  
+    def _read_header(self):
+        f = open(self._filename, 'rb')
+        try:
+            (version, self._num_objects) = \
+                    read_pack_header(f)
+            f.seek(self._size-20)
+            (self._stored_checksum,) = read_pack_tail(f)
+        finally:
+            f.close()
+  
+    def __len__(self):
+        """Returns the number of objects in this pack."""
+        return self._num_objects
+  
+    def calculate_checksum(self):
+        f = open(self._filename, 'rb')
+        try:
+            map = simple_mmap(f, 0, self._size)
+            return hashlib.sha1(map[:-20]).digest()
+        finally:
+            f.close()
+  
+    def iterobjects(self):
+        offset = self._header_size
+        f = open(self._filename, 'rb')
+        for i in range(len(self)):
+            map = simple_mmap(f, offset, self._size-offset)
+            (type, obj, total_size) = unpack_object(map)
+            yield offset, type, obj
+            offset += total_size
         f.close()
-
-  def iterobjects(self):
-    offset = self._header_size
-    f = open(self._filename, 'rb')
-    for i in range(len(self)):
-        map = simple_mmap(f, offset, self._size-offset)
-        (type, obj, total_size) = unpack_object(map)
-        yield offset, type, obj
-        offset += total_size
-    f.close()
-
-  def iterentries(self, ext_resolve_ref=None):
-    found = {}
-    at = {}
-    postponed = defaultdict(list)
-    class Postpone(Exception):
-        """Raised to postpone delta resolving."""
-        
-    def get_ref_text(sha):
-        if sha in found:
-            return found[sha]
-        if ext_resolve_ref:
+  
+    def iterentries(self, ext_resolve_ref=None):
+        found = {}
+        at = {}
+        postponed = defaultdict(list)
+        class Postpone(Exception):
+            """Raised to postpone delta resolving."""
+          
+        def get_ref_text(sha):
+            if sha in found:
+                return found[sha]
+            if ext_resolve_ref:
+                try:
+                    return ext_resolve_ref(sha)
+                except KeyError:
+                    pass
+            raise Postpone, (sha, )
+        todo = list(self.iterobjects())
+        while todo:
+            (offset, type, obj) = todo.pop(0)
+            at[offset] = (type, obj)
+            assert isinstance(offset, int)
+            assert isinstance(type, int)
+            assert isinstance(obj, tuple) or isinstance(obj, str)
             try:
-                return ext_resolve_ref(sha)
-            except KeyError:
-                pass
-        raise Postpone, (sha, )
-    todo = list(self.iterobjects())
-    while todo:
-      (offset, type, obj) = todo.pop(0)
-      at[offset] = (type, obj)
-      assert isinstance(offset, int)
-      assert isinstance(type, int)
-      assert isinstance(obj, tuple) or isinstance(obj, str)
-      try:
-        type, obj = resolve_object(offset, type, obj, get_ref_text,
-            at.__getitem__)
-      except Postpone, (sha, ):
-        postponed[sha].append((offset, type, obj))
-      else:
-        shafile = ShaFile.from_raw_string(type, obj)
-        sha = shafile.sha().digest()
-        found[sha] = (type, obj)
-        yield sha, offset, shafile.crc32()
-        todo += postponed.get(sha, [])
-    if postponed:
-        raise KeyError([sha_to_hex(h) for h in postponed.keys()])
-
-  def sorted_entries(self, resolve_ext_ref=None):
-    ret = list(self.iterentries(resolve_ext_ref))
-    ret.sort()
-    return ret
-
-  def create_index_v1(self, filename, resolve_ext_ref=None):
-    entries = self.sorted_entries(resolve_ext_ref)
-    write_pack_index_v1(filename, entries, self.calculate_checksum())
-
-  def create_index_v2(self, filename, resolve_ext_ref=None):
-    entries = self.sorted_entries(resolve_ext_ref)
-    write_pack_index_v2(filename, entries, self.calculate_checksum())
-
-  def get_stored_checksum(self):
-    return self._stored_checksum
-
-  def check(self):
-    return (self.calculate_checksum() == self.get_stored_checksum())
-
-  def get_object_at(self, offset):
-    """Given an offset in to the packfile return the object that is there.
-
-    Using the associated index the location of an object can be looked up, and
-    then the packfile can be asked directly for that object using this
-    function.
-    """
-    assert isinstance(offset, long) or isinstance(offset, int),\
-            "offset was %r" % offset
-    assert offset >= self._header_size
-    size = os.path.getsize(self._filename)
-    assert size == self._size, "Pack data %s has changed size, I don't " \
-         "like that" % self._filename
-    f = open(self._filename, 'rb')
-    try:
-      map = simple_mmap(f, offset, size-offset)
-      return unpack_object(map)[:2]
-    finally:
-      f.close()
+                type, obj = resolve_object(offset, type, obj, get_ref_text,
+                    at.__getitem__)
+            except Postpone, (sha, ):
+                postponed[sha].append((offset, type, obj))
+            else:
+                shafile = ShaFile.from_raw_string(type, obj)
+                sha = shafile.sha().digest()
+                found[sha] = (type, obj)
+                yield sha, offset, shafile.crc32()
+                todo += postponed.get(sha, [])
+        if postponed:
+            raise KeyError([sha_to_hex(h) for h in postponed.keys()])
+  
+    def sorted_entries(self, resolve_ext_ref=None):
+        ret = list(self.iterentries(resolve_ext_ref))
+        ret.sort()
+        return ret
+  
+    def create_index_v1(self, filename, resolve_ext_ref=None):
+        entries = self.sorted_entries(resolve_ext_ref)
+        write_pack_index_v1(filename, entries, self.calculate_checksum())
+  
+    def create_index_v2(self, filename, resolve_ext_ref=None):
+        entries = self.sorted_entries(resolve_ext_ref)
+        write_pack_index_v2(filename, entries, self.calculate_checksum())
+  
+    def get_stored_checksum(self):
+        return self._stored_checksum
+  
+    def check(self):
+        return (self.calculate_checksum() == self.get_stored_checksum())
+  
+    def get_object_at(self, offset):
+        """Given an offset in to the packfile return the object that is there.
+    
+        Using the associated index the location of an object can be looked up, and
+        then the packfile can be asked directly for that object using this
+        function.
+        """
+        assert isinstance(offset, long) or isinstance(offset, int),\
+                "offset was %r" % offset
+        assert offset >= self._header_size
+        size = os.path.getsize(self._filename)
+        assert size == self._size, "Pack data %s has changed size, I don't " \
+             "like that" % self._filename
+        f = open(self._filename, 'rb')
+        try:
+            map = simple_mmap(f, offset, size-offset)
+            return unpack_object(map)[:2]
+        finally:
+            f.close()
 
 
 class SHA1Writer(object):

+ 244 - 245
dulwich/repo.py

@@ -68,250 +68,249 @@ class Tags(object):
 
 class Repo(object):
 
-  ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
-
-  def __init__(self, root):
-    if os.path.isdir(os.path.join(root, ".git", "objects")):
-      self.bare = False
-      self._controldir = os.path.join(root, ".git")
-    elif os.path.isdir(os.path.join(root, "objects")):
-      self.bare = True
-      self._controldir = root
-    else:
-      raise NotGitRepository(root)
-    self.path = root
-    self.tags = Tags(self.tagdir(), self.get_tags())
-    self._object_store = None
-
-  def controldir(self):
-    return self._controldir
-
-  def find_missing_objects(self, determine_wants, graph_walker, progress):
-    """Fetch the missing objects required for a set of revisions.
-
-    :param determine_wants: Function that takes a dictionary with heads 
-        and returns the list of heads to fetch.
-    :param graph_walker: Object that can iterate over the list of revisions 
-        to fetch and has an "ack" method that will be called to acknowledge 
-        that a revision is present.
-    :param progress: Simple progress function that will be called with 
-        updated progress strings.
-    """
-    wants = determine_wants(self.get_refs())
-    objects_to_send = set(wants)
-    sha_done = set()
-
-    def parse_tree(tree, sha_done):
-        for mode, name, sha in tree.entries():
-            if (sha, name) in sha_done:
-                continue
-            if mode & stat.S_IFDIR:
-                parse_tree(self.tree(sha), sha_done)
-            sha_done.add((sha, name))
-
-    def parse_commit(commit, sha_done):
-        treesha = c.tree
-        if c.tree not in sha_done:
-            parse_tree(self.tree(c.tree), sha_done)
-            sha_done.add((c.tree, None))
-
-    ref = graph_walker.next()
-    while ref:
-        if ref in self.object_store:
-            graph_walker.ack(ref)
-        ref = graph_walker.next()
-    while objects_to_send:
-        sha = objects_to_send.pop()
-        if (sha, None) in sha_done:
-            continue
-
-        c = self.object_store[sha]
-        if isinstance(c, Commit):
-            parse_commit(c, sha_done)
-            objects_to_send.update([p for p in c.parents if not p in sha_done])
-        sha_done.add((sha, None))
-
-        progress("counting objects: %d\r" % len(sha_done))
-    return sha_done
-
-  def fetch_objects(self, determine_wants, graph_walker, progress):
-    """Fetch the missing objects required for a set of revisions.
-
-    :param determine_wants: Function that takes a dictionary with heads 
-        and returns the list of heads to fetch.
-    :param graph_walker: Object that can iterate over the list of revisions 
-        to fetch and has an "ack" method that will be called to acknowledge 
-        that a revision is present.
-    :param progress: Simple progress function that will be called with 
-        updated progress strings.
-    :return: tuple with number of objects, iterator over objects
-    """
-    return self.object_store.iter_shas(
-        self.find_missing_objects(determine_wants, graph_walker, progress))
-
-  def object_dir(self):
-    return os.path.join(self.controldir(), OBJECTDIR)
-
-  @property
-  def object_store(self):
-    if self._object_store is None:
-        self._object_store = ObjectStore(self.object_dir())
-    return self._object_store
-
-  def pack_dir(self):
-    return os.path.join(self.object_dir(), PACKDIR)
-
-  def _get_ref(self, file):
-    f = open(file, 'rb')
-    try:
-      contents = f.read()
-      if contents.startswith(SYMREF):
-        ref = contents[len(SYMREF):]
-        if ref[-1] == '\n':
-          ref = ref[:-1]
-        return self.ref(ref)
-      assert len(contents) == 41, 'Invalid ref in %s' % file
-      return contents[:-1]
-    finally:
-      f.close()
-
-  def ref(self, name):
-    for dir in self.ref_locs:
-      file = os.path.join(self.controldir(), dir, name)
-      if os.path.exists(file):
-        return self._get_ref(file)
-
-  def get_refs(self):
-    ret = {}
-    if self.head():
-        ret['HEAD'] = self.head()
-    for dir in ["refs/heads", "refs/tags"]:
-        for name in os.listdir(os.path.join(self.controldir(), dir)):
-          path = os.path.join(self.controldir(), dir, name)
-          if os.path.isfile(path):
-            ret["/".join([dir, name])] = self._get_ref(path)
-    return ret
-
-  def set_ref(self, name, value):
-    file = os.path.join(self.controldir(), name)
-    open(file, 'w').write(value+"\n")
-
-  def remove_ref(self, name):
-    file = os.path.join(self.controldir(), name)
-    if os.path.exists(file):
-      os.remove(file)
-      return
-
-  def tagdir(self):
-    return os.path.join(self.controldir(), 'refs', 'tags')
-
-  def get_tags(self):
-    ret = {}
-    for root, dirs, files in os.walk(self.tagdir()):
-      for name in files:
-        ret[name] = self._get_ref(os.path.join(root, name))
-    return ret
-
-  def heads(self):
-    ret = {}
-    for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
-      for name in files:
-        ret[name] = self._get_ref(os.path.join(root, name))
-    return ret
-
-  def head(self):
-    return self.ref('HEAD')
-
-  def _get_object(self, sha, cls):
-    assert len(sha) in (20, 40)
-    ret = self.get_object(sha)
-    if ret._type != cls._type:
-        if cls is Commit:
-            raise NotCommitError(ret)
-        elif cls is Blob:
-            raise NotBlobError(ret)
-        elif cls is Tree:
-            raise NotTreeError(ret)
+    ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
+
+    def __init__(self, root):
+        if os.path.isdir(os.path.join(root, ".git", "objects")):
+            self.bare = False
+            self._controldir = os.path.join(root, ".git")
+        elif os.path.isdir(os.path.join(root, "objects")):
+            self.bare = True
+            self._controldir = root
         else:
-            raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
-    return ret
-
-  def get_object(self, sha):
-    return self.object_store[sha]
-
-  def get_parents(self, sha):
-    return self.commit(sha).parents
-
-  def commit(self, sha):
-    return self._get_object(sha, Commit)
-
-  def tree(self, sha):
-    return self._get_object(sha, Tree)
-
-  def tag(self, sha):
-    return self._get_object(sha, Tag)
-
-  def get_blob(self, sha):
-    return self._get_object(sha, Blob)
-
-  def revision_history(self, head):
-    """Returns a list of the commits reachable from head.
-
-    Returns a list of commit objects. the first of which will be the commit
-    of head, then following theat will be the parents.
-
-    Raises NotCommitError if any no commits are referenced, including if the
-    head parameter isn't the sha of a commit.
-
-    XXX: work out how to handle merges.
-    """
-    # We build the list backwards, as parents are more likely to be older
-    # than children
-    pending_commits = [head]
-    history = []
-    while pending_commits != []:
-      head = pending_commits.pop(0)
-      try:
-          commit = self.commit(head)
-      except KeyError:
-        raise MissingCommitError(head)
-      if commit in history:
-        continue
-      i = 0
-      for known_commit in history:
-        if known_commit.commit_time > commit.commit_time:
-          break
-        i += 1
-      history.insert(i, commit)
-      parents = commit.parents
-      pending_commits += parents
-    history.reverse()
-    return history
-
-  def __repr__(self):
-      return "<Repo at %r>" % self.path
-
-  @classmethod
-  def init(cls, path, mkdir=True):
-      controldir = os.path.join(path, ".git")
-      os.mkdir(controldir)
-      cls.init_bare(controldir)
-
-  @classmethod
-  def init_bare(cls, path, mkdir=True):
-      for d in [["objects"], 
-                ["objects", "info"], 
-                ["objects", "pack"],
-                ["branches"],
-                ["refs"],
-                ["refs", "tags"],
-                ["refs", "heads"],
-                ["hooks"],
-                ["info"]]:
-          os.mkdir(os.path.join(path, *d))
-      open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
-      open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
-      open(os.path.join(path, 'info', 'excludes'), 'w').write("")
-
-  create = init_bare
+            raise NotGitRepository(root)
+        self.path = root
+        self.tags = Tags(self.tagdir(), self.get_tags())
+        self._object_store = None
+
+    def controldir(self):
+        return self._controldir
+
+    def find_missing_objects(self, determine_wants, graph_walker, progress):
+        """Fetch the missing objects required for a set of revisions.
+
+        :param determine_wants: Function that takes a dictionary with heads 
+            and returns the list of heads to fetch.
+        :param graph_walker: Object that can iterate over the list of revisions 
+            to fetch and has an "ack" method that will be called to acknowledge 
+            that a revision is present.
+        :param progress: Simple progress function that will be called with 
+            updated progress strings.
+        """
+        wants = determine_wants(self.get_refs())
+        objects_to_send = set(wants)
+        sha_done = set()
+
+        def parse_tree(tree, sha_done):
+            for mode, name, sha in tree.entries():
+                if (sha, name) in sha_done:
+                    continue
+                if mode & stat.S_IFDIR:
+                    parse_tree(self.tree(sha), sha_done)
+                sha_done.add((sha, name))
+
+        def parse_commit(commit, sha_done):
+            treesha = c.tree
+            if c.tree not in sha_done:
+                parse_tree(self.tree(c.tree), sha_done)
+                sha_done.add((c.tree, None))
+
+        ref = graph_walker.next()
+        while ref:
+            if ref in self.object_store:
+                graph_walker.ack(ref)
+            ref = graph_walker.next()
+        while objects_to_send:
+            sha = objects_to_send.pop()
+            if (sha, None) in sha_done:
+                continue
+    
+            c = self.object_store[sha]
+            if isinstance(c, Commit):
+                parse_commit(c, sha_done)
+                objects_to_send.update([p for p in c.parents if not p in sha_done])
+            sha_done.add((sha, None))
+    
+            progress("counting objects: %d\r" % len(sha_done))
+        return sha_done
+
+    def fetch_objects(self, determine_wants, graph_walker, progress):
+        """Fetch the missing objects required for a set of revisions.
+
+        :param determine_wants: Function that takes a dictionary with heads 
+            and returns the list of heads to fetch.
+        :param graph_walker: Object that can iterate over the list of revisions 
+            to fetch and has an "ack" method that will be called to acknowledge 
+            that a revision is present.
+        :param progress: Simple progress function that will be called with 
+            updated progress strings.
+        :return: tuple with number of objects, iterator over objects
+        """
+        return self.object_store.iter_shas(
+            self.find_missing_objects(determine_wants, graph_walker, progress))
+
+    def object_dir(self):
+        return os.path.join(self.controldir(), OBJECTDIR)
+
+    @property
+    def object_store(self):
+        if self._object_store is None:
+            self._object_store = ObjectStore(self.object_dir())
+        return self._object_store
+
+    def pack_dir(self):
+        return os.path.join(self.object_dir(), PACKDIR)
+
+    def _get_ref(self, file):
+        f = open(file, 'rb')
+        try:
+            contents = f.read()
+            if contents.startswith(SYMREF):
+                ref = contents[len(SYMREF):]
+                if ref[-1] == '\n':
+                    ref = ref[:-1]
+                return self.ref(ref)
+            assert len(contents) == 41, 'Invalid ref in %s' % file
+            return contents[:-1]
+        finally:
+            f.close()
+
+    def ref(self, name):
+        for dir in self.ref_locs:
+            file = os.path.join(self.controldir(), dir, name)
+            if os.path.exists(file):
+                return self._get_ref(file)
+
+    def get_refs(self):
+        ret = {}
+        if self.head():
+            ret['HEAD'] = self.head()
+        for dir in ["refs/heads", "refs/tags"]:
+            for name in os.listdir(os.path.join(self.controldir(), dir)):
+                path = os.path.join(self.controldir(), dir, name)
+                if os.path.isfile(path):
+                    ret["/".join([dir, name])] = self._get_ref(path)
+        return ret
+
+    def set_ref(self, name, value):
+        file = os.path.join(self.controldir(), name)
+        open(file, 'w').write(value+"\n")
+
+    def remove_ref(self, name):
+        file = os.path.join(self.controldir(), name)
+        if os.path.exists(file):
+            os.remove(file)
+
+    def tagdir(self):
+        return os.path.join(self.controldir(), 'refs', 'tags')
+
+    def get_tags(self):
+        ret = {}
+        for root, dirs, files in os.walk(self.tagdir()):
+            for name in files:
+                ret[name] = self._get_ref(os.path.join(root, name))
+        return ret
+
+    def heads(self):
+        ret = {}
+        for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
+            for name in files:
+                ret[name] = self._get_ref(os.path.join(root, name))
+        return ret
+
+    def head(self):
+        return self.ref('HEAD')
+
+    def _get_object(self, sha, cls):
+        assert len(sha) in (20, 40)
+        ret = self.get_object(sha)
+        if ret._type != cls._type:
+            if cls is Commit:
+                raise NotCommitError(ret)
+            elif cls is Blob:
+                raise NotBlobError(ret)
+            elif cls is Tree:
+                raise NotTreeError(ret)
+            else:
+                raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
+        return ret
+
+    def get_object(self, sha):
+        return self.object_store[sha]
+
+    def get_parents(self, sha):
+        return self.commit(sha).parents
+
+    def commit(self, sha):
+        return self._get_object(sha, Commit)
+
+    def tree(self, sha):
+        return self._get_object(sha, Tree)
+
+    def tag(self, sha):
+        return self._get_object(sha, Tag)
+
+    def get_blob(self, sha):
+        return self._get_object(sha, Blob)
+
+    def revision_history(self, head):
+        """Returns a list of the commits reachable from head.
+
+        Returns a list of commit objects. the first of which will be the commit
+        of head, then following theat will be the parents.
+
+        Raises NotCommitError if any no commits are referenced, including if the
+        head parameter isn't the sha of a commit.
+
+        XXX: work out how to handle merges.
+        """
+        # We build the list backwards, as parents are more likely to be older
+        # than children
+        pending_commits = [head]
+        history = []
+        while pending_commits != []:
+            head = pending_commits.pop(0)
+            try:
+                commit = self.commit(head)
+            except KeyError:
+                raise MissingCommitError(head)
+            if commit in history:
+                continue
+            i = 0
+            for known_commit in history:
+                if known_commit.commit_time > commit.commit_time:
+                    break
+                i += 1
+            history.insert(i, commit)
+            parents = commit.parents
+            pending_commits += parents
+        history.reverse()
+        return history
+
+    def __repr__(self):
+        return "<Repo at %r>" % self.path
+
+    @classmethod
+    def init(cls, path, mkdir=True):
+        controldir = os.path.join(path, ".git")
+        os.mkdir(controldir)
+        cls.init_bare(controldir)
+
+    @classmethod
+    def init_bare(cls, path, mkdir=True):
+        for d in [["objects"], 
+                  ["objects", "info"], 
+                  ["objects", "pack"],
+                  ["branches"],
+                  ["refs"],
+                  ["refs", "tags"],
+                  ["refs", "heads"],
+                  ["hooks"],
+                  ["info"]]:
+            os.mkdir(os.path.join(path, *d))
+        open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
+        open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
+        open(os.path.join(path, 'info', 'excludes'), 'w').write("")
+
+    create = init_bare
 

+ 9 - 9
dulwich/tests/__init__.py

@@ -23,15 +23,15 @@ import test_repository
 import test_pack
 
 def test_suite():
-  test_modules = [test_objects, test_repository, test_pack]
-  loader = unittest.TestLoader()
-  suite = unittest.TestSuite()
-  for mod in test_modules:
-    suite.addTest(loader.loadTestsFromModule(mod))
-  return suite
+    test_modules = [test_objects, test_repository, test_pack]
+    loader = unittest.TestLoader()
+    suite = unittest.TestSuite()
+    for mod in test_modules:
+        suite.addTest(loader.loadTestsFromModule(mod))
+    return suite
 
 if __name__ == '__main__':
-  suite = test_suite()
-  from unittest import TextTestRunner
-  TextTestRunner().run(suite)
+    suite = test_suite()
+    from unittest import TextTestRunner
+    TextTestRunner().run(suite)
 

+ 98 - 98
dulwich/tests/test_objects.py

@@ -33,101 +33,101 @@ tree_sha = '70c190eb48fa8bbb50ddc692a17b44cb781af7f6'
 tag_sha = '71033db03a03c6a36721efcf1968dd8f8e0cf023'
 
 class BlobReadTests(unittest.TestCase):
-  """Test decompression of blobs"""
-
-  def get_sha_file(self, obj, base, sha):
-    return obj.from_file(os.path.join(os.path.dirname(__file__),
-                                      'data', base, sha))
-
-  def get_blob(self, sha):
-    """Return the blob named sha from the test data dir"""
-    return self.get_sha_file(Blob, 'blobs', sha)
-
-  def get_tree(self, sha):
-    return self.get_sha_file(Tree, 'trees', sha)
-
-  def get_tag(self, sha):
-    return self.get_sha_file(Tag, 'tags', sha)
-
-  def commit(self, sha):
-    return self.get_sha_file(Commit, 'commits', sha)
-
-  def test_decompress_simple_blob(self):
-    b = self.get_blob(a_sha)
-    self.assertEqual(b.data, 'test 1\n')
-    self.assertEqual(b.sha().hexdigest(), a_sha)
-
-  def test_parse_empty_blob_object(self):
-    sha = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
-    b = self.get_blob(sha)
-    self.assertEqual(b.data, '')
-    self.assertEqual(b.sha().hexdigest(), sha)
-
-  def test_create_blob_from_string(self):
-    string = 'test 2\n'
-    b = Blob.from_string(string)
-    self.assertEqual(b.data, string)
-    self.assertEqual(b.sha().hexdigest(), b_sha)
-
-  def test_parse_legacy_blob(self):
-    string = 'test 3\n'
-    b = self.get_blob(c_sha)
-    self.assertEqual(b.data, string)
-    self.assertEqual(b.sha().hexdigest(), c_sha)
-
-  def test_eq(self):
-    blob1 = self.get_blob(a_sha)
-    blob2 = self.get_blob(a_sha)
-    self.assertEqual(blob1, blob2)
-
-  def test_read_tree_from_file(self):
-    t = self.get_tree(tree_sha)
-    self.assertEqual(t.entries()[0], (33188, 'a', a_sha))
-    self.assertEqual(t.entries()[1], (33188, 'b', b_sha))
-
-  def test_read_tag_from_file(self):
-    t = self.get_tag(tag_sha)
-    self.assertEqual(t.object, (Commit, '51b668fd5bf7061b7d6fa525f88803e6cfadaa51'))
-    self.assertEqual(t.name,'signed')
-    self.assertEqual(t.tagger,'Ali Sabil <ali.sabil@gmail.com>')
-    self.assertEqual(t.tag_time, 1231203091)
-    self.assertEqual(t.message, 'This is a signed tag\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v1.4.9 (GNU/Linux)\n\niEYEABECAAYFAkliqx8ACgkQqSMmLy9u/kcx5ACfakZ9NnPl02tOyYP6pkBoEkU1\n5EcAn0UFgokaSvS371Ym/4W9iJj6vh3h\n=ql7y\n-----END PGP SIGNATURE-----\n')
-
-
-  def test_read_commit_from_file(self):
-    sha = '60dacdc733de308bb77bb76ce0fb0f9b44c9769e'
-    c = self.commit(sha)
-    self.assertEqual(c.tree, tree_sha)
-    self.assertEqual(c.parents, ['0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
-    self.assertEqual(c.author,
-        'James Westby <jw+debian@jameswestby.net>')
-    self.assertEqual(c.committer,
-        'James Westby <jw+debian@jameswestby.net>')
-    self.assertEqual(c.commit_time, 1174759230)
-    self.assertEqual(c.message, 'Test commit\n')
-
-  def test_read_commit_no_parents(self):
-    sha = '0d89f20333fbb1d2f3a94da77f4981373d8f4310'
-    c = self.commit(sha)
-    self.assertEqual(c.tree, '90182552c4a85a45ec2a835cadc3451bebdfe870')
-    self.assertEqual(c.parents, [])
-    self.assertEqual(c.author,
-        'James Westby <jw+debian@jameswestby.net>')
-    self.assertEqual(c.committer,
-        'James Westby <jw+debian@jameswestby.net>')
-    self.assertEqual(c.commit_time, 1174758034)
-    self.assertEqual(c.message, 'Test commit\n')
-
-  def test_read_commit_two_parents(self):
-    sha = '5dac377bdded4c9aeb8dff595f0faeebcc8498cc'
-    c = self.commit(sha)
-    self.assertEqual(c.tree, 'd80c186a03f423a81b39df39dc87fd269736ca86')
-    self.assertEqual(c.parents, ['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
-                                   '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'])
-    self.assertEqual(c.author,
-        'James Westby <jw+debian@jameswestby.net>')
-    self.assertEqual(c.committer,
-        'James Westby <jw+debian@jameswestby.net>')
-    self.assertEqual(c.commit_time, 1174773719)
-    self.assertEqual(c.message, 'Merge ../b\n')
-
+    """Test decompression of blobs"""
+  
+    def get_sha_file(self, obj, base, sha):
+        return obj.from_file(os.path.join(os.path.dirname(__file__),
+                                          'data', base, sha))
+  
+    def get_blob(self, sha):
+        """Return the blob named sha from the test data dir"""
+        return self.get_sha_file(Blob, 'blobs', sha)
+  
+    def get_tree(self, sha):
+        return self.get_sha_file(Tree, 'trees', sha)
+  
+    def get_tag(self, sha):
+        return self.get_sha_file(Tag, 'tags', sha)
+  
+    def commit(self, sha):
+        return self.get_sha_file(Commit, 'commits', sha)
+  
+    def test_decompress_simple_blob(self):
+        b = self.get_blob(a_sha)
+        self.assertEqual(b.data, 'test 1\n')
+        self.assertEqual(b.sha().hexdigest(), a_sha)
+  
+    def test_parse_empty_blob_object(self):
+        sha = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
+        b = self.get_blob(sha)
+        self.assertEqual(b.data, '')
+        self.assertEqual(b.sha().hexdigest(), sha)
+  
+    def test_create_blob_from_string(self):
+        string = 'test 2\n'
+        b = Blob.from_string(string)
+        self.assertEqual(b.data, string)
+        self.assertEqual(b.sha().hexdigest(), b_sha)
+  
+    def test_parse_legacy_blob(self):
+        string = 'test 3\n'
+        b = self.get_blob(c_sha)
+        self.assertEqual(b.data, string)
+        self.assertEqual(b.sha().hexdigest(), c_sha)
+  
+    def test_eq(self):
+        blob1 = self.get_blob(a_sha)
+        blob2 = self.get_blob(a_sha)
+        self.assertEqual(blob1, blob2)
+  
+    def test_read_tree_from_file(self):
+        t = self.get_tree(tree_sha)
+        self.assertEqual(t.entries()[0], (33188, 'a', a_sha))
+        self.assertEqual(t.entries()[1], (33188, 'b', b_sha))
+  
+    def test_read_tag_from_file(self):
+        t = self.get_tag(tag_sha)
+        self.assertEqual(t.object, (Commit, '51b668fd5bf7061b7d6fa525f88803e6cfadaa51'))
+        self.assertEqual(t.name,'signed')
+        self.assertEqual(t.tagger,'Ali Sabil <ali.sabil@gmail.com>')
+        self.assertEqual(t.tag_time, 1231203091)
+        self.assertEqual(t.message, 'This is a signed tag\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v1.4.9 (GNU/Linux)\n\niEYEABECAAYFAkliqx8ACgkQqSMmLy9u/kcx5ACfakZ9NnPl02tOyYP6pkBoEkU1\n5EcAn0UFgokaSvS371Ym/4W9iJj6vh3h\n=ql7y\n-----END PGP SIGNATURE-----\n')
+  
+  
+    def test_read_commit_from_file(self):
+        sha = '60dacdc733de308bb77bb76ce0fb0f9b44c9769e'
+        c = self.commit(sha)
+        self.assertEqual(c.tree, tree_sha)
+        self.assertEqual(c.parents, ['0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
+        self.assertEqual(c.author,
+            'James Westby <jw+debian@jameswestby.net>')
+        self.assertEqual(c.committer,
+            'James Westby <jw+debian@jameswestby.net>')
+        self.assertEqual(c.commit_time, 1174759230)
+        self.assertEqual(c.message, 'Test commit\n')
+  
+    def test_read_commit_no_parents(self):
+        sha = '0d89f20333fbb1d2f3a94da77f4981373d8f4310'
+        c = self.commit(sha)
+        self.assertEqual(c.tree, '90182552c4a85a45ec2a835cadc3451bebdfe870')
+        self.assertEqual(c.parents, [])
+        self.assertEqual(c.author,
+            'James Westby <jw+debian@jameswestby.net>')
+        self.assertEqual(c.committer,
+            'James Westby <jw+debian@jameswestby.net>')
+        self.assertEqual(c.commit_time, 1174758034)
+        self.assertEqual(c.message, 'Test commit\n')
+  
+    def test_read_commit_two_parents(self):
+        sha = '5dac377bdded4c9aeb8dff595f0faeebcc8498cc'
+        c = self.commit(sha)
+        self.assertEqual(c.tree, 'd80c186a03f423a81b39df39dc87fd269736ca86')
+        self.assertEqual(c.parents, ['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
+                                       '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'])
+        self.assertEqual(c.author,
+            'James Westby <jw+debian@jameswestby.net>')
+        self.assertEqual(c.committer,
+            'James Westby <jw+debian@jameswestby.net>')
+        self.assertEqual(c.commit_time, 1174773719)
+        self.assertEqual(c.message, 'Merge ../b\n')
+  

+ 102 - 104
dulwich/tests/test_pack.py

@@ -44,118 +44,116 @@ tree_sha = 'b2a2766a2879c209ab1176e7e778b81ae422eeaa'
 commit_sha = 'f18faa16531ac570a3fdc8c7ca16682548dafd12'
 
 class PackTests(unittest.TestCase):
-  """Base class for testing packs"""
-
-  datadir = os.path.join(os.path.dirname(__file__), 'data/packs')
-
-  def get_pack_index(self, sha):
-    """Returns a PackIndex from the datadir with the given sha"""
-    return PackIndex(os.path.join(self.datadir, 'pack-%s.idx' % sha))
-
-  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))
-
-  def get_pack(self, sha):
-    return Pack(os.path.join(self.datadir, 'pack-%s' % sha))
+    """Base class for testing packs"""
+  
+    datadir = os.path.join(os.path.dirname(__file__), 'data/packs')
+  
+    def get_pack_index(self, sha):
+        """Returns a PackIndex from the datadir with the given sha"""
+        return PackIndex(os.path.join(self.datadir, 'pack-%s.idx' % sha))
+  
+    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))
+  
+    def get_pack(self, sha):
+        return Pack(os.path.join(self.datadir, 'pack-%s' % sha))
 
 
 class PackIndexTests(PackTests):
-  """Class that tests the index of packfiles"""
-
-  def test_object_index(self):
-    """Tests that the correct object offset is returned from the index."""
-    p = self.get_pack_index(pack1_sha)
-    self.assertEqual(p.object_index(pack1_sha), None)
-    self.assertEqual(p.object_index(a_sha), 178)
-    self.assertEqual(p.object_index(tree_sha), 138)
-    self.assertEqual(p.object_index(commit_sha), 12)
-
-  def test_index_len(self):
-    p = self.get_pack_index(pack1_sha)
-    self.assertEquals(3, len(p))
-
-  def test_get_stored_checksum(self):
-    p = self.get_pack_index(pack1_sha)
-    self.assertEquals("\xf2\x84\x8e*\xd1o2\x9a\xe1\xc9.;\x95\xe9\x18\x88\xda\xa5\xbd\x01", str(p.get_stored_checksums()[1]))
-    self.assertEquals( 'r\x19\x80\xe8f\xaf\x9a_\x93\xadgAD\xe1E\x9b\x8b\xa3\xe7\xb7' , str(p.get_stored_checksums()[0]))
-
-  def test_index_check(self):
-    p = self.get_pack_index(pack1_sha)
-    self.assertEquals(True, p.check())
-
-
-  def test_iterentries(self):
-    p = self.get_pack_index(pack1_sha)
-    self.assertEquals([('og\x0c\x0f\xb5?\x94cv\x0br\x95\xfb\xb8\x14\xe9e\xfb \xc8', 178, None), ('\xb2\xa2vj(y\xc2\t\xab\x11v\xe7\xe7x\xb8\x1a\xe4"\xee\xaa', 138, None), ('\xf1\x8f\xaa\x16S\x1a\xc5p\xa3\xfd\xc8\xc7\xca\x16h%H\xda\xfd\x12', 12, None)], list(p.iterentries()))
-
-  def test_iter(self):
-    p = self.get_pack_index(pack1_sha)
-    self.assertEquals(set([tree_sha, commit_sha, a_sha]), set(p))
-
+    """Class that tests the index of packfiles"""
+  
+    def test_object_index(self):
+        """Tests that the correct object offset is returned from the index."""
+        p = self.get_pack_index(pack1_sha)
+        self.assertEqual(p.object_index(pack1_sha), None)
+        self.assertEqual(p.object_index(a_sha), 178)
+        self.assertEqual(p.object_index(tree_sha), 138)
+        self.assertEqual(p.object_index(commit_sha), 12)
+  
+    def test_index_len(self):
+        p = self.get_pack_index(pack1_sha)
+        self.assertEquals(3, len(p))
+  
+    def test_get_stored_checksum(self):
+        p = self.get_pack_index(pack1_sha)
+        self.assertEquals("\xf2\x84\x8e*\xd1o2\x9a\xe1\xc9.;\x95\xe9\x18\x88\xda\xa5\xbd\x01", str(p.get_stored_checksums()[1]))
+        self.assertEquals( 'r\x19\x80\xe8f\xaf\x9a_\x93\xadgAD\xe1E\x9b\x8b\xa3\xe7\xb7' , str(p.get_stored_checksums()[0]))
+  
+    def test_index_check(self):
+        p = self.get_pack_index(pack1_sha)
+        self.assertEquals(True, p.check())
+  
+    def test_iterentries(self):
+        p = self.get_pack_index(pack1_sha)
+        self.assertEquals([('og\x0c\x0f\xb5?\x94cv\x0br\x95\xfb\xb8\x14\xe9e\xfb \xc8', 178, None), ('\xb2\xa2vj(y\xc2\t\xab\x11v\xe7\xe7x\xb8\x1a\xe4"\xee\xaa', 138, None), ('\xf1\x8f\xaa\x16S\x1a\xc5p\xa3\xfd\xc8\xc7\xca\x16h%H\xda\xfd\x12', 12, None)], list(p.iterentries()))
+  
+    def test_iter(self):
+        p = self.get_pack_index(pack1_sha)
+        self.assertEquals(set([tree_sha, commit_sha, a_sha]), set(p))
+  
 
 class TestPackDeltas(unittest.TestCase):
-
-  test_string1 = "The answer was flailing in the wind"
-  test_string2 = "The answer was falling down the pipe"
-  test_string3 = "zzzzz"
-
-  test_string_empty = ""
-  test_string_big = "Z" * 8192
-
-  def _test_roundtrip(self, base, target):
-    self.assertEquals(target,
-      apply_delta(base, create_delta(base, target)))
-
-  def test_nochange(self):
-    self._test_roundtrip(self.test_string1, self.test_string1)
-
-  def test_change(self):
-    self._test_roundtrip(self.test_string1, self.test_string2)
-
-  def test_rewrite(self):
-    self._test_roundtrip(self.test_string1, self.test_string3)
-
-  def test_overflow(self):
-    self._test_roundtrip(self.test_string_empty, self.test_string_big)
+  
+    test_string1 = "The answer was flailing in the wind"
+    test_string2 = "The answer was falling down the pipe"
+    test_string3 = "zzzzz"
+  
+    test_string_empty = ""
+    test_string_big = "Z" * 8192
+  
+    def _test_roundtrip(self, base, target):
+        self.assertEquals(target,
+            apply_delta(base, create_delta(base, target)))
+  
+    def test_nochange(self):
+        self._test_roundtrip(self.test_string1, self.test_string1)
+  
+    def test_change(self):
+        self._test_roundtrip(self.test_string1, self.test_string2)
+  
+    def test_rewrite(self):
+        self._test_roundtrip(self.test_string1, self.test_string3)
+  
+    def test_overflow(self):
+        self._test_roundtrip(self.test_string_empty, self.test_string_big)
 
 
 class TestPackData(PackTests):
-  """Tests getting the data from the packfile."""
-
-  def test_create_pack(self):
-    p = self.get_pack_data(pack1_sha)
-
-  def test_pack_len(self):
-    p = self.get_pack_data(pack1_sha)
-    self.assertEquals(3, len(p))
-
-  def test_index_check(self):
-    p = self.get_pack_data(pack1_sha)
-    self.assertEquals(True, p.check())
-
-  def test_iterobjects(self):
-    p = self.get_pack_data(pack1_sha)
-    self.assertEquals([(12, 1, 'tree b2a2766a2879c209ab1176e7e778b81ae422eeaa\nauthor James Westby <jw+debian@jameswestby.net> 1174945067 +0100\ncommitter James Westby <jw+debian@jameswestby.net> 1174945067 +0100\n\nTest commit\n'), (138, 2, '100644 a\x00og\x0c\x0f\xb5?\x94cv\x0br\x95\xfb\xb8\x14\xe9e\xfb \xc8'), (178, 3, 'test 1\n')], list(p.iterobjects()))
-
-  def test_iterentries(self):
-    p = self.get_pack_data(pack1_sha)
-    self.assertEquals(set([('og\x0c\x0f\xb5?\x94cv\x0br\x95\xfb\xb8\x14\xe9e\xfb \xc8', 178, -1718046665), ('\xb2\xa2vj(y\xc2\t\xab\x11v\xe7\xe7x\xb8\x1a\xe4"\xee\xaa', 138, -901046474), ('\xf1\x8f\xaa\x16S\x1a\xc5p\xa3\xfd\xc8\xc7\xca\x16h%H\xda\xfd\x12', 12, 1185722901)]), set(p.iterentries()))
-
-  def test_create_index_v1(self):
-    p = self.get_pack_data(pack1_sha)
-    p.create_index_v1("v1test.idx")
-    idx1 = PackIndex("v1test.idx")
-    idx2 = self.get_pack_index(pack1_sha)
-    self.assertEquals(idx1, idx2)
-
-  def test_create_index_v2(self):
-    p = self.get_pack_data(pack1_sha)
-    p.create_index_v2("v2test.idx")
-    idx1 = PackIndex("v2test.idx")
-    idx2 = self.get_pack_index(pack1_sha)
-    self.assertEquals(idx1, idx2)
-
+    """Tests getting the data from the packfile."""
+  
+    def test_create_pack(self):
+        p = self.get_pack_data(pack1_sha)
+  
+    def test_pack_len(self):
+        p = self.get_pack_data(pack1_sha)
+        self.assertEquals(3, len(p))
+  
+    def test_index_check(self):
+        p = self.get_pack_data(pack1_sha)
+        self.assertEquals(True, p.check())
+  
+    def test_iterobjects(self):
+        p = self.get_pack_data(pack1_sha)
+        self.assertEquals([(12, 1, 'tree b2a2766a2879c209ab1176e7e778b81ae422eeaa\nauthor James Westby <jw+debian@jameswestby.net> 1174945067 +0100\ncommitter James Westby <jw+debian@jameswestby.net> 1174945067 +0100\n\nTest commit\n'), (138, 2, '100644 a\x00og\x0c\x0f\xb5?\x94cv\x0br\x95\xfb\xb8\x14\xe9e\xfb \xc8'), (178, 3, 'test 1\n')], list(p.iterobjects()))
+  
+    def test_iterentries(self):
+        p = self.get_pack_data(pack1_sha)
+        self.assertEquals(set([('og\x0c\x0f\xb5?\x94cv\x0br\x95\xfb\xb8\x14\xe9e\xfb \xc8', 178, -1718046665), ('\xb2\xa2vj(y\xc2\t\xab\x11v\xe7\xe7x\xb8\x1a\xe4"\xee\xaa', 138, -901046474), ('\xf1\x8f\xaa\x16S\x1a\xc5p\xa3\xfd\xc8\xc7\xca\x16h%H\xda\xfd\x12', 12, 1185722901)]), set(p.iterentries()))
+  
+    def test_create_index_v1(self):
+        p = self.get_pack_data(pack1_sha)
+        p.create_index_v1("v1test.idx")
+        idx1 = PackIndex("v1test.idx")
+        idx2 = self.get_pack_index(pack1_sha)
+        self.assertEquals(idx1, idx2)
+  
+    def test_create_index_v2(self):
+        p = self.get_pack_data(pack1_sha)
+        p.create_index_v2("v2test.idx")
+        idx1 = PackIndex("v2test.idx")
+        idx2 = self.get_pack_index(pack1_sha)
+        self.assertEquals(idx1, idx2)
 
 
 class TestPack(PackTests):

+ 105 - 105
dulwich/tests/test_repository.py

@@ -26,108 +26,108 @@ from dulwich.repo import Repo
 missing_sha = 'b91fa4d900g17e99b433218e988c4eb4a3e9a097'
 
 class RepositoryTests(unittest.TestCase):
-
-  def open_repo(self, name):
-    return Repo(os.path.join(os.path.dirname(__file__),
-                      'data/repos', name, '.git'))
-
-  def test_simple_props(self):
-    r = self.open_repo('a')
-    basedir = os.path.join(os.path.dirname(__file__), 'data/repos/a/.git')
-    self.assertEqual(r.controldir(), basedir)
-    self.assertEqual(r.object_dir(), os.path.join(basedir, 'objects'))
-
-  def test_ref(self):
-    r = self.open_repo('a')
-    self.assertEqual(r.ref('master'),
-                     'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
-
-  def test_get_refs(self):
-    r = self.open_repo('a')
-    self.assertEquals({
-        'HEAD': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097', 
-        'refs/heads/master': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097'
-        }, r.get_refs())
-
-  def test_head(self):
-    r = self.open_repo('a')
-    self.assertEqual(r.head(), 'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
-
-  def test_get_object(self):
-    r = self.open_repo('a')
-    obj = r.get_object(r.head())
-    self.assertEqual(obj._type, 'commit')
-
-  def test_get_object_non_existant(self):
-    r = self.open_repo('a')
-    self.assertRaises(KeyError, r.get_object, missing_sha)
-
-  def test_commit(self):
-    r = self.open_repo('a')
-    obj = r.commit(r.head())
-    self.assertEqual(obj._type, 'commit')
-
-  def test_commit_not_commit(self):
-    r = self.open_repo('a')
-    self.assertRaises(errors.NotCommitError,
-                      r.commit, '4f2e6529203aa6d44b5af6e3292c837ceda003f9')
-
-  def test_tree(self):
-    r = self.open_repo('a')
-    commit = r.commit(r.head())
-    tree = r.tree(commit.tree)
-    self.assertEqual(tree._type, 'tree')
-    self.assertEqual(tree.sha().hexdigest(), commit.tree)
-
-  def test_tree_not_tree(self):
-    r = self.open_repo('a')
-    self.assertRaises(errors.NotTreeError, r.tree, r.head())
-
-  def test_get_blob(self):
-    r = self.open_repo('a')
-    commit = r.commit(r.head())
-    tree = r.tree(commit.tree())
-    blob_sha = tree.entries()[0][2]
-    blob = r.get_blob(blob_sha)
-    self.assertEqual(blob._type, 'blob')
-    self.assertEqual(blob.sha().hexdigest(), blob_sha)
-
-  def test_get_blob(self):
-    r = self.open_repo('a')
-    self.assertRaises(errors.NotBlobError, r.get_blob, r.head())
-
-  def test_linear_history(self):
-    r = self.open_repo('a')
-    history = r.revision_history(r.head())
-    shas = [c.sha().hexdigest() for c in history]
-    self.assertEqual(shas, [r.head(),
-                            '2a72d929692c41d8554c07f6301757ba18a65d91'])
-
-  def test_merge_history(self):
-    r = self.open_repo('simple_merge')
-    history = r.revision_history(r.head())
-    shas = [c.sha().hexdigest() for c in history]
-    self.assertEqual(shas, ['5dac377bdded4c9aeb8dff595f0faeebcc8498cc',
-                            'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
-                            '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6',
-                            '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
-                            '0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
-
-  def test_revision_history_missing_commit(self):
-    r = self.open_repo('simple_merge')
-    self.assertRaises(errors.MissingCommitError, r.revision_history,
-                      missing_sha)
-
-  def test_out_of_order_merge(self):
-    """Test that revision history is ordered by date, not parent order."""
-    r = self.open_repo('ooo_merge')
-    history = r.revision_history(r.head())
-    shas = [c.sha().hexdigest() for c in history]
-    self.assertEqual(shas, ['7601d7f6231db6a57f7bbb79ee52e4d462fd44d1',
-                            'f507291b64138b875c28e03469025b1ea20bc614',
-                            'fb5b0425c7ce46959bec94d54b9a157645e114f5',
-                            'f9e39b120c68182a4ba35349f832d0e4e61f485c'])
-
-  def test_get_tags_empty(self):
-   r = self.open_repo('ooo_merge')
-   self.assertEquals({}, r.get_tags())
+  
+    def open_repo(self, name):
+        return Repo(os.path.join(os.path.dirname(__file__),
+                          'data/repos', name, '.git'))
+  
+    def test_simple_props(self):
+        r = self.open_repo('a')
+        basedir = os.path.join(os.path.dirname(__file__), 'data/repos/a/.git')
+        self.assertEqual(r.controldir(), basedir)
+        self.assertEqual(r.object_dir(), os.path.join(basedir, 'objects'))
+  
+    def test_ref(self):
+        r = self.open_repo('a')
+        self.assertEqual(r.ref('master'),
+                         'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
+  
+    def test_get_refs(self):
+        r = self.open_repo('a')
+        self.assertEquals({
+            'HEAD': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097', 
+            'refs/heads/master': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097'
+            }, r.get_refs())
+  
+    def test_head(self):
+        r = self.open_repo('a')
+        self.assertEqual(r.head(), 'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
+  
+    def test_get_object(self):
+        r = self.open_repo('a')
+        obj = r.get_object(r.head())
+        self.assertEqual(obj._type, 'commit')
+  
+    def test_get_object_non_existant(self):
+        r = self.open_repo('a')
+        self.assertRaises(KeyError, r.get_object, missing_sha)
+  
+    def test_commit(self):
+        r = self.open_repo('a')
+        obj = r.commit(r.head())
+        self.assertEqual(obj._type, 'commit')
+  
+    def test_commit_not_commit(self):
+        r = self.open_repo('a')
+        self.assertRaises(errors.NotCommitError,
+                          r.commit, '4f2e6529203aa6d44b5af6e3292c837ceda003f9')
+  
+    def test_tree(self):
+        r = self.open_repo('a')
+        commit = r.commit(r.head())
+        tree = r.tree(commit.tree)
+        self.assertEqual(tree._type, 'tree')
+        self.assertEqual(tree.sha().hexdigest(), commit.tree)
+  
+    def test_tree_not_tree(self):
+        r = self.open_repo('a')
+        self.assertRaises(errors.NotTreeError, r.tree, r.head())
+  
+    def test_get_blob(self):
+        r = self.open_repo('a')
+        commit = r.commit(r.head())
+        tree = r.tree(commit.tree())
+        blob_sha = tree.entries()[0][2]
+        blob = r.get_blob(blob_sha)
+        self.assertEqual(blob._type, 'blob')
+        self.assertEqual(blob.sha().hexdigest(), blob_sha)
+  
+    def test_get_blob(self):
+        r = self.open_repo('a')
+        self.assertRaises(errors.NotBlobError, r.get_blob, r.head())
+    
+    def test_linear_history(self):
+        r = self.open_repo('a')
+        history = r.revision_history(r.head())
+        shas = [c.sha().hexdigest() for c in history]
+        self.assertEqual(shas, [r.head(),
+                                '2a72d929692c41d8554c07f6301757ba18a65d91'])
+  
+    def test_merge_history(self):
+        r = self.open_repo('simple_merge')
+        history = r.revision_history(r.head())
+        shas = [c.sha().hexdigest() for c in history]
+        self.assertEqual(shas, ['5dac377bdded4c9aeb8dff595f0faeebcc8498cc',
+                                'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
+                                '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6',
+                                '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
+                                '0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
+  
+    def test_revision_history_missing_commit(self):
+        r = self.open_repo('simple_merge')
+        self.assertRaises(errors.MissingCommitError, r.revision_history,
+                          missing_sha)
+  
+    def test_out_of_order_merge(self):
+        """Test that revision history is ordered by date, not parent order."""
+        r = self.open_repo('ooo_merge')
+        history = r.revision_history(r.head())
+        shas = [c.sha().hexdigest() for c in history]
+        self.assertEqual(shas, ['7601d7f6231db6a57f7bbb79ee52e4d462fd44d1',
+                                'f507291b64138b875c28e03469025b1ea20bc614',
+                                'fb5b0425c7ce46959bec94d54b9a157645e114f5',
+                                'f9e39b120c68182a4ba35349f832d0e4e61f485c'])
+  
+    def test_get_tags_empty(self):
+        r = self.open_repo('ooo_merge')
+        self.assertEquals({}, r.get_tags())