feat: git lfs — pointer, store, batch, transfer, locks #22

merged colechristensen cole.christensen@gmail.com wants to merge feat/lfs into main
No CI

Summary

Full Git LFS (spec v1) support, closes #38. Exposed as pure request/response modules that mirror the existing `UploadPack`/`ReceivePack` style — no HTTP server is introduced; consumers wire the handlers into their own routing.

What’s in

  • `ExGitObjectstore.Lfs.Pointer` — parser/emitter with strict spec-v1 validation (version-first, alphabetical keys, sha256-only OIDs, canonical decimal size, trailing LF).
  • `ExGitObjectstore.Lfs.Store` — behaviour parallel to `Storage`, keyed by SHA256. Streaming `put/4` verifies the observed SHA256 matches the claimed OID; on mismatch the write is discarded. Backends: Memory, Filesystem (2+2 fanout, atomic rename), and S3 (multipart upload with optional presigned URLs). Shared conformance macro at `ExGitObjectstore.Test.LfsStoreConformance` exercises every backend identically.
  • `ExGitObjectstore.Lfs.Batch` — Batch API returning spec-compliant upload/download/verify actions. Uses presigned URLs when the backend supports them; falls back to library-served URLs for Filesystem/Memory.
  • `ExGitObjectstore.Lfs.Transfer` — basic-transfer handlers for `GET/PUT /objects/:oid` and `POST /objects/:oid/verify`.
  • `ExGitObjectstore.Lfs.Locks` — v1 locks API (create, list, verify, unlock with owner/force).
  • Telemetry at `[:ex_git_objectstore, :lfs, :batch | :transfer | :lock]`.
  • `Repo` gains optional `:lfs_storage` alongside `:storage`.
  • CHANGELOG entry under `[Unreleased]`.
  • Anvil requirements REQ-LFS-001…006 created; tests carry `@moduletag requirements: […]`.

Test plan

  • 91 new LFS tests pass (14 S3 tests excluded without MinIO)
  • Full suite: 891 tests, 0 failures
  • `mix credo –strict` clean for all new code
  • `mix format` clean
  • `anvil requirement status` passes
  • Manual interop against `git lfs` client (follow-up, not in PR)

🤖 Generated with Claude Code

Created Apr 20, 2026 at 06:14 UTC | Merged Apr 20, 2026 at 13:33 UTC by colechristensen cole.christensen@gmail.com