test_porcelain_lfs.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. # test_porcelain_lfs.py -- Tests for LFS porcelain functions
  2. # Copyright (C) 2024 Jelmer Vernooij
  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 public 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 LFS porcelain functions."""
  22. import os
  23. import tempfile
  24. import unittest
  25. from dulwich import porcelain
  26. from dulwich.lfs import LFSPointer, LFSStore
  27. from dulwich.repo import Repo
  28. from tests import TestCase
  29. class LFSPorcelainTestCase(TestCase):
  30. """Test case for LFS porcelain functions."""
  31. def setUp(self):
  32. super().setUp()
  33. self.test_dir = tempfile.mkdtemp()
  34. self.addCleanup(self._cleanup_test_dir)
  35. self.repo = Repo.init(self.test_dir)
  36. self.addCleanup(self.repo.close)
  37. def _cleanup_test_dir(self):
  38. """Clean up test directory recursively."""
  39. import shutil
  40. shutil.rmtree(self.test_dir, ignore_errors=True)
  41. def test_lfs_init(self):
  42. """Test LFS initialization."""
  43. porcelain.lfs_init(self.repo)
  44. # Check that LFS store was created
  45. lfs_dir = os.path.join(self.repo.controldir(), "lfs")
  46. self.assertTrue(os.path.exists(lfs_dir))
  47. self.assertTrue(os.path.exists(os.path.join(lfs_dir, "objects")))
  48. self.assertTrue(os.path.exists(os.path.join(lfs_dir, "tmp")))
  49. # Check that config was set
  50. config = self.repo.get_config()
  51. self.assertEqual(
  52. config.get((b"filter", b"lfs"), b"process"), b"git-lfs filter-process"
  53. )
  54. self.assertEqual(config.get((b"filter", b"lfs"), b"required"), b"true")
  55. def test_lfs_track(self):
  56. """Test tracking patterns with LFS."""
  57. # Track some patterns
  58. patterns = ["*.bin", "*.pdf"]
  59. tracked = porcelain.lfs_track(self.repo, patterns)
  60. self.assertEqual(set(tracked), set(patterns))
  61. # Check .gitattributes was created
  62. gitattributes_path = os.path.join(self.repo.path, ".gitattributes")
  63. self.assertTrue(os.path.exists(gitattributes_path))
  64. # Read and verify content
  65. with open(gitattributes_path, "rb") as f:
  66. content = f.read()
  67. self.assertIn(b"*.bin diff=lfs filter=lfs merge=lfs -text", content)
  68. self.assertIn(b"*.pdf diff=lfs filter=lfs merge=lfs -text", content)
  69. # Test listing tracked patterns
  70. tracked = porcelain.lfs_track(self.repo)
  71. self.assertEqual(set(tracked), set(patterns))
  72. def test_lfs_untrack(self):
  73. """Test untracking patterns from LFS."""
  74. # First track some patterns
  75. patterns = ["*.bin", "*.pdf", "*.zip"]
  76. porcelain.lfs_track(self.repo, patterns)
  77. # Untrack one pattern
  78. remaining = porcelain.lfs_untrack(self.repo, ["*.pdf"])
  79. self.assertEqual(set(remaining), {"*.bin", "*.zip"})
  80. # Verify .gitattributes
  81. with open(os.path.join(self.repo.path, ".gitattributes"), "rb") as f:
  82. content = f.read()
  83. self.assertIn(b"*.bin diff=lfs filter=lfs merge=lfs -text", content)
  84. self.assertNotIn(b"*.pdf diff=lfs filter=lfs merge=lfs -text", content)
  85. self.assertIn(b"*.zip diff=lfs filter=lfs merge=lfs -text", content)
  86. def test_lfs_clean(self):
  87. """Test cleaning a file to LFS pointer."""
  88. # Initialize LFS
  89. porcelain.lfs_init(self.repo)
  90. # Create a test file
  91. test_content = b"This is test content for LFS"
  92. test_file = os.path.join(self.repo.path, "test.bin")
  93. with open(test_file, "wb") as f:
  94. f.write(test_content)
  95. # Clean the file
  96. pointer_content = porcelain.lfs_clean(self.repo, "test.bin")
  97. # Verify it's a valid LFS pointer
  98. pointer = LFSPointer.from_bytes(pointer_content)
  99. self.assertIsNotNone(pointer)
  100. self.assertEqual(pointer.size, len(test_content))
  101. # Verify the content was stored in LFS
  102. lfs_store = LFSStore.from_repo(self.repo)
  103. with lfs_store.open_object(pointer.oid) as f:
  104. stored_content = f.read()
  105. self.assertEqual(stored_content, test_content)
  106. def test_lfs_smudge(self):
  107. """Test smudging an LFS pointer to content."""
  108. # Initialize LFS
  109. porcelain.lfs_init(self.repo)
  110. # Create test content and store it
  111. test_content = b"This is test content for smudging"
  112. lfs_store = LFSStore.from_repo(self.repo)
  113. oid = lfs_store.write_object([test_content])
  114. # Create LFS pointer
  115. pointer = LFSPointer(oid, len(test_content))
  116. pointer_content = pointer.to_bytes()
  117. # Smudge the pointer
  118. smudged_content = porcelain.lfs_smudge(self.repo, pointer_content)
  119. self.assertEqual(smudged_content, test_content)
  120. def test_lfs_ls_files(self):
  121. """Test listing LFS files."""
  122. # Initialize repo with some LFS files
  123. porcelain.lfs_init(self.repo)
  124. # Create a test file and convert to LFS
  125. test_content = b"Large file content"
  126. test_file = os.path.join(self.repo.path, "large.bin")
  127. with open(test_file, "wb") as f:
  128. f.write(test_content)
  129. # Clean to LFS pointer
  130. pointer_content = porcelain.lfs_clean(self.repo, "large.bin")
  131. with open(test_file, "wb") as f:
  132. f.write(pointer_content)
  133. # Add and commit
  134. porcelain.add(self.repo, paths=["large.bin"])
  135. porcelain.commit(self.repo, message=b"Add LFS file")
  136. # List LFS files
  137. lfs_files = porcelain.lfs_ls_files(self.repo)
  138. self.assertEqual(len(lfs_files), 1)
  139. path, oid, size = lfs_files[0]
  140. self.assertEqual(path, "large.bin")
  141. self.assertEqual(size, len(test_content))
  142. def test_lfs_migrate(self):
  143. """Test migrating files to LFS."""
  144. # Create some files
  145. files = {
  146. "small.txt": b"Small file",
  147. "large1.bin": b"X" * 1000,
  148. "large2.dat": b"Y" * 2000,
  149. "exclude.bin": b"Z" * 1500,
  150. }
  151. for filename, content in files.items():
  152. path = os.path.join(self.repo.path, filename)
  153. with open(path, "wb") as f:
  154. f.write(content)
  155. # Add files to index
  156. porcelain.add(self.repo, paths=list(files.keys()))
  157. # Migrate with patterns
  158. count = porcelain.lfs_migrate(
  159. self.repo, include=["*.bin", "*.dat"], exclude=["exclude.*"]
  160. )
  161. self.assertEqual(count, 2) # large1.bin and large2.dat
  162. # Verify files were converted to LFS pointers
  163. for filename in ["large1.bin", "large2.dat"]:
  164. path = os.path.join(self.repo.path, filename)
  165. with open(path, "rb") as f:
  166. content = f.read()
  167. pointer = LFSPointer.from_bytes(content)
  168. self.assertIsNotNone(pointer)
  169. def test_lfs_pointer_check(self):
  170. """Test checking if files are LFS pointers."""
  171. # Initialize LFS
  172. porcelain.lfs_init(self.repo)
  173. # Create an LFS pointer file
  174. test_content = b"LFS content"
  175. lfs_file = os.path.join(self.repo.path, "lfs.bin")
  176. # First create the file
  177. with open(lfs_file, "wb") as f:
  178. f.write(test_content)
  179. pointer_content = porcelain.lfs_clean(self.repo, "lfs.bin")
  180. with open(lfs_file, "wb") as f:
  181. f.write(pointer_content)
  182. # Create a regular file
  183. regular_file = os.path.join(self.repo.path, "regular.txt")
  184. with open(regular_file, "wb") as f:
  185. f.write(b"Regular content")
  186. # Check both files
  187. results = porcelain.lfs_pointer_check(
  188. self.repo, paths=["lfs.bin", "regular.txt", "nonexistent.txt"]
  189. )
  190. self.assertIsNotNone(results["lfs.bin"])
  191. self.assertIsNone(results["regular.txt"])
  192. self.assertIsNone(results["nonexistent.txt"])
  193. if __name__ == "__main__":
  194. unittest.main()