test_diff_tree.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. # test_diff_tree.py -- Tests for file and tree diff utilities.
  2. # Copyright (C) 2010 Google, Inc.
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; either version 2
  7. # or (at your option) a later version of the License.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  17. # MA 02110-1301, USA.
  18. """Tests for file and tree diff utilities."""
  19. from itertools import permutations
  20. from dulwich.diff_tree import (
  21. CHANGE_MODIFY,
  22. CHANGE_RENAME,
  23. CHANGE_COPY,
  24. CHANGE_UNCHANGED,
  25. TreeChange,
  26. _merge_entries,
  27. _merge_entries_py,
  28. tree_changes,
  29. tree_changes_for_merge,
  30. _count_blocks,
  31. _count_blocks_py,
  32. _similarity_score,
  33. _tree_change_key,
  34. RenameDetector,
  35. _is_tree,
  36. _is_tree_py
  37. )
  38. from dulwich.index import (
  39. commit_tree,
  40. )
  41. from dulwich.object_store import (
  42. MemoryObjectStore,
  43. )
  44. from dulwich.objects import (
  45. ShaFile,
  46. Blob,
  47. TreeEntry,
  48. Tree,
  49. )
  50. from dulwich.tests import (
  51. TestCase,
  52. )
  53. from dulwich.tests.utils import (
  54. F,
  55. make_object,
  56. functest_builder,
  57. ext_functest_builder,
  58. skipIfPY3,
  59. )
  60. @skipIfPY3
  61. class DiffTestCase(TestCase):
  62. def setUp(self):
  63. super(DiffTestCase, self).setUp()
  64. self.store = MemoryObjectStore()
  65. self.empty_tree = self.commit_tree([])
  66. def commit_tree(self, entries):
  67. commit_blobs = []
  68. for entry in entries:
  69. if len(entry) == 2:
  70. path, obj = entry
  71. mode = F
  72. else:
  73. path, obj, mode = entry
  74. if isinstance(obj, Blob):
  75. self.store.add_object(obj)
  76. sha = obj.id
  77. else:
  78. sha = obj
  79. commit_blobs.append((path, sha, mode))
  80. return self.store[commit_tree(self.store, commit_blobs)]
  81. @skipIfPY3
  82. class TreeChangesTest(DiffTestCase):
  83. def setUp(self):
  84. super(TreeChangesTest, self).setUp()
  85. self.detector = RenameDetector(self.store)
  86. def assertMergeFails(self, merge_entries, name, mode, sha):
  87. t = Tree()
  88. t[name] = (mode, sha)
  89. self.assertRaises((TypeError, ValueError), merge_entries, '', t, t)
  90. def _do_test_merge_entries(self, merge_entries):
  91. blob_a1 = make_object(Blob, data=b'a1')
  92. blob_a2 = make_object(Blob, data=b'a2')
  93. blob_b1 = make_object(Blob, data=b'b1')
  94. blob_c2 = make_object(Blob, data=b'c2')
  95. tree1 = self.commit_tree([(b'a', blob_a1, 0o100644),
  96. (b'b', blob_b1, 0o100755)])
  97. tree2 = self.commit_tree([(b'a', blob_a2, 0o100644),
  98. (b'c', blob_c2, 0o100755)])
  99. self.assertEqual([], merge_entries(b'', self.empty_tree,
  100. self.empty_tree))
  101. self.assertEqual(
  102. [((None, None, None), (b'a', 0o100644, blob_a1.id)),
  103. ((None, None, None), (b'b', 0o100755, blob_b1.id)), ],
  104. merge_entries(b'', self.empty_tree, tree1))
  105. self.assertEqual(
  106. [((None, None, None), (b'x/a', 0o100644, blob_a1.id)),
  107. ((None, None, None), (b'x/b', 0o100755, blob_b1.id)), ],
  108. merge_entries(b'x', self.empty_tree, tree1))
  109. self.assertEqual(
  110. [((b'a', 0o100644, blob_a2.id), (None, None, None)),
  111. ((b'c', 0o100755, blob_c2.id), (None, None, None)), ],
  112. merge_entries(b'', tree2, self.empty_tree))
  113. self.assertEqual(
  114. [((b'a', 0o100644, blob_a1.id), (b'a', 0o100644, blob_a2.id)),
  115. ((b'b', 0o100755, blob_b1.id), (None, None, None)),
  116. ((None, None, None), (b'c', 0o100755, blob_c2.id)), ],
  117. merge_entries(b'', tree1, tree2))
  118. self.assertEqual(
  119. [((b'a', 0o100644, blob_a2.id), (b'a', 0o100644, blob_a1.id)),
  120. ((None, None, None), (b'b', 0o100755, blob_b1.id)),
  121. ((b'c', 0o100755, blob_c2.id), (None, None, None)), ],
  122. merge_entries(b'', tree2, tree1))
  123. self.assertMergeFails(merge_entries, 0xdeadbeef, 0o100644, '1' * 40)
  124. self.assertMergeFails(merge_entries, b'a', b'deadbeef', '1' * 40)
  125. self.assertMergeFails(merge_entries, b'a', 0o100644, 0xdeadbeef)
  126. test_merge_entries = functest_builder(_do_test_merge_entries,
  127. _merge_entries_py)
  128. test_merge_entries_extension = ext_functest_builder(_do_test_merge_entries,
  129. _merge_entries)
  130. def _do_test_is_tree(self, is_tree):
  131. self.assertFalse(is_tree(TreeEntry(None, None, None)))
  132. self.assertFalse(is_tree(TreeEntry(b'a', 0o100644, b'a' * 40)))
  133. self.assertFalse(is_tree(TreeEntry(b'a', 0o100755, b'a' * 40)))
  134. self.assertFalse(is_tree(TreeEntry(b'a', 0o120000, b'a' * 40)))
  135. self.assertTrue(is_tree(TreeEntry(b'a', 0o040000, b'a' * 40)))
  136. self.assertRaises(TypeError, is_tree, TreeEntry(b'a', b'x', b'a' * 40))
  137. self.assertRaises(AttributeError, is_tree, 1234)
  138. test_is_tree = functest_builder(_do_test_is_tree, _is_tree_py)
  139. test_is_tree_extension = ext_functest_builder(_do_test_is_tree, _is_tree)
  140. def assertChangesEqual(self, expected, tree1, tree2, **kwargs):
  141. actual = list(tree_changes(self.store, tree1.id, tree2.id, **kwargs))
  142. self.assertEqual(expected, actual)
  143. # For brevity, the following tests use tuples instead of TreeEntry objects.
  144. def test_tree_changes_empty(self):
  145. self.assertChangesEqual([], self.empty_tree, self.empty_tree)
  146. def test_tree_changes_no_changes(self):
  147. blob = make_object(Blob, data=b'blob')
  148. tree = self.commit_tree([(b'a', blob), (b'b/c', blob)])
  149. self.assertChangesEqual([], self.empty_tree, self.empty_tree)
  150. self.assertChangesEqual([], tree, tree)
  151. self.assertChangesEqual(
  152. [TreeChange(CHANGE_UNCHANGED, (b'a', F, blob.id), (b'a', F, blob.id)),
  153. TreeChange(CHANGE_UNCHANGED, (b'b/c', F, blob.id),
  154. (b'b/c', F, blob.id))],
  155. tree, tree, want_unchanged=True)
  156. def test_tree_changes_add_delete(self):
  157. blob_a = make_object(Blob, data=b'a')
  158. blob_b = make_object(Blob, data=b'b')
  159. tree = self.commit_tree([(b'a', blob_a, 0o100644),
  160. (b'x/b', blob_b, 0o100755)])
  161. self.assertChangesEqual(
  162. [TreeChange.add((b'a', 0o100644, blob_a.id)),
  163. TreeChange.add((b'x/b', 0o100755, blob_b.id))],
  164. self.empty_tree, tree)
  165. self.assertChangesEqual(
  166. [TreeChange.delete((b'a', 0o100644, blob_a.id)),
  167. TreeChange.delete((b'x/b', 0o100755, blob_b.id))],
  168. tree, self.empty_tree)
  169. def test_tree_changes_modify_contents(self):
  170. blob_a1 = make_object(Blob, data=b'a1')
  171. blob_a2 = make_object(Blob, data=b'a2')
  172. tree1 = self.commit_tree([(b'a', blob_a1)])
  173. tree2 = self.commit_tree([(b'a', blob_a2)])
  174. self.assertChangesEqual(
  175. [TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
  176. (b'a', F, blob_a2.id))],
  177. tree1, tree2)
  178. def test_tree_changes_modify_mode(self):
  179. blob_a = make_object(Blob, data=b'a')
  180. tree1 = self.commit_tree([(b'a', blob_a, 0o100644)])
  181. tree2 = self.commit_tree([(b'a', blob_a, 0o100755)])
  182. self.assertChangesEqual(
  183. [TreeChange(CHANGE_MODIFY, (b'a', 0o100644, blob_a.id),
  184. (b'a', 0o100755, blob_a.id))],
  185. tree1, tree2)
  186. def test_tree_changes_change_type(self):
  187. blob_a1 = make_object(Blob, data=b'a')
  188. blob_a2 = make_object(Blob, data=b'/foo/bar')
  189. tree1 = self.commit_tree([(b'a', blob_a1, 0o100644)])
  190. tree2 = self.commit_tree([(b'a', blob_a2, 0o120000)])
  191. self.assertChangesEqual(
  192. [TreeChange.delete((b'a', 0o100644, blob_a1.id)),
  193. TreeChange.add((b'a', 0o120000, blob_a2.id))],
  194. tree1, tree2)
  195. def test_tree_changes_to_tree(self):
  196. blob_a = make_object(Blob, data=b'a')
  197. blob_x = make_object(Blob, data=b'x')
  198. tree1 = self.commit_tree([(b'a', blob_a)])
  199. tree2 = self.commit_tree([(b'a/x', blob_x)])
  200. self.assertChangesEqual(
  201. [TreeChange.delete((b'a', F, blob_a.id)),
  202. TreeChange.add((b'a/x', F, blob_x.id))],
  203. tree1, tree2)
  204. def test_tree_changes_complex(self):
  205. blob_a_1 = make_object(Blob, data=b'a1_1')
  206. blob_bx1_1 = make_object(Blob, data=b'bx1_1')
  207. blob_bx2_1 = make_object(Blob, data=b'bx2_1')
  208. blob_by1_1 = make_object(Blob, data=b'by1_1')
  209. blob_by2_1 = make_object(Blob, data=b'by2_1')
  210. tree1 = self.commit_tree([
  211. (b'a', blob_a_1),
  212. (b'b/x/1', blob_bx1_1),
  213. (b'b/x/2', blob_bx2_1),
  214. (b'b/y/1', blob_by1_1),
  215. (b'b/y/2', blob_by2_1),
  216. ])
  217. blob_a_2 = make_object(Blob, data=b'a1_2')
  218. blob_bx1_2 = blob_bx1_1
  219. blob_by_2 = make_object(Blob, data=b'by_2')
  220. blob_c_2 = make_object(Blob, data=b'c_2')
  221. tree2 = self.commit_tree([
  222. (b'a', blob_a_2),
  223. (b'b/x/1', blob_bx1_2),
  224. (b'b/y', blob_by_2),
  225. (b'c', blob_c_2),
  226. ])
  227. self.assertChangesEqual(
  228. [TreeChange(CHANGE_MODIFY, (b'a', F, blob_a_1.id),
  229. (b'a', F, blob_a_2.id)),
  230. TreeChange.delete((b'b/x/2', F, blob_bx2_1.id)),
  231. TreeChange.add((b'b/y', F, blob_by_2.id)),
  232. TreeChange.delete((b'b/y/1', F, blob_by1_1.id)),
  233. TreeChange.delete((b'b/y/2', F, blob_by2_1.id)),
  234. TreeChange.add((b'c', F, blob_c_2.id))],
  235. tree1, tree2)
  236. def test_tree_changes_name_order(self):
  237. blob = make_object(Blob, data=b'a')
  238. tree1 = self.commit_tree([(b'a', blob), (b'a.', blob), (b'a..', blob)])
  239. # Tree order is the reverse of this, so if we used tree order, 'a..'
  240. # would not be merged.
  241. tree2 = self.commit_tree([(b'a/x', blob), (b'a./x', blob), (b'a..', blob)])
  242. self.assertChangesEqual(
  243. [TreeChange.delete((b'a', F, blob.id)),
  244. TreeChange.add((b'a/x', F, blob.id)),
  245. TreeChange.delete((b'a.', F, blob.id)),
  246. TreeChange.add((b'a./x', F, blob.id))],
  247. tree1, tree2)
  248. def test_tree_changes_prune(self):
  249. blob_a1 = make_object(Blob, data=b'a1')
  250. blob_a2 = make_object(Blob, data=b'a2')
  251. blob_x = make_object(Blob, data=b'x')
  252. tree1 = self.commit_tree([(b'a', blob_a1), (b'b/x', blob_x)])
  253. tree2 = self.commit_tree([(b'a', blob_a2), (b'b/x', blob_x)])
  254. # Remove identical items so lookups will fail unless we prune.
  255. subtree = self.store[tree1[b'b'][1]]
  256. for entry in subtree.items():
  257. del self.store[entry.sha]
  258. del self.store[subtree.id]
  259. self.assertChangesEqual(
  260. [TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
  261. (b'a', F, blob_a2.id))],
  262. tree1, tree2)
  263. def test_tree_changes_rename_detector(self):
  264. blob_a1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  265. blob_a2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  266. blob_b = make_object(Blob, data=b'b')
  267. tree1 = self.commit_tree([(b'a', blob_a1), (b'b', blob_b)])
  268. tree2 = self.commit_tree([(b'c', blob_a2), (b'b', blob_b)])
  269. detector = RenameDetector(self.store)
  270. self.assertChangesEqual(
  271. [TreeChange.delete((b'a', F, blob_a1.id)),
  272. TreeChange.add((b'c', F, blob_a2.id))],
  273. tree1, tree2)
  274. self.assertChangesEqual(
  275. [TreeChange.delete((b'a', F, blob_a1.id)),
  276. TreeChange(CHANGE_UNCHANGED, (b'b', F, blob_b.id),
  277. (b'b', F, blob_b.id)),
  278. TreeChange.add((b'c', F, blob_a2.id))],
  279. tree1, tree2, want_unchanged=True)
  280. self.assertChangesEqual(
  281. [TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
  282. (b'c', F, blob_a2.id))],
  283. tree1, tree2, rename_detector=detector)
  284. self.assertChangesEqual(
  285. [TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
  286. (b'c', F, blob_a2.id)),
  287. TreeChange(CHANGE_UNCHANGED, (b'b', F, blob_b.id),
  288. (b'b', F, blob_b.id))],
  289. tree1, tree2, rename_detector=detector, want_unchanged=True)
  290. def assertChangesForMergeEqual(self, expected, parent_trees, merge_tree,
  291. **kwargs):
  292. parent_tree_ids = [t.id for t in parent_trees]
  293. actual = list(tree_changes_for_merge(
  294. self.store, parent_tree_ids, merge_tree.id, **kwargs))
  295. self.assertEqual(expected, actual)
  296. parent_tree_ids.reverse()
  297. expected = [list(reversed(cs)) for cs in expected]
  298. actual = list(tree_changes_for_merge(
  299. self.store, parent_tree_ids, merge_tree.id, **kwargs))
  300. self.assertEqual(expected, actual)
  301. def test_tree_changes_for_merge_add_no_conflict(self):
  302. blob = make_object(Blob, data=b'blob')
  303. parent1 = self.commit_tree([])
  304. parent2 = merge = self.commit_tree([(b'a', blob)])
  305. self.assertChangesForMergeEqual([], [parent1, parent2], merge)
  306. self.assertChangesForMergeEqual([], [parent2, parent2], merge)
  307. def test_tree_changes_for_merge_add_modify_conflict(self):
  308. blob1 = make_object(Blob, data=b'1')
  309. blob2 = make_object(Blob, data=b'2')
  310. parent1 = self.commit_tree([])
  311. parent2 = self.commit_tree([(b'a', blob1)])
  312. merge = self.commit_tree([(b'a', blob2)])
  313. self.assertChangesForMergeEqual(
  314. [[TreeChange.add((b'a', F, blob2.id)),
  315. TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob2.id))]],
  316. [parent1, parent2], merge)
  317. def test_tree_changes_for_merge_modify_modify_conflict(self):
  318. blob1 = make_object(Blob, data=b'1')
  319. blob2 = make_object(Blob, data=b'2')
  320. blob3 = make_object(Blob, data=b'3')
  321. parent1 = self.commit_tree([(b'a', blob1)])
  322. parent2 = self.commit_tree([(b'a', blob2)])
  323. merge = self.commit_tree([(b'a', blob3)])
  324. self.assertChangesForMergeEqual(
  325. [[TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob3.id)),
  326. TreeChange(CHANGE_MODIFY, (b'a', F, blob2.id), (b'a', F, blob3.id))]],
  327. [parent1, parent2], merge)
  328. def test_tree_changes_for_merge_modify_no_conflict(self):
  329. blob1 = make_object(Blob, data=b'1')
  330. blob2 = make_object(Blob, data=b'2')
  331. parent1 = self.commit_tree([(b'a', blob1)])
  332. parent2 = merge = self.commit_tree([(b'a', blob2)])
  333. self.assertChangesForMergeEqual([], [parent1, parent2], merge)
  334. def test_tree_changes_for_merge_delete_delete_conflict(self):
  335. blob1 = make_object(Blob, data=b'1')
  336. blob2 = make_object(Blob, data=b'2')
  337. parent1 = self.commit_tree([(b'a', blob1)])
  338. parent2 = self.commit_tree([(b'a', blob2)])
  339. merge = self.commit_tree([])
  340. self.assertChangesForMergeEqual(
  341. [[TreeChange.delete((b'a', F, blob1.id)),
  342. TreeChange.delete((b'a', F, blob2.id))]],
  343. [parent1, parent2], merge)
  344. def test_tree_changes_for_merge_delete_no_conflict(self):
  345. blob = make_object(Blob, data=b'blob')
  346. has = self.commit_tree([(b'a', blob)])
  347. doesnt_have = self.commit_tree([])
  348. self.assertChangesForMergeEqual([], [has, has], doesnt_have)
  349. self.assertChangesForMergeEqual([], [has, doesnt_have], doesnt_have)
  350. def test_tree_changes_for_merge_octopus_no_conflict(self):
  351. r = list(range(5))
  352. blobs = [make_object(Blob, data=bytes(i)) for i in r]
  353. parents = [self.commit_tree([(b'a', blobs[i])]) for i in r]
  354. for i in r:
  355. # Take the SHA from each of the parents.
  356. self.assertChangesForMergeEqual([], parents, parents[i])
  357. def test_tree_changes_for_merge_octopus_modify_conflict(self):
  358. # Because the octopus merge strategy is limited, I doubt it's possible
  359. # to create this with the git command line. But the output is well-
  360. # defined, so test it anyway.
  361. r = list(range(5))
  362. parent_blobs = [make_object(Blob, data=bytes(i)) for i in r]
  363. merge_blob = make_object(Blob, data=b'merge')
  364. parents = [self.commit_tree([(b'a', parent_blobs[i])]) for i in r]
  365. merge = self.commit_tree([(b'a', merge_blob)])
  366. expected = [[TreeChange(CHANGE_MODIFY, (b'a', F, parent_blobs[i].id),
  367. (b'a', F, merge_blob.id)) for i in r]]
  368. self.assertChangesForMergeEqual(expected, parents, merge)
  369. def test_tree_changes_for_merge_octopus_delete(self):
  370. blob1 = make_object(Blob, data=b'1')
  371. blob2 = make_object(Blob, data=b'3')
  372. parent1 = self.commit_tree([(b'a', blob1)])
  373. parent2 = self.commit_tree([(b'a', blob2)])
  374. parent3 = merge = self.commit_tree([])
  375. self.assertChangesForMergeEqual([], [parent1, parent1, parent1], merge)
  376. self.assertChangesForMergeEqual([], [parent1, parent1, parent3], merge)
  377. self.assertChangesForMergeEqual([], [parent1, parent3, parent3], merge)
  378. self.assertChangesForMergeEqual(
  379. [[TreeChange.delete((b'a', F, blob1.id)),
  380. TreeChange.delete((b'a', F, blob2.id)),
  381. None]],
  382. [parent1, parent2, parent3], merge)
  383. def test_tree_changes_for_merge_add_add_same_conflict(self):
  384. blob = make_object(Blob, data=b'a\nb\nc\nd\n')
  385. parent1 = self.commit_tree([(b'a', blob)])
  386. parent2 = self.commit_tree([])
  387. merge = self.commit_tree([(b'b', blob)])
  388. add = TreeChange.add((b'b', F, blob.id))
  389. self.assertChangesForMergeEqual([[add, add]], [parent1, parent2], merge)
  390. def test_tree_changes_for_merge_add_exact_rename_conflict(self):
  391. blob = make_object(Blob, data=b'a\nb\nc\nd\n')
  392. parent1 = self.commit_tree([(b'a', blob)])
  393. parent2 = self.commit_tree([])
  394. merge = self.commit_tree([(b'b', blob)])
  395. self.assertChangesForMergeEqual(
  396. [[TreeChange(CHANGE_RENAME, (b'a', F, blob.id), (b'b', F, blob.id)),
  397. TreeChange.add((b'b', F, blob.id))]],
  398. [parent1, parent2], merge, rename_detector=self.detector)
  399. def test_tree_changes_for_merge_add_content_rename_conflict(self):
  400. blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  401. blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  402. parent1 = self.commit_tree([(b'a', blob1)])
  403. parent2 = self.commit_tree([])
  404. merge = self.commit_tree([(b'b', blob2)])
  405. self.assertChangesForMergeEqual(
  406. [[TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob2.id)),
  407. TreeChange.add((b'b', F, blob2.id))]],
  408. [parent1, parent2], merge, rename_detector=self.detector)
  409. def test_tree_changes_for_merge_modify_rename_conflict(self):
  410. blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  411. blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  412. parent1 = self.commit_tree([(b'a', blob1)])
  413. parent2 = self.commit_tree([(b'b', blob1)])
  414. merge = self.commit_tree([(b'b', blob2)])
  415. self.assertChangesForMergeEqual(
  416. [[TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob2.id)),
  417. TreeChange(CHANGE_MODIFY, (b'b', F, blob1.id), (b'b', F, blob2.id))]],
  418. [parent1, parent2], merge, rename_detector=self.detector)
  419. @skipIfPY3
  420. class RenameDetectionTest(DiffTestCase):
  421. def _do_test_count_blocks(self, count_blocks):
  422. blob = make_object(Blob, data=b'a\nb\na\n')
  423. self.assertEqual({hash(b'a\n'): 4, hash(b'b\n'): 2}, count_blocks(blob))
  424. test_count_blocks = functest_builder(_do_test_count_blocks,
  425. _count_blocks_py)
  426. test_count_blocks_extension = ext_functest_builder(_do_test_count_blocks,
  427. _count_blocks)
  428. def _do_test_count_blocks_no_newline(self, count_blocks):
  429. blob = make_object(Blob, data=b'a\na')
  430. self.assertEqual({hash(b'a\n'): 2, hash(b'a'): 1}, _count_blocks(blob))
  431. test_count_blocks_no_newline = functest_builder(
  432. _do_test_count_blocks_no_newline, _count_blocks_py)
  433. test_count_blocks_no_newline_extension = ext_functest_builder(
  434. _do_test_count_blocks_no_newline, _count_blocks)
  435. def _do_test_count_blocks_chunks(self, count_blocks):
  436. blob = ShaFile.from_raw_chunks(Blob.type_num, [b'a\nb', b'\na\n'])
  437. self.assertEqual({hash(b'a\n'): 4, hash(b'b\n'): 2}, _count_blocks(blob))
  438. test_count_blocks_chunks = functest_builder(_do_test_count_blocks_chunks,
  439. _count_blocks_py)
  440. test_count_blocks_chunks_extension = ext_functest_builder(
  441. _do_test_count_blocks_chunks, _count_blocks)
  442. def _do_test_count_blocks_long_lines(self, count_blocks):
  443. a = b'a' * 64
  444. data = a + b'xxx\ny\n' + a + b'zzz\n'
  445. blob = make_object(Blob, data=data)
  446. self.assertEqual({hash(b'a' * 64): 128, hash(b'xxx\n'): 4, hash(b'y\n'): 2,
  447. hash(b'zzz\n'): 4},
  448. _count_blocks(blob))
  449. test_count_blocks_long_lines = functest_builder(
  450. _do_test_count_blocks_long_lines, _count_blocks_py)
  451. test_count_blocks_long_lines_extension = ext_functest_builder(
  452. _do_test_count_blocks_long_lines, _count_blocks)
  453. def assertSimilar(self, expected_score, blob1, blob2):
  454. self.assertEqual(expected_score, _similarity_score(blob1, blob2))
  455. self.assertEqual(expected_score, _similarity_score(blob2, blob1))
  456. def test_similarity_score(self):
  457. blob0 = make_object(Blob, data=b'')
  458. blob1 = make_object(Blob, data=b'ab\ncd\ncd\n')
  459. blob2 = make_object(Blob, data=b'ab\n')
  460. blob3 = make_object(Blob, data=b'cd\n')
  461. blob4 = make_object(Blob, data=b'cd\ncd\n')
  462. self.assertSimilar(100, blob0, blob0)
  463. self.assertSimilar(0, blob0, blob1)
  464. self.assertSimilar(33, blob1, blob2)
  465. self.assertSimilar(33, blob1, blob3)
  466. self.assertSimilar(66, blob1, blob4)
  467. self.assertSimilar(0, blob2, blob3)
  468. self.assertSimilar(50, blob3, blob4)
  469. def test_similarity_score_cache(self):
  470. blob1 = make_object(Blob, data=b'ab\ncd\n')
  471. blob2 = make_object(Blob, data=b'ab\n')
  472. block_cache = {}
  473. self.assertEqual(
  474. 50, _similarity_score(blob1, blob2, block_cache=block_cache))
  475. self.assertEqual(set([blob1.id, blob2.id]), set(block_cache))
  476. def fail_chunks():
  477. self.fail('Unexpected call to as_raw_chunks()')
  478. blob1.as_raw_chunks = blob2.as_raw_chunks = fail_chunks
  479. blob1.raw_length = lambda: 6
  480. blob2.raw_length = lambda: 3
  481. self.assertEqual(
  482. 50, _similarity_score(blob1, blob2, block_cache=block_cache))
  483. def test_tree_entry_sort(self):
  484. sha = 'abcd' * 10
  485. expected_entries = [
  486. TreeChange.add(TreeEntry(b'aaa', F, sha)),
  487. TreeChange(CHANGE_COPY, TreeEntry(b'bbb', F, sha),
  488. TreeEntry(b'aab', F, sha)),
  489. TreeChange(CHANGE_MODIFY, TreeEntry(b'bbb', F, sha),
  490. TreeEntry(b'bbb', F, b'dabc' * 10)),
  491. TreeChange(CHANGE_RENAME, TreeEntry(b'bbc', F, sha),
  492. TreeEntry(b'ddd', F, sha)),
  493. TreeChange.delete(TreeEntry(b'ccc', F, sha)),
  494. ]
  495. for perm in permutations(expected_entries):
  496. self.assertEqual(expected_entries,
  497. sorted(perm, key=_tree_change_key))
  498. def detect_renames(self, tree1, tree2, want_unchanged=False, **kwargs):
  499. detector = RenameDetector(self.store, **kwargs)
  500. return detector.changes_with_renames(tree1.id, tree2.id,
  501. want_unchanged=want_unchanged)
  502. def test_no_renames(self):
  503. blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  504. blob2 = make_object(Blob, data=b'a\nb\ne\nf\n')
  505. blob3 = make_object(Blob, data=b'a\nb\ng\nh\n')
  506. tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
  507. tree2 = self.commit_tree([(b'a', blob1), (b'b', blob3)])
  508. self.assertEqual(
  509. [TreeChange(CHANGE_MODIFY, (b'b', F, blob2.id), (b'b', F, blob3.id))],
  510. self.detect_renames(tree1, tree2))
  511. def test_exact_rename_one_to_one(self):
  512. blob1 = make_object(Blob, data=b'1')
  513. blob2 = make_object(Blob, data=b'2')
  514. tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
  515. tree2 = self.commit_tree([(b'c', blob1), (b'd', blob2)])
  516. self.assertEqual(
  517. [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'c', F, blob1.id)),
  518. TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'd', F, blob2.id))],
  519. self.detect_renames(tree1, tree2))
  520. def test_exact_rename_split_different_type(self):
  521. blob = make_object(Blob, data=b'/foo')
  522. tree1 = self.commit_tree([(b'a', blob, 0o100644)])
  523. tree2 = self.commit_tree([(b'a', blob, 0o120000)])
  524. self.assertEqual(
  525. [TreeChange.add((b'a', 0o120000, blob.id)),
  526. TreeChange.delete((b'a', 0o100644, blob.id))],
  527. self.detect_renames(tree1, tree2))
  528. def test_exact_rename_and_different_type(self):
  529. blob1 = make_object(Blob, data=b'1')
  530. blob2 = make_object(Blob, data=b'2')
  531. tree1 = self.commit_tree([(b'a', blob1)])
  532. tree2 = self.commit_tree([(b'a', blob2, 0o120000), (b'b', blob1)])
  533. self.assertEqual(
  534. [TreeChange.add((b'a', 0o120000, blob2.id)),
  535. TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob1.id))],
  536. self.detect_renames(tree1, tree2))
  537. def test_exact_rename_one_to_many(self):
  538. blob = make_object(Blob, data=b'1')
  539. tree1 = self.commit_tree([(b'a', blob)])
  540. tree2 = self.commit_tree([(b'b', blob), (b'c', blob)])
  541. self.assertEqual(
  542. [TreeChange(CHANGE_RENAME, (b'a', F, blob.id), (b'b', F, blob.id)),
  543. TreeChange(CHANGE_COPY, (b'a', F, blob.id), (b'c', F, blob.id))],
  544. self.detect_renames(tree1, tree2))
  545. def test_exact_rename_many_to_one(self):
  546. blob = make_object(Blob, data=b'1')
  547. tree1 = self.commit_tree([(b'a', blob), (b'b', blob)])
  548. tree2 = self.commit_tree([(b'c', blob)])
  549. self.assertEqual(
  550. [TreeChange(CHANGE_RENAME, (b'a', F, blob.id), (b'c', F, blob.id)),
  551. TreeChange.delete((b'b', F, blob.id))],
  552. self.detect_renames(tree1, tree2))
  553. def test_exact_rename_many_to_many(self):
  554. blob = make_object(Blob, data=b'1')
  555. tree1 = self.commit_tree([(b'a', blob), (b'b', blob)])
  556. tree2 = self.commit_tree([(b'c', blob), (b'd', blob), (b'e', blob)])
  557. self.assertEqual(
  558. [TreeChange(CHANGE_RENAME, (b'a', F, blob.id), (b'c', F, blob.id)),
  559. TreeChange(CHANGE_COPY, (b'a', F, blob.id), (b'e', F, blob.id)),
  560. TreeChange(CHANGE_RENAME, (b'b', F, blob.id), (b'd', F, blob.id))],
  561. self.detect_renames(tree1, tree2))
  562. def test_exact_copy_modify(self):
  563. blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  564. blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  565. tree1 = self.commit_tree([(b'a', blob1)])
  566. tree2 = self.commit_tree([(b'a', blob2), (b'b', blob1)])
  567. self.assertEqual(
  568. [TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob2.id)),
  569. TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'b', F, blob1.id))],
  570. self.detect_renames(tree1, tree2))
  571. def test_exact_copy_change_mode(self):
  572. blob = make_object(Blob, data=b'a\nb\nc\nd\n')
  573. tree1 = self.commit_tree([(b'a', blob)])
  574. tree2 = self.commit_tree([(b'a', blob, 0o100755), (b'b', blob)])
  575. self.assertEqual(
  576. [TreeChange(CHANGE_MODIFY, (b'a', F, blob.id),
  577. (b'a', 0o100755, blob.id)),
  578. TreeChange(CHANGE_COPY, (b'a', F, blob.id), (b'b', F, blob.id))],
  579. self.detect_renames(tree1, tree2))
  580. def test_rename_threshold(self):
  581. blob1 = make_object(Blob, data=b'a\nb\nc\n')
  582. blob2 = make_object(Blob, data=b'a\nb\nd\n')
  583. tree1 = self.commit_tree([(b'a', blob1)])
  584. tree2 = self.commit_tree([(b'b', blob2)])
  585. self.assertEqual(
  586. [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob2.id))],
  587. self.detect_renames(tree1, tree2, rename_threshold=50))
  588. self.assertEqual(
  589. [TreeChange.delete((b'a', F, blob1.id)),
  590. TreeChange.add((b'b', F, blob2.id))],
  591. self.detect_renames(tree1, tree2, rename_threshold=75))
  592. def test_content_rename_max_files(self):
  593. blob1 = make_object(Blob, data=b'a\nb\nc\nd')
  594. blob4 = make_object(Blob, data=b'a\nb\nc\ne\n')
  595. blob2 = make_object(Blob, data=b'e\nf\ng\nh\n')
  596. blob3 = make_object(Blob, data=b'e\nf\ng\ni\n')
  597. tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
  598. tree2 = self.commit_tree([(b'c', blob3), (b'd', blob4)])
  599. self.assertEqual(
  600. [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'd', F, blob4.id)),
  601. TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'c', F, blob3.id))],
  602. self.detect_renames(tree1, tree2))
  603. self.assertEqual(
  604. [TreeChange.delete((b'a', F, blob1.id)),
  605. TreeChange.delete((b'b', F, blob2.id)),
  606. TreeChange.add((b'c', F, blob3.id)),
  607. TreeChange.add((b'd', F, blob4.id))],
  608. self.detect_renames(tree1, tree2, max_files=1))
  609. def test_content_rename_one_to_one(self):
  610. b11 = make_object(Blob, data=b'a\nb\nc\nd\n')
  611. b12 = make_object(Blob, data=b'a\nb\nc\ne\n')
  612. b21 = make_object(Blob, data=b'e\nf\ng\n\h')
  613. b22 = make_object(Blob, data=b'e\nf\ng\n\i')
  614. tree1 = self.commit_tree([(b'a', b11), (b'b', b21)])
  615. tree2 = self.commit_tree([(b'c', b12), (b'd', b22)])
  616. self.assertEqual(
  617. [TreeChange(CHANGE_RENAME, (b'a', F, b11.id), (b'c', F, b12.id)),
  618. TreeChange(CHANGE_RENAME, (b'b', F, b21.id), (b'd', F, b22.id))],
  619. self.detect_renames(tree1, tree2))
  620. def test_content_rename_one_to_one_ordering(self):
  621. blob1 = make_object(Blob, data=b'a\nb\nc\nd\ne\nf\n')
  622. blob2 = make_object(Blob, data=b'a\nb\nc\nd\ng\nh\n')
  623. # 6/10 match to blob1, 8/10 match to blob2
  624. blob3 = make_object(Blob, data=b'a\nb\nc\nd\ng\ni\n')
  625. tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
  626. tree2 = self.commit_tree([(b'c', blob3)])
  627. self.assertEqual(
  628. [TreeChange.delete((b'a', F, blob1.id)),
  629. TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'c', F, blob3.id))],
  630. self.detect_renames(tree1, tree2))
  631. tree3 = self.commit_tree([(b'a', blob2), (b'b', blob1)])
  632. tree4 = self.commit_tree([(b'c', blob3)])
  633. self.assertEqual(
  634. [TreeChange(CHANGE_RENAME, (b'a', F, blob2.id), (b'c', F, blob3.id)),
  635. TreeChange.delete((b'b', F, blob1.id))],
  636. self.detect_renames(tree3, tree4))
  637. def test_content_rename_one_to_many(self):
  638. blob1 = make_object(Blob, data=b'aa\nb\nc\nd\ne\n')
  639. blob2 = make_object(Blob, data=b'ab\nb\nc\nd\ne\n') # 8/11 match
  640. blob3 = make_object(Blob, data=b'aa\nb\nc\nd\nf\n') # 9/11 match
  641. tree1 = self.commit_tree([(b'a', blob1)])
  642. tree2 = self.commit_tree([(b'b', blob2), (b'c', blob3)])
  643. self.assertEqual(
  644. [TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'b', F, blob2.id)),
  645. TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'c', F, blob3.id))],
  646. self.detect_renames(tree1, tree2))
  647. def test_content_rename_many_to_one(self):
  648. blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  649. blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  650. blob3 = make_object(Blob, data=b'a\nb\nc\nf\n')
  651. tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
  652. tree2 = self.commit_tree([(b'c', blob3)])
  653. self.assertEqual(
  654. [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'c', F, blob3.id)),
  655. TreeChange.delete((b'b', F, blob2.id))],
  656. self.detect_renames(tree1, tree2))
  657. def test_content_rename_many_to_many(self):
  658. blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  659. blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  660. blob3 = make_object(Blob, data=b'a\nb\nc\nf\n')
  661. blob4 = make_object(Blob, data=b'a\nb\nc\ng\n')
  662. tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
  663. tree2 = self.commit_tree([(b'c', blob3), (b'd', blob4)])
  664. # TODO(dborowitz): Distribute renames rather than greedily choosing
  665. # copies.
  666. self.assertEqual(
  667. [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'c', F, blob3.id)),
  668. TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'd', F, blob4.id)),
  669. TreeChange.delete((b'b', F, blob2.id))],
  670. self.detect_renames(tree1, tree2))
  671. def test_content_rename_with_more_deletions(self):
  672. blob1 = make_object(Blob, data='')
  673. tree1 = self.commit_tree([('a', blob1), ('b', blob1), ('c', blob1), ('d', blob1)])
  674. tree2 = self.commit_tree([('e', blob1), ('f', blob1), ('g', blob1)])
  675. self.maxDiff = None
  676. self.assertEqual(
  677. [TreeChange(CHANGE_RENAME, ('a', F, blob1.id), ('e', F, blob1.id)),
  678. TreeChange(CHANGE_RENAME, ('b', F, blob1.id), ('f', F, blob1.id)),
  679. TreeChange(CHANGE_RENAME, ('c', F, blob1.id), ('g', F, blob1.id)),
  680. TreeChange.delete(('d', F, blob1.id))],
  681. self.detect_renames(tree1, tree2))
  682. def test_content_rename_gitlink(self):
  683. blob1 = make_object(Blob, data=b'blob1')
  684. blob2 = make_object(Blob, data=b'blob2')
  685. link1 = b'1' * 40
  686. link2 = b'2' * 40
  687. tree1 = self.commit_tree([(b'a', blob1), (b'b', link1, 0o160000)])
  688. tree2 = self.commit_tree([(b'c', blob2), (b'd', link2, 0o160000)])
  689. self.assertEqual(
  690. [TreeChange.delete((b'a', 0o100644, blob1.id)),
  691. TreeChange.delete((b'b', 0o160000, link1)),
  692. TreeChange.add((b'c', 0o100644, blob2.id)),
  693. TreeChange.add((b'd', 0o160000, link2))],
  694. self.detect_renames(tree1, tree2))
  695. def test_exact_rename_swap(self):
  696. blob1 = make_object(Blob, data=b'1')
  697. blob2 = make_object(Blob, data=b'2')
  698. tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
  699. tree2 = self.commit_tree([(b'a', blob2), (b'b', blob1)])
  700. self.assertEqual(
  701. [TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob2.id)),
  702. TreeChange(CHANGE_MODIFY, (b'b', F, blob2.id), (b'b', F, blob1.id))],
  703. self.detect_renames(tree1, tree2))
  704. self.assertEqual(
  705. [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob1.id)),
  706. TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'a', F, blob2.id))],
  707. self.detect_renames(tree1, tree2, rewrite_threshold=50))
  708. def test_content_rename_swap(self):
  709. blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  710. blob2 = make_object(Blob, data=b'e\nf\ng\nh\n')
  711. blob3 = make_object(Blob, data=b'a\nb\nc\ne\n')
  712. blob4 = make_object(Blob, data=b'e\nf\ng\ni\n')
  713. tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
  714. tree2 = self.commit_tree([(b'a', blob4), (b'b', blob3)])
  715. self.assertEqual(
  716. [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob3.id)),
  717. TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'a', F, blob4.id))],
  718. self.detect_renames(tree1, tree2, rewrite_threshold=60))
  719. def test_rewrite_threshold(self):
  720. blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  721. blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  722. blob3 = make_object(Blob, data=b'a\nb\nf\ng\n')
  723. tree1 = self.commit_tree([(b'a', blob1)])
  724. tree2 = self.commit_tree([(b'a', blob3), (b'b', blob2)])
  725. no_renames = [
  726. TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob3.id)),
  727. TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'b', F, blob2.id))]
  728. self.assertEqual(
  729. no_renames, self.detect_renames(tree1, tree2))
  730. self.assertEqual(
  731. no_renames, self.detect_renames(tree1, tree2, rewrite_threshold=40))
  732. self.assertEqual(
  733. [TreeChange.add((b'a', F, blob3.id)),
  734. TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob2.id))],
  735. self.detect_renames(tree1, tree2, rewrite_threshold=80))
  736. def test_find_copies_harder_exact(self):
  737. blob = make_object(Blob, data=b'blob')
  738. tree1 = self.commit_tree([(b'a', blob)])
  739. tree2 = self.commit_tree([(b'a', blob), (b'b', blob)])
  740. self.assertEqual([TreeChange.add((b'b', F, blob.id))],
  741. self.detect_renames(tree1, tree2))
  742. self.assertEqual(
  743. [TreeChange(CHANGE_COPY, (b'a', F, blob.id), (b'b', F, blob.id))],
  744. self.detect_renames(tree1, tree2, find_copies_harder=True))
  745. def test_find_copies_harder_content(self):
  746. blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  747. blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  748. tree1 = self.commit_tree([(b'a', blob1)])
  749. tree2 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
  750. self.assertEqual([TreeChange.add((b'b', F, blob2.id))],
  751. self.detect_renames(tree1, tree2))
  752. self.assertEqual(
  753. [TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'b', F, blob2.id))],
  754. self.detect_renames(tree1, tree2, find_copies_harder=True))
  755. def test_find_copies_harder_with_rewrites(self):
  756. blob_a1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  757. blob_a2 = make_object(Blob, data=b'f\ng\nh\ni\n')
  758. blob_b2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  759. tree1 = self.commit_tree([(b'a', blob_a1)])
  760. tree2 = self.commit_tree([(b'a', blob_a2), (b'b', blob_b2)])
  761. self.assertEqual(
  762. [TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
  763. (b'a', F, blob_a2.id)),
  764. TreeChange(CHANGE_COPY, (b'a', F, blob_a1.id), (b'b', F, blob_b2.id))],
  765. self.detect_renames(tree1, tree2, find_copies_harder=True))
  766. self.assertEqual(
  767. [TreeChange.add((b'a', F, blob_a2.id)),
  768. TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
  769. (b'b', F, blob_b2.id))],
  770. self.detect_renames(tree1, tree2, rewrite_threshold=50,
  771. find_copies_harder=True))
  772. def test_reuse_detector(self):
  773. blob = make_object(Blob, data=b'blob')
  774. tree1 = self.commit_tree([(b'a', blob)])
  775. tree2 = self.commit_tree([(b'b', blob)])
  776. detector = RenameDetector(self.store)
  777. changes = [TreeChange(CHANGE_RENAME, (b'a', F, blob.id),
  778. (b'b', F, blob.id))]
  779. self.assertEqual(changes,
  780. detector.changes_with_renames(tree1.id, tree2.id))
  781. self.assertEqual(changes,
  782. detector.changes_with_renames(tree1.id, tree2.id))
  783. def test_want_unchanged(self):
  784. blob_a1 = make_object(Blob, data=b'a\nb\nc\nd\n')
  785. blob_b = make_object(Blob, data=b'b')
  786. blob_c2 = make_object(Blob, data=b'a\nb\nc\ne\n')
  787. tree1 = self.commit_tree([(b'a', blob_a1), (b'b', blob_b)])
  788. tree2 = self.commit_tree([(b'c', blob_c2), (b'b', blob_b)])
  789. self.assertEqual(
  790. [TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
  791. (b'c', F, blob_c2.id))],
  792. self.detect_renames(tree1, tree2))
  793. self.assertEqual(
  794. [TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
  795. (b'c', F, blob_c2.id)),
  796. TreeChange(CHANGE_UNCHANGED, (b'b', F, blob_b.id),
  797. (b'b', F, blob_b.id))],
  798. self.detect_renames(tree1, tree2, want_unchanged=True))