diff --git a/LFS-IPFS.md b/LFS-IPFS.md index a6a9f2c7..a165834e 100644 --- a/LFS-IPFS.md +++ b/LFS-IPFS.md @@ -37,12 +37,15 @@ file-CID mapping travels with the repository itself, avoiding a single point of content first if the repository is private (see below). Batched into one call (rather than one `rad lfs store` call per file) specifically so a private repo's passphrase is only prompted for once per commit, not once per file. -- **On push**: committing writes the note locally, but pushing it to the remote is a **separate - step** from pushing your commit — `rad lfs init` only configures a push refspec for the notes - ref, not the branch, so after committing you need both `git push rad ` (your commit) - and a bare `git push rad` (the notes mapping). See [Troubleshooting](#troubleshooting) below; - this is the single most common way to end up with content that looks committed but that a - collaborator can't fetch. +- **On push**: `rad lfs init` configures `remote.rad.push` with *two* refspecs -- one for your + branches (`+refs/heads/*:refs/heads/*`), one for the notes mapping + (`+refs/notes/rad-lfs/local:refs/notes/rad-lfs`) -- so a single bare `git push rad` pushes + your commit and its notes mapping together. This wasn't always true: earlier builds only configured the notes + refspec, so a bare `git push rad` silently pushed *only* the mapping, leaving the commit + itself unpushed -- the single most common way to end up with content that looks committed but + that a collaborator can't fetch (`no CID recorded for oid ...`). Fixed, but only takes effect + once `rad lfs init` has been (re-)run with a build that includes the fix -- see + [Troubleshooting](#troubleshooting) below if you're still seeing the old behavior. - **The actual bytes** move through [`rad-lfs-transfer`][rad-lfs-transfer], a Git LFS custom transfer agent invoked automatically by `git`/`git-lfs` during a push or checkout — see that repo for details. It's a separate binary/repository since it has nothing Radicle-specific in @@ -191,8 +194,7 @@ git lfs track "*.psd" # or whatever large-file patterns you need git add .gitattributes git add my-large-file.psd git commit -m "Add asset" # pre-commit hook pins it to IPFS and records the CID here -git push rad # pushes your commit -git push rad # pushes the refs/notes/rad-lfs mapping (separate step -- see the push gotcha below) +git push rad # pushes your commit(s) and the refs/notes/rad-lfs mapping together ``` Someone else cloning the same repository: @@ -238,8 +240,8 @@ previously-committed LFS objects too, not just ones committed after they were ad | `private-repo LFS operations need to perform a key-agreement (ECDH) operation ...` | Your keystore is encrypted, ssh-agent can't do key agreement (protocol limitation), and no passphrase is available — normally you'd just be prompted interactively; this only happens in a non-interactive context (no TTY, e.g. CI). Set `RAD_PASSPHRASE`, or use an unencrypted keystore, or run it from a terminal. | | `you are not an authorized recipient of this object` | Either genuinely unauthorized (not a delegate/allow-listed DID for this private repo), or authorized *after* this specific object was committed and nobody has run `rad lfs rekey` since — ask an already-authorized collaborator to run it and push. | | `fatal: couldn't find remote ref refs/notes/rad-lfs` on `git fetch`/`git pull` (breaks *all* fetching, not just LFS) | Fixed — but if this repo had `rad lfs init` run against an older build, re-run `rad lfs init` once to replace the stale, now-invalid fetch refspec it configured (see `NOTES-lfs-notes-fetch-bug.md` for the full story). | -| Plain `git push rad` (no branch name) doesn't push your commits, only the notes ref | Expected: `rad lfs init` only configures a push refspec for `refs/notes/rad-lfs`, not the branch — so `git push rad ` and bare `git push rad` each push only what their own refspec covers. After committing an LFS-tracked file, run **both**: `git push rad ` for the commit, then `git push rad` for the notes mapping. | -| Collaborator gets `no CID recorded for oid ...; the peer that has this object needs to push its refs/notes/rad-lfs ref` even though they can see your commit | You (or whoever committed the LFS file) pushed the branch but forgot the follow-up bare `git push rad` — see the row above. Push it, then have them `git fetch rad` (or re-clone/re-pull). | +| Plain `git push rad` (no branch name) doesn't push your commits, only the notes ref | Your repository was set up with an older build: `rad lfs init` used to only configure a push refspec for `refs/notes/rad-lfs`, not the branch, so a bare push covered only that. Fixed — re-run `rad lfs init` once (harmless, idempotent) to add the missing `+refs/heads/*:refs/heads/*` push refspec, then a bare `git push rad` pushes both together. Until then, push both explicitly: `git push rad ` for the commit, then `git push rad` for the notes mapping. | +| Collaborator gets `no CID recorded for oid ...; the peer that has this object needs to push its refs/notes/rad-lfs ref` even though they can see your commit | You (or whoever committed the LFS file) pushed from a repository still missing the branch push refspec — see the row above. Re-run `rad lfs init`, push again, then have them `git fetch rad` (or re-clone/re-pull). | ## Status diff --git a/README.md b/README.md index 9f470005..2f606c8e 100644 --- a/README.md +++ b/README.md @@ -66,14 +66,12 @@ > git add .gitattributes > ``` > -> Day to day — same as plain Git LFS, just remember to push **both** the branch and the -> `refs/notes/rad-lfs` mapping (see the push gotcha below): +> Day to day — same as plain Git LFS: > > ```sh > git add my-large-file.psd > git commit -m "Add asset" # pre-commit hook pins it to IPFS, records the CID as a git note -> git push rad # pushes your commit -> git push rad # pushes the refs/notes/rad-lfs mapping (separate step, see below) +> git push rad # pushes your commit(s) and the refs/notes/rad-lfs mapping together > ``` > > Someone else, cloning the repository for the first time: @@ -97,12 +95,12 @@ > git push rad # publish the updated wrapped keys > ``` > -> **The push gotcha, explained**: `rad lfs init` only configures a push refspec for the notes -> ref, not the branch — so `git push rad ` and bare `git push rad` each push only what -> their own refspec covers, and you need *both* after committing an LFS-tracked file. Forgetting -> the bare `git push rad` is the most common way to end up with "it works for me, but my -> collaborator gets `no CID recorded for oid ...`" — their clone has your commit but not your -> note yet. +> **Repository set up with an older build?** A bare `git push rad` used to push *only* the +> notes mapping, silently leaving your commit unpushed (`rad lfs init` only configured a push +> refspec for the notes ref, not the branch). Fixed — but only for repositories where +> `rad lfs init` has been (re-)run with this build; re-run it once (harmless, idempotent) to +> pick up the fix on a repo set up before this. Until then, push both explicitly: +> `git push rad ` then `git push rad`. > > Full design, encryption details, and a longer troubleshooting table: > [`LFS-IPFS.md`](LFS-IPFS.md). diff --git a/crates/radicle-cli/src/commands/lfs/init.rs b/crates/radicle-cli/src/commands/lfs/init.rs index 2f45623e..00ce22a4 100644 --- a/crates/radicle-cli/src/commands/lfs/init.rs +++ b/crates/radicle-cli/src/commands/lfs/init.rs @@ -167,7 +167,7 @@ pub fn run() -> anyhow::Result<()> { // so the fetch refspec needs a wildcard to pull them all; the LFS // tooling merges across whatever's fetched (see // `lfs_crypto::find_envelope`) rather than expecting one ref. - let push_refspec = format!("+{LOCAL_NOTES_REF}:{NOTES_REF}"); + let notes_push_refspec = format!("+{LOCAL_NOTES_REF}:{NOTES_REF}"); let fetch_refspec = format!("+{NOTES_REF}/*:{NOTES_REF}/*"); let push_key = format!("remote.{RAD_REMOTE}.push"); let fetch_key = format!("remote.{RAD_REMOTE}.fetch"); @@ -186,10 +186,27 @@ pub fn run() -> anyhow::Result<()> { // with the corrected asymmetric refspec above. remove_refspec_if_present(workdir, &push_key, &format!("+{NOTES_REF}:{NOTES_REF}"))?; - ensure_refspec(workdir, &push_key, &push_refspec)?; + ensure_refspec(workdir, &push_key, ¬es_push_refspec)?; ensure_refspec(workdir, &fetch_key, &fetch_refspec)?; + + // As soon as *any* explicit push refspec is configured on a remote + // (the notes one above), git stops falling back to its usual + // push.default-driven behavior (pushing whatever branch you're on) for + // a bare `git push rad` -- it only pushes what's explicitly listed. + // Without a branch refspec too, that means a bare `git push rad` after + // committing an LFS-tracked file would silently push *only* the notes + // mapping, not the commit itself -- exactly the gotcha that caused a + // collaborator to see "no CID recorded for oid ..." even though the + // committer had "pushed". Configuring this explicitly, mirroring the + // existing `+refs/heads/*:refs/remotes/rad/*` fetch refspec above, + // makes a single bare `git push rad` push every local branch *and* + // the notes mapping together. + let heads_push_refspec = "+refs/heads/*:refs/heads/*".to_string(); + ensure_refspec(workdir, &push_key, &heads_push_refspec)?; + term::success!( - "Configured the `{RAD_REMOTE}` remote to push/fetch the `{NOTES_REF}` notes refs" + "Configured the `{RAD_REMOTE}` remote so a single `git push {RAD_REMOTE}` pushes your \ + branches and the `{NOTES_REF}` notes mapping together" ); migrate_local_notes_ref(&repo)?; @@ -200,7 +217,7 @@ pub fn run() -> anyhow::Result<()> { term::blank(); term::success!("Git LFS is now configured for this repository, backed by your local IPFS node."); term::info!( - "File CIDs are recorded in the `{NOTES_REF}` git-notes ref. Remember: after committing, `git push rad ` pushes your commit but not this mapping -- run a separate bare `git push rad` too (see LFS-IPFS.md's Troubleshooting section)." + "File CIDs are recorded in the `{NOTES_REF}` git-notes ref, which now travels with a plain `git push {RAD_REMOTE}` / `git pull {RAD_REMOTE}` alongside your branches." ); Ok(())