test_objectspec.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. # test_objectspec.py -- tests for objectspec.py
  2. # Copyright (C) 2014 Jelmer Vernooij <jelmer@jelmer.uk>
  3. #
  4. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
  5. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  6. # General Public License as published 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 revision spec parsing."""
  22. # TODO: Round-trip parse-serialize-parse and serialize-parse-serialize tests.
  23. from dulwich.objects import Blob, Commit, Tag
  24. from dulwich.objectspec import (
  25. parse_commit,
  26. parse_commit_range,
  27. parse_object,
  28. parse_ref,
  29. parse_refs,
  30. parse_reftuple,
  31. parse_reftuples,
  32. parse_tree,
  33. )
  34. from dulwich.repo import MemoryRepo
  35. from dulwich.tests.utils import build_commit_graph
  36. from . import TestCase
  37. class ParseObjectTests(TestCase):
  38. """Test parse_object."""
  39. def test_nonexistent(self) -> None:
  40. r = MemoryRepo()
  41. self.assertRaises(KeyError, parse_object, r, "thisdoesnotexist")
  42. def test_blob_by_sha(self) -> None:
  43. r = MemoryRepo()
  44. b = Blob.from_string(b"Blah")
  45. r.object_store.add_object(b)
  46. self.assertEqual(b, parse_object(r, b.id))
  47. class ParseCommitRangeTests(TestCase):
  48. """Test parse_commit_range."""
  49. def test_nonexistent(self) -> None:
  50. r = MemoryRepo()
  51. self.assertRaises(KeyError, parse_commit_range, r, "thisdoesnotexist..HEAD")
  52. def test_commit_by_sha(self) -> None:
  53. r = MemoryRepo()
  54. c1, c2, c3 = build_commit_graph(r.object_store, [[1], [2, 1], [3, 1, 2]])
  55. self.assertIsNone(parse_commit_range(r, c1.id))
  56. def test_commit_range(self) -> None:
  57. r = MemoryRepo()
  58. c1, c2, c3 = build_commit_graph(r.object_store, [[1], [2, 1], [3, 1, 2]])
  59. result = parse_commit_range(r, f"{c1.id.decode()}..{c2.id.decode()}")
  60. self.assertIsNotNone(result)
  61. start_commit, end_commit = result
  62. self.assertEqual(c1, start_commit)
  63. self.assertEqual(c2, end_commit)
  64. class ParseCommitTests(TestCase):
  65. """Test parse_commit."""
  66. def test_nonexistent(self) -> None:
  67. r = MemoryRepo()
  68. self.assertRaises(KeyError, parse_commit, r, "thisdoesnotexist")
  69. def test_commit_by_sha(self) -> None:
  70. r = MemoryRepo()
  71. [c1] = build_commit_graph(r.object_store, [[1]])
  72. self.assertEqual(c1, parse_commit(r, c1.id))
  73. def test_commit_by_short_sha(self) -> None:
  74. r = MemoryRepo()
  75. [c1] = build_commit_graph(r.object_store, [[1]])
  76. self.assertEqual(c1, parse_commit(r, c1.id[:10]))
  77. def test_annotated_tag(self) -> None:
  78. r = MemoryRepo()
  79. [c1] = build_commit_graph(r.object_store, [[1]])
  80. # Create an annotated tag pointing to the commit
  81. tag = Tag()
  82. tag.name = b"v1.0"
  83. tag.message = b"Test tag"
  84. tag.tag_time = 1234567890
  85. tag.tag_timezone = 0
  86. tag.object = (Commit, c1.id)
  87. tag.tagger = b"Test Tagger <test@example.com>"
  88. r.object_store.add_object(tag)
  89. # parse_commit should follow the tag to the commit
  90. self.assertEqual(c1, parse_commit(r, tag.id))
  91. def test_nested_tags(self) -> None:
  92. r = MemoryRepo()
  93. [c1] = build_commit_graph(r.object_store, [[1]])
  94. # Create an annotated tag pointing to the commit
  95. tag1 = Tag()
  96. tag1.name = b"v1.0"
  97. tag1.message = b"Test tag"
  98. tag1.tag_time = 1234567890
  99. tag1.tag_timezone = 0
  100. tag1.object = (Commit, c1.id)
  101. tag1.tagger = b"Test Tagger <test@example.com>"
  102. r.object_store.add_object(tag1)
  103. # Create another tag pointing to the first tag
  104. tag2 = Tag()
  105. tag2.name = b"v1.0-release"
  106. tag2.message = b"Release tag"
  107. tag2.tag_time = 1234567900
  108. tag2.tag_timezone = 0
  109. tag2.object = (Tag, tag1.id)
  110. tag2.tagger = b"Test Tagger <test@example.com>"
  111. r.object_store.add_object(tag2)
  112. # parse_commit should follow both tags to the commit
  113. self.assertEqual(c1, parse_commit(r, tag2.id))
  114. def test_tag_to_missing_commit(self) -> None:
  115. r = MemoryRepo()
  116. # Create a tag pointing to a non-existent commit
  117. missing_sha = b"1234567890123456789012345678901234567890"
  118. tag = Tag()
  119. tag.name = b"v1.0"
  120. tag.message = b"Test tag"
  121. tag.tag_time = 1234567890
  122. tag.tag_timezone = 0
  123. tag.object = (Commit, missing_sha)
  124. tag.tagger = b"Test Tagger <test@example.com>"
  125. r.object_store.add_object(tag)
  126. # Should raise KeyError for missing commit
  127. self.assertRaises(KeyError, parse_commit, r, tag.id)
  128. def test_tag_to_blob(self) -> None:
  129. r = MemoryRepo()
  130. # Create a blob
  131. blob = Blob.from_string(b"Test content")
  132. r.object_store.add_object(blob)
  133. # Create a tag pointing to the blob
  134. tag = Tag()
  135. tag.name = b"blob-tag"
  136. tag.message = b"Tag pointing to blob"
  137. tag.tag_time = 1234567890
  138. tag.tag_timezone = 0
  139. tag.object = (Blob, blob.id)
  140. tag.tagger = b"Test Tagger <test@example.com>"
  141. r.object_store.add_object(tag)
  142. # Should raise ValueError as it's not a commit
  143. self.assertRaises(ValueError, parse_commit, r, tag.id)
  144. def test_commit_object(self) -> None:
  145. r = MemoryRepo()
  146. [c1] = build_commit_graph(r.object_store, [[1]])
  147. # Test that passing a Commit object directly returns the same object
  148. self.assertEqual(c1, parse_commit(r, c1))
  149. class ParseRefTests(TestCase):
  150. def test_nonexistent(self) -> None:
  151. r = {}
  152. self.assertRaises(KeyError, parse_ref, r, b"thisdoesnotexist")
  153. def test_ambiguous_ref(self) -> None:
  154. r = {
  155. b"ambig1": "bla",
  156. b"refs/ambig1": "bla",
  157. b"refs/tags/ambig1": "bla",
  158. b"refs/heads/ambig1": "bla",
  159. b"refs/remotes/ambig1": "bla",
  160. b"refs/remotes/ambig1/HEAD": "bla",
  161. }
  162. self.assertEqual(b"ambig1", parse_ref(r, b"ambig1"))
  163. def test_ambiguous_ref2(self) -> None:
  164. r = {
  165. b"refs/ambig2": "bla",
  166. b"refs/tags/ambig2": "bla",
  167. b"refs/heads/ambig2": "bla",
  168. b"refs/remotes/ambig2": "bla",
  169. b"refs/remotes/ambig2/HEAD": "bla",
  170. }
  171. self.assertEqual(b"refs/ambig2", parse_ref(r, b"ambig2"))
  172. def test_ambiguous_tag(self) -> None:
  173. r = {
  174. b"refs/tags/ambig3": "bla",
  175. b"refs/heads/ambig3": "bla",
  176. b"refs/remotes/ambig3": "bla",
  177. b"refs/remotes/ambig3/HEAD": "bla",
  178. }
  179. self.assertEqual(b"refs/tags/ambig3", parse_ref(r, b"ambig3"))
  180. def test_ambiguous_head(self) -> None:
  181. r = {
  182. b"refs/heads/ambig4": "bla",
  183. b"refs/remotes/ambig4": "bla",
  184. b"refs/remotes/ambig4/HEAD": "bla",
  185. }
  186. self.assertEqual(b"refs/heads/ambig4", parse_ref(r, b"ambig4"))
  187. def test_ambiguous_remote(self) -> None:
  188. r = {b"refs/remotes/ambig5": "bla", b"refs/remotes/ambig5/HEAD": "bla"}
  189. self.assertEqual(b"refs/remotes/ambig5", parse_ref(r, b"ambig5"))
  190. def test_ambiguous_remote_head(self) -> None:
  191. r = {b"refs/remotes/ambig6/HEAD": "bla"}
  192. self.assertEqual(b"refs/remotes/ambig6/HEAD", parse_ref(r, b"ambig6"))
  193. def test_heads_full(self) -> None:
  194. r = {b"refs/heads/foo": "bla"}
  195. self.assertEqual(b"refs/heads/foo", parse_ref(r, b"refs/heads/foo"))
  196. def test_heads_partial(self) -> None:
  197. r = {b"refs/heads/foo": "bla"}
  198. self.assertEqual(b"refs/heads/foo", parse_ref(r, b"heads/foo"))
  199. def test_tags_partial(self) -> None:
  200. r = {b"refs/tags/foo": "bla"}
  201. self.assertEqual(b"refs/tags/foo", parse_ref(r, b"tags/foo"))
  202. class ParseRefsTests(TestCase):
  203. def test_nonexistent(self) -> None:
  204. r = {}
  205. self.assertRaises(KeyError, parse_refs, r, [b"thisdoesnotexist"])
  206. def test_head(self) -> None:
  207. r = {b"refs/heads/foo": "bla"}
  208. self.assertEqual([b"refs/heads/foo"], parse_refs(r, [b"foo"]))
  209. def test_full(self) -> None:
  210. r = {b"refs/heads/foo": "bla"}
  211. self.assertEqual([b"refs/heads/foo"], parse_refs(r, b"refs/heads/foo"))
  212. class ParseReftupleTests(TestCase):
  213. def test_nonexistent(self) -> None:
  214. r = {}
  215. self.assertRaises(KeyError, parse_reftuple, r, r, b"thisdoesnotexist")
  216. def test_head(self) -> None:
  217. r = {b"refs/heads/foo": "bla"}
  218. self.assertEqual(
  219. (b"refs/heads/foo", b"refs/heads/foo", False),
  220. parse_reftuple(r, r, b"foo"),
  221. )
  222. self.assertEqual(
  223. (b"refs/heads/foo", b"refs/heads/foo", True),
  224. parse_reftuple(r, r, b"+foo"),
  225. )
  226. self.assertEqual(
  227. (b"refs/heads/foo", b"refs/heads/foo", True),
  228. parse_reftuple(r, {}, b"+foo"),
  229. )
  230. self.assertEqual(
  231. (b"refs/heads/foo", b"refs/heads/foo", True),
  232. parse_reftuple(r, {}, b"foo", True),
  233. )
  234. def test_full(self) -> None:
  235. r = {b"refs/heads/foo": "bla"}
  236. self.assertEqual(
  237. (b"refs/heads/foo", b"refs/heads/foo", False),
  238. parse_reftuple(r, r, b"refs/heads/foo"),
  239. )
  240. def test_no_left_ref(self) -> None:
  241. r = {b"refs/heads/foo": "bla"}
  242. self.assertEqual(
  243. (None, b"refs/heads/foo", False),
  244. parse_reftuple(r, r, b":refs/heads/foo"),
  245. )
  246. def test_no_right_ref(self) -> None:
  247. r = {b"refs/heads/foo": "bla"}
  248. self.assertEqual(
  249. (b"refs/heads/foo", None, False),
  250. parse_reftuple(r, r, b"refs/heads/foo:"),
  251. )
  252. def test_default_with_string(self) -> None:
  253. r = {b"refs/heads/foo": "bla"}
  254. self.assertEqual(
  255. (b"refs/heads/foo", b"refs/heads/foo", False),
  256. parse_reftuple(r, r, "foo"),
  257. )
  258. class ParseReftuplesTests(TestCase):
  259. def test_nonexistent(self) -> None:
  260. r = {}
  261. self.assertRaises(KeyError, parse_reftuples, r, r, [b"thisdoesnotexist"])
  262. def test_head(self) -> None:
  263. r = {b"refs/heads/foo": "bla"}
  264. self.assertEqual(
  265. [(b"refs/heads/foo", b"refs/heads/foo", False)],
  266. parse_reftuples(r, r, [b"foo"]),
  267. )
  268. def test_full(self) -> None:
  269. r = {b"refs/heads/foo": "bla"}
  270. self.assertEqual(
  271. [(b"refs/heads/foo", b"refs/heads/foo", False)],
  272. parse_reftuples(r, r, b"refs/heads/foo"),
  273. )
  274. r = {b"refs/heads/foo": "bla"}
  275. self.assertEqual(
  276. [(b"refs/heads/foo", b"refs/heads/foo", True)],
  277. parse_reftuples(r, r, b"refs/heads/foo", True),
  278. )
  279. class ParseTreeTests(TestCase):
  280. """Test parse_tree."""
  281. def test_nonexistent(self) -> None:
  282. r = MemoryRepo()
  283. self.assertRaises(KeyError, parse_tree, r, "thisdoesnotexist")
  284. def test_from_commit(self) -> None:
  285. r = MemoryRepo()
  286. c1, c2, c3 = build_commit_graph(r.object_store, [[1], [2, 1], [3, 1, 2]])
  287. self.assertEqual(r[c1.tree], parse_tree(r, c1.id))
  288. self.assertEqual(r[c1.tree], parse_tree(r, c1.tree))
  289. def test_from_ref(self) -> None:
  290. r = MemoryRepo()
  291. c1, c2, c3 = build_commit_graph(r.object_store, [[1], [2, 1], [3, 1, 2]])
  292. r.refs[b"refs/heads/foo"] = c1.id
  293. self.assertEqual(r[c1.tree], parse_tree(r, b"foo"))
  294. def test_tree_object(self) -> None:
  295. r = MemoryRepo()
  296. [c1] = build_commit_graph(r.object_store, [[1]])
  297. tree = r[c1.tree]
  298. # Test that passing a Tree object directly returns the same object
  299. self.assertEqual(tree, parse_tree(r, tree))
  300. def test_commit_object(self) -> None:
  301. r = MemoryRepo()
  302. [c1] = build_commit_graph(r.object_store, [[1]])
  303. # Test that passing a Commit object returns its tree
  304. self.assertEqual(r[c1.tree], parse_tree(r, c1))
  305. def test_tag_object(self) -> None:
  306. r = MemoryRepo()
  307. [c1] = build_commit_graph(r.object_store, [[1]])
  308. # Create an annotated tag pointing to the commit
  309. tag = Tag()
  310. tag.name = b"v1.0"
  311. tag.message = b"Test tag"
  312. tag.tag_time = 1234567890
  313. tag.tag_timezone = 0
  314. tag.object = (Commit, c1.id)
  315. tag.tagger = b"Test Tagger <test@example.com>"
  316. r.object_store.add_object(tag)
  317. # parse_tree should follow the tag to the commit's tree
  318. self.assertEqual(r[c1.tree], parse_tree(r, tag))