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 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 `rad lfs store` call per file) specifically so a private repo's passphrase is only prompted
for once per commit, not once per file. for once per commit, not once per file.
- **On push**: committing writes the note locally, but pushing it to the remote is a **separate - **On push**: `rad lfs init` configures `remote.rad.push` with *two* refspecs -- one for your
step** from pushing your commit — `rad lfs init` only configures a push refspec for the notes branches (`+refs/heads/*:refs/heads/*`), one for the notes mapping
ref, not the branch, so after committing you need both `git push rad <branch>` (your commit) (`+refs/notes/rad-lfs/local:refs/notes/rad-lfs`) -- so a single bare `git push rad` pushes
and a bare `git push rad` (the notes mapping). See [Troubleshooting](#troubleshooting) below; your commit and its notes mapping together. This wasn't always true: earlier builds only configured the notes
this is the single most common way to end up with content that looks committed but that a refspec, so a bare `git push rad` silently pushed *only* the mapping, leaving the commit
collaborator can't fetch. 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 - **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 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 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 .gitattributes
git add my-large-file.psd git add my-large-file.psd
git commit -m "Add asset" # pre-commit hook pins it to IPFS and records the CID here 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 your commit(s) and the refs/notes/rad-lfs mapping together
git push rad # pushes the refs/notes/rad-lfs mapping (separate step -- see the push gotcha below)
``` ```
Someone else cloning the same repository: 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. | | `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. | | `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). | | `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. | | 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 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). | | 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 ## Status

View File

@ -66,14 +66,12 @@
> git add .gitattributes > git add .gitattributes
> ``` > ```
> >
> Day to day — same as plain Git LFS, just remember to push **both** the branch and the > Day to day — same as plain Git LFS:
> `refs/notes/rad-lfs` mapping (see the push gotcha below):
> >
> ```sh > ```sh
> git add my-large-file.psd > 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 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 your commit(s) and the refs/notes/rad-lfs mapping together
> git push rad # pushes the refs/notes/rad-lfs mapping (separate step, see below)
> ``` > ```
> >
> Someone else, cloning the repository for the first time: > Someone else, cloning the repository for the first time:
@ -97,12 +95,12 @@
> git push rad # publish the updated wrapped keys > git push rad # publish the updated wrapped keys
> ``` > ```
> >
> **The push gotcha, explained**: `rad lfs init` only configures a push refspec for the notes > **Repository set up with an older build?** A bare `git push rad` used to push *only* the
> ref, not the branch — so `git push rad <branch>` and bare `git push rad` each push only what > notes mapping, silently leaving your commit unpushed (`rad lfs init` only configured a push
> their own refspec covers, and you need *both* after committing an LFS-tracked file. Forgetting > refspec for the notes ref, not the branch). Fixed — but only for repositories where
> the bare `git push rad` is the most common way to end up with "it works for me, but my > `rad lfs init` has been (re-)run with this build; re-run it once (harmless, idempotent) to
> collaborator gets `no CID recorded for oid ...`" — their clone has your commit but not your > pick up the fix on a repo set up before this. Until then, push both explicitly:
> note yet. > `git push rad <branch>` then `git push rad`.
> >
> Full design, encryption details, and a longer troubleshooting table: > Full design, encryption details, and a longer troubleshooting table:
> [`LFS-IPFS.md`](LFS-IPFS.md). > [`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 // so the fetch refspec needs a wildcard to pull them all; the LFS
// tooling merges across whatever's fetched (see // tooling merges across whatever's fetched (see
// `lfs_crypto::find_envelope`) rather than expecting one ref. // `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 fetch_refspec = format!("+{NOTES_REF}/*:{NOTES_REF}/*");
let push_key = format!("remote.{RAD_REMOTE}.push"); let push_key = format!("remote.{RAD_REMOTE}.push");
let fetch_key = format!("remote.{RAD_REMOTE}.fetch"); let fetch_key = format!("remote.{RAD_REMOTE}.fetch");
@ -186,10 +186,27 @@ pub fn run() -> anyhow::Result<()> {
// with the corrected asymmetric refspec above. // with the corrected asymmetric refspec above.
remove_refspec_if_present(workdir, &push_key, &format!("+{NOTES_REF}:{NOTES_REF}"))?; 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)?; 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!( 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)?; migrate_local_notes_ref(&repo)?;
@ -200,7 +217,7 @@ pub fn run() -> anyhow::Result<()> {
term::blank(); term::blank();
term::success!("Git LFS is now configured for this repository, backed by your local IPFS node."); term::success!("Git LFS is now configured for this repository, backed by your local IPFS node.");
term::info!( 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(()) Ok(())