test_fastexport.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. # test_fastexport.py -- Fast export/import functionality
  2. # Copyright (C) 2010 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. import stat
  22. from io import BytesIO
  23. from dulwich.object_store import MemoryObjectStore
  24. from dulwich.objects import ZERO_SHA, Blob, Commit, Tree
  25. from dulwich.repo import MemoryRepo
  26. from dulwich.tests.utils import build_commit_graph, make_commit
  27. from . import DependencyMissing, TestCase
  28. class GitFastExporterTests(TestCase):
  29. """Tests for the GitFastExporter tests."""
  30. def setUp(self) -> None:
  31. super().setUp()
  32. self.store = MemoryObjectStore()
  33. self.stream = BytesIO()
  34. try:
  35. from dulwich.fastexport import GitFastExporter
  36. except ImportError as exc:
  37. raise DependencyMissing("python-fastimport") from exc
  38. self.fastexporter = GitFastExporter(self.stream, self.store)
  39. def test_emit_blob(self) -> None:
  40. b = Blob()
  41. b.data = b"fooBAR"
  42. self.fastexporter.emit_blob(b)
  43. self.assertEqual(b"blob\nmark :1\ndata 6\nfooBAR\n", self.stream.getvalue())
  44. def test_emit_commit(self) -> None:
  45. b = Blob()
  46. b.data = b"FOO"
  47. t = Tree()
  48. t.add(b"foo", stat.S_IFREG | 0o644, b.id)
  49. c = make_commit(
  50. author=b"Jelmer <jelmer@host>",
  51. committer=b"Jelmer <jelmer@host>",
  52. author_time=1271345553,
  53. commit_time=1271345553,
  54. author_timezone=0,
  55. commit_timezone=0,
  56. message=b"msg",
  57. tree=t.id,
  58. )
  59. self.store.add_objects([(b, None), (t, None), (c, None)])
  60. self.fastexporter.emit_commit(c, b"refs/heads/master")
  61. self.assertEqual(
  62. b"""blob
  63. mark :1
  64. data 3
  65. FOO
  66. commit refs/heads/master
  67. mark :2
  68. author Jelmer <jelmer@host> 1271345553 +0000
  69. committer Jelmer <jelmer@host> 1271345553 +0000
  70. data 3
  71. msg
  72. M 644 :1 foo
  73. """,
  74. self.stream.getvalue(),
  75. )
  76. class GitImportProcessorTests(TestCase):
  77. """Tests for the GitImportProcessor tests."""
  78. def setUp(self) -> None:
  79. super().setUp()
  80. self.repo = MemoryRepo()
  81. self.addCleanup(self.repo.close)
  82. try:
  83. from dulwich.fastexport import GitImportProcessor
  84. except ImportError as exc:
  85. raise DependencyMissing("python-fastimport") from exc
  86. self.processor = GitImportProcessor(self.repo)
  87. def test_reset_handler(self) -> None:
  88. from fastimport import commands
  89. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  90. cmd = commands.ResetCommand(b"refs/heads/foo", c1.id)
  91. self.processor.reset_handler(cmd)
  92. self.assertEqual(c1.id, self.repo.get_refs()[b"refs/heads/foo"])
  93. self.assertEqual(c1.id, self.processor.last_commit)
  94. def test_reset_handler_marker(self) -> None:
  95. from fastimport import commands
  96. [c1, _c2] = build_commit_graph(self.repo.object_store, [[1], [2]])
  97. self.processor.markers[b"10"] = c1.id
  98. cmd = commands.ResetCommand(b"refs/heads/foo", b":10")
  99. self.processor.reset_handler(cmd)
  100. self.assertEqual(c1.id, self.repo.get_refs()[b"refs/heads/foo"])
  101. def test_reset_handler_default(self) -> None:
  102. from fastimport import commands
  103. [_c1, _c2] = build_commit_graph(self.repo.object_store, [[1], [2]])
  104. cmd = commands.ResetCommand(b"refs/heads/foo", None)
  105. self.processor.reset_handler(cmd)
  106. self.assertEqual(ZERO_SHA, self.repo.get_refs()[b"refs/heads/foo"])
  107. def test_commit_handler(self) -> None:
  108. from fastimport import commands
  109. cmd = commands.CommitCommand(
  110. b"refs/heads/foo",
  111. b"mrkr",
  112. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  113. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  114. b"FOO",
  115. None,
  116. [],
  117. [],
  118. )
  119. self.processor.commit_handler(cmd)
  120. commit = self.repo[self.processor.last_commit]
  121. self.assertEqual(b"Jelmer <jelmer@samba.org>", commit.author)
  122. self.assertEqual(b"Jelmer <jelmer@samba.org>", commit.committer)
  123. self.assertEqual(b"FOO", commit.message)
  124. self.assertEqual([], commit.parents)
  125. self.assertEqual(432432432.0, commit.commit_time)
  126. self.assertEqual(432432432.0, commit.author_time)
  127. self.assertEqual(3600, commit.commit_timezone)
  128. self.assertEqual(3600, commit.author_timezone)
  129. self.assertEqual(commit, self.repo[b"refs/heads/foo"])
  130. def test_commit_handler_markers(self) -> None:
  131. from fastimport import commands
  132. [c1, c2, c3] = build_commit_graph(self.repo.object_store, [[1], [2], [3]])
  133. self.processor.markers[b"10"] = c1.id
  134. self.processor.markers[b"42"] = c2.id
  135. self.processor.markers[b"98"] = c3.id
  136. cmd = commands.CommitCommand(
  137. b"refs/heads/foo",
  138. b"mrkr",
  139. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  140. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  141. b"FOO",
  142. b":10",
  143. [b":42", b":98"],
  144. [],
  145. )
  146. self.processor.commit_handler(cmd)
  147. commit = self.repo[self.processor.last_commit]
  148. self.assertEqual(c1.id, commit.parents[0])
  149. self.assertEqual(c2.id, commit.parents[1])
  150. self.assertEqual(c3.id, commit.parents[2])
  151. def test_import_stream(self) -> None:
  152. markers = self.processor.import_stream(
  153. BytesIO(
  154. b"""blob
  155. mark :1
  156. data 11
  157. text for a
  158. commit refs/heads/master
  159. mark :2
  160. committer Joe Foo <joe@foo.com> 1288287382 +0000
  161. data 20
  162. <The commit message>
  163. M 100644 :1 a
  164. """
  165. )
  166. )
  167. self.assertEqual(2, len(markers))
  168. self.assertIsInstance(self.repo[markers[b"1"]], Blob)
  169. self.assertIsInstance(self.repo[markers[b"2"]], Commit)
  170. def test_file_add(self) -> None:
  171. from fastimport import commands
  172. cmd = commands.BlobCommand(b"23", b"data")
  173. self.processor.blob_handler(cmd)
  174. cmd = commands.CommitCommand(
  175. b"refs/heads/foo",
  176. b"mrkr",
  177. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  178. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  179. b"FOO",
  180. None,
  181. [],
  182. [commands.FileModifyCommand(b"path", 0o100644, b":23", None)],
  183. )
  184. self.processor.commit_handler(cmd)
  185. commit = self.repo[self.processor.last_commit]
  186. self.assertEqual(
  187. [(b"path", 0o100644, b"6320cd248dd8aeaab759d5871f8781b5c0505172")],
  188. self.repo[commit.tree].items(),
  189. )
  190. def simple_commit(self):
  191. from fastimport import commands
  192. cmd = commands.BlobCommand(b"23", b"data")
  193. self.processor.blob_handler(cmd)
  194. cmd = commands.CommitCommand(
  195. b"refs/heads/foo",
  196. b"mrkr",
  197. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  198. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  199. b"FOO",
  200. None,
  201. [],
  202. [commands.FileModifyCommand(b"path", 0o100644, b":23", None)],
  203. )
  204. self.processor.commit_handler(cmd)
  205. commit = self.repo[self.processor.last_commit]
  206. return commit
  207. def make_file_commit(self, file_cmds):
  208. """Create a trivial commit with the specified file commands.
  209. Args:
  210. file_cmds: File commands to run.
  211. Returns: The created commit object
  212. """
  213. from fastimport import commands
  214. cmd = commands.CommitCommand(
  215. b"refs/heads/foo",
  216. b"mrkr",
  217. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  218. (b"Jelmer", b"jelmer@samba.org", 432432432.0, 3600),
  219. b"FOO",
  220. None,
  221. [],
  222. file_cmds,
  223. )
  224. self.processor.commit_handler(cmd)
  225. return self.repo[self.processor.last_commit]
  226. def test_file_copy(self) -> None:
  227. from fastimport import commands
  228. self.simple_commit()
  229. commit = self.make_file_commit([commands.FileCopyCommand(b"path", b"new_path")])
  230. self.assertEqual(
  231. [
  232. (
  233. b"new_path",
  234. 0o100644,
  235. b"6320cd248dd8aeaab759d5871f8781b5c0505172",
  236. ),
  237. (
  238. b"path",
  239. 0o100644,
  240. b"6320cd248dd8aeaab759d5871f8781b5c0505172",
  241. ),
  242. ],
  243. self.repo[commit.tree].items(),
  244. )
  245. def test_file_move(self) -> None:
  246. from fastimport import commands
  247. self.simple_commit()
  248. commit = self.make_file_commit(
  249. [commands.FileRenameCommand(b"path", b"new_path")]
  250. )
  251. self.assertEqual(
  252. [
  253. (
  254. b"new_path",
  255. 0o100644,
  256. b"6320cd248dd8aeaab759d5871f8781b5c0505172",
  257. ),
  258. ],
  259. self.repo[commit.tree].items(),
  260. )
  261. def test_file_delete(self) -> None:
  262. from fastimport import commands
  263. self.simple_commit()
  264. commit = self.make_file_commit([commands.FileDeleteCommand(b"path")])
  265. self.assertEqual([], self.repo[commit.tree].items())
  266. def test_file_deleteall(self) -> None:
  267. from fastimport import commands
  268. self.simple_commit()
  269. commit = self.make_file_commit([commands.FileDeleteAllCommand()])
  270. self.assertEqual([], self.repo[commit.tree].items())