test_walk.py 23 KB

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