]> arthur.barton.de Git - bup.git/blob - lib/bup/vfs.py
vfs: supply ctime/mtime for the root of each commit.
[bup.git] / lib / bup / vfs.py
1 import os, re, stat, time
2 from bup import git
3 from helpers import *
4
5 EMPTY_SHA='\0'*20
6
7 _cp = None
8 def cp():
9     global _cp
10     if not _cp:
11         _cp = git.CatPipe()
12     return _cp
13
14 class NodeError(Exception):
15     pass
16 class NoSuchFile(NodeError):
17     pass
18 class NotDir(NodeError):
19     pass
20 class NotFile(NodeError):
21     pass
22 class TooManySymlinks(NodeError):
23     pass
24
25
26 class FileReader:
27     def __init__(self, node):
28         self.n = node
29         self.ofs = 0
30         self.size = self.n.size()
31
32     def seek(self, ofs):
33         if ofs > self.size:
34             self.ofs = self.size
35         elif ofs < 0:
36             self.ofs = 0
37         else:
38             self.ofs = ofs
39
40     def tell(self):
41         return self.ofs
42
43     def read(self, count = -1):
44         if count < 0:
45             count = self.size - self.ofs
46         buf = self.n.readbytes(self.ofs, count)
47         self.ofs += len(buf)
48         return buf
49
50
51 class Node:
52     def __init__(self, parent, name, mode, hash):
53         self.parent = parent
54         self.name = name
55         self.mode = mode
56         self.hash = hash
57         self.ctime = self.mtime = self.atime = 0
58         self._subs = None
59         
60     def __cmp__(a, b):
61         return cmp(a.name or None, b.name or None)
62     
63     def __iter__(self):
64         return iter(self.subs())
65     
66     def fullname(self):
67         if self.parent:
68             return os.path.join(self.parent.fullname(), self.name)
69         else:
70             return self.name
71     
72     def _mksubs(self):
73         self._subs = {}
74         
75     def subs(self):
76         if self._subs == None:
77             self._mksubs()
78         return sorted(self._subs.values())
79         
80     def sub(self, name):
81         if self._subs == None:
82             self._mksubs()
83         ret = self._subs.get(name)
84         if not ret:
85             raise NoSuchFile("no file %r in %r" % (name, self.name))
86         return ret
87
88     def top(self):
89         if self.parent:
90             return self.parent.top()
91         else:
92             return self
93
94     def _lresolve(self, parts):
95         #log('_lresolve %r in %r\n' % (parts, self.name))
96         if not parts:
97             return self
98         (first, rest) = (parts[0], parts[1:])
99         if first == '.':
100             return self._lresolve(rest)
101         elif first == '..':
102             if not self.parent:
103                 raise NoSuchFile("no parent dir for %r" % self.name)
104             return self.parent._lresolve(rest)
105         elif rest:
106             return self.sub(first)._lresolve(rest)
107         else:
108             return self.sub(first)
109
110     def lresolve(self, path):
111         start = self
112         if path.startswith('/'):
113             start = self.top()
114             path = path[1:]
115         parts = re.split(r'/+', path or '.')
116         if not parts[-1]:
117             parts[-1] = '.'
118         #log('parts: %r %r\n' % (path, parts))
119         return start._lresolve(parts)
120
121     def resolve(self, path):
122         return self.lresolve(path).lresolve('')
123     
124     def nlinks(self):
125         if self._subs == None:
126             self._mksubs()
127         return 1
128
129     def size(self):
130         return 0
131
132     def open(self):
133         raise NotFile('%s is not a regular file' % self.name)
134     
135     def readbytes(self, ofs, count):
136         raise NotFile('%s is not a regular file' % self.name)
137     
138     def read(self, num = -1):
139         if num < 0:
140             num = self.size()
141         return self.readbytes(0, num)
142     
143     
144 class File(Node):
145     def _content(self):
146         return cp().join(self.hash.encode('hex'))
147
148     def open(self):
149         return FileReader(self)
150     
151     def size(self):
152         # FIXME inefficient
153         return sum(len(blob) for blob in self._content())
154     
155     def readbytes(self, ofs, count):
156         # FIXME inefficient
157         buf = ''.join(self._content())
158         return buf[ofs:ofs+count]
159     
160
161 _symrefs = 0
162 class Symlink(File):
163     def __init__(self, parent, name, hash):
164         File.__init__(self, parent, name, 0120000, hash)
165
166     def readlink(self):
167         return self.read(1024)
168
169     def dereference(self):
170         global _symrefs
171         if _symrefs > 100:
172             raise TooManySymlinks('too many levels of symlinks: %r'
173                                   % self.fullname())
174         _symrefs += 1
175         try:
176             return self.parent.lresolve(self.readlink())
177         finally:
178             _symrefs -= 1
179
180     def _lresolve(self, parts):
181         return self.dereference()._lresolve(parts)
182     
183
184 class FakeSymlink(Symlink):
185     def __init__(self, parent, name, toname):
186         Symlink.__init__(self, parent, name, EMPTY_SHA)
187         self.toname = toname
188         
189     def _content(self):
190         return self.toname
191     
192
193 class Dir(Node):
194     def _mksubs(self):
195         self._subs = {}
196         it = cp().get(self.hash.encode('hex'))
197         type = it.next()
198         if type == 'commit':
199             del it
200             it = cp().get(self.hash.encode('hex') + ':')
201             type = it.next()
202         assert(type == 'tree')
203         for (mode,name,sha) in git._treeparse(''.join(it)):
204             mode = int(mode, 8)
205             if stat.S_ISDIR(mode):
206                 self._subs[name] = Dir(self, name, mode, sha)
207             elif stat.S_ISLNK(mode):
208                 self._subs[name] = Symlink(self, name, sha)
209             else:
210                 self._subs[name] = File(self, name, mode, sha)
211                 
212
213 class CommitList(Node):
214     def __init__(self, parent, name, hash):
215         Node.__init__(self, parent, name, 040000, hash)
216         
217     def _mksubs(self):
218         self._subs = {}
219         revs = list(git.rev_list(self.hash.encode('hex')))
220         for (date, commit) in revs:
221             l = time.localtime(date)
222             ls = time.strftime('%Y-%m-%d-%H%M%S', l)
223             commithex = '.' + commit.encode('hex')
224             n1 = Dir(self, commithex, 040000, commit)
225             n2 = FakeSymlink(self, ls, commithex)
226             n1.ctime = n1.mtime = n2.ctime = n2.mtime = date
227             self._subs[commithex] = n1
228             self._subs[ls] = n2
229             latest = max(revs)
230         if latest:
231             (date, commit) = latest
232             commithex = '.' + commit.encode('hex')
233             n2 = FakeSymlink(self, 'latest', commithex)
234             n2.ctime = n2.mtime = date
235             self._subs['latest'] = n2
236
237     
238 class RefList(Node):
239     def __init__(self, parent):
240         Node.__init__(self, parent, '/', 040000, EMPTY_SHA)
241         
242     def _mksubs(self):
243         self._subs = {}
244         for (name,sha) in git.list_refs():
245             if name.startswith('refs/heads/'):
246                 name = name[11:]
247                 date = git.rev_get_date(sha.encode('hex'))
248                 n1 = CommitList(self, name, sha)
249                 n1.ctime = n1.mtime = date
250                 self._subs[name] = n1
251         
252