3.6 KiB
Git LFS support, backed by IPFS
This is a fork of Radicle's heartwood adding Git LFS (large file) support,
with large file content stored on each contributor's own local IPFS node rather than on a
central server.
Why not a server?
Radicle's own FAQ notes that its first-generation protocol was built on IPFS and moved away from it — IPFS is a content-addressed store, and repositories are mutable, so it wasn't a fit for Radicle's core storage. That objection doesn't apply here: Git LFS's own pattern already separates a small, mutable pointer file (which Radicle replicates exactly as it replicates any other git object) from immutable large-file content addressed by hash — which is precisely what IPFS is good at. This fork only uses IPFS for that second part.
A seed-hosted HTTP LFS server was considered and prototyped first, but rejected: it would have recreated a single point of failure, and Radicle's node/address book has no existing mechanism to advertise a seed's HTTP endpoint (only P2P gossip addresses), so real seed discovery would have needed new protocol work outside this fork's scope. Instead, every peer uses their own local IPFS node, and the file-CID mapping travels with the repository itself.
How it works
- The mapping: a
cid=<cid>git note onrefs/notes/rad-lfs, attached to each LFS pointer file's own git blob hash (not the LFS oid — a different value). Verified against this repo's actual replication code (references_ofincrates/radicle/src/storage/git.rs) that Radicle replicates every ref under a peer's namespace exceptrefs/tmp/heads/*— there's no fixed allowlist, so this notes ref replicates the same wayrefs/heads/refs/tags/refs/cobsdo, once the push/fetch refspecs below are configured. - On commit: a pre-commit hook (installed by
rad lfs init) adds each staged LFS-tracked file to your local IPFS daemon, pins it, and writes the note above. - On push/pull: the actual bytes move through
rad-lfs-transfer, a Git LFS custom transfer agent — see that repo for details. It's a separate binary/repository since it has nothing Radicle-specific in it; it only needs a local IPFS daemon and a git repository with the notes above. - Seeding:
rad seednow also pins a repository's known LFS objects in your local IPFS node (mirroring "seeding = keep a full copy");rad unseedunpins them. Known prototype limitation: no cross-repository pin refcounting, so unseeding one repository can unpin content still needed by another seeded repository that happens to share the same CID.
Setup
# Build & install both binaries (from a checkout with the submodule fetched)
git submodule update --init
cargo install --path .
cargo install --path radicle-lfs-transfer
# One-time, per repository
rad lfs init
rad lfs init requires a local IPFS (Kubo) daemon to be reachable — it checks upfront and
fails with a clear message (e.g. "start one with ipfs daemon") rather than silently
continuing. Nothing else in this fork requires IPFS. Building, installing, and using rad
for anything other than the lfs command works exactly as upstream, with no IPFS dependency
at all. rad seed/rad unseed will warn (not fail) if a local IPFS daemon isn't reachable
when they try to pin/unpin LFS objects — seeding/unseeding itself still succeeds.
Status
This is a prototype, not upstream Radicle functionality. See the commits on the
rad-lfs-ipfs branch for the full change set relative to upstream heartwood.