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 def __init__(self, node):
30 self.size = self.n.size()
43 def read(self, count = -1):
45 count = self.size - self.ofs
46 buf = self.n.readbytes(self.ofs, count)
52 def __init__(self, parent, name, mode, hash):
57 self.ctime = self.mtime = self.atime = 0
61 return cmp(a.name or None, b.name or None)
64 return iter(self.subs())
68 return os.path.join(self.parent.fullname(), self.name)
76 if self._subs == None:
78 return sorted(self._subs.values())
81 if self._subs == None:
83 ret = self._subs.get(name)
85 raise NoSuchFile("no file %r in %r" % (name, self.name))
90 return self.parent.top()
94 def _lresolve(self, parts):
95 #log('_lresolve %r in %r\n' % (parts, self.name))
98 (first, rest) = (parts[0], parts[1:])
100 return self._lresolve(rest)
103 raise NoSuchFile("no parent dir for %r" % self.name)
104 return self.parent._lresolve(rest)
106 return self.sub(first)._lresolve(rest)
108 return self.sub(first)
110 def lresolve(self, path):
112 if path.startswith('/'):
115 parts = re.split(r'/+', path or '.')
118 #log('parts: %r %r\n' % (path, parts))
119 return start._lresolve(parts)
121 def resolve(self, path):
122 return self.lresolve(path).lresolve('')
125 if self._subs == None:
133 raise NotFile('%s is not a regular file' % self.name)
135 def readbytes(self, ofs, count):
136 raise NotFile('%s is not a regular file' % self.name)
138 def read(self, num = -1):
141 return self.readbytes(0, num)
146 return cp().join(self.hash.encode('hex'))
149 return FileReader(self)
152 # FIXME inefficient. If a file is chunked, we could just check
153 # the offset + size of the very last chunk.
154 return sum(len(blob) for blob in self._content())
156 def readbytes(self, ofs, count):
157 # FIXME inefficient. If a file is chunked, we could choose to
158 # read only the required chunks rather than joining the whole thing.
159 buf = ''.join(self._content())
160 return buf[ofs:ofs+count]
165 def __init__(self, parent, name, hash):
166 File.__init__(self, parent, name, 0120000, hash)
169 return self.read(1024)
171 def dereference(self):
174 raise TooManySymlinks('too many levels of symlinks: %r'
178 return self.parent.lresolve(self.readlink())
182 def _lresolve(self, parts):
183 return self.dereference()._lresolve(parts)
186 class FakeSymlink(Symlink):
187 def __init__(self, parent, name, toname):
188 Symlink.__init__(self, parent, name, EMPTY_SHA)
198 it = cp().get(self.hash.encode('hex'))
202 it = cp().get(self.hash.encode('hex') + ':')
204 assert(type == 'tree')
205 for (mode,mangled_name,sha) in git._treeparse(''.join(it)):
208 (name,bupmode) = git.demangle_name(mangled_name)
209 if bupmode == git.BUP_CHUNKED:
211 if stat.S_ISDIR(mode):
212 self._subs[name] = Dir(self, name, mode, sha)
213 elif stat.S_ISLNK(mode):
214 self._subs[name] = Symlink(self, name, sha)
216 self._subs[name] = File(self, name, mode, sha)
219 class CommitList(Node):
220 def __init__(self, parent, name, hash):
221 Node.__init__(self, parent, name, 040000, hash)
225 revs = list(git.rev_list(self.hash.encode('hex')))
226 for (date, commit) in revs:
227 l = time.localtime(date)
228 ls = time.strftime('%Y-%m-%d-%H%M%S', l)
229 commithex = '.' + commit.encode('hex')
230 n1 = Dir(self, commithex, 040000, commit)
231 n2 = FakeSymlink(self, ls, commithex)
232 n1.ctime = n1.mtime = n2.ctime = n2.mtime = date
233 self._subs[commithex] = n1
237 (date, commit) = latest
238 commithex = '.' + commit.encode('hex')
239 n2 = FakeSymlink(self, 'latest', commithex)
240 n2.ctime = n2.mtime = date
241 self._subs['latest'] = n2
245 def __init__(self, parent):
246 Node.__init__(self, parent, '/', 040000, EMPTY_SHA)
250 for (name,sha) in git.list_refs():
251 if name.startswith('refs/heads/'):
253 date = git.rev_get_date(sha.encode('hex'))
254 n1 = CommitList(self, name, sha)
255 n1.ctime = n1.mtime = date
256 self._subs[name] = n1