test_walk.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. # test_walk.py -- Tests for commit walking functionality.
  2. # Copyright (C) 2010 Google, Inc.
  3. #
  4. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  5. # General Public License as public by the Free Software Foundation; version 2.0
  6. # or (at your option) any later version. You can redistribute it and/or
  7. # modify it under the terms of either of these two licenses.
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. #
  15. # You should have received a copy of the licenses; if not, see
  16. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  17. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  18. # License, Version 2.0.
  19. #
  20. """Tests for commit walking functionality."""
  21. from itertools import (
  22. permutations,
  23. )
  24. from unittest import expectedFailure
  25. from dulwich.diff_tree import (
  26. CHANGE_MODIFY,
  27. CHANGE_RENAME,
  28. TreeChange,
  29. RenameDetector,
  30. )
  31. from dulwich.errors import (
  32. MissingCommitError,
  33. )
  34. from dulwich.object_store import (
  35. MemoryObjectStore,
  36. )
  37. from dulwich.objects import (
  38. Commit,
  39. Blob,
  40. )
  41. from dulwich.walk import ORDER_TOPO, WalkEntry, Walker, _topo_reorder
  42. from dulwich.tests import TestCase
  43. from dulwich.tests.utils import (
  44. F,
  45. make_object,
  46. make_tag,
  47. build_commit_graph,
  48. )
  49. class TestWalkEntry(object):
  50. def __init__(self, commit, changes):
  51. self.commit = commit
  52. self.changes = changes
  53. def __repr__(self):
  54. return "<TestWalkEntry commit=%s, changes=%r>" % (
  55. self.commit.id,
  56. self.changes,
  57. )
  58. def __eq__(self, other):
  59. if not isinstance(other, WalkEntry) or self.commit != other.commit:
  60. return False
  61. if self.changes is None:
  62. return True
  63. return self.changes == other.changes()
  64. class WalkerTest(TestCase):
  65. def setUp(self):
  66. super(WalkerTest, self).setUp()
  67. self.store = MemoryObjectStore()
  68. def make_commits(self, commit_spec, **kwargs):
  69. times = kwargs.pop("times", [])
  70. attrs = kwargs.pop("attrs", {})
  71. for i, t in enumerate(times):
  72. attrs.setdefault(i + 1, {})["commit_time"] = t
  73. return build_commit_graph(self.store, commit_spec, attrs=attrs, **kwargs)
  74. def make_linear_commits(self, num_commits, **kwargs):
  75. commit_spec = []
  76. for i in range(1, num_commits + 1):
  77. c = [i]
  78. if i > 1:
  79. c.append(i - 1)
  80. commit_spec.append(c)
  81. return self.make_commits(commit_spec, **kwargs)
  82. def assertWalkYields(self, expected, *args, **kwargs):
  83. walker = Walker(self.store, *args, **kwargs)
  84. expected = list(expected)
  85. for i, entry in enumerate(expected):
  86. if isinstance(entry, Commit):
  87. expected[i] = TestWalkEntry(entry, None)
  88. actual = list(walker)
  89. self.assertEqual(expected, actual)
  90. def test_tag(self):
  91. c1, c2, c3 = self.make_linear_commits(3)
  92. t2 = make_tag(target=c2)
  93. self.store.add_object(t2)
  94. self.assertWalkYields([c2, c1], [t2.id])
  95. def test_linear(self):
  96. c1, c2, c3 = self.make_linear_commits(3)
  97. self.assertWalkYields([c1], [c1.id])
  98. self.assertWalkYields([c2, c1], [c2.id])
  99. self.assertWalkYields([c3, c2, c1], [c3.id])
  100. self.assertWalkYields([c3, c2, c1], [c3.id, c1.id])
  101. self.assertWalkYields([c3, c2], [c3.id], exclude=[c1.id])
  102. self.assertWalkYields([c3, c2], [c3.id, c1.id], exclude=[c1.id])
  103. self.assertWalkYields([c3], [c3.id, c1.id], exclude=[c2.id])
  104. def test_missing(self):
  105. cs = list(reversed(self.make_linear_commits(20)))
  106. self.assertWalkYields(cs, [cs[0].id])
  107. # Exactly how close we can get to a missing commit depends on our
  108. # implementation (in particular the choice of _MAX_EXTRA_COMMITS), but
  109. # we should at least be able to walk some history in a broken repo.
  110. del self.store[cs[-1].id]
  111. for i in range(1, 11):
  112. self.assertWalkYields(cs[:i], [cs[0].id], max_entries=i)
  113. self.assertRaises(MissingCommitError, Walker, self.store, [cs[-1].id])
  114. def test_branch(self):
  115. c1, x2, x3, y4 = self.make_commits([[1], [2, 1], [3, 2], [4, 1]])
  116. self.assertWalkYields([x3, x2, c1], [x3.id])
  117. self.assertWalkYields([y4, c1], [y4.id])
  118. self.assertWalkYields([y4, x2, c1], [y4.id, x2.id])
  119. self.assertWalkYields([y4, x2], [y4.id, x2.id], exclude=[c1.id])
  120. self.assertWalkYields([y4, x3], [y4.id, x3.id], exclude=[x2.id])
  121. self.assertWalkYields([y4], [y4.id], exclude=[x3.id])
  122. self.assertWalkYields([x3, x2], [x3.id], exclude=[y4.id])
  123. def test_merge(self):
  124. c1, c2, c3, c4 = self.make_commits([[1], [2, 1], [3, 1], [4, 2, 3]])
  125. self.assertWalkYields([c4, c3, c2, c1], [c4.id])
  126. self.assertWalkYields([c3, c1], [c3.id])
  127. self.assertWalkYields([c2, c1], [c2.id])
  128. self.assertWalkYields([c4, c3], [c4.id], exclude=[c2.id])
  129. self.assertWalkYields([c4, c2], [c4.id], exclude=[c3.id])
  130. def test_merge_of_new_branch_from_old_base(self):
  131. # The commit on the branch was made at a time after any of the
  132. # commits on master, but the branch was from an older commit.
  133. # See also test_merge_of_old_branch
  134. self.maxDiff = None
  135. c1, c2, c3, c4, c5 = self.make_commits(
  136. [[1], [2, 1], [3, 2], [4, 1], [5, 3, 4]],
  137. times=[1, 2, 3, 4, 5],
  138. )
  139. self.assertWalkYields([c5, c4, c3, c2, c1], [c5.id])
  140. self.assertWalkYields([c3, c2, c1], [c3.id])
  141. self.assertWalkYields([c2, c1], [c2.id])
  142. @expectedFailure
  143. def test_merge_of_old_branch(self):
  144. # The commit on the branch was made at a time before any of
  145. # the commits on master, but it was merged into master after
  146. # those commits.
  147. # See also test_merge_of_new_branch_from_old_base
  148. self.maxDiff = None
  149. c1, c2, c3, c4, c5 = self.make_commits(
  150. [[1], [2, 1], [3, 2], [4, 1], [5, 3, 4]],
  151. times=[1, 3, 4, 2, 5],
  152. )
  153. self.assertWalkYields([c5, c4, c3, c2, c1], [c5.id])
  154. self.assertWalkYields([c3, c2, c1], [c3.id])
  155. self.assertWalkYields([c2, c1], [c2.id])
  156. def test_reverse(self):
  157. c1, c2, c3 = self.make_linear_commits(3)
  158. self.assertWalkYields([c1, c2, c3], [c3.id], reverse=True)
  159. def test_max_entries(self):
  160. c1, c2, c3 = self.make_linear_commits(3)
  161. self.assertWalkYields([c3, c2, c1], [c3.id], max_entries=3)
  162. self.assertWalkYields([c3, c2], [c3.id], max_entries=2)
  163. self.assertWalkYields([c3], [c3.id], max_entries=1)
  164. def test_reverse_after_max_entries(self):
  165. c1, c2, c3 = self.make_linear_commits(3)
  166. self.assertWalkYields([c1, c2, c3], [c3.id], max_entries=3, reverse=True)
  167. self.assertWalkYields([c2, c3], [c3.id], max_entries=2, reverse=True)
  168. self.assertWalkYields([c3], [c3.id], max_entries=1, reverse=True)
  169. def test_changes_one_parent(self):
  170. blob_a1 = make_object(Blob, data=b"a1")
  171. blob_a2 = make_object(Blob, data=b"a2")
  172. blob_b2 = make_object(Blob, data=b"b2")
  173. c1, c2 = self.make_linear_commits(
  174. 2,
  175. trees={
  176. 1: [(b"a", blob_a1)],
  177. 2: [(b"a", blob_a2), (b"b", blob_b2)],
  178. },
  179. )
  180. e1 = TestWalkEntry(c1, [TreeChange.add((b"a", F, blob_a1.id))])
  181. e2 = TestWalkEntry(
  182. c2,
  183. [
  184. TreeChange(CHANGE_MODIFY, (b"a", F, blob_a1.id), (b"a", F, blob_a2.id)),
  185. TreeChange.add((b"b", F, blob_b2.id)),
  186. ],
  187. )
  188. self.assertWalkYields([e2, e1], [c2.id])
  189. def test_changes_multiple_parents(self):
  190. blob_a1 = make_object(Blob, data=b"a1")
  191. blob_b2 = make_object(Blob, data=b"b2")
  192. blob_a3 = make_object(Blob, data=b"a3")
  193. c1, c2, c3 = self.make_commits(
  194. [[1], [2], [3, 1, 2]],
  195. trees={
  196. 1: [(b"a", blob_a1)],
  197. 2: [(b"b", blob_b2)],
  198. 3: [(b"a", blob_a3), (b"b", blob_b2)],
  199. },
  200. )
  201. # a is a modify/add conflict and b is not conflicted.
  202. changes = [
  203. [
  204. TreeChange(CHANGE_MODIFY, (b"a", F, blob_a1.id), (b"a", F, blob_a3.id)),
  205. TreeChange.add((b"a", F, blob_a3.id)),
  206. ]
  207. ]
  208. self.assertWalkYields(
  209. [TestWalkEntry(c3, changes)], [c3.id], exclude=[c1.id, c2.id]
  210. )
  211. def test_path_matches(self):
  212. walker = Walker(None, [], paths=[b"foo", b"bar", b"baz/quux"])
  213. self.assertTrue(walker._path_matches(b"foo"))
  214. self.assertTrue(walker._path_matches(b"foo/a"))
  215. self.assertTrue(walker._path_matches(b"foo/a/b"))
  216. self.assertTrue(walker._path_matches(b"bar"))
  217. self.assertTrue(walker._path_matches(b"baz/quux"))
  218. self.assertTrue(walker._path_matches(b"baz/quux/a"))
  219. self.assertFalse(walker._path_matches(None))
  220. self.assertFalse(walker._path_matches(b"oops"))
  221. self.assertFalse(walker._path_matches(b"fool"))
  222. self.assertFalse(walker._path_matches(b"baz"))
  223. self.assertFalse(walker._path_matches(b"baz/quu"))
  224. def test_paths(self):
  225. blob_a1 = make_object(Blob, data=b"a1")
  226. blob_b2 = make_object(Blob, data=b"b2")
  227. blob_a3 = make_object(Blob, data=b"a3")
  228. blob_b3 = make_object(Blob, data=b"b3")
  229. c1, c2, c3 = self.make_linear_commits(
  230. 3,
  231. trees={
  232. 1: [(b"a", blob_a1)],
  233. 2: [(b"a", blob_a1), (b"x/b", blob_b2)],
  234. 3: [(b"a", blob_a3), (b"x/b", blob_b3)],
  235. },
  236. )
  237. self.assertWalkYields([c3, c2, c1], [c3.id])
  238. self.assertWalkYields([c3, c1], [c3.id], paths=[b"a"])
  239. self.assertWalkYields([c3, c2], [c3.id], paths=[b"x/b"])
  240. # All changes are included, not just for requested paths.
  241. changes = [
  242. TreeChange(CHANGE_MODIFY, (b"a", F, blob_a1.id), (b"a", F, blob_a3.id)),
  243. TreeChange(CHANGE_MODIFY, (b"x/b", F, blob_b2.id), (b"x/b", F, blob_b3.id)),
  244. ]
  245. self.assertWalkYields(
  246. [TestWalkEntry(c3, changes)], [c3.id], max_entries=1, paths=[b"a"]
  247. )
  248. def test_paths_subtree(self):
  249. blob_a = make_object(Blob, data=b"a")
  250. blob_b = make_object(Blob, data=b"b")
  251. c1, c2, c3 = self.make_linear_commits(
  252. 3,
  253. trees={
  254. 1: [(b"x/a", blob_a)],
  255. 2: [(b"b", blob_b), (b"x/a", blob_a)],
  256. 3: [(b"b", blob_b), (b"x/a", blob_a), (b"x/b", blob_b)],
  257. },
  258. )
  259. self.assertWalkYields([c2], [c3.id], paths=[b"b"])
  260. self.assertWalkYields([c3, c1], [c3.id], paths=[b"x"])
  261. def test_paths_max_entries(self):
  262. blob_a = make_object(Blob, data=b"a")
  263. blob_b = make_object(Blob, data=b"b")
  264. c1, c2 = self.make_linear_commits(
  265. 2, trees={1: [(b"a", blob_a)], 2: [(b"a", blob_a), (b"b", blob_b)]}
  266. )
  267. self.assertWalkYields([c2], [c2.id], paths=[b"b"], max_entries=1)
  268. self.assertWalkYields([c1], [c1.id], paths=[b"a"], max_entries=1)
  269. def test_paths_merge(self):
  270. blob_a1 = make_object(Blob, data=b"a1")
  271. blob_a2 = make_object(Blob, data=b"a2")
  272. blob_a3 = make_object(Blob, data=b"a3")
  273. x1, y2, m3, m4 = self.make_commits(
  274. [[1], [2], [3, 1, 2], [4, 1, 2]],
  275. trees={
  276. 1: [(b"a", blob_a1)],
  277. 2: [(b"a", blob_a2)],
  278. 3: [(b"a", blob_a3)],
  279. 4: [(b"a", blob_a1)],
  280. },
  281. ) # Non-conflicting
  282. self.assertWalkYields([m3, y2, x1], [m3.id], paths=[b"a"])
  283. self.assertWalkYields([y2, x1], [m4.id], paths=[b"a"])
  284. def test_changes_with_renames(self):
  285. blob = make_object(Blob, data=b"blob")
  286. c1, c2 = self.make_linear_commits(
  287. 2, trees={1: [(b"a", blob)], 2: [(b"b", blob)]}
  288. )
  289. entry_a = (b"a", F, blob.id)
  290. entry_b = (b"b", F, blob.id)
  291. changes_without_renames = [
  292. TreeChange.delete(entry_a),
  293. TreeChange.add(entry_b),
  294. ]
  295. changes_with_renames = [TreeChange(CHANGE_RENAME, entry_a, entry_b)]
  296. self.assertWalkYields(
  297. [TestWalkEntry(c2, changes_without_renames)],
  298. [c2.id],
  299. max_entries=1,
  300. )
  301. detector = RenameDetector(self.store)
  302. self.assertWalkYields(
  303. [TestWalkEntry(c2, changes_with_renames)],
  304. [c2.id],
  305. max_entries=1,
  306. rename_detector=detector,
  307. )
  308. def test_follow_rename(self):
  309. blob = make_object(Blob, data=b"blob")
  310. names = [b"a", b"a", b"b", b"b", b"c", b"c"]
  311. trees = {i + 1: [(n, blob, F)] for i, n in enumerate(names)}
  312. c1, c2, c3, c4, c5, c6 = self.make_linear_commits(6, trees=trees)
  313. self.assertWalkYields([c5], [c6.id], paths=[b"c"])
  314. def e(n):
  315. return (n, F, blob.id)
  316. self.assertWalkYields(
  317. [
  318. TestWalkEntry(c5, [TreeChange(CHANGE_RENAME, e(b"b"), e(b"c"))]),
  319. TestWalkEntry(c3, [TreeChange(CHANGE_RENAME, e(b"a"), e(b"b"))]),
  320. TestWalkEntry(c1, [TreeChange.add(e(b"a"))]),
  321. ],
  322. [c6.id],
  323. paths=[b"c"],
  324. follow=True,
  325. )
  326. def test_follow_rename_remove_path(self):
  327. blob = make_object(Blob, data=b"blob")
  328. _, _, _, c4, c5, c6 = self.make_linear_commits(
  329. 6,
  330. trees={
  331. 1: [(b"a", blob), (b"c", blob)],
  332. 2: [],
  333. 3: [],
  334. 4: [(b"b", blob)],
  335. 5: [(b"a", blob)],
  336. 6: [(b"c", blob)],
  337. },
  338. )
  339. def e(n):
  340. return (n, F, blob.id)
  341. # Once the path changes to b, we aren't interested in a or c anymore.
  342. self.assertWalkYields(
  343. [
  344. TestWalkEntry(c6, [TreeChange(CHANGE_RENAME, e(b"a"), e(b"c"))]),
  345. TestWalkEntry(c5, [TreeChange(CHANGE_RENAME, e(b"b"), e(b"a"))]),
  346. TestWalkEntry(c4, [TreeChange.add(e(b"b"))]),
  347. ],
  348. [c6.id],
  349. paths=[b"c"],
  350. follow=True,
  351. )
  352. def test_since(self):
  353. c1, c2, c3 = self.make_linear_commits(3)
  354. self.assertWalkYields([c3, c2, c1], [c3.id], since=-1)
  355. self.assertWalkYields([c3, c2, c1], [c3.id], since=0)
  356. self.assertWalkYields([c3, c2], [c3.id], since=1)
  357. self.assertWalkYields([c3, c2], [c3.id], since=99)
  358. self.assertWalkYields([c3, c2], [c3.id], since=100)
  359. self.assertWalkYields([c3], [c3.id], since=101)
  360. self.assertWalkYields([c3], [c3.id], since=199)
  361. self.assertWalkYields([c3], [c3.id], since=200)
  362. self.assertWalkYields([], [c3.id], since=201)
  363. self.assertWalkYields([], [c3.id], since=300)
  364. def test_until(self):
  365. c1, c2, c3 = self.make_linear_commits(3)
  366. self.assertWalkYields([], [c3.id], until=-1)
  367. self.assertWalkYields([c1], [c3.id], until=0)
  368. self.assertWalkYields([c1], [c3.id], until=1)
  369. self.assertWalkYields([c1], [c3.id], until=99)
  370. self.assertWalkYields([c2, c1], [c3.id], until=100)
  371. self.assertWalkYields([c2, c1], [c3.id], until=101)
  372. self.assertWalkYields([c2, c1], [c3.id], until=199)
  373. self.assertWalkYields([c3, c2, c1], [c3.id], until=200)
  374. self.assertWalkYields([c3, c2, c1], [c3.id], until=201)
  375. self.assertWalkYields([c3, c2, c1], [c3.id], until=300)
  376. def test_since_until(self):
  377. c1, c2, c3 = self.make_linear_commits(3)
  378. self.assertWalkYields([], [c3.id], since=100, until=99)
  379. self.assertWalkYields([c3, c2, c1], [c3.id], since=-1, until=201)
  380. self.assertWalkYields([c2], [c3.id], since=100, until=100)
  381. self.assertWalkYields([c2], [c3.id], since=50, until=150)
  382. def test_since_over_scan(self):
  383. commits = self.make_linear_commits(11, times=[9, 0, 1, 2, 3, 4, 5, 8, 6, 7, 9])
  384. c8, _, c10, c11 = commits[-4:]
  385. del self.store[commits[0].id]
  386. # c9 is older than we want to walk, but is out of order with its
  387. # parent, so we need to walk past it to get to c8.
  388. # c1 would also match, but we've deleted it, and it should get pruned
  389. # even with over-scanning.
  390. self.assertWalkYields([c11, c10, c8], [c11.id], since=7)
  391. def assertTopoOrderEqual(self, expected_commits, commits):
  392. entries = [TestWalkEntry(c, None) for c in commits]
  393. actual_ids = [e.commit.id for e in list(_topo_reorder(entries))]
  394. self.assertEqual([c.id for c in expected_commits], actual_ids)
  395. def test_topo_reorder_linear(self):
  396. commits = self.make_linear_commits(5)
  397. commits.reverse()
  398. for perm in permutations(commits):
  399. self.assertTopoOrderEqual(commits, perm)
  400. def test_topo_reorder_multiple_parents(self):
  401. c1, c2, c3 = self.make_commits([[1], [2], [3, 1, 2]])
  402. # Already sorted, so totally FIFO.
  403. self.assertTopoOrderEqual([c3, c2, c1], [c3, c2, c1])
  404. self.assertTopoOrderEqual([c3, c1, c2], [c3, c1, c2])
  405. # c3 causes one parent to be yielded.
  406. self.assertTopoOrderEqual([c3, c2, c1], [c2, c3, c1])
  407. self.assertTopoOrderEqual([c3, c1, c2], [c1, c3, c2])
  408. # c3 causes both parents to be yielded.
  409. self.assertTopoOrderEqual([c3, c2, c1], [c1, c2, c3])
  410. self.assertTopoOrderEqual([c3, c2, c1], [c2, c1, c3])
  411. def test_topo_reorder_multiple_children(self):
  412. c1, c2, c3 = self.make_commits([[1], [2, 1], [3, 1]])
  413. # c2 and c3 are FIFO but c1 moves to the end.
  414. self.assertTopoOrderEqual([c3, c2, c1], [c3, c2, c1])
  415. self.assertTopoOrderEqual([c3, c2, c1], [c3, c1, c2])
  416. self.assertTopoOrderEqual([c3, c2, c1], [c1, c3, c2])
  417. self.assertTopoOrderEqual([c2, c3, c1], [c2, c3, c1])
  418. self.assertTopoOrderEqual([c2, c3, c1], [c2, c1, c3])
  419. self.assertTopoOrderEqual([c2, c3, c1], [c1, c2, c3])
  420. def test_out_of_order_children(self):
  421. c1, c2, c3, c4, c5 = self.make_commits(
  422. [[1], [2, 1], [3, 2], [4, 1], [5, 3, 4]], times=[2, 1, 3, 4, 5]
  423. )
  424. self.assertWalkYields([c5, c4, c3, c1, c2], [c5.id])
  425. self.assertWalkYields([c5, c4, c3, c2, c1], [c5.id], order=ORDER_TOPO)
  426. def test_out_of_order_with_exclude(self):
  427. # Create the following graph:
  428. # c1-------x2---m6
  429. # \ /
  430. # \-y3--y4-/--y5
  431. # Due to skew, y5 is the oldest commit.
  432. c1, x2, y3, y4, y5, m6 = self.make_commits(
  433. [[1], [2, 1], [3, 1], [4, 3], [5, 4], [6, 2, 4]],
  434. times=[2, 3, 4, 5, 1, 6],
  435. )
  436. self.assertWalkYields([m6, y4, y3, x2, c1], [m6.id])
  437. # Ensure that c1..y4 get excluded even though they're popped from the
  438. # priority queue long before y5.
  439. self.assertWalkYields([m6, x2], [m6.id], exclude=[y5.id])
  440. def test_empty_walk(self):
  441. c1, c2, c3 = self.make_linear_commits(3)
  442. self.assertWalkYields([], [c3.id], exclude=[c3.id])
  443. class WalkEntryTest(TestCase):
  444. def setUp(self):
  445. super(WalkEntryTest, self).setUp()
  446. self.store = MemoryObjectStore()
  447. def make_commits(self, commit_spec, **kwargs):
  448. times = kwargs.pop("times", [])
  449. attrs = kwargs.pop("attrs", {})
  450. for i, t in enumerate(times):
  451. attrs.setdefault(i + 1, {})["commit_time"] = t
  452. return build_commit_graph(self.store, commit_spec, attrs=attrs, **kwargs)
  453. def make_linear_commits(self, num_commits, **kwargs):
  454. commit_spec = []
  455. for i in range(1, num_commits + 1):
  456. c = [i]
  457. if i > 1:
  458. c.append(i - 1)
  459. commit_spec.append(c)
  460. return self.make_commits(commit_spec, **kwargs)
  461. def test_all_changes(self):
  462. # Construct a commit with 2 files in different subdirectories.
  463. blob_a = make_object(Blob, data=b"a")
  464. blob_b = make_object(Blob, data=b"b")
  465. c1 = self.make_linear_commits(
  466. 1,
  467. trees={1: [(b"x/a", blob_a), (b"y/b", blob_b)]},
  468. )[0]
  469. # Get the WalkEntry for the commit.
  470. walker = Walker(self.store, c1.id)
  471. walker_entry = list(walker)[0]
  472. changes = walker_entry.changes()
  473. # Compare the changes with the expected values.
  474. entry_a = (b"x/a", F, blob_a.id)
  475. entry_b = (b"y/b", F, blob_b.id)
  476. self.assertEqual(
  477. [TreeChange.add(entry_a), TreeChange.add(entry_b)],
  478. changes,
  479. )
  480. def test_all_with_merge(self):
  481. blob_a = make_object(Blob, data=b"a")
  482. blob_a2 = make_object(Blob, data=b"a2")
  483. blob_b = make_object(Blob, data=b"b")
  484. blob_b2 = make_object(Blob, data=b"b2")
  485. x1, y2, m3 = self.make_commits(
  486. [[1], [2], [3, 1, 2]],
  487. trees={
  488. 1: [(b"x/a", blob_a)],
  489. 2: [(b"y/b", blob_b)],
  490. 3: [(b"x/a", blob_a2), (b"y/b", blob_b2)],
  491. },
  492. )
  493. # Get the WalkEntry for the merge commit.
  494. walker = Walker(self.store, m3.id)
  495. entries = list(walker)
  496. walker_entry = entries[0]
  497. self.assertEqual(walker_entry.commit.id, m3.id)
  498. changes = walker_entry.changes()
  499. self.assertEqual(2, len(changes))
  500. entry_a = (b"x/a", F, blob_a.id)
  501. entry_a2 = (b"x/a", F, blob_a2.id)
  502. entry_b = (b"y/b", F, blob_b.id)
  503. entry_b2 = (b"y/b", F, blob_b2.id)
  504. self.assertEqual(
  505. [
  506. [
  507. TreeChange(CHANGE_MODIFY, entry_a, entry_a2),
  508. TreeChange.add(entry_a2),
  509. ],
  510. [
  511. TreeChange.add(entry_b2),
  512. TreeChange(CHANGE_MODIFY, entry_b, entry_b2),
  513. ],
  514. ],
  515. changes,
  516. )
  517. def test_filter_changes(self):
  518. # Construct a commit with 2 files in different subdirectories.
  519. blob_a = make_object(Blob, data=b"a")
  520. blob_b = make_object(Blob, data=b"b")
  521. c1 = self.make_linear_commits(
  522. 1,
  523. trees={1: [(b"x/a", blob_a), (b"y/b", blob_b)]},
  524. )[0]
  525. # Get the WalkEntry for the commit.
  526. walker = Walker(self.store, c1.id)
  527. walker_entry = list(walker)[0]
  528. changes = walker_entry.changes(path_prefix=b"x")
  529. # Compare the changes with the expected values.
  530. entry_a = (b"a", F, blob_a.id)
  531. self.assertEqual(
  532. [TreeChange.add(entry_a)],
  533. changes,
  534. )
  535. def test_filter_with_merge(self):
  536. blob_a = make_object(Blob, data=b"a")
  537. blob_a2 = make_object(Blob, data=b"a2")
  538. blob_b = make_object(Blob, data=b"b")
  539. blob_b2 = make_object(Blob, data=b"b2")
  540. x1, y2, m3 = self.make_commits(
  541. [[1], [2], [3, 1, 2]],
  542. trees={
  543. 1: [(b"x/a", blob_a)],
  544. 2: [(b"y/b", blob_b)],
  545. 3: [(b"x/a", blob_a2), (b"y/b", blob_b2)],
  546. },
  547. )
  548. # Get the WalkEntry for the merge commit.
  549. walker = Walker(self.store, m3.id)
  550. entries = list(walker)
  551. walker_entry = entries[0]
  552. self.assertEqual(walker_entry.commit.id, m3.id)
  553. changes = walker_entry.changes(b"x")
  554. self.assertEqual(1, len(changes))
  555. entry_a = (b"a", F, blob_a.id)
  556. entry_a2 = (b"a", F, blob_a2.id)
  557. self.assertEqual(
  558. [[TreeChange(CHANGE_MODIFY, entry_a, entry_a2)]],
  559. changes,
  560. )