]> arthur.barton.de Git - bup.git/blob - git.py
Write git pack files instead of loose object files.
[bup.git] / git.py
1 import os, errno, zlib, time, sha, subprocess, struct
2 from helpers import *
3
4
5 def _old_write_object(bin, type, content):
6     hex = bin.encode('hex')
7     header = '%s %d\0' % (type, len(content))
8     dir = '.git/objects/%s' % hex[0:2]
9     fn = '%s/%s' % (dir, hex[2:])
10     if not os.path.exists(fn):
11         #log('creating %s' % fn)
12         try:
13             os.mkdir(dir)
14         except OSError, e:
15             if e.errno != errno.EEXIST:
16                 raise
17         tfn = '.git/objects/bup%d.tmp' % os.getpid()
18         f = open(tfn, 'w')
19         z = zlib.compressobj(1)
20         f.write(z.compress(header))
21         f.write(z.compress(content))
22         f.write(z.flush())
23         f.close()
24         os.rename(tfn, fn)
25
26
27 _typemap = dict(blob=3, tree=2, commit=1, tag=8)
28 class PackWriter:
29     def __init__(self):
30         self.count = 0
31         self.binlist = []
32         self.filename = '.git/objects/bup%d' % os.getpid()
33         self.file = open(self.filename + '.pack', 'w+')
34         self.file.write('PACK\0\0\0\2\0\0\0\0')
35
36     def write(self, bin, type, content):
37         global _typemap
38         f = self.file
39
40         sz = len(content)
41         szbits = (sz & 0x0f) | (_typemap[type]<<4)
42         sz >>= 4
43         while 1:
44             if sz: szbits |= 0x80
45             f.write(chr(szbits))
46             if not sz:
47                 break
48             szbits = sz & 0x7f
49             sz >>= 7
50         
51         z = zlib.compressobj(1)
52         f.write(z.compress(content))
53         f.write(z.flush())
54
55         self.count += 1
56         self.binlist.append(bin)
57
58     def close(self):
59         f = self.file
60
61         # update object count
62         f.seek(8)
63         cp = struct.pack('!i', self.count)
64         assert(len(cp) == 4)
65         f.write(cp)
66
67         # calculate the pack sha1sum
68         f.seek(0)
69         sum = sha.sha()
70         while 1:
71             b = f.read(65536)
72             sum.update(b)
73             if not b: break
74         f.write(sum.digest())
75         
76         f.close()
77
78         p = subprocess.Popen(['git', 'index-pack', '-v',
79                               self.filename + '.pack'],
80                              preexec_fn = lambda: _gitenv('.git'),
81                              stdout = subprocess.PIPE)
82         out = p.stdout.read().strip()
83         if p.wait() or not out:
84             raise Exception('git index-pack returned an error')
85         os.rename(self.filename + '.pack', '.git/objects/pack/%s.pack' % out)
86         os.rename(self.filename + '.idx', '.git/objects/pack/%s.idx' % out)
87
88 _packout = None
89 def _write_object(bin, type, content):
90     global _packout
91     if not _packout:
92         _packout = PackWriter()
93     _packout.write(bin, type, content)
94
95
96 def flush_pack():
97     global _packout
98     if _packout:
99         _packout.close()
100
101
102 _objcache = {}
103 def hash_raw(type, s):
104     global _objcache
105     header = '%s %d\0' % (type, len(s))
106     sum = sha.sha(header)
107     sum.update(s)
108     bin = sum.digest()
109     hex = sum.hexdigest()
110     if bin in _objcache:
111         return hex
112     else:
113         _write_object(bin, type, s)
114         _objcache[bin] = 1
115         return hex
116
117
118 def hash_blob(blob):
119     return hash_raw('blob', blob)
120
121
122 def gen_tree(shalist):
123     shalist = sorted(shalist, key = lambda x: x[1])
124     l = ['%s %s\0%s' % (mode,name,hex.decode('hex')) 
125          for (mode,name,hex) in shalist]
126     return hash_raw('tree', ''.join(l))
127
128
129 def _git_date(date):
130     return time.strftime('%s %z', time.localtime(date))
131
132
133 def _gitenv(repo):
134     os.environ['GIT_DIR'] = os.path.abspath(repo)
135
136
137 def _read_ref(repo, refname):
138     p = subprocess.Popen(['git', 'show-ref', '--', refname],
139                          preexec_fn = lambda: _gitenv(repo),
140                          stdout = subprocess.PIPE)
141     out = p.stdout.read().strip()
142     p.wait()
143     if out:
144         return out.split()[0]
145     else:
146         return None
147
148
149 def _update_ref(repo, refname, newval, oldval):
150     if not oldval:
151         oldval = ''
152     p = subprocess.Popen(['git', 'update-ref', '--', refname, newval, oldval],
153                          preexec_fn = lambda: _gitenv(repo))
154     p.wait()
155     return newval
156
157
158 def gen_commit(tree, parent, author, adate, committer, cdate, msg):
159     l = []
160     if tree: l.append('tree %s' % tree)
161     if parent: l.append('parent %s' % parent)
162     if author: l.append('author %s %s' % (author, _git_date(adate)))
163     if committer: l.append('committer %s %s' % (committer, _git_date(cdate)))
164     l.append('')
165     l.append(msg)
166     return hash_raw('commit', '\n'.join(l))
167
168
169 def gen_commit_easy(ref, tree, msg):
170     now = time.time()
171     userline = '%s <%s@%s>' % (userfullname(), username(), hostname())
172     oldref = ref and _read_ref('.git', ref) or None
173     commit = gen_commit(tree, oldref, userline, now, userline, now, msg)
174     if ref:
175         _update_ref('.git', ref, commit, oldref)
176     return commit