2
0

test_pack.py 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238
  1. # test_pack.py -- Tests for the handling of git packs.
  2. # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
  3. # Copyright (C) 2008 Jelmer Vernooij <jelmer@jelmer.uk>
  4. #
  5. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  6. # General Public License as public by the Free Software Foundation; version 2.0
  7. # or (at your option) any later version. You can redistribute it and/or
  8. # modify it under the terms of either of these two licenses.
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. # You should have received a copy of the licenses; if not, see
  17. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  18. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  19. # License, Version 2.0.
  20. #
  21. """Tests for Dulwich packs."""
  22. from io import BytesIO
  23. from hashlib import sha1
  24. import os
  25. import shutil
  26. import tempfile
  27. import zlib
  28. from dulwich.errors import (
  29. ApplyDeltaError,
  30. ChecksumMismatch,
  31. )
  32. from dulwich.file import (
  33. GitFile,
  34. )
  35. from dulwich.object_store import (
  36. MemoryObjectStore,
  37. )
  38. from dulwich.objects import (
  39. hex_to_sha,
  40. sha_to_hex,
  41. Commit,
  42. Tree,
  43. Blob,
  44. )
  45. from dulwich.pack import (
  46. OFS_DELTA,
  47. REF_DELTA,
  48. MemoryPackIndex,
  49. Pack,
  50. PackData,
  51. apply_delta,
  52. create_delta,
  53. deltify_pack_objects,
  54. load_pack_index,
  55. UnpackedObject,
  56. read_zlib_chunks,
  57. write_pack_header,
  58. write_pack_index_v1,
  59. write_pack_index_v2,
  60. write_pack_object,
  61. write_pack,
  62. unpack_object,
  63. compute_file_sha,
  64. PackStreamReader,
  65. DeltaChainIterator,
  66. _delta_encode_size,
  67. _encode_copy_operation,
  68. )
  69. from dulwich.tests import (
  70. TestCase,
  71. )
  72. from dulwich.tests.utils import (
  73. make_object,
  74. build_pack,
  75. )
  76. pack1_sha = b"bc63ddad95e7321ee734ea11a7a62d314e0d7481"
  77. a_sha = b"6f670c0fb53f9463760b7295fbb814e965fb20c8"
  78. tree_sha = b"b2a2766a2879c209ab1176e7e778b81ae422eeaa"
  79. commit_sha = b"f18faa16531ac570a3fdc8c7ca16682548dafd12"
  80. class PackTests(TestCase):
  81. """Base class for testing packs"""
  82. def setUp(self):
  83. super(PackTests, self).setUp()
  84. self.tempdir = tempfile.mkdtemp()
  85. self.addCleanup(shutil.rmtree, self.tempdir)
  86. datadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "data/packs"))
  87. def get_pack_index(self, sha):
  88. """Returns a PackIndex from the datadir with the given sha"""
  89. return load_pack_index(
  90. os.path.join(self.datadir, "pack-%s.idx" % sha.decode("ascii"))
  91. )
  92. def get_pack_data(self, sha):
  93. """Returns a PackData object from the datadir with the given sha"""
  94. return PackData(
  95. os.path.join(self.datadir, "pack-%s.pack" % sha.decode("ascii"))
  96. )
  97. def get_pack(self, sha):
  98. return Pack(os.path.join(self.datadir, "pack-%s" % sha.decode("ascii")))
  99. def assertSucceeds(self, func, *args, **kwargs):
  100. try:
  101. func(*args, **kwargs)
  102. except ChecksumMismatch as e:
  103. self.fail(e)
  104. class PackIndexTests(PackTests):
  105. """Class that tests the index of packfiles"""
  106. def test_object_index(self):
  107. """Tests that the correct object offset is returned from the index."""
  108. p = self.get_pack_index(pack1_sha)
  109. self.assertRaises(KeyError, p.object_index, pack1_sha)
  110. self.assertEqual(p.object_index(a_sha), 178)
  111. self.assertEqual(p.object_index(tree_sha), 138)
  112. self.assertEqual(p.object_index(commit_sha), 12)
  113. def test_object_sha1(self):
  114. """Tests that the correct object offset is returned from the index."""
  115. p = self.get_pack_index(pack1_sha)
  116. self.assertRaises(KeyError, p.object_sha1, 876)
  117. self.assertEqual(p.object_sha1(178), hex_to_sha(a_sha))
  118. self.assertEqual(p.object_sha1(138), hex_to_sha(tree_sha))
  119. self.assertEqual(p.object_sha1(12), hex_to_sha(commit_sha))
  120. def test_index_len(self):
  121. p = self.get_pack_index(pack1_sha)
  122. self.assertEqual(3, len(p))
  123. def test_get_stored_checksum(self):
  124. p = self.get_pack_index(pack1_sha)
  125. self.assertEqual(
  126. b"f2848e2ad16f329ae1c92e3b95e91888daa5bd01",
  127. sha_to_hex(p.get_stored_checksum()),
  128. )
  129. self.assertEqual(
  130. b"721980e866af9a5f93ad674144e1459b8ba3e7b7",
  131. sha_to_hex(p.get_pack_checksum()),
  132. )
  133. def test_index_check(self):
  134. p = self.get_pack_index(pack1_sha)
  135. self.assertSucceeds(p.check)
  136. def test_iterentries(self):
  137. p = self.get_pack_index(pack1_sha)
  138. entries = [(sha_to_hex(s), o, c) for s, o, c in p.iterentries()]
  139. self.assertEqual(
  140. [
  141. (b"6f670c0fb53f9463760b7295fbb814e965fb20c8", 178, None),
  142. (b"b2a2766a2879c209ab1176e7e778b81ae422eeaa", 138, None),
  143. (b"f18faa16531ac570a3fdc8c7ca16682548dafd12", 12, None),
  144. ],
  145. entries,
  146. )
  147. def test_iter(self):
  148. p = self.get_pack_index(pack1_sha)
  149. self.assertEqual(set([tree_sha, commit_sha, a_sha]), set(p))
  150. class TestPackDeltas(TestCase):
  151. test_string1 = b"The answer was flailing in the wind"
  152. test_string2 = b"The answer was falling down the pipe"
  153. test_string3 = b"zzzzz"
  154. test_string_empty = b""
  155. test_string_big = b"Z" * 8192
  156. test_string_huge = b"Z" * 100000
  157. def _test_roundtrip(self, base, target):
  158. self.assertEqual(
  159. target, b"".join(apply_delta(base, create_delta(base, target)))
  160. )
  161. def test_nochange(self):
  162. self._test_roundtrip(self.test_string1, self.test_string1)
  163. def test_nochange_huge(self):
  164. self._test_roundtrip(self.test_string_huge, self.test_string_huge)
  165. def test_change(self):
  166. self._test_roundtrip(self.test_string1, self.test_string2)
  167. def test_rewrite(self):
  168. self._test_roundtrip(self.test_string1, self.test_string3)
  169. def test_empty_to_big(self):
  170. self._test_roundtrip(self.test_string_empty, self.test_string_big)
  171. def test_empty_to_huge(self):
  172. self._test_roundtrip(self.test_string_empty, self.test_string_huge)
  173. def test_huge_copy(self):
  174. self._test_roundtrip(
  175. self.test_string_huge + self.test_string1,
  176. self.test_string_huge + self.test_string2,
  177. )
  178. def test_dest_overflow(self):
  179. self.assertRaises(
  180. ApplyDeltaError,
  181. apply_delta,
  182. b"a" * 0x10000,
  183. b"\x80\x80\x04\x80\x80\x04\x80" + b"a" * 0x10000,
  184. )
  185. self.assertRaises(
  186. ApplyDeltaError, apply_delta, b"", b"\x00\x80\x02\xb0\x11\x11"
  187. )
  188. def test_pypy_issue(self):
  189. # Test for https://github.com/jelmer/dulwich/issues/509 /
  190. # https://bitbucket.org/pypy/pypy/issues/2499/cpyext-pystring_asstring-doesnt-work
  191. chunks = [
  192. b"tree 03207ccf58880a748188836155ceed72f03d65d6\n"
  193. b"parent 408fbab530fd4abe49249a636a10f10f44d07a21\n"
  194. b"author Victor Stinner <victor.stinner@gmail.com> "
  195. b"1421355207 +0100\n"
  196. b"committer Victor Stinner <victor.stinner@gmail.com> "
  197. b"1421355207 +0100\n"
  198. b"\n"
  199. b"Backout changeset 3a06020af8cf\n"
  200. b"\nStreamWriter: close() now clears the reference to the "
  201. b"transport\n"
  202. b"\nStreamWriter now raises an exception if it is closed: "
  203. b"write(), writelines(),\n"
  204. b"write_eof(), can_write_eof(), get_extra_info(), drain().\n"
  205. ]
  206. delta = [
  207. b"\xcd\x03\xad\x03]tree ff3c181a393d5a7270cddc01ea863818a8621ca8\n"
  208. b"parent 20a103cc90135494162e819f98d0edfc1f1fba6b\x91]7\x0510738"
  209. b"\x91\x99@\x0b10738 +0100\x93\x04\x01\xc9"
  210. ]
  211. res = apply_delta(chunks, delta)
  212. expected = [
  213. b"tree ff3c181a393d5a7270cddc01ea863818a8621ca8\n"
  214. b"parent 20a103cc90135494162e819f98d0edfc1f1fba6b",
  215. b"\nauthor Victor Stinner <victor.stinner@gmail.com> 14213",
  216. b"10738",
  217. b" +0100\ncommitter Victor Stinner <victor.stinner@gmail.com> " b"14213",
  218. b"10738 +0100",
  219. b"\n\nStreamWriter: close() now clears the reference to the "
  220. b"transport\n\n"
  221. b"StreamWriter now raises an exception if it is closed: "
  222. b"write(), writelines(),\n"
  223. b"write_eof(), can_write_eof(), get_extra_info(), drain().\n",
  224. ]
  225. self.assertEqual(b"".join(expected), b"".join(res))
  226. class TestPackData(PackTests):
  227. """Tests getting the data from the packfile."""
  228. def test_create_pack(self):
  229. self.get_pack_data(pack1_sha).close()
  230. def test_from_file(self):
  231. path = os.path.join(self.datadir, "pack-%s.pack" % pack1_sha.decode("ascii"))
  232. with open(path, "rb") as f:
  233. PackData.from_file(f, os.path.getsize(path))
  234. def test_pack_len(self):
  235. with self.get_pack_data(pack1_sha) as p:
  236. self.assertEqual(3, len(p))
  237. def test_index_check(self):
  238. with self.get_pack_data(pack1_sha) as p:
  239. self.assertSucceeds(p.check)
  240. def test_iterobjects(self):
  241. with self.get_pack_data(pack1_sha) as p:
  242. commit_data = (
  243. b"tree b2a2766a2879c209ab1176e7e778b81ae422eeaa\n"
  244. b"author James Westby <jw+debian@jameswestby.net> "
  245. b"1174945067 +0100\n"
  246. b"committer James Westby <jw+debian@jameswestby.net> "
  247. b"1174945067 +0100\n"
  248. b"\n"
  249. b"Test commit\n"
  250. )
  251. blob_sha = b"6f670c0fb53f9463760b7295fbb814e965fb20c8"
  252. tree_data = b"100644 a\0" + hex_to_sha(blob_sha)
  253. actual = []
  254. for offset, type_num, chunks, crc32 in p.iterobjects():
  255. actual.append((offset, type_num, b"".join(chunks), crc32))
  256. self.assertEqual(
  257. [
  258. (12, 1, commit_data, 3775879613),
  259. (138, 2, tree_data, 912998690),
  260. (178, 3, b"test 1\n", 1373561701),
  261. ],
  262. actual,
  263. )
  264. def test_iterentries(self):
  265. with self.get_pack_data(pack1_sha) as p:
  266. entries = {(sha_to_hex(s), o, c) for s, o, c in p.iterentries()}
  267. self.assertEqual(
  268. set(
  269. [
  270. (
  271. b"6f670c0fb53f9463760b7295fbb814e965fb20c8",
  272. 178,
  273. 1373561701,
  274. ),
  275. (
  276. b"b2a2766a2879c209ab1176e7e778b81ae422eeaa",
  277. 138,
  278. 912998690,
  279. ),
  280. (
  281. b"f18faa16531ac570a3fdc8c7ca16682548dafd12",
  282. 12,
  283. 3775879613,
  284. ),
  285. ]
  286. ),
  287. entries,
  288. )
  289. def test_create_index_v1(self):
  290. with self.get_pack_data(pack1_sha) as p:
  291. filename = os.path.join(self.tempdir, "v1test.idx")
  292. p.create_index_v1(filename)
  293. idx1 = load_pack_index(filename)
  294. idx2 = self.get_pack_index(pack1_sha)
  295. self.assertEqual(oct(os.stat(filename).st_mode), "0o100644")
  296. self.assertEqual(idx1, idx2)
  297. def test_create_index_v2(self):
  298. with self.get_pack_data(pack1_sha) as p:
  299. filename = os.path.join(self.tempdir, "v2test.idx")
  300. p.create_index_v2(filename)
  301. idx1 = load_pack_index(filename)
  302. idx2 = self.get_pack_index(pack1_sha)
  303. self.assertEqual(oct(os.stat(filename).st_mode), "0o100644")
  304. self.assertEqual(idx1, idx2)
  305. def test_compute_file_sha(self):
  306. f = BytesIO(b"abcd1234wxyz")
  307. self.assertEqual(
  308. sha1(b"abcd1234wxyz").hexdigest(), compute_file_sha(f).hexdigest()
  309. )
  310. self.assertEqual(
  311. sha1(b"abcd1234wxyz").hexdigest(),
  312. compute_file_sha(f, buffer_size=5).hexdigest(),
  313. )
  314. self.assertEqual(
  315. sha1(b"abcd1234").hexdigest(),
  316. compute_file_sha(f, end_ofs=-4).hexdigest(),
  317. )
  318. self.assertEqual(
  319. sha1(b"1234wxyz").hexdigest(),
  320. compute_file_sha(f, start_ofs=4).hexdigest(),
  321. )
  322. self.assertEqual(
  323. sha1(b"1234").hexdigest(),
  324. compute_file_sha(f, start_ofs=4, end_ofs=-4).hexdigest(),
  325. )
  326. def test_compute_file_sha_short_file(self):
  327. f = BytesIO(b"abcd1234wxyz")
  328. self.assertRaises(AssertionError, compute_file_sha, f, end_ofs=-20)
  329. self.assertRaises(AssertionError, compute_file_sha, f, end_ofs=20)
  330. self.assertRaises(
  331. AssertionError, compute_file_sha, f, start_ofs=10, end_ofs=-12
  332. )
  333. class TestPack(PackTests):
  334. def test_len(self):
  335. with self.get_pack(pack1_sha) as p:
  336. self.assertEqual(3, len(p))
  337. def test_contains(self):
  338. with self.get_pack(pack1_sha) as p:
  339. self.assertTrue(tree_sha in p)
  340. def test_get(self):
  341. with self.get_pack(pack1_sha) as p:
  342. self.assertEqual(type(p[tree_sha]), Tree)
  343. def test_iter(self):
  344. with self.get_pack(pack1_sha) as p:
  345. self.assertEqual(set([tree_sha, commit_sha, a_sha]), set(p))
  346. def test_iterobjects(self):
  347. with self.get_pack(pack1_sha) as p:
  348. expected = set([p[s] for s in [commit_sha, tree_sha, a_sha]])
  349. self.assertEqual(expected, set(list(p.iterobjects())))
  350. def test_pack_tuples(self):
  351. with self.get_pack(pack1_sha) as p:
  352. tuples = p.pack_tuples()
  353. expected = set([(p[s], None) for s in [commit_sha, tree_sha, a_sha]])
  354. self.assertEqual(expected, set(list(tuples)))
  355. self.assertEqual(expected, set(list(tuples)))
  356. self.assertEqual(3, len(tuples))
  357. def test_get_object_at(self):
  358. """Tests random access for non-delta objects"""
  359. with self.get_pack(pack1_sha) as p:
  360. obj = p[a_sha]
  361. self.assertEqual(obj.type_name, b"blob")
  362. self.assertEqual(obj.sha().hexdigest().encode("ascii"), a_sha)
  363. obj = p[tree_sha]
  364. self.assertEqual(obj.type_name, b"tree")
  365. self.assertEqual(obj.sha().hexdigest().encode("ascii"), tree_sha)
  366. obj = p[commit_sha]
  367. self.assertEqual(obj.type_name, b"commit")
  368. self.assertEqual(obj.sha().hexdigest().encode("ascii"), commit_sha)
  369. def test_copy(self):
  370. with self.get_pack(pack1_sha) as origpack:
  371. self.assertSucceeds(origpack.index.check)
  372. basename = os.path.join(self.tempdir, "Elch")
  373. write_pack(basename, origpack.pack_tuples())
  374. with Pack(basename) as newpack:
  375. self.assertEqual(origpack, newpack)
  376. self.assertSucceeds(newpack.index.check)
  377. self.assertEqual(origpack.name(), newpack.name())
  378. self.assertEqual(
  379. origpack.index.get_pack_checksum(),
  380. newpack.index.get_pack_checksum(),
  381. )
  382. wrong_version = origpack.index.version != newpack.index.version
  383. orig_checksum = origpack.index.get_stored_checksum()
  384. new_checksum = newpack.index.get_stored_checksum()
  385. self.assertTrue(wrong_version or orig_checksum == new_checksum)
  386. def test_commit_obj(self):
  387. with self.get_pack(pack1_sha) as p:
  388. commit = p[commit_sha]
  389. self.assertEqual(b"James Westby <jw+debian@jameswestby.net>", commit.author)
  390. self.assertEqual([], commit.parents)
  391. def _copy_pack(self, origpack):
  392. basename = os.path.join(self.tempdir, "somepack")
  393. write_pack(basename, origpack.pack_tuples())
  394. return Pack(basename)
  395. def test_keep_no_message(self):
  396. with self.get_pack(pack1_sha) as p:
  397. p = self._copy_pack(p)
  398. with p:
  399. keepfile_name = p.keep()
  400. # file should exist
  401. self.assertTrue(os.path.exists(keepfile_name))
  402. with open(keepfile_name, "r") as f:
  403. buf = f.read()
  404. self.assertEqual("", buf)
  405. def test_keep_message(self):
  406. with self.get_pack(pack1_sha) as p:
  407. p = self._copy_pack(p)
  408. msg = b"some message"
  409. with p:
  410. keepfile_name = p.keep(msg)
  411. # file should exist
  412. self.assertTrue(os.path.exists(keepfile_name))
  413. # and contain the right message, with a linefeed
  414. with open(keepfile_name, "rb") as f:
  415. buf = f.read()
  416. self.assertEqual(msg + b"\n", buf)
  417. def test_name(self):
  418. with self.get_pack(pack1_sha) as p:
  419. self.assertEqual(pack1_sha, p.name())
  420. def test_length_mismatch(self):
  421. with self.get_pack_data(pack1_sha) as data:
  422. index = self.get_pack_index(pack1_sha)
  423. Pack.from_objects(data, index).check_length_and_checksum()
  424. data._file.seek(12)
  425. bad_file = BytesIO()
  426. write_pack_header(bad_file, 9999)
  427. bad_file.write(data._file.read())
  428. bad_file = BytesIO(bad_file.getvalue())
  429. bad_data = PackData("", file=bad_file)
  430. bad_pack = Pack.from_lazy_objects(lambda: bad_data, lambda: index)
  431. self.assertRaises(AssertionError, lambda: bad_pack.data)
  432. self.assertRaises(
  433. AssertionError, bad_pack.check_length_and_checksum
  434. )
  435. def test_checksum_mismatch(self):
  436. with self.get_pack_data(pack1_sha) as data:
  437. index = self.get_pack_index(pack1_sha)
  438. Pack.from_objects(data, index).check_length_and_checksum()
  439. data._file.seek(0)
  440. bad_file = BytesIO(data._file.read()[:-20] + (b"\xff" * 20))
  441. bad_data = PackData("", file=bad_file)
  442. bad_pack = Pack.from_lazy_objects(lambda: bad_data, lambda: index)
  443. self.assertRaises(ChecksumMismatch, lambda: bad_pack.data)
  444. self.assertRaises(
  445. ChecksumMismatch, bad_pack.check_length_and_checksum
  446. )
  447. def test_iterobjects_2(self):
  448. with self.get_pack(pack1_sha) as p:
  449. objs = {o.id: o for o in p.iterobjects()}
  450. self.assertEqual(3, len(objs))
  451. self.assertEqual(sorted(objs), sorted(p.index))
  452. self.assertTrue(isinstance(objs[a_sha], Blob))
  453. self.assertTrue(isinstance(objs[tree_sha], Tree))
  454. self.assertTrue(isinstance(objs[commit_sha], Commit))
  455. class TestThinPack(PackTests):
  456. def setUp(self):
  457. super(TestThinPack, self).setUp()
  458. self.store = MemoryObjectStore()
  459. self.blobs = {}
  460. for blob in (b"foo", b"bar", b"foo1234", b"bar2468"):
  461. self.blobs[blob] = make_object(Blob, data=blob)
  462. self.store.add_object(self.blobs[b"foo"])
  463. self.store.add_object(self.blobs[b"bar"])
  464. # Build a thin pack. 'foo' is as an external reference, 'bar' an
  465. # internal reference.
  466. self.pack_dir = tempfile.mkdtemp()
  467. self.addCleanup(shutil.rmtree, self.pack_dir)
  468. self.pack_prefix = os.path.join(self.pack_dir, "pack")
  469. with open(self.pack_prefix + ".pack", "wb") as f:
  470. build_pack(
  471. f,
  472. [
  473. (REF_DELTA, (self.blobs[b"foo"].id, b"foo1234")),
  474. (Blob.type_num, b"bar"),
  475. (REF_DELTA, (self.blobs[b"bar"].id, b"bar2468")),
  476. ],
  477. store=self.store,
  478. )
  479. # Index the new pack.
  480. with self.make_pack(True) as pack:
  481. with PackData(pack._data_path) as data:
  482. data.pack = pack
  483. data.create_index(self.pack_prefix + ".idx")
  484. del self.store[self.blobs[b"bar"].id]
  485. def make_pack(self, resolve_ext_ref):
  486. return Pack(
  487. self.pack_prefix,
  488. resolve_ext_ref=self.store.get_raw if resolve_ext_ref else None,
  489. )
  490. def test_get_raw(self):
  491. with self.make_pack(False) as p:
  492. self.assertRaises(KeyError, p.get_raw, self.blobs[b"foo1234"].id)
  493. with self.make_pack(True) as p:
  494. self.assertEqual((3, b"foo1234"), p.get_raw(self.blobs[b"foo1234"].id))
  495. def test_get_raw_unresolved(self):
  496. with self.make_pack(False) as p:
  497. self.assertEqual(
  498. (
  499. 7,
  500. b"\x19\x10(\x15f=#\xf8\xb7ZG\xe7\xa0\x19e\xdc\xdc\x96F\x8c",
  501. [b"x\x9ccf\x9f\xc0\xccbhdl\x02\x00\x06f\x01l"],
  502. ),
  503. p.get_raw_unresolved(self.blobs[b"foo1234"].id),
  504. )
  505. with self.make_pack(True) as p:
  506. self.assertEqual(
  507. (
  508. 7,
  509. b"\x19\x10(\x15f=#\xf8\xb7ZG\xe7\xa0\x19e\xdc\xdc\x96F\x8c",
  510. [b"x\x9ccf\x9f\xc0\xccbhdl\x02\x00\x06f\x01l"],
  511. ),
  512. p.get_raw_unresolved(self.blobs[b"foo1234"].id),
  513. )
  514. def test_iterobjects(self):
  515. with self.make_pack(False) as p:
  516. self.assertRaises(KeyError, list, p.iterobjects())
  517. with self.make_pack(True) as p:
  518. self.assertEqual(
  519. sorted(
  520. [
  521. self.blobs[b"foo1234"].id,
  522. self.blobs[b"bar"].id,
  523. self.blobs[b"bar2468"].id,
  524. ]
  525. ),
  526. sorted(o.id for o in p.iterobjects()),
  527. )
  528. class WritePackTests(TestCase):
  529. def test_write_pack_header(self):
  530. f = BytesIO()
  531. write_pack_header(f, 42)
  532. self.assertEqual(b"PACK\x00\x00\x00\x02\x00\x00\x00*", f.getvalue())
  533. def test_write_pack_object(self):
  534. f = BytesIO()
  535. f.write(b"header")
  536. offset = f.tell()
  537. crc32 = write_pack_object(f, Blob.type_num, b"blob")
  538. self.assertEqual(crc32, zlib.crc32(f.getvalue()[6:]) & 0xFFFFFFFF)
  539. f.write(b"x") # unpack_object needs extra trailing data.
  540. f.seek(offset)
  541. unpacked, unused = unpack_object(f.read, compute_crc32=True)
  542. self.assertEqual(Blob.type_num, unpacked.pack_type_num)
  543. self.assertEqual(Blob.type_num, unpacked.obj_type_num)
  544. self.assertEqual([b"blob"], unpacked.decomp_chunks)
  545. self.assertEqual(crc32, unpacked.crc32)
  546. self.assertEqual(b"x", unused)
  547. def test_write_pack_object_sha(self):
  548. f = BytesIO()
  549. f.write(b"header")
  550. offset = f.tell()
  551. sha_a = sha1(b"foo")
  552. sha_b = sha_a.copy()
  553. write_pack_object(f, Blob.type_num, b"blob", sha=sha_a)
  554. self.assertNotEqual(sha_a.digest(), sha_b.digest())
  555. sha_b.update(f.getvalue()[offset:])
  556. self.assertEqual(sha_a.digest(), sha_b.digest())
  557. def test_write_pack_object_compression_level(self):
  558. f = BytesIO()
  559. f.write(b"header")
  560. offset = f.tell()
  561. sha_a = sha1(b"foo")
  562. sha_b = sha_a.copy()
  563. write_pack_object(f, Blob.type_num, b"blob", sha=sha_a, compression_level=6)
  564. self.assertNotEqual(sha_a.digest(), sha_b.digest())
  565. sha_b.update(f.getvalue()[offset:])
  566. self.assertEqual(sha_a.digest(), sha_b.digest())
  567. pack_checksum = hex_to_sha("721980e866af9a5f93ad674144e1459b8ba3e7b7")
  568. class BaseTestPackIndexWriting(object):
  569. def assertSucceeds(self, func, *args, **kwargs):
  570. try:
  571. func(*args, **kwargs)
  572. except ChecksumMismatch as e:
  573. self.fail(e)
  574. def index(self, filename, entries, pack_checksum):
  575. raise NotImplementedError(self.index)
  576. def test_empty(self):
  577. idx = self.index("empty.idx", [], pack_checksum)
  578. self.assertEqual(idx.get_pack_checksum(), pack_checksum)
  579. self.assertEqual(0, len(idx))
  580. def test_large(self):
  581. entry1_sha = hex_to_sha("4e6388232ec39792661e2e75db8fb117fc869ce6")
  582. entry2_sha = hex_to_sha("e98f071751bd77f59967bfa671cd2caebdccc9a2")
  583. entries = [
  584. (entry1_sha, 0xF2972D0830529B87, 24),
  585. (entry2_sha, (~0xF2972D0830529B87) & (2 ** 64 - 1), 92),
  586. ]
  587. if not self._supports_large:
  588. self.assertRaises(
  589. TypeError, self.index, "single.idx", entries, pack_checksum
  590. )
  591. return
  592. idx = self.index("single.idx", entries, pack_checksum)
  593. self.assertEqual(idx.get_pack_checksum(), pack_checksum)
  594. self.assertEqual(2, len(idx))
  595. actual_entries = list(idx.iterentries())
  596. self.assertEqual(len(entries), len(actual_entries))
  597. for mine, actual in zip(entries, actual_entries):
  598. my_sha, my_offset, my_crc = mine
  599. actual_sha, actual_offset, actual_crc = actual
  600. self.assertEqual(my_sha, actual_sha)
  601. self.assertEqual(my_offset, actual_offset)
  602. if self._has_crc32_checksum:
  603. self.assertEqual(my_crc, actual_crc)
  604. else:
  605. self.assertTrue(actual_crc is None)
  606. def test_single(self):
  607. entry_sha = hex_to_sha("6f670c0fb53f9463760b7295fbb814e965fb20c8")
  608. my_entries = [(entry_sha, 178, 42)]
  609. idx = self.index("single.idx", my_entries, pack_checksum)
  610. self.assertEqual(idx.get_pack_checksum(), pack_checksum)
  611. self.assertEqual(1, len(idx))
  612. actual_entries = list(idx.iterentries())
  613. self.assertEqual(len(my_entries), len(actual_entries))
  614. for mine, actual in zip(my_entries, actual_entries):
  615. my_sha, my_offset, my_crc = mine
  616. actual_sha, actual_offset, actual_crc = actual
  617. self.assertEqual(my_sha, actual_sha)
  618. self.assertEqual(my_offset, actual_offset)
  619. if self._has_crc32_checksum:
  620. self.assertEqual(my_crc, actual_crc)
  621. else:
  622. self.assertTrue(actual_crc is None)
  623. class BaseTestFilePackIndexWriting(BaseTestPackIndexWriting):
  624. def setUp(self):
  625. self.tempdir = tempfile.mkdtemp()
  626. def tearDown(self):
  627. shutil.rmtree(self.tempdir)
  628. def index(self, filename, entries, pack_checksum):
  629. path = os.path.join(self.tempdir, filename)
  630. self.writeIndex(path, entries, pack_checksum)
  631. idx = load_pack_index(path)
  632. self.assertSucceeds(idx.check)
  633. self.assertEqual(idx.version, self._expected_version)
  634. return idx
  635. def writeIndex(self, filename, entries, pack_checksum):
  636. # FIXME: Write to BytesIO instead rather than hitting disk ?
  637. with GitFile(filename, "wb") as f:
  638. self._write_fn(f, entries, pack_checksum)
  639. class TestMemoryIndexWriting(TestCase, BaseTestPackIndexWriting):
  640. def setUp(self):
  641. TestCase.setUp(self)
  642. self._has_crc32_checksum = True
  643. self._supports_large = True
  644. def index(self, filename, entries, pack_checksum):
  645. return MemoryPackIndex(entries, pack_checksum)
  646. def tearDown(self):
  647. TestCase.tearDown(self)
  648. class TestPackIndexWritingv1(TestCase, BaseTestFilePackIndexWriting):
  649. def setUp(self):
  650. TestCase.setUp(self)
  651. BaseTestFilePackIndexWriting.setUp(self)
  652. self._has_crc32_checksum = False
  653. self._expected_version = 1
  654. self._supports_large = False
  655. self._write_fn = write_pack_index_v1
  656. def tearDown(self):
  657. TestCase.tearDown(self)
  658. BaseTestFilePackIndexWriting.tearDown(self)
  659. class TestPackIndexWritingv2(TestCase, BaseTestFilePackIndexWriting):
  660. def setUp(self):
  661. TestCase.setUp(self)
  662. BaseTestFilePackIndexWriting.setUp(self)
  663. self._has_crc32_checksum = True
  664. self._supports_large = True
  665. self._expected_version = 2
  666. self._write_fn = write_pack_index_v2
  667. def tearDown(self):
  668. TestCase.tearDown(self)
  669. BaseTestFilePackIndexWriting.tearDown(self)
  670. class ReadZlibTests(TestCase):
  671. decomp = (
  672. b"tree 4ada885c9196b6b6fa08744b5862bf92896fc002\n"
  673. b"parent None\n"
  674. b"author Jelmer Vernooij <jelmer@samba.org> 1228980214 +0000\n"
  675. b"committer Jelmer Vernooij <jelmer@samba.org> 1228980214 +0000\n"
  676. b"\n"
  677. b"Provide replacement for mmap()'s offset argument."
  678. )
  679. comp = zlib.compress(decomp)
  680. extra = b"nextobject"
  681. def setUp(self):
  682. super(ReadZlibTests, self).setUp()
  683. self.read = BytesIO(self.comp + self.extra).read
  684. self.unpacked = UnpackedObject(Tree.type_num, None, len(self.decomp), 0)
  685. def test_decompress_size(self):
  686. good_decomp_len = len(self.decomp)
  687. self.unpacked.decomp_len = -1
  688. self.assertRaises(ValueError, read_zlib_chunks, self.read, self.unpacked)
  689. self.unpacked.decomp_len = good_decomp_len - 1
  690. self.assertRaises(zlib.error, read_zlib_chunks, self.read, self.unpacked)
  691. self.unpacked.decomp_len = good_decomp_len + 1
  692. self.assertRaises(zlib.error, read_zlib_chunks, self.read, self.unpacked)
  693. def test_decompress_truncated(self):
  694. read = BytesIO(self.comp[:10]).read
  695. self.assertRaises(zlib.error, read_zlib_chunks, read, self.unpacked)
  696. read = BytesIO(self.comp).read
  697. self.assertRaises(zlib.error, read_zlib_chunks, read, self.unpacked)
  698. def test_decompress_empty(self):
  699. unpacked = UnpackedObject(Tree.type_num, None, 0, None)
  700. comp = zlib.compress(b"")
  701. read = BytesIO(comp + self.extra).read
  702. unused = read_zlib_chunks(read, unpacked)
  703. self.assertEqual(b"", b"".join(unpacked.decomp_chunks))
  704. self.assertNotEqual(b"", unused)
  705. self.assertEqual(self.extra, unused + read())
  706. def test_decompress_no_crc32(self):
  707. self.unpacked.crc32 = None
  708. read_zlib_chunks(self.read, self.unpacked)
  709. self.assertEqual(None, self.unpacked.crc32)
  710. def _do_decompress_test(self, buffer_size, **kwargs):
  711. unused = read_zlib_chunks(
  712. self.read, self.unpacked, buffer_size=buffer_size, **kwargs
  713. )
  714. self.assertEqual(self.decomp, b"".join(self.unpacked.decomp_chunks))
  715. self.assertEqual(zlib.crc32(self.comp), self.unpacked.crc32)
  716. self.assertNotEqual(b"", unused)
  717. self.assertEqual(self.extra, unused + self.read())
  718. def test_simple_decompress(self):
  719. self._do_decompress_test(4096)
  720. self.assertEqual(None, self.unpacked.comp_chunks)
  721. # These buffer sizes are not intended to be realistic, but rather simulate
  722. # larger buffer sizes that may end at various places.
  723. def test_decompress_buffer_size_1(self):
  724. self._do_decompress_test(1)
  725. def test_decompress_buffer_size_2(self):
  726. self._do_decompress_test(2)
  727. def test_decompress_buffer_size_3(self):
  728. self._do_decompress_test(3)
  729. def test_decompress_buffer_size_4(self):
  730. self._do_decompress_test(4)
  731. def test_decompress_include_comp(self):
  732. self._do_decompress_test(4096, include_comp=True)
  733. self.assertEqual(self.comp, b"".join(self.unpacked.comp_chunks))
  734. class DeltifyTests(TestCase):
  735. def test_empty(self):
  736. self.assertEqual([], list(deltify_pack_objects([])))
  737. def test_single(self):
  738. b = Blob.from_string(b"foo")
  739. self.assertEqual(
  740. [(b.type_num, b.sha().digest(), None, b.as_raw_string())],
  741. list(deltify_pack_objects([(b, b"")])),
  742. )
  743. def test_simple_delta(self):
  744. b1 = Blob.from_string(b"a" * 101)
  745. b2 = Blob.from_string(b"a" * 100)
  746. delta = create_delta(b1.as_raw_string(), b2.as_raw_string())
  747. self.assertEqual(
  748. [
  749. (b1.type_num, b1.sha().digest(), None, b1.as_raw_string()),
  750. (b2.type_num, b2.sha().digest(), b1.sha().digest(), delta),
  751. ],
  752. list(deltify_pack_objects([(b1, b""), (b2, b"")])),
  753. )
  754. class TestPackStreamReader(TestCase):
  755. def test_read_objects_emtpy(self):
  756. f = BytesIO()
  757. build_pack(f, [])
  758. reader = PackStreamReader(f.read)
  759. self.assertEqual(0, len(list(reader.read_objects())))
  760. def test_read_objects(self):
  761. f = BytesIO()
  762. entries = build_pack(
  763. f,
  764. [
  765. (Blob.type_num, b"blob"),
  766. (OFS_DELTA, (0, b"blob1")),
  767. ],
  768. )
  769. reader = PackStreamReader(f.read)
  770. objects = list(reader.read_objects(compute_crc32=True))
  771. self.assertEqual(2, len(objects))
  772. unpacked_blob, unpacked_delta = objects
  773. self.assertEqual(entries[0][0], unpacked_blob.offset)
  774. self.assertEqual(Blob.type_num, unpacked_blob.pack_type_num)
  775. self.assertEqual(Blob.type_num, unpacked_blob.obj_type_num)
  776. self.assertEqual(None, unpacked_blob.delta_base)
  777. self.assertEqual(b"blob", b"".join(unpacked_blob.decomp_chunks))
  778. self.assertEqual(entries[0][4], unpacked_blob.crc32)
  779. self.assertEqual(entries[1][0], unpacked_delta.offset)
  780. self.assertEqual(OFS_DELTA, unpacked_delta.pack_type_num)
  781. self.assertEqual(None, unpacked_delta.obj_type_num)
  782. self.assertEqual(
  783. unpacked_delta.offset - unpacked_blob.offset,
  784. unpacked_delta.delta_base,
  785. )
  786. delta = create_delta(b"blob", b"blob1")
  787. self.assertEqual(delta, b"".join(unpacked_delta.decomp_chunks))
  788. self.assertEqual(entries[1][4], unpacked_delta.crc32)
  789. def test_read_objects_buffered(self):
  790. f = BytesIO()
  791. build_pack(
  792. f,
  793. [
  794. (Blob.type_num, b"blob"),
  795. (OFS_DELTA, (0, b"blob1")),
  796. ],
  797. )
  798. reader = PackStreamReader(f.read, zlib_bufsize=4)
  799. self.assertEqual(2, len(list(reader.read_objects())))
  800. def test_read_objects_empty(self):
  801. reader = PackStreamReader(BytesIO().read)
  802. self.assertEqual([], list(reader.read_objects()))
  803. class TestPackIterator(DeltaChainIterator):
  804. _compute_crc32 = True
  805. def __init__(self, *args, **kwargs):
  806. super(TestPackIterator, self).__init__(*args, **kwargs)
  807. self._unpacked_offsets = set()
  808. def _result(self, unpacked):
  809. """Return entries in the same format as build_pack."""
  810. return (
  811. unpacked.offset,
  812. unpacked.obj_type_num,
  813. b"".join(unpacked.obj_chunks),
  814. unpacked.sha(),
  815. unpacked.crc32,
  816. )
  817. def _resolve_object(self, offset, pack_type_num, base_chunks):
  818. assert offset not in self._unpacked_offsets, (
  819. "Attempted to re-inflate offset %i" % offset
  820. )
  821. self._unpacked_offsets.add(offset)
  822. return super(TestPackIterator, self)._resolve_object(
  823. offset, pack_type_num, base_chunks
  824. )
  825. class DeltaChainIteratorTests(TestCase):
  826. def setUp(self):
  827. super(DeltaChainIteratorTests, self).setUp()
  828. self.store = MemoryObjectStore()
  829. self.fetched = set()
  830. def store_blobs(self, blobs_data):
  831. blobs = []
  832. for data in blobs_data:
  833. blob = make_object(Blob, data=data)
  834. blobs.append(blob)
  835. self.store.add_object(blob)
  836. return blobs
  837. def get_raw_no_repeat(self, bin_sha):
  838. """Wrapper around store.get_raw that doesn't allow repeat lookups."""
  839. hex_sha = sha_to_hex(bin_sha)
  840. self.assertFalse(
  841. hex_sha in self.fetched,
  842. "Attempted to re-fetch object %s" % hex_sha,
  843. )
  844. self.fetched.add(hex_sha)
  845. return self.store.get_raw(hex_sha)
  846. def make_pack_iter(self, f, thin=None):
  847. if thin is None:
  848. thin = bool(list(self.store))
  849. resolve_ext_ref = thin and self.get_raw_no_repeat or None
  850. data = PackData("test.pack", file=f)
  851. return TestPackIterator.for_pack_data(data, resolve_ext_ref=resolve_ext_ref)
  852. def assertEntriesMatch(self, expected_indexes, entries, pack_iter):
  853. expected = [entries[i] for i in expected_indexes]
  854. self.assertEqual(expected, list(pack_iter._walk_all_chains()))
  855. def test_no_deltas(self):
  856. f = BytesIO()
  857. entries = build_pack(
  858. f,
  859. [
  860. (Commit.type_num, b"commit"),
  861. (Blob.type_num, b"blob"),
  862. (Tree.type_num, b"tree"),
  863. ],
  864. )
  865. self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f))
  866. def test_ofs_deltas(self):
  867. f = BytesIO()
  868. entries = build_pack(
  869. f,
  870. [
  871. (Blob.type_num, b"blob"),
  872. (OFS_DELTA, (0, b"blob1")),
  873. (OFS_DELTA, (0, b"blob2")),
  874. ],
  875. )
  876. self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f))
  877. def test_ofs_deltas_chain(self):
  878. f = BytesIO()
  879. entries = build_pack(
  880. f,
  881. [
  882. (Blob.type_num, b"blob"),
  883. (OFS_DELTA, (0, b"blob1")),
  884. (OFS_DELTA, (1, b"blob2")),
  885. ],
  886. )
  887. self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f))
  888. def test_ref_deltas(self):
  889. f = BytesIO()
  890. entries = build_pack(
  891. f,
  892. [
  893. (REF_DELTA, (1, b"blob1")),
  894. (Blob.type_num, (b"blob")),
  895. (REF_DELTA, (1, b"blob2")),
  896. ],
  897. )
  898. self.assertEntriesMatch([1, 0, 2], entries, self.make_pack_iter(f))
  899. def test_ref_deltas_chain(self):
  900. f = BytesIO()
  901. entries = build_pack(
  902. f,
  903. [
  904. (REF_DELTA, (2, b"blob1")),
  905. (Blob.type_num, (b"blob")),
  906. (REF_DELTA, (1, b"blob2")),
  907. ],
  908. )
  909. self.assertEntriesMatch([1, 2, 0], entries, self.make_pack_iter(f))
  910. def test_ofs_and_ref_deltas(self):
  911. # Deltas pending on this offset are popped before deltas depending on
  912. # this ref.
  913. f = BytesIO()
  914. entries = build_pack(
  915. f,
  916. [
  917. (REF_DELTA, (1, b"blob1")),
  918. (Blob.type_num, (b"blob")),
  919. (OFS_DELTA, (1, b"blob2")),
  920. ],
  921. )
  922. self.assertEntriesMatch([1, 2, 0], entries, self.make_pack_iter(f))
  923. def test_mixed_chain(self):
  924. f = BytesIO()
  925. entries = build_pack(
  926. f,
  927. [
  928. (Blob.type_num, b"blob"),
  929. (REF_DELTA, (2, b"blob2")),
  930. (OFS_DELTA, (0, b"blob1")),
  931. (OFS_DELTA, (1, b"blob3")),
  932. (OFS_DELTA, (0, b"bob")),
  933. ],
  934. )
  935. self.assertEntriesMatch([0, 2, 4, 1, 3], entries, self.make_pack_iter(f))
  936. def test_long_chain(self):
  937. n = 100
  938. objects_spec = [(Blob.type_num, b"blob")]
  939. for i in range(n):
  940. objects_spec.append((OFS_DELTA, (i, b"blob" + str(i).encode("ascii"))))
  941. f = BytesIO()
  942. entries = build_pack(f, objects_spec)
  943. self.assertEntriesMatch(range(n + 1), entries, self.make_pack_iter(f))
  944. def test_branchy_chain(self):
  945. n = 100
  946. objects_spec = [(Blob.type_num, b"blob")]
  947. for i in range(n):
  948. objects_spec.append((OFS_DELTA, (0, b"blob" + str(i).encode("ascii"))))
  949. f = BytesIO()
  950. entries = build_pack(f, objects_spec)
  951. self.assertEntriesMatch(range(n + 1), entries, self.make_pack_iter(f))
  952. def test_ext_ref(self):
  953. (blob,) = self.store_blobs([b"blob"])
  954. f = BytesIO()
  955. entries = build_pack(f, [(REF_DELTA, (blob.id, b"blob1"))], store=self.store)
  956. pack_iter = self.make_pack_iter(f)
  957. self.assertEntriesMatch([0], entries, pack_iter)
  958. self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
  959. def test_ext_ref_chain(self):
  960. (blob,) = self.store_blobs([b"blob"])
  961. f = BytesIO()
  962. entries = build_pack(
  963. f,
  964. [
  965. (REF_DELTA, (1, b"blob2")),
  966. (REF_DELTA, (blob.id, b"blob1")),
  967. ],
  968. store=self.store,
  969. )
  970. pack_iter = self.make_pack_iter(f)
  971. self.assertEntriesMatch([1, 0], entries, pack_iter)
  972. self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
  973. def test_ext_ref_chain_degenerate(self):
  974. # Test a degenerate case where the sender is sending a REF_DELTA
  975. # object that expands to an object already in the repository.
  976. (blob,) = self.store_blobs([b"blob"])
  977. (blob2,) = self.store_blobs([b"blob2"])
  978. assert blob.id < blob2.id
  979. f = BytesIO()
  980. entries = build_pack(
  981. f,
  982. [
  983. (REF_DELTA, (blob.id, b"blob2")),
  984. (REF_DELTA, (0, b"blob3")),
  985. ],
  986. store=self.store,
  987. )
  988. pack_iter = self.make_pack_iter(f)
  989. self.assertEntriesMatch([0, 1], entries, pack_iter)
  990. self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
  991. def test_ext_ref_multiple_times(self):
  992. (blob,) = self.store_blobs([b"blob"])
  993. f = BytesIO()
  994. entries = build_pack(
  995. f,
  996. [
  997. (REF_DELTA, (blob.id, b"blob1")),
  998. (REF_DELTA, (blob.id, b"blob2")),
  999. ],
  1000. store=self.store,
  1001. )
  1002. pack_iter = self.make_pack_iter(f)
  1003. self.assertEntriesMatch([0, 1], entries, pack_iter)
  1004. self.assertEqual([hex_to_sha(blob.id)], pack_iter.ext_refs())
  1005. def test_multiple_ext_refs(self):
  1006. b1, b2 = self.store_blobs([b"foo", b"bar"])
  1007. f = BytesIO()
  1008. entries = build_pack(
  1009. f,
  1010. [
  1011. (REF_DELTA, (b1.id, b"foo1")),
  1012. (REF_DELTA, (b2.id, b"bar2")),
  1013. ],
  1014. store=self.store,
  1015. )
  1016. pack_iter = self.make_pack_iter(f)
  1017. self.assertEntriesMatch([0, 1], entries, pack_iter)
  1018. self.assertEqual([hex_to_sha(b1.id), hex_to_sha(b2.id)], pack_iter.ext_refs())
  1019. def test_bad_ext_ref_non_thin_pack(self):
  1020. (blob,) = self.store_blobs([b"blob"])
  1021. f = BytesIO()
  1022. build_pack(f, [(REF_DELTA, (blob.id, b"blob1"))], store=self.store)
  1023. pack_iter = self.make_pack_iter(f, thin=False)
  1024. try:
  1025. list(pack_iter._walk_all_chains())
  1026. self.fail()
  1027. except KeyError as e:
  1028. self.assertEqual(([blob.id],), e.args)
  1029. def test_bad_ext_ref_thin_pack(self):
  1030. b1, b2, b3 = self.store_blobs([b"foo", b"bar", b"baz"])
  1031. f = BytesIO()
  1032. build_pack(
  1033. f,
  1034. [
  1035. (REF_DELTA, (1, b"foo99")),
  1036. (REF_DELTA, (b1.id, b"foo1")),
  1037. (REF_DELTA, (b2.id, b"bar2")),
  1038. (REF_DELTA, (b3.id, b"baz3")),
  1039. ],
  1040. store=self.store,
  1041. )
  1042. del self.store[b2.id]
  1043. del self.store[b3.id]
  1044. pack_iter = self.make_pack_iter(f)
  1045. try:
  1046. list(pack_iter._walk_all_chains())
  1047. self.fail()
  1048. except KeyError as e:
  1049. self.assertEqual((sorted([b2.id, b3.id]),), (sorted(e.args[0]),))
  1050. class DeltaEncodeSizeTests(TestCase):
  1051. def test_basic(self):
  1052. self.assertEqual(b"\x00", _delta_encode_size(0))
  1053. self.assertEqual(b"\x01", _delta_encode_size(1))
  1054. self.assertEqual(b"\xfa\x01", _delta_encode_size(250))
  1055. self.assertEqual(b"\xe8\x07", _delta_encode_size(1000))
  1056. self.assertEqual(b"\xa0\x8d\x06", _delta_encode_size(100000))
  1057. class EncodeCopyOperationTests(TestCase):
  1058. def test_basic(self):
  1059. self.assertEqual(b"\x80", _encode_copy_operation(0, 0))
  1060. self.assertEqual(b"\x91\x01\x0a", _encode_copy_operation(1, 10))
  1061. self.assertEqual(b"\xb1\x64\xe8\x03", _encode_copy_operation(100, 1000))
  1062. self.assertEqual(b"\x93\xe8\x03\x01", _encode_copy_operation(1000, 1))