cmux link sync push: copy repos that borrow objects (alternates) to Cloud
Leo picked a project and a Cloud machine in a DEV build, and the send was refused with:
Couldn't copy the folder to leo-cloud: /Users/leoli/Projects/cmux borrows objects from another repository (objects/info/alternates); clone it without --shared or --reference first
Leo: "chatgpt for chatgpt work we can select project etc / we wanna have that be possible too". Selecting a project for Cloud has to work for any local project, and a repo with alternates is common: git clone --shared and --reference make them, and so do worktree mirrors.
What changed
cmux link sync push (the copy behind Move to Cloud and cloud.startChat, in cmux-tui/crates/cmux-tui/src/link/sync_cli/local.rs) no longer refuses these repos. The copy now stands on its own:
- The object folders a repo borrows from (
objects/info/alternates) are followed, including their own alternates (up to git's 5 levels, absolute or relative paths, C-quoted lines too). Their object files (loose objects, packs with their.idx/.rev/.promisor/.bitmap, commit-graph layers) go into the copy's.git/objects/. If the repo and a borrowed store hold a file of the same name, the repo's own copy wins (same hash, same data). - The alternates file itself never goes, since its paths point at folders on this Mac. Neither do the borrowed stores' own indexes that cover only their objects (
multi-pack-index,info/packs, the commit-graph chain file,info/commit-graph), nortmp_*or.lockfiles. - Pack names are stable, so the existing per-file manifest still dedupes. A second push with no changes writes nothing, which the test checks.
- Partial (blobless) clones keep working: a borrowed promisor pack brings its
.promisormarker along.
I didn't use a bundle: git bundle create on a blobless partial clone lazily fetches every missing blob. I also didn't clone from origin on the machine: the sync talks to the machine only through the daemon's fs-v1 file ops, which can't run git there.
Logic is in Rust only. No Swift changes.
Tests
tests/link_sync.rs::a_repo_that_borrows_objects_arrives_standing_alone: a chain ofgit clone --sharedrepos (copy->mid->app, withmid's link rewritten to a relative path), with a commit in each and an unstaged edit. It checks that:- the push writes no alternates file;
- a second push writes nothing;
- after both borrowed repos are deleted,
git fsck --fullpasses on the machine's copy, HEAD, history (--all), stash and status match, and a 13 MiB blob that only the farthest repo held is there.
- Unit tests for alternates-line unquoting and for the skip rules on borrowed stores.
Follow-up (not in this PR)
The copy still sends the whole object store. On a big local store (Leo's ~/Projects/cmux has 83 GB of local objects) that means hashing and uploading all of it on the first push. Sending only what the machine can't fetch from origin needs a git step on the machine, which needs a machine-side exec path beyond fs-v1.
Agent session: agent session
š¤ Generated with Claude Code