1 import os, stat, time, struct, tempfile, mmap
6 INDEX_HDR = 'BUPI\0\0\0\1'
7 INDEX_SIG = '!IIIIIQII20sH'
8 ENTLEN = struct.calcsize(INDEX_SIG)
13 class Error(Exception):
18 def __init__(self, name, m, ofs, tstart):
23 (self.dev, self.ctime, self.mtime, self.uid, self.gid,
24 self.size, self.mode, self.gitmode, self.sha,
25 self.flags) = struct.unpack(INDEX_SIG, str(buffer(m, ofs, ENTLEN)))
28 return ("(%s,0x%04x,%d,%d,%d,%d,%d,0x%04x)"
29 % (self.name, self.dev,
30 self.ctime, self.mtime, self.uid, self.gid,
31 self.size, self.flags))
34 return struct.pack(INDEX_SIG,
35 self.dev, self.ctime, self.mtime,
36 self.uid, self.gid, self.size, self.mode,
37 self.gitmode, self.sha, self.flags)
40 self._m[self._ofs:self._ofs+ENTLEN] = self.packed()
42 def from_stat(self, st):
43 old = (self.dev, self.ctime, self.mtime,
44 self.uid, self.gid, self.size, self.flags & IX_EXISTS)
45 new = (st.st_dev, int(st.st_ctime), int(st.st_mtime),
46 st.st_uid, st.st_gid, st.st_size, IX_EXISTS)
48 self.ctime = int(st.st_ctime)
49 self.mtime = int(st.st_mtime)
52 self.size = st.st_size
53 self.mode = st.st_mode
54 self.flags |= IX_EXISTS
55 if int(st.st_ctime) >= self.tstart or old != new:
56 self.flags &= ~IX_HASHVALID
61 def validate(self, sha):
64 self.flags |= IX_HASHVALID
67 return cmp(a.name, b.name)
71 def __init__(self, filename):
72 self.filename = filename
77 f = open(filename, 'r+')
79 if e.errno == errno.ENOENT:
84 b = f.read(len(INDEX_HDR))
86 raise Error('%s: header: expected %r, got %r'
87 % (filename, INDEX_HDR, b))
88 st = os.fstat(f.fileno())
90 self.m = mmap.mmap(f.fileno(), 0,
92 mmap.PROT_READ|mmap.PROT_WRITE)
93 f.close() # map will persist beyond file close
100 tstart = int(time.time())
102 while ofs < len(self.m):
103 eon = self.m.find('\0', ofs)
105 yield Entry(buffer(self.m, ofs, eon-ofs),
106 self.m, eon+1, tstart = tstart)
107 ofs = eon + 1 + ENTLEN
117 self.writable = False
119 def filter(self, prefixes):
120 #log("filtering %r\n" % prefixes)
121 paths = reduce_paths(prefixes)
122 #log("filtering %r\n" % paths)
124 (rpin, pin) = pi.next()
126 #log('checking %r vs %r\n' % (ent.name, rpin))
127 while ent.name < rpin:
129 (rpin, pin) = pi.next()
130 except StopIteration:
131 return # no more files can possibly match
132 if not ent.name.startswith(rpin):
133 continue # not interested
135 name = pin + ent.name[len(rpin):]
139 # Read all the iters in order; when more than one iter has the same entry,
140 # the *later* iter in the list wins. (ie. more recent iter entries replace
142 def _last_writer_wins_iter(iters):
147 l.append([it.next(), it])
148 except StopIteration:
150 del iters # to avoid accidents
155 for (i,(v,it)) in enumerate(l):
156 #log('(%d) considering %d: %r\n' % (len(l), i, v))
165 l[i][0] = l[i][1].next()
166 except StopIteration:
172 def __init__(self, filename):
177 self.filename = filename = realpath(filename)
178 (dir,name) = os.path.split(filename)
179 (ffd,self.tmpname) = tempfile.mkstemp('.tmp', filename, dir)
180 self.f = os.fdopen(ffd, 'wb', 65536)
181 self.f.write(INDEX_HDR)
191 os.unlink(self.tmpname)
199 os.rename(self.tmpname, self.filename)
201 if os.path.exists(self.filename):
202 os.unlink(self.filename)
203 os.rename(self.tmpname, self.filename)
205 def _write(self, data):
209 def add(self, name, st, hashgen=None):
210 #log('ADDING %r\n' % name)
212 assert(cmp(self.lastfile, name) > 0) # reverse order only
217 (gitmode, sha) = hashgen(name)
219 flags |= IX_HASHVALID
221 (gitmode, sha) = (0, EMPTY_SHA)
222 data = name + '\0' + \
223 struct.pack(INDEX_SIG, st.st_dev, int(st.st_ctime),
224 int(st.st_mtime), st.st_uid, st.st_gid,
225 st.st_size, st.st_mode, gitmode, sha, flags)
228 def add_ixentry(self, e):
229 if self.lastfile and self.lastfile <= e.name:
230 raise Error('%r must come before %r'
231 % (e.name, self.lastfile))
232 self.lastfile = e.name
233 data = e.name + '\0' + e.packed()
236 def new_reader(self):
238 return Reader(self.tmpname)
241 # like os.path.realpath, but doesn't follow a symlink for the last element.
242 # (ie. if 'p' itself is itself a symlink, this one won't follow it)
248 if st and stat.S_ISLNK(st.st_mode):
249 (dir, name) = os.path.split(p)
250 dir = os.path.realpath(dir)
251 out = os.path.join(dir, name)
253 out = os.path.realpath(p)
254 #log('realpathing:%r,%r\n' % (p, out))
258 def reduce_paths(paths):
263 if stat.S_ISDIR(st.st_mode):
266 xpaths.append((rp, p))
271 for (rp, p) in xpaths:
272 if prev and (prev == rp
273 or (prev.endswith('/') and rp.startswith(prev))):
274 continue # already superceded by previous path
275 paths.append((rp, p))
277 paths.sort(reverse=True)