1 import os, errno, zlib, time, sha, subprocess, struct, mmap, stat
7 return os.path.join(os.environ.get('BUP_DIR', '.git'), sub)
11 def __init__(self, filename):
14 self.map = mmap.mmap(f.fileno(), 0,
15 mmap.MAP_SHARED, mmap.PROT_READ)
16 f.close() # map will persist beyond file close
17 assert(str(self.map[0:8]) == '\377tOc\0\0\0\2')
18 self.fanout = list(struct.unpack('!256I', buffer(self.map, 8, 256*4)))
19 self.fanout.append(0) # entry "-1"
20 nsha = self.fanout[255]
21 self.ofstable = buffer(self.map,
22 8 + 256*4 + nsha*20 + nsha*4,
24 self.ofs64table = buffer(self.map,
25 8 + 256*4 + nsha*20 + nsha*4 + nsha*4)
27 def _ofs_from_idx(self, idx):
28 ofs = struct.unpack('!I', buffer(self.ofstable, idx*4, 4))[0]
30 idx64 = ofs & 0x7fffffff
31 ofs = struct.unpack('!I', buffer(self.ofs64table, idx64*8, 8))[0]
34 def _idx_from_hash(self, hash):
35 assert(len(hash) == 20)
37 start = self.fanout[b1-1] # range -1..254
38 end = self.fanout[b1] # range 0..255
39 buf = buffer(self.map, 8 + 256*4, end*20)
42 mid = start + (end-start)/2
43 v = buffer(buf, mid*20, 20)
52 def find_offset(self, hash):
53 idx = self._idx_from_hash(hash)
55 return self._ofs_from_idx(idx)
58 def exists(self, hash):
59 return (self._idx_from_hash(hash) != None) and True or None
63 def __init__(self, dir):
66 for f in os.listdir(dir):
67 if f.endswith('.idx'):
68 self.packs.append(PackIndex(os.path.join(dir, f)))
70 def exists(self, hash):
73 for i in range(len(self.packs)):
76 # reorder so most recently used packs are searched first
77 self.packs = [p] + self.packs[:i] + self.packs[i+1:]
88 def calc_hash(type, content):
89 header = '%s %d\0' % (type, len(content))
95 def _shalist_sort_key(ent):
96 (mode, name, id) = ent
97 if stat.S_ISDIR(int(mode, 8)):
103 _typemap = dict(blob=3, tree=2, commit=1, tag=8)
108 self.objcache = MultiPackIndex(repodir('objects/pack'))
116 assert(not self.file)
117 self.objcache.zap_also()
118 self.filename = repodir('objects/bup%d' % os.getpid())
119 self.file = open(self.filename + '.pack', 'w+')
120 self.file.write('PACK\0\0\0\2\0\0\0\0')
122 def _write(self, bin, type, content):
131 szbits = (sz & 0x0f) | (_typemap[type]<<4)
134 if sz: szbits |= 0x80
141 z = zlib.compressobj(1)
142 f.write(z.compress(content))
146 self.binlist.append(bin)
149 def write(self, type, content):
150 return self._write(calc_hash(type, content), type, content)
152 def maybe_write(self, type, content):
153 bin = calc_hash(type, content)
154 if not self.objcache.exists(bin):
155 self._write(bin, type, content)
156 self.objcache.add(bin)
159 def new_blob(self, blob):
160 return self.maybe_write('blob', blob)
162 def new_tree(self, shalist):
163 shalist = sorted(shalist, key = _shalist_sort_key)
164 l = ['%s %s\0%s' % (mode,name,bin)
165 for (mode,name,bin) in shalist]
166 return self.maybe_write('tree', ''.join(l))
168 def _new_commit(self, tree, parent, author, adate, committer, cdate, msg):
170 if tree: l.append('tree %s' % tree.encode('hex'))
171 if parent: l.append('parent %s' % parent)
172 if author: l.append('author %s %s' % (author, _git_date(adate)))
173 if committer: l.append('committer %s %s' % (committer, _git_date(cdate)))
176 return self.maybe_write('commit', '\n'.join(l))
178 def new_commit(self, ref, tree, msg):
180 userline = '%s <%s@%s>' % (userfullname(), username(), hostname())
181 oldref = ref and _read_ref(ref) or None
182 commit = self._new_commit(tree, oldref,
183 userline, now, userline, now,
186 self.close() # UGLY: needed so _update_ref can see the new objects
187 _update_ref(ref, commit.encode('hex'), oldref)
195 os.unlink(self.filename + '.pack')
199 if not f: return None
202 # update object count
204 cp = struct.pack('!i', self.count)
208 # calculate the pack sha1sum
215 f.write(sum.digest())
219 p = subprocess.Popen(['git', 'index-pack', '-v',
220 self.filename + '.pack'],
221 preexec_fn = _gitenv,
222 stdout = subprocess.PIPE)
223 out = p.stdout.read().strip()
224 if p.wait() or not out:
225 raise Exception('git index-pack returned an error')
226 nameprefix = repodir('objects/pack/%s' % out)
227 os.rename(self.filename + '.pack', nameprefix + '.pack')
228 os.rename(self.filename + '.idx', nameprefix + '.idx')
233 return time.strftime('%s %z', time.localtime(date))
237 os.environ['GIT_DIR'] = os.path.abspath(repodir())
240 def _read_ref(refname):
241 p = subprocess.Popen(['git', 'show-ref', '--', refname],
242 preexec_fn = _gitenv,
243 stdout = subprocess.PIPE)
244 out = p.stdout.read().strip()
247 return out.split()[0]
252 def _update_ref(refname, newval, oldval):
255 p = subprocess.Popen(['git', 'update-ref', '--', refname, newval, oldval],
256 preexec_fn = _gitenv)
263 if os.path.exists(d) and not os.path.isdir(os.path.join(d, '.')):
264 raise Exception('"%d" exists but is not a directory\n' % d)
265 p = subprocess.Popen(['git', 'init', '--bare'],
266 preexec_fn = _gitenv)