link: cmux link seed pull brings a seeded Cloud folder's changes back as a diff

Merged
TE
4d ago
+1576-61

Leo's words:

Your project folder is copied up.
we'll want to deal with the edge cases for this too

This is piece 6 of the Cloud project seed, rule 6 of "Getting the project onto the machine" in the Cloud design spec: the folder's changes come back to the Mac as a diff, not as a copy of the folder. It is the Mac side of git.seed.export (#30633, merged) and reads its base from the seed record that cmux link seed push keeps (#30634, merged).

What it does

cmux link seed pull --socket PATH LOCAL REMOTE [--base-head SHA --base-index SHA --base-work SHA]

  1. Base. Read from this Mac's seed record for LOCAL and REMOTE. The three --base-* flags, given all together or not at all, override it. It answers seed.no_previous in these cases:
    • there is no record;
    • the record is for another machine;
    • the last push was a copy, which cmux link sync pull brings back instead;
    • the record is incomplete.
  2. Export. Ask the machine for git.seed.export since that base, wait for the job, and download the bundle over fs-v1. The machine must serve both fs-v1 and git-seed-export-v1; otherwise the answer is machine_outdated.
  3. Fetch. Fetch the bundle into a quarantine repo. It borrows this repo's objects, checks every object with transfer.fsckObjects, and checks that the bundle builds on the base.
  4. Merge three ways against the base:
    • The branch fast-forwards only when this Mac's HEAD is still the seed's. A switched branch, rewritten history, or both sides moving is a .git/HEAD conflict.
    • The machine's staged tree is staged here only when this Mac's staged tree is unchanged.
    • Each working-tree file takes whichever side changed. If both sides changed it differently, it is a conflict.
  5. On conflict nothing changes. The answer is {"ok":false,"error_code":"sync_conflict","conflicts":[...]}.
  6. Otherwise apply. Files move through place.rs's transaction. The branch then moves by compare-and-swap and the index follows. Any failure puts back what changed.
  7. Never brought back: a .git path, a hook (including core.hooksPath), a nested repo, a symlink or submodule the machine changed, an ignored file, or an untracked secret.
  8. The answer's base is saved as the new record, so the next pull and the next cmux link seed push --since-last build on it. A machine with no changes answers unchanged and nothing is downloaded.

The answer is {ok, unchanged, written, deleted, bytes, bundle_bytes, fast_forwarded, staged, head, base{head,index,work}, left_out}.

Layout

  • The CLI lives in link/seed_cli/pull.rs, next to push and clone, and handles the record.
  • The engine lives in link/sync_cli/seed_pull.rs, because it uses the sync internals: Remote, git_back's quarantine, and place.
  • link/seed/plan.rs gains current(), shared by the seed and the pull, and leaves out a secret by its raw (non-UTF-8) name.

Size

About 1,400 lines, of which about 360 are the journey test. The engine is one state machine (export, receive, the three-way plan, the transaction) with its tests. Splitting it would land a part that can't be run on its own.

Tests

  • tests/seed_pull_journey.rs runs real Mac and VM daemons on Linux, with the VM cloning from file:// through the CMUX_TEST_GIT_SEED_GITHUB seam:
    • It seeds with cmux link seed push, then pulls from the record. Covered: commits, edits, adds, deletes and staged changes come back; ignored files and secrets stay on the VM; a second pull is unchanged; the next push --since-last builds on the pulled base.
    • Both sides edit different files and both edits are kept. A same-file conflict changes nothing, and a rewritten VM history is a .git/HEAD conflict.
    • Hooks, a hook-folder file and a nested repo never come back.
  • Unit tests cover parsing, picking the base from the record, and the plan's non-UTF-8 secret.
  • On cmux-ci: link::seed 24 passed, seed_pull_journey 3 passed, link_seed 6 passed, cargo fmt clean. Clippy flags only files already flagged on base (start_chat_cli.rs, environments.rs).

Risk: new CLI only. Nothing calls seed pull yet, and the push side is unchanged.

Agent session: agent session

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Adds cmux link seed pull, the Mac side of the Cloud project seed: after a push copies a folder to the machine, pull brings its changes back as a diff merged against the seed, never as a folder copy. Nothing calls it yet and the push side is unchanged.

New Features

  • Merges three ways against the seed's HEAD, INDEX, and WORK: the branch fast-forwards only while this Mac's HEAD is still the seed's, the machine's staged tree stages here when this Mac's staged tree is unchanged, and each working-tree file keeps whichever side changed; conflicts change nothing and answer sync_conflict.
  • The base comes from this Mac's seed record, overridable by the three --base-* flags given all together, and every pull keeps the machine's state as the next pull's and push --since-last's base.
  • Hooks, nested repositories, symlinks or submodules the machine changed, ignored files, and untracked secrets never come back.

Bug Fixes

  • A file saved during a pull (or one git assumes unchanged) is now a conflict, not an overwrite: the transaction checks bytes against the planned blob, and the index is replaced under git's own index.lock.
  • Seed plans now leave untracked secrets out by their raw name, so a secret whose name is not UTF-8 no longer reaches the machine.