lfs: configure a branch push refspec too, single `git push rad` now works

`rad lfs init` previously only configured a push refspec for the
notes ref (`+refs/notes/rad-lfs/local:refs/notes/rad-lfs`) -- once
any explicit push refspec exists on a remote, git stops falling back
to push.default-driven behavior for a bare `git push rad`, so it only
ever pushed the notes mapping, never the branch. That's the exact
footgun that just caused a real "blog did not update" report: `git
push rad` (bare) after a commit only sent the notes ref, silently
leaving the commit itself unpushed.

Add a second push refspec, `+refs/heads/*:refs/heads/*`, mirroring
the existing `+refs/heads/*:refs/remotes/rad/*` fetch refspec already
configured. A bare `git push rad` now pushes every local branch and
the notes mapping together in one command.

Only takes effect once `rad lfs init` has been (re-)run with this
build -- it's an additive, idempotent config change via the existing
`ensure_refspec` helper, no migration/cleanup needed since there's no
stale conflicting entry to remove this time. Updated `rad lfs init`'s
own success/info messages plus README.md/LFS-IPFS.md/
radicle-lfs-transfer's README to match -- the "push gotcha" workaround
they documented is now the *old*-repository fallback path, not the
primary instructions.
This commit is contained in:
Maciek "mab122" Bator 2026-07-16 15:23:00 +02:00
parent 245fdd3fdc
commit a84aeb23f7
3 changed files with 41 additions and 24 deletions

View File

@ -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 <branch>` (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 <branch> # 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 <branch>` and bare `git push rad` each push only what their own refspec covers. After committing an LFS-tracked file, run **both**: `git push rad <branch>` 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 <branch>` 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

View File

@ -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 <branch> # 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 <branch>` 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 <branch>` then `git push rad`.
>
> Full design, encryption details, and a longer troubleshooting table:
> [`LFS-IPFS.md`](LFS-IPFS.md).

View File

@ -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, &notes_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 <branch>` 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(())