+def _treeget(hash):
+ it = cp().get(hash.encode('hex'))
+ type = it.next()
+ assert(type == 'tree')
+ return git._treeparse(''.join(it))
+
+
+def _tree_decode(hash):
+ tree = [(int(name,16),stat.S_ISDIR(int(mode,8)),sha)
+ for (mode,name,sha)
+ in _treeget(hash)]
+ assert(tree == list(sorted(tree)))
+ return tree
+
+
+def _chunk_len(hash):
+ return sum(len(b) for b in cp().join(hash.encode('hex')))
+
+
+def _last_chunk_info(hash):
+ tree = _tree_decode(hash)
+ assert(tree)
+ (ofs,isdir,sha) = tree[-1]
+ if isdir:
+ (subofs, sublen) = _last_chunk_info(sha)
+ return (ofs+subofs, sublen)
+ else:
+ return (ofs, _chunk_len(sha))
+
+
+def _total_size(hash):
+ (lastofs, lastsize) = _last_chunk_info(hash)
+ return lastofs + lastsize
+
+
+def _chunkiter(hash, startofs):
+ assert(startofs >= 0)
+ tree = _tree_decode(hash)
+
+ # skip elements before startofs
+ for i in xrange(len(tree)):
+ if i+1 >= len(tree) or tree[i+1][0] > startofs:
+ break
+ first = i
+
+ # iterate through what's left
+ for i in xrange(first, len(tree)):
+ (ofs,isdir,sha) = tree[i]
+ skipmore = startofs-ofs
+ if skipmore < 0:
+ skipmore = 0
+ if isdir:
+ for b in _chunkiter(sha, skipmore):
+ yield b
+ else:
+ yield ''.join(cp().join(sha.encode('hex')))[skipmore:]
+
+
+class _ChunkReader:
+ def __init__(self, hash, isdir, startofs):
+ if isdir:
+ self.it = _chunkiter(hash, startofs)
+ self.blob = None
+ else:
+ self.it = None
+ self.blob = cp().get(hash.encode('hex'))
+
+ def next(self, size):
+ out = ''
+ while len(out) < size:
+ if self.it and not self.blob:
+ try:
+ self.blob = self.it.next()
+ except StopIteration:
+ self.it = None
+ if self.blob:
+ want = size - len(out)
+ out += self.blob[:want]
+ self.blob = self.blob[want:]
+ if not self.it:
+ break
+ log('next(%d) returned %d\n' % (size, len(out)))
+ return out
+
+
+class _FileReader:
+ def __init__(self, hash, size, isdir):
+ self.hash = hash