2 from __future__ import absolute_import, print_function
3 from binascii import hexlify, unhexlify
4 from subprocess import check_call
5 import struct, os, time
9 from bup import git, path
10 from bup.compat import bytes_from_byte, environ, range
11 from bup.helpers import localtime, log, mkdirp, readpipe
12 from buptest import no_lingering_errors, test_tempdir
19 print(repr(cmd), file=sys.stderr)
24 print(repr(cmd), file=sys.stderr)
29 def test_git_version_detection():
30 with no_lingering_errors():
31 # Test version types from git's tag history
32 for expected, ver in \
33 (('insufficient', b'git version 0.99'),
34 ('insufficient', b'git version 0.99.1'),
35 ('insufficient', b'git version 0.99.7a'),
36 ('insufficient', b'git version 1.0rc1'),
37 ('insufficient', b'git version 1.0.1'),
38 ('insufficient', b'git version 1.4.2.1'),
39 ('insufficient', b'git version 1.5.5'),
40 ('insufficient', b'git version 1.5.6-rc0'),
41 ('suitable', b'git version 1.5.6'),
42 ('suitable', b'git version 1.5.6.1'),
43 ('suitable', b'git version 2.14.0-rc0'),
44 ('suitable', b'git version 2.14.0 (something ...)'),
45 ('suitable', b'git version 111.222.333.444-rc555'),
46 ('unrecognized', b'huh?')):
47 WVMSG('Checking version validation: %r' % ver)
48 WVPASSEQ(expected, git.is_suitable_git(ver_str=ver))
50 if expected == 'insufficient':
51 WVEXCEPT(SystemExit, git.require_suitable_git, ver)
52 elif expected == 'suitable':
53 git.require_suitable_git(ver_str=ver)
54 elif expected == 'unrecognized':
55 WVEXCEPT(git.GitError, git.require_suitable_git, ver)
61 environ[b'BUP_GIT_VERSION_IS_FINE'] = b'true'
62 git.require_suitable_git(ver_str=ver)
64 del environ[b'BUP_GIT_VERSION_IS_FINE']
70 with no_lingering_errors():
76 WVPASSEQ(git.mangle_name(b'a', adir2, adir), b'a')
77 WVPASSEQ(git.mangle_name(b'.bup', adir2, adir), b'.bup.bupl')
78 WVPASSEQ(git.mangle_name(b'a.bupa', adir2, adir), b'a.bupa.bupl')
79 WVPASSEQ(git.mangle_name(b'b.bup', alink, alink), b'b.bup.bupl')
80 WVPASSEQ(git.mangle_name(b'b.bu', alink, alink), b'b.bu')
81 WVPASSEQ(git.mangle_name(b'f', afile, afile2), b'f')
82 WVPASSEQ(git.mangle_name(b'f.bup', afile, afile2), b'f.bup.bupl')
83 WVPASSEQ(git.mangle_name(b'f.bup', afile, adir), b'f.bup.bup')
84 WVPASSEQ(git.mangle_name(b'f', afile, adir), b'f.bup')
86 WVPASSEQ(git.demangle_name(b'f.bup', afile), (b'f', git.BUP_CHUNKED))
87 WVPASSEQ(git.demangle_name(b'f.bupl', afile), (b'f', git.BUP_NORMAL))
88 WVPASSEQ(git.demangle_name(b'f.bup.bupl', afile), (b'f.bup', git.BUP_NORMAL))
90 WVPASSEQ(git.demangle_name(b'.bupm', afile), (b'', git.BUP_NORMAL))
91 WVPASSEQ(git.demangle_name(b'.bupm', adir), (b'', git.BUP_CHUNKED))
93 # for safety, we ignore .bup? suffixes we don't recognize. Future
94 # versions might implement a .bup[a-z] extension as something other
96 WVPASSEQ(git.demangle_name(b'f.bupa', afile), (b'f.bupa', git.BUP_NORMAL))
101 with no_lingering_errors():
103 looseb = b''.join(git._encode_looseobj(b'blob', s))
104 looset = b''.join(git._encode_looseobj(b'tree', s))
105 loosec = b''.join(git._encode_looseobj(b'commit', s))
106 packb = b''.join(git._encode_packobj(b'blob', s))
107 packt = b''.join(git._encode_packobj(b'tree', s))
108 packc = b''.join(git._encode_packobj(b'commit', s))
109 packlb = b''.join(git._encode_packobj(b'blob', s * 200))
110 WVPASSEQ(git._decode_looseobj(looseb), (b'blob', s))
111 WVPASSEQ(git._decode_looseobj(looset), (b'tree', s))
112 WVPASSEQ(git._decode_looseobj(loosec), (b'commit', s))
113 WVPASSEQ(git._decode_packobj(packb), (b'blob', s))
114 WVPASSEQ(git._decode_packobj(packt), (b'tree', s))
115 WVPASSEQ(git._decode_packobj(packc), (b'commit', s))
116 WVPASSEQ(git._decode_packobj(packlb), (b'blob', s * 200))
118 WVPASS(git._encode_looseobj(b'blob', s, compression_level=i))
120 return b''.join(git._encode_packobj(b'blob', s, compression_level=n))
121 WVEXCEPT(ValueError, encode_pobj, -1)
122 WVEXCEPT(ValueError, encode_pobj, 10)
123 WVEXCEPT(ValueError, encode_pobj, b'x')
128 with no_lingering_errors():
129 with test_tempdir(b'bup-tgit-') as tmpdir:
130 environ[b'BUP_DIR'] = bupdir = tmpdir + b'/bup'
131 git.init_repo(bupdir)
135 w.new_blob(os.urandom(100))
136 w.new_blob(os.urandom(100))
142 for i in range(nobj):
143 hashes.append(w.new_blob(b'%d' % i))
145 nameprefix = w.close()
146 print(repr(nameprefix))
147 WVPASS(os.path.exists(nameprefix + b'.pack'))
148 WVPASS(os.path.exists(nameprefix + b'.idx'))
150 r = git.open_idx(nameprefix + b'.idx')
151 print(repr(r.fanout))
153 for i in range(nobj):
154 WVPASS(r.find_offset(hashes[i]) > 0)
155 WVPASS(r.exists(hashes[99]))
156 WVFAIL(r.exists(b'\0'*20))
159 for h in sorted(hashes):
160 WVPASSEQ(hexlify(next(pi)), hexlify(h))
162 WVFAIL(r.find_offset(b'\0'*20))
164 r = git.PackIdxList(bupdir + b'/objects/pack')
165 WVPASS(r.exists(hashes[5]))
166 WVPASS(r.exists(hashes[6]))
167 WVFAIL(r.exists(b'\0'*20))
171 def test_pack_name_lookup():
172 with no_lingering_errors():
173 with test_tempdir(b'bup-tgit-') as tmpdir:
174 environ[b'BUP_DIR'] = bupdir = tmpdir + b'/bup'
175 git.init_repo(bupdir)
177 packdir = git.repo(b'objects/pack')
182 for start in range(0,28,2):
184 for i in range(start, start+2):
185 hashes.append(w.new_blob(b'%d' % i))
187 idxnames.append(os.path.basename(w.close() + b'.idx'))
189 r = git.PackIdxList(packdir)
190 WVPASSEQ(len(r.packs), 2)
191 for e,idxname in enumerate(idxnames):
192 for i in range(e*2, (e+1)*2):
193 WVPASSEQ(idxname, r.exists(hashes[i], want_source=True))
197 def test_long_index():
198 with no_lingering_errors():
199 with test_tempdir(b'bup-tgit-') as tmpdir:
200 environ[b'BUP_DIR'] = bupdir = tmpdir + b'/bup'
201 git.init_repo(bupdir)
203 obj_bin = struct.pack('!IIIII',
204 0x00112233, 0x44556677, 0x88990011, 0x22334455, 0x66778899)
205 obj2_bin = struct.pack('!IIIII',
206 0x11223344, 0x55667788, 0x99001122, 0x33445566, 0x77889900)
207 obj3_bin = struct.pack('!IIIII',
208 0x22334455, 0x66778899, 0x00112233, 0x44556677, 0x88990011)
209 pack_bin = struct.pack('!IIIII',
210 0x99887766, 0x55443322, 0x11009988, 0x77665544, 0x33221100)
211 idx = list(list() for i in range(256))
212 idx[0].append((obj_bin, 1, 0xfffffffff))
213 idx[0x11].append((obj2_bin, 2, 0xffffffffff))
214 idx[0x22].append((obj3_bin, 3, 0xff))
216 name = tmpdir + b'/tmp.idx'
217 r = w._write_pack_idx_v2(name, idx, pack_bin)
218 i = git.PackIdxV2(name, open(name, 'rb'))
219 WVPASSEQ(i.find_offset(obj_bin), 0xfffffffff)
220 WVPASSEQ(i.find_offset(obj2_bin), 0xffffffffff)
221 WVPASSEQ(i.find_offset(obj3_bin), 0xff)
225 def test_check_repo_or_die():
226 with no_lingering_errors():
227 with test_tempdir(b'bup-tgit-') as tmpdir:
228 environ[b'BUP_DIR'] = bupdir = tmpdir + b'/bup'
229 orig_cwd = os.getcwd()
232 git.init_repo(bupdir)
233 git.check_repo_or_die()
234 # if we reach this point the call above passed
235 WVPASS('check_repo_or_die')
237 os.rename(bupdir + b'/objects/pack',
238 bupdir + b'/objects/pack.tmp')
239 open(bupdir + b'/objects/pack', 'w').close()
241 git.check_repo_or_die()
242 except SystemExit as e:
246 os.unlink(bupdir + b'/objects/pack')
247 os.rename(bupdir + b'/objects/pack.tmp',
248 bupdir + b'/objects/pack')
251 git.check_repo_or_die(b'nonexistantbup.tmp')
252 except SystemExit as e:
261 def test_commit_parsing():
263 def restore_env_var(name, val):
269 def showval(commit, val):
270 return readpipe([b'git', b'show', b'-s',
271 b'--pretty=format:%s' % val, commit]).strip()
273 with no_lingering_errors():
274 with test_tempdir(b'bup-tgit-') as tmpdir:
275 orig_cwd = os.getcwd()
276 workdir = tmpdir + b'/work'
277 repodir = workdir + b'/.git'
278 orig_author_name = environ.get(b'GIT_AUTHOR_NAME')
279 orig_author_email = environ.get(b'GIT_AUTHOR_EMAIL')
280 orig_committer_name = environ.get(b'GIT_COMMITTER_NAME')
281 orig_committer_email = environ.get(b'GIT_COMMITTER_EMAIL')
282 environ[b'GIT_AUTHOR_NAME'] = b'bup test'
283 environ[b'GIT_COMMITTER_NAME'] = environ[b'GIT_AUTHOR_NAME']
284 environ[b'GIT_AUTHOR_EMAIL'] = b'bup@a425bc70a02811e49bdf73ee56450e6f'
285 environ[b'GIT_COMMITTER_EMAIL'] = environ[b'GIT_AUTHOR_EMAIL']
287 readpipe([b'git', b'init', workdir])
288 environ[b'GIT_DIR'] = environ[b'BUP_DIR'] = repodir
289 git.check_repo_or_die(repodir)
291 with open('foo', 'w') as f:
293 readpipe([b'git', b'add', b'.'])
294 readpipe([b'git', b'commit', b'-am', b'Do something',
295 b'--author', b'Someone <someone@somewhere>',
296 b'--date', b'Sat Oct 3 19:48:49 2009 -0400'])
297 commit = readpipe([b'git', b'show-ref', b'-s', b'master']).strip()
298 parents = showval(commit, b'%P')
299 tree = showval(commit, b'%T')
300 cname = showval(commit, b'%cn')
301 cmail = showval(commit, b'%ce')
302 cdate = showval(commit, b'%ct')
303 coffs = showval(commit, b'%ci')
305 coff = (int(coffs[-4:-2]) * 60 * 60) + (int(coffs[-2:]) * 60)
306 if bytes_from_byte(coffs[-5]) == b'-':
308 commit_items = git.get_commit_items(commit, git.cp())
309 WVPASSEQ(commit_items.parents, [])
310 WVPASSEQ(commit_items.tree, tree)
311 WVPASSEQ(commit_items.author_name, b'Someone')
312 WVPASSEQ(commit_items.author_mail, b'someone@somewhere')
313 WVPASSEQ(commit_items.author_sec, 1254613729)
314 WVPASSEQ(commit_items.author_offset, -(4 * 60 * 60))
315 WVPASSEQ(commit_items.committer_name, cname)
316 WVPASSEQ(commit_items.committer_mail, cmail)
317 WVPASSEQ(commit_items.committer_sec, int(cdate))
318 WVPASSEQ(commit_items.committer_offset, coff)
319 WVPASSEQ(commit_items.message, b'Do something\n')
320 with open(b'bar', 'wb') as f:
322 readpipe([b'git', b'add', '.'])
323 readpipe([b'git', b'commit', b'-am', b'Do something else'])
324 child = readpipe([b'git', b'show-ref', b'-s', b'master']).strip()
325 parents = showval(child, b'%P')
326 commit_items = git.get_commit_items(child, git.cp())
327 WVPASSEQ(commit_items.parents, [commit])
330 restore_env_var(b'GIT_AUTHOR_NAME', orig_author_name)
331 restore_env_var(b'GIT_AUTHOR_EMAIL', orig_author_email)
332 restore_env_var(b'GIT_COMMITTER_NAME', orig_committer_name)
333 restore_env_var(b'GIT_COMMITTER_EMAIL', orig_committer_email)
337 def test_new_commit():
338 with no_lingering_errors():
339 with test_tempdir(b'bup-tgit-') as tmpdir:
340 environ[b'BUP_DIR'] = bupdir = tmpdir + b'/bup'
341 git.init_repo(bupdir)
345 tree = os.urandom(20)
346 parent = os.urandom(20)
347 author_name = b'Author'
348 author_mail = b'author@somewhere'
349 adate_sec = 1439657836
350 cdate_sec = adate_sec + 1
351 committer_name = b'Committer'
352 committer_mail = b'committer@somewhere'
353 adate_tz_sec = cdate_tz_sec = None
354 commit = w.new_commit(tree, parent,
355 b'%s <%s>' % (author_name, author_mail),
356 adate_sec, adate_tz_sec,
357 b'%s <%s>' % (committer_name, committer_mail),
358 cdate_sec, cdate_tz_sec,
359 b'There is a small mailbox here')
360 adate_tz_sec = -60 * 60
361 cdate_tz_sec = 120 * 60
362 commit_off = w.new_commit(tree, parent,
363 b'%s <%s>' % (author_name, author_mail),
364 adate_sec, adate_tz_sec,
365 b'%s <%s>' % (committer_name, committer_mail),
366 cdate_sec, cdate_tz_sec,
367 b'There is a small mailbox here')
370 commit_items = git.get_commit_items(hexlify(commit), git.cp())
371 local_author_offset = localtime(adate_sec).tm_gmtoff
372 local_committer_offset = localtime(cdate_sec).tm_gmtoff
373 WVPASSEQ(tree, unhexlify(commit_items.tree))
374 WVPASSEQ(1, len(commit_items.parents))
375 WVPASSEQ(parent, unhexlify(commit_items.parents[0]))
376 WVPASSEQ(author_name, commit_items.author_name)
377 WVPASSEQ(author_mail, commit_items.author_mail)
378 WVPASSEQ(adate_sec, commit_items.author_sec)
379 WVPASSEQ(local_author_offset, commit_items.author_offset)
380 WVPASSEQ(committer_name, commit_items.committer_name)
381 WVPASSEQ(committer_mail, commit_items.committer_mail)
382 WVPASSEQ(cdate_sec, commit_items.committer_sec)
383 WVPASSEQ(local_committer_offset, commit_items.committer_offset)
385 commit_items = git.get_commit_items(hexlify(commit_off), git.cp())
386 WVPASSEQ(tree, unhexlify(commit_items.tree))
387 WVPASSEQ(1, len(commit_items.parents))
388 WVPASSEQ(parent, unhexlify(commit_items.parents[0]))
389 WVPASSEQ(author_name, commit_items.author_name)
390 WVPASSEQ(author_mail, commit_items.author_mail)
391 WVPASSEQ(adate_sec, commit_items.author_sec)
392 WVPASSEQ(adate_tz_sec, commit_items.author_offset)
393 WVPASSEQ(committer_name, commit_items.committer_name)
394 WVPASSEQ(committer_mail, commit_items.committer_mail)
395 WVPASSEQ(cdate_sec, commit_items.committer_sec)
396 WVPASSEQ(cdate_tz_sec, commit_items.committer_offset)
400 def test_list_refs():
401 with no_lingering_errors():
402 with test_tempdir(b'bup-tgit-') as tmpdir:
403 environ[b'BUP_DIR'] = bupdir = tmpdir + b'/bup'
404 src = tmpdir + b'/src'
406 with open(src + b'/1', 'wb+') as f:
407 f.write(b'something\n')
408 with open(src + b'/2', 'wb+') as f:
409 f.write(b'something else\n')
410 git.init_repo(bupdir)
411 emptyset = frozenset()
412 WVPASSEQ(frozenset(git.list_refs()), emptyset)
413 WVPASSEQ(frozenset(git.list_refs(limit_to_tags=True)), emptyset)
414 WVPASSEQ(frozenset(git.list_refs(limit_to_heads=True)), emptyset)
415 exc(bup_exe, b'index', src)
416 exc(bup_exe, b'save', b'-n', b'src', b'--strip', src)
417 src_hash = exo(b'git', b'--git-dir', bupdir,
418 b'rev-parse', b'src').strip().split(b'\n')
419 assert(len(src_hash) == 1)
420 src_hash = unhexlify(src_hash[0])
421 tree_hash = unhexlify(exo(b'git', b'--git-dir', bupdir,
423 b'src:').strip().split(b'\n')[0])
424 blob_hash = unhexlify(exo(b'git', b'--git-dir', bupdir,
426 b'src:1').strip().split(b'\n')[0])
427 WVPASSEQ(frozenset(git.list_refs()),
428 frozenset([(b'refs/heads/src', src_hash)]))
429 WVPASSEQ(frozenset(git.list_refs(limit_to_tags=True)), emptyset)
430 WVPASSEQ(frozenset(git.list_refs(limit_to_heads=True)),
431 frozenset([(b'refs/heads/src', src_hash)]))
432 exc(b'git', b'--git-dir', bupdir, b'tag', b'commit-tag', b'src')
433 WVPASSEQ(frozenset(git.list_refs()),
434 frozenset([(b'refs/heads/src', src_hash),
435 (b'refs/tags/commit-tag', src_hash)]))
436 WVPASSEQ(frozenset(git.list_refs(limit_to_tags=True)),
437 frozenset([(b'refs/tags/commit-tag', src_hash)]))
438 WVPASSEQ(frozenset(git.list_refs(limit_to_heads=True)),
439 frozenset([(b'refs/heads/src', src_hash)]))
440 exc(b'git', b'--git-dir', bupdir, b'tag', b'tree-tag', b'src:')
441 exc(b'git', b'--git-dir', bupdir, b'tag', b'blob-tag', b'src:1')
442 os.unlink(bupdir + b'/refs/heads/src')
443 expected_tags = frozenset([(b'refs/tags/commit-tag', src_hash),
444 (b'refs/tags/tree-tag', tree_hash),
445 (b'refs/tags/blob-tag', blob_hash)])
446 WVPASSEQ(frozenset(git.list_refs()), expected_tags)
447 WVPASSEQ(frozenset(git.list_refs(limit_to_heads=True)), frozenset([]))
448 WVPASSEQ(frozenset(git.list_refs(limit_to_tags=True)), expected_tags)
452 def test__git_date_str():
453 with no_lingering_errors():
454 WVPASSEQ(b'0 +0000', git._git_date_str(0, 0))
455 WVPASSEQ(b'0 -0130', git._git_date_str(0, -90 * 60))
456 WVPASSEQ(b'0 +0130', git._git_date_str(0, 90 * 60))
461 with no_lingering_errors():
462 with test_tempdir(b'bup-tgit-') as tmpdir:
463 environ[b'BUP_DIR'] = bupdir = tmpdir + b'/bup'
464 src = tmpdir + b'/src'
466 with open(src + b'/1', 'wb+') as f:
467 f.write(b'something\n')
468 with open(src + b'/2', 'wb+') as f:
469 f.write(b'something else\n')
470 git.init_repo(bupdir)
471 exc(bup_exe, b'index', src)
472 oidx = exo(bup_exe, b'save', b'-cn', b'src', b'--strip',
474 typ = exo(b'git', b'--git-dir', bupdir,
475 b'cat-file', b'-t', b'src').strip()
476 size = int(exo(b'git', b'--git-dir', bupdir,
477 b'cat-file', b'-s', b'src'))
478 it = git.cp().get(b'src')
482 WVPASSEQ((oidx, typ, size), get_info)