@@ -290,3 +290,84 @@ def test_clone_existing_locked_tag(tmp_path: Path, temp_repo: TempRepoFixture) -
290290 f"Try again later or remove the { tag_ref_lock } manually"
291291 " if you are sure no other process is holding it."
292292 )
293+
294+
295+ @pytest .mark .skip_git_mock
296+ def test_clone_with_lfs_files (tmp_path : Path ) -> None :
297+ """Test cloning a repository with Git LFS files (issue #8723)."""
298+ from dulwich import porcelain
299+ from dulwich .lfs import LFSStore
300+
301+ # Create a source repository with LFS support
302+ source_path = tmp_path / "source-repo"
303+ source_path .mkdir ()
304+ repo = Repo .init (str (source_path ))
305+
306+ # Set up LFS in the repository
307+ lfs_dir = source_path / ".git" / "lfs"
308+ lfs_dir .mkdir (parents = True )
309+ lfs_store = LFSStore .create (str (lfs_dir ))
310+
311+ # Configure LFS URL in the repository config
312+ config = repo .get_config ()
313+ config .set ((b"lfs" ,), b"url" , lfs_dir .as_uri ().encode ())
314+ config .write_to_path ()
315+
316+ # Configure .gitattributes to track large files with LFS
317+ gitattributes = source_path / ".gitattributes"
318+ gitattributes .write_text ("*.bin filter=lfs diff=lfs merge=lfs -text\n " )
319+ porcelain .add (repo , str (gitattributes ))
320+
321+ # Create a regular file
322+ regular_file = source_path / "regular.txt"
323+ regular_file .write_text ("This is a regular file" )
324+ porcelain .add (repo , str (regular_file ))
325+
326+ # Create an LFS file with a pointer
327+ lfs_content = b"This is a large binary file content for LFS storage"
328+ lfs_file = source_path / "large.bin"
329+
330+ # Store the actual content in LFS store and create pointer
331+ lfs_object_id = lfs_store .write_object ([lfs_content ])
332+ lfs_pointer = (
333+ f"version https://git-lfs.github.com/spec/v1\n "
334+ f"oid sha256:{ lfs_object_id } \n "
335+ f"size { len (lfs_content )} \n "
336+ )
337+ lfs_file .write_text (lfs_pointer )
338+ porcelain .add (repo , str (lfs_file ))
339+
340+ # Commit the files
341+ porcelain .commit (
342+ repo ,
343+ message = b"Add files with LFS support" ,
344+ author = b"Test <test@example.com>" ,
345+ committer = b"Test <test@example.com>" ,
346+ )
347+
348+ # Clone the repository
349+ source_root_dir = tmp_path / "clone-root"
350+ source_root_dir .mkdir ()
351+ Git .clone (
352+ url = source_path .as_uri (),
353+ source_root = source_root_dir ,
354+ name = "clone-test" ,
355+ )
356+
357+ # Verify the clone succeeded
358+ clone_dir = source_root_dir / "clone-test"
359+ assert (clone_dir / ".git" ).is_dir ()
360+
361+ # Verify regular file is present
362+ assert (clone_dir / "regular.txt" ).exists ()
363+ assert (clone_dir / "regular.txt" ).read_text () == "This is a regular file"
364+
365+ # Verify .gitattributes is present
366+ assert (clone_dir / ".gitattributes" ).exists ()
367+ assert "filter=lfs" in (clone_dir / ".gitattributes" ).read_text ()
368+
369+ # Verify LFS file is present with actual content (not just pointer)
370+ # The LFS system should automatically retrieve the actual content
371+ assert (clone_dir / "large.bin" ).exists ()
372+ lfs_file_content = (clone_dir / "large.bin" ).read_bytes ()
373+ assert lfs_file_content == lfs_content
0 commit comments