1 import os, errno, zlib, time, sha, subprocess, struct, mmap
8 def __init__(self, filename):
11 self.map = mmap.mmap(f.fileno(), 0,
12 mmap.MAP_SHARED, mmap.PROT_READ)
13 f.close() # map will persist beyond file close
14 assert(str(self.map[0:8]) == '\377tOc\0\0\0\2')
15 self.fanout = list(struct.unpack('!256I', buffer(self.map, 8, 256*4)))
16 self.fanout.append(0) # entry "-1"
17 nsha = self.fanout[255]
18 self.ofstable = buffer(self.map,
19 8 + 256*4 + nsha*20 + nsha*4,
21 self.ofs64table = buffer(self.map,
22 8 + 256*4 + nsha*20 + nsha*4 + nsha*4)
24 def _ofs_from_idx(self, idx):
25 ofs = struct.unpack('!I', buffer(self.ofstable, idx*4, 4))[0]
27 idx64 = ofs & 0x7fffffff
28 ofs = struct.unpack('!I', buffer(self.ofs64table, idx64*8, 8))[0]
31 def _idx_from_hash(self, hash):
32 assert(len(hash) == 20)
34 start = self.fanout[b1-1] # range -1..254
35 end = self.fanout[b1] # range 0..255
36 buf = buffer(self.map, 8 + 256*4, end*20)
39 mid = start + (end-start)/2
40 v = buffer(buf, mid*20, 20)
49 def find_offset(self, hash):
50 idx = self._idx_from_hash(hash)
52 return self._ofs_from_idx(idx)
55 def exists(self, hash):
56 return (self._idx_from_hash(hash) != None) and True or None
60 def __init__(self, dir):
63 for f in os.listdir(dir):
64 if f.endswith('.idx'):
65 self.packs.append(PackIndex(os.path.join(dir, f)))
67 def exists(self, hash):
70 for i in range(len(self.packs)):
73 # reorder so most recently used packs are searched first
74 self.packs = [p] + self.packs[:i] + self.packs[i+1:]
82 def calc_hash(type, content):
83 header = '%s %d\0' % (type, len(content))
89 _typemap = dict(blob=3, tree=2, commit=1, tag=8)
94 self.filename = '.git/objects/bup%d' % os.getpid()
95 self.file = open(self.filename + '.pack', 'w+')
96 self.file.write('PACK\0\0\0\2\0\0\0\0')
98 def write(self, bin, type, content):
106 szbits = (sz & 0x0f) | (_typemap[type]<<4)
109 if sz: szbits |= 0x80
116 z = zlib.compressobj(1)
117 f.write(z.compress(content))
121 self.binlist.append(bin)
124 def easy_write(self, type, content):
125 return self.write(calc_hash(type, content), type, content)
129 os.unlink(self.filename + '.pack')
134 # update object count
136 cp = struct.pack('!i', self.count)
140 # calculate the pack sha1sum
147 f.write(sum.digest())
151 p = subprocess.Popen(['git', 'index-pack', '-v',
152 self.filename + '.pack'],
153 preexec_fn = lambda: _gitenv('.git'),
154 stdout = subprocess.PIPE)
155 out = p.stdout.read().strip()
156 if p.wait() or not out:
157 raise Exception('git index-pack returned an error')
158 nameprefix = '.git/objects/pack/%s' % out
159 os.rename(self.filename + '.pack', nameprefix + '.pack')
160 os.rename(self.filename + '.idx', nameprefix + '.idx')
165 def _write_object(bin, type, content):
168 _packout = PackWriter()
169 _packout.write(bin, type, content)
187 def hash_raw(type, s):
190 _objcache = MultiPackIndex('.git/objects/pack')
191 bin = calc_hash(type, s)
192 if _objcache.exists(bin):
195 _write_object(bin, type, s)
201 return hash_raw('blob', blob)
204 def gen_tree(shalist):
205 shalist = sorted(shalist, key = lambda x: x[1])
206 l = ['%s %s\0%s' % (mode,name,bin)
207 for (mode,name,bin) in shalist]
208 return hash_raw('tree', ''.join(l))
212 return time.strftime('%s %z', time.localtime(date))
216 os.environ['GIT_DIR'] = os.path.abspath(repo)
219 def _read_ref(repo, refname):
220 p = subprocess.Popen(['git', 'show-ref', '--', refname],
221 preexec_fn = lambda: _gitenv(repo),
222 stdout = subprocess.PIPE)
223 out = p.stdout.read().strip()
226 return out.split()[0]
231 def _update_ref(repo, refname, newval, oldval):
234 p = subprocess.Popen(['git', 'update-ref', '--', refname, newval, oldval],
235 preexec_fn = lambda: _gitenv(repo))
240 def gen_commit(tree, parent, author, adate, committer, cdate, msg):
242 if tree: l.append('tree %s' % tree.encode('hex'))
243 if parent: l.append('parent %s' % parent)
244 if author: l.append('author %s %s' % (author, _git_date(adate)))
245 if committer: l.append('committer %s %s' % (committer, _git_date(cdate)))
248 return hash_raw('commit', '\n'.join(l))
251 def gen_commit_easy(ref, tree, msg):
253 userline = '%s <%s@%s>' % (userfullname(), username(), hostname())
254 oldref = ref and _read_ref('.git', ref) or None
255 commit = gen_commit(tree, oldref, userline, now, userline, now, msg)
258 _update_ref('.git', ref, commit.encode('hex'), oldref)