@@ -424,3 +424,79 @@ def test_clone_nested_annotated_tags(tmp_path: Path) -> None:
424424 assert (clone_dir / ".git" ).is_dir ()
425425 assert (clone_dir / "test.txt" ).exists ()
426426 assert (clone_dir / "test.txt" ).read_text (encoding = "utf-8" ) == "nested tag test"
427+
428+
429+ @pytest .mark .skip_git_mock
430+ def test_clone_with_lfs_files (tmp_path : Path ) -> None :
431+ """Test cloning a repository with Git LFS files (issue #8723)."""
432+ from dulwich import porcelain
433+ from dulwich .lfs import LFSStore
434+
435+ # Create a source repository with LFS support
436+ source_path = tmp_path / "source-repo"
437+ source_path .mkdir ()
438+ repo = Repo .init (str (source_path ))
439+
440+ # Set up LFS in the repository
441+ lfs_dir = source_path / ".git" / "lfs"
442+ lfs_dir .mkdir (parents = True )
443+ lfs_store = LFSStore .create (str (lfs_dir ))
444+
445+ # Configure .gitattributes to track large files with LFS
446+ gitattributes = source_path / ".gitattributes"
447+ gitattributes .write_text ("*.bin filter=lfs diff=lfs merge=lfs -text\n " )
448+ porcelain .add (repo , str (gitattributes ))
449+
450+ # Create a regular file
451+ regular_file = source_path / "regular.txt"
452+ regular_file .write_text ("This is a regular file" )
453+ porcelain .add (repo , str (regular_file ))
454+
455+ # Create an LFS file with a pointer
456+ lfs_content = b"This is a large binary file content for LFS storage"
457+ lfs_file = source_path / "large.bin"
458+
459+ # Store the actual content in LFS store and create pointer
460+ lfs_object_id = lfs_store .write_object ([lfs_content ])
461+ lfs_pointer = (
462+ f"version https://git-lfs.github.com/spec/v1\n "
463+ f"oid sha256:{ lfs_object_id } \n "
464+ f"size { len (lfs_content )} \n "
465+ )
466+ lfs_file .write_text (lfs_pointer )
467+ porcelain .add (repo , str (lfs_file ))
468+
469+ # Commit the files
470+ porcelain .commit (
471+ repo ,
472+ message = b"Add files with LFS support" ,
473+ author = b"Test <test@example.com>" ,
474+ committer = b"Test <test@example.com>" ,
475+ )
476+
477+ # Clone the repository
478+ source_root_dir = tmp_path / "clone-root"
479+ source_root_dir .mkdir ()
480+ Git .clone (
481+ url = source_path .as_uri (),
482+ source_root = source_root_dir ,
483+ name = "clone-test" ,
484+ )
485+
486+ # Verify the clone succeeded
487+ clone_dir = source_root_dir / "clone-test"
488+ assert (clone_dir / ".git" ).is_dir ()
489+
490+ # Verify regular file is present
491+ assert (clone_dir / "regular.txt" ).exists ()
492+ assert (clone_dir / "regular.txt" ).read_text () == "This is a regular file"
493+
494+ # Verify .gitattributes is present
495+ assert (clone_dir / ".gitattributes" ).exists ()
496+ assert "filter=lfs" in (clone_dir / ".gitattributes" ).read_text ()
497+
498+ # Verify LFS file is present with actual content (not just pointer)
499+ # The LFS system should automatically retrieve the actual content
500+ assert (clone_dir / "large.bin" ).exists ()
501+ lfs_file_content = (clone_dir / "large.bin" ).read_bytes ()
502+ assert lfs_file_content == lfs_content
0 commit comments