1 import os, re, stat, time
14 class NodeError(Exception):
16 class NoSuchFile(NodeError):
18 class NotDir(NodeError):
20 class NotFile(NodeError):
22 class TooManySymlinks(NodeError):
27 it = cp().get(hash.encode('hex'))
29 assert(type == 'tree')
30 return git._treeparse(''.join(it))
33 def _tree_decode(hash):
34 tree = [(int(name,16),stat.S_ISDIR(int(mode,8)),sha)
37 assert(tree == list(sorted(tree)))
42 return sum(len(b) for b in cp().join(hash.encode('hex')))
45 def _last_chunk_info(hash):
46 tree = _tree_decode(hash)
48 (ofs,isdir,sha) = tree[-1]
50 (subofs, sublen) = _last_chunk_info(sha)
51 return (ofs+subofs, sublen)
53 return (ofs, _chunk_len(sha))
56 def _total_size(hash):
57 (lastofs, lastsize) = _last_chunk_info(hash)
58 return lastofs + lastsize
61 def _chunkiter(hash, startofs):
63 tree = _tree_decode(hash)
65 # skip elements before startofs
66 for i in xrange(len(tree)):
67 if i+1 >= len(tree) or tree[i+1][0] > startofs:
71 # iterate through what's left
72 for i in xrange(first, len(tree)):
73 (ofs,isdir,sha) = tree[i]
74 skipmore = startofs-ofs
78 for b in _chunkiter(sha, skipmore):
81 yield ''.join(cp().join(sha.encode('hex')))[skipmore:]
85 def __init__(self, hash, isdir, startofs):
87 self.it = _chunkiter(hash, startofs)
91 self.blob = ''.join(cp().join(hash.encode('hex')))[startofs:]
96 while len(out) < size:
97 if self.it and not self.blob:
99 self.blob = self.it.next()
100 except StopIteration:
103 want = size - len(out)
104 out += self.blob[:want]
105 self.blob = self.blob[want:]
108 log('next(%d) returned %d\n' % (size, len(out)))
114 def __init__(self, hash, size, isdir):
132 def read(self, count = -1):
134 count = self.size - self.ofs
135 if not self.reader or self.reader.ofs != self.ofs:
136 self.reader = _ChunkReader(self.hash, self.isdir, self.ofs)
138 buf = self.reader.next(count)
141 raise # our offsets will be all screwed up otherwise
150 def __init__(self, parent, name, mode, hash):
155 self.ctime = self.mtime = self.atime = 0
159 return cmp(a.name or None, b.name or None)
162 return iter(self.subs())
166 return os.path.join(self.parent.fullname(), self.name)
174 if self._subs == None:
176 return sorted(self._subs.values())
179 if self._subs == None:
181 ret = self._subs.get(name)
183 raise NoSuchFile("no file %r in %r" % (name, self.name))
188 return self.parent.top()
192 def _lresolve(self, parts):
193 #log('_lresolve %r in %r\n' % (parts, self.name))
196 (first, rest) = (parts[0], parts[1:])
198 return self._lresolve(rest)
201 raise NoSuchFile("no parent dir for %r" % self.name)
202 return self.parent._lresolve(rest)
204 return self.sub(first)._lresolve(rest)
206 return self.sub(first)
208 # walk into a given sub-path of this node. If the last element is
209 # a symlink, leave it as a symlink, don't resolve it. (like lstat())
210 def lresolve(self, path):
214 if path.startswith('/'):
217 parts = re.split(r'/+', path or '.')
220 #log('parts: %r %r\n' % (path, parts))
221 return start._lresolve(parts)
223 # walk into the given sub-path of this node, and dereference it if it
225 def resolve(self, path = ''):
226 return self.lresolve(path).lresolve('.')
228 # like resolve(), but don't worry if the last symlink points at an
230 # (still returns an error if any intermediate nodes were invalid)
231 def try_resolve(self, path = ''):
232 n = self.lresolve(path)
240 if self._subs == None:
248 raise NotFile('%s is not a regular file' % self.name)
252 def __init__(self, parent, name, mode, hash, bupmode):
253 Node.__init__(self, parent, name, mode, hash)
254 self.bupmode = bupmode
255 self._cached_size = None
256 self._filereader = None
259 # You'd think FUSE might call this only once each time a file is
260 # opened, but no; it's really more of a refcount, and it's called
261 # once per read(). Thus, it's important to cache the filereader
262 # object here so we're not constantly re-seeking.
263 if not self._filereader:
264 self._filereader = _FileReader(self.hash, self.size(),
265 self.bupmode == git.BUP_CHUNKED)
266 self._filereader.seek(0)
267 return self._filereader
270 if self._cached_size == None:
271 log('<<<<File.size() is calculating...\n')
272 if self.bupmode == git.BUP_CHUNKED:
273 self._cached_size = _total_size(self.hash)
275 self._cached_size = _chunk_len(self.hash)
276 log('<<<<File.size() done.\n')
277 return self._cached_size
282 def __init__(self, parent, name, hash, bupmode):
283 File.__init__(self, parent, name, 0120000, hash, bupmode)
286 return len(self.readlink())
289 return ''.join(cp().join(self.hash.encode('hex')))
291 def dereference(self):
294 raise TooManySymlinks('too many levels of symlinks: %r'
298 return self.parent.lresolve(self.readlink())
300 raise NoSuchFile("%s: broken symlink to %r"
301 % (self.fullname(), self.readlink()))
305 def _lresolve(self, parts):
306 return self.dereference()._lresolve(parts)
309 class FakeSymlink(Symlink):
310 def __init__(self, parent, name, toname):
311 Symlink.__init__(self, parent, name, EMPTY_SHA, git.BUP_NORMAL)
321 it = cp().get(self.hash.encode('hex'))
325 it = cp().get(self.hash.encode('hex') + ':')
327 assert(type == 'tree')
328 for (mode,mangled_name,sha) in git._treeparse(''.join(it)):
331 (name,bupmode) = git.demangle_name(mangled_name)
332 if bupmode == git.BUP_CHUNKED:
334 if stat.S_ISDIR(mode):
335 self._subs[name] = Dir(self, name, mode, sha)
336 elif stat.S_ISLNK(mode):
337 self._subs[name] = Symlink(self, name, sha, bupmode)
339 self._subs[name] = File(self, name, mode, sha, bupmode)
342 class CommitList(Node):
343 def __init__(self, parent, name, hash):
344 Node.__init__(self, parent, name, 040000, hash)
348 revs = list(git.rev_list(self.hash.encode('hex')))
349 for (date, commit) in revs:
350 l = time.localtime(date)
351 ls = time.strftime('%Y-%m-%d-%H%M%S', l)
352 commithex = '.' + commit.encode('hex')
353 n1 = Dir(self, commithex, 040000, commit)
354 n2 = FakeSymlink(self, ls, commithex)
355 n1.ctime = n1.mtime = n2.ctime = n2.mtime = date
356 self._subs[commithex] = n1
360 (date, commit) = latest
361 commithex = '.' + commit.encode('hex')
362 n2 = FakeSymlink(self, 'latest', commithex)
363 n2.ctime = n2.mtime = date
364 self._subs['latest'] = n2
368 def __init__(self, parent):
369 Node.__init__(self, parent, '/', 040000, EMPTY_SHA)
373 for (name,sha) in git.list_refs():
374 if name.startswith('refs/heads/'):
376 date = git.rev_get_date(sha.encode('hex'))
377 n1 = CommitList(self, name, sha)
378 n1.ctime = n1.mtime = date
379 self._subs[name] = n1