Skip to content

Commit c04069d

Browse files
authored
Add test for Git LFS repository cloning (#10724)
Verifies that Poetry can successfully clone Git repositories that use Git Large File Storage (LFS) and that LFS files are properly retrieved with their actual content rather than just pointer files. This test uses dulwich's built-in LFS support to create a repository with LFS files and verifies that cloning works correctly without requiring manual configuration. Note that while LFS support works in Dulwich >=1, the tests specifically rely on it being able to use locally cloned LFS stores, which requires dulwich 1.2.1.
1 parent 811a12d commit c04069d

3 files changed

Lines changed: 83 additions & 3 deletions

File tree

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ pre-commit = ">=2.10"
7272
[tool.poetry.group.test.dependencies]
7373
coverage = ">=7.2.0"
7474
deepdiff = ">=6.3"
75+
# test_clone_with_lfs_files relies on being able to use
76+
# locally cloned LFS stores, which requires dulwich>=1.2.1
77+
dulwich = ">=1.2.1"
7578
responses = ">=0.25.8"
7679
jaraco-classes = ">=3.3.1"
7780
pytest = ">=9.0"

tests/vcs/git/test_backend.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,3 +455,80 @@ def test_clone_legacy_strips_ref_prefixes(
455455

456456
mock_clone.assert_called_once()
457457
mock_checkout.assert_called_once_with(expected_checkout, target)
458+
459+
460+
@pytest.mark.skip_git_mock
461+
def test_clone_with_lfs_files(tmp_path: Path) -> None:
462+
"""Test cloning a repository with Git LFS files (issue #8723)."""
463+
from dulwich import porcelain
464+
from dulwich.lfs import LFSStore
465+
466+
# Create a source repository with LFS support
467+
source_path = tmp_path / "source-repo"
468+
source_path.mkdir()
469+
repo = Repo.init(str(source_path))
470+
471+
# Set up LFS in the repository
472+
lfs_dir = source_path / ".git" / "lfs"
473+
lfs_dir.mkdir(parents=True)
474+
lfs_store = LFSStore.create(str(lfs_dir))
475+
476+
# Configure .gitattributes to track large files with LFS
477+
gitattributes = source_path / ".gitattributes"
478+
gitattributes.write_text("*.bin filter=lfs diff=lfs merge=lfs -text\n")
479+
porcelain.add(repo, str(gitattributes))
480+
481+
# Create a regular file
482+
regular_file = source_path / "regular.txt"
483+
regular_file.write_text("This is a regular file")
484+
porcelain.add(repo, str(regular_file))
485+
486+
# Create an LFS file with a pointer
487+
lfs_content = b"This is a large binary file content for LFS storage"
488+
lfs_file = source_path / "large.bin"
489+
490+
# Store the actual content in LFS store and create pointer
491+
lfs_object_id = lfs_store.write_object([lfs_content])
492+
lfs_pointer = (
493+
f"version https://git-lfs.github.com/spec/v1\n"
494+
f"oid sha256:{lfs_object_id}\n"
495+
f"size {len(lfs_content)}\n"
496+
)
497+
lfs_file.write_text(lfs_pointer)
498+
porcelain.add(repo, str(lfs_file))
499+
500+
# Commit the files
501+
porcelain.commit(
502+
repo,
503+
message=b"Add files with LFS support",
504+
author=b"Test <test@example.com>",
505+
committer=b"Test <test@example.com>",
506+
sign=False,
507+
)
508+
509+
# Clone the repository
510+
source_root_dir = tmp_path / "clone-root"
511+
source_root_dir.mkdir()
512+
Git.clone(
513+
url=source_path.as_uri(),
514+
source_root=source_root_dir,
515+
name="clone-test",
516+
)
517+
518+
# Verify the clone succeeded
519+
clone_dir = source_root_dir / "clone-test"
520+
assert (clone_dir / ".git").is_dir()
521+
522+
# Verify regular file is present
523+
assert (clone_dir / "regular.txt").exists()
524+
assert (clone_dir / "regular.txt").read_text() == "This is a regular file"
525+
526+
# Verify .gitattributes is present
527+
assert (clone_dir / ".gitattributes").exists()
528+
assert "filter=lfs" in (clone_dir / ".gitattributes").read_text()
529+
530+
# Verify LFS file is present with actual content (not just pointer)
531+
# The LFS system should automatically retrieve the actual content
532+
assert (clone_dir / "large.bin").exists()
533+
lfs_file_content = (clone_dir / "large.bin").read_bytes()
534+
assert lfs_file_content == lfs_content

0 commit comments

Comments
 (0)