Repository navigation
The primary's fork checkout, and its nested submodules, are moved to the commit its tree pins - #718
Conversation
The primary built std, and decided whether to bootstrap, from whatever commit its `rust/` held: `fork_checkout` returned the primary's checkout as it stood (since 8603559), and `toolchain::ensure` decided the bootstrap before asking it. `git pull` moves the gitlink and never the submodule, so after #702 the primary's `rust/` at 95960d6c compiled against a `toyos-abi` that had moved on, and the owner's build of `main` broke. The licence gate read the same stale `library/`. Now the primary's checkout is moved to the pin whenever its `HEAD` differs, ahead or behind: it is no workspace, so nothing ahead is kept. It is refused by name when it has local changes, or does not hold the pinned commit (the refusal names the `git fetch` that fetches it); no fetch is made for it. The move holds the worktree lock and then the global lock exclusively, in `buildlock.rs`'s declared order, because every worktree's compiler and LLVM are built from that checkout. An uninitialised `rust/` (a runner's) is left to whoever initialises it: git there would walk up into the superproject. `toolchain::ensure` moves the checkout before it decides the bootstrap. Two latent gaps the same defect left in CI: `ensure_shallow_fork` accepted any `rust/` with an `x.py`, at whatever commit, and `release::bootstrap` keyed its layers from `rust/` as it stood. The first now refuses a checkout off the pin, and the second runs it first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
… refused Two tests the first commit's mutations showed it lacked: removing the global lock from the primary's move, and accepting a shallow fork at any commit, each stayed green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Seen in this branch's whole-suite run at 72621e9, which changes no guest byte; the host's load is recorded with it, as root CLAUDE.md asks of a red seen under load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Mutation and negative-control patches for the runs the body reports. Each was applied with negative-control.patchdiff --git a/src/buildlock.rs b/src/buildlock.rs
index 9968ef74a..d4a95e7af 100644
--- a/src/buildlock.rs
+++ b/src/buildlock.rs
@@ -208,13 +208,6 @@ pub fn compiler_shared(root: &Path, what: &str) -> Guard {
acquire(&git_lock_dir(root), LOCK_SH, what, BUILD)
}
-/// The global lock exclusively: what the primary holds, inside its worktree
-/// lock held exclusively, while it moves its fork checkout, which every
-/// worktree's compiler is built from.
-pub fn global_exclusive(root: &Path, what: &str) -> Guard {
- acquire(&git_lock_dir(root), LOCK_EX, what, BUILD)
-}
-
/// Exclusive lock over the shared cargo artifact paths.
///
/// Cargo keys an artifact path on (crate, target, profile) and nothing else, so
diff --git a/src/lib.rs b/src/lib.rs
index 3b94b39f6..83d302956 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -120,18 +120,12 @@ pub fn ensure_submodules(repo_dir: &Path) {
/// `rust/` at the commit this tree pins and with no history, where a CI
/// runner's checkout has none: what reading the fork's sources and building
-/// the toolchain from them need. One already there at another commit is
-/// refused. Refused in a linked worktree, whose `rust/` is
+/// the toolchain from them need. Refused in a linked worktree, whose `rust/` is
/// `sysroot::fork_checkout`'s to make: `git submodule` there rewrites the
/// `core.worktree` of the primary's fork.
pub fn ensure_shallow_fork(root: &Path) -> Result<(), String> {
- let fork = root.join("rust");
- if fork.join("x.py").exists() {
- let head = sysroot::git_out(&fork, &["rev-parse", "HEAD"]).trim().to_string();
- let pinned = sysroot::pinned_fork(root);
- return (head == pinned)
- .then_some(())
- .ok_or_else(|| format!("{} is at {head}, and this tree pins the fork at {pinned}", fork.display()));
+ if root.join("rust/x.py").exists() {
+ return Ok(());
}
if let toolchain::Owner::Elsewhere(_) = toolchain::owner(root) {
return Err(format!(
diff --git a/src/release.rs b/src/release.rs
index 1ff7c20e2..2ba36b343 100644
--- a/src/release.rs
+++ b/src/release.rs
@@ -196,7 +196,6 @@ fn outputs(layers: &[Layer]) -> String {
/// than its build does, and so is one the build left not whole under its key.
pub fn bootstrap(root: &Path) -> Result<String, String> {
let file = step_outputs()?;
- crate::ensure_shallow_fork(root)?;
let layers = layers(root);
let restored: Vec<bool> = layers.iter().map(|layer| root.join(&layer.paths[0]).exists()).collect();
whole(&layers, &restored, |layer| defect(root, layer)).map_err(|why| format!("restored, {why}"))?;
diff --git a/src/sysroot.rs b/src/sysroot.rs
index 720a8289c..59cce0351 100644
--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -19,8 +19,7 @@
//! it, and a crate built against a sysroot learns which targets' libraries
//! moved ([`Identity`]). Each build refuses dep-info that says otherwise.
//!
-//! **Each worktree builds std in its own fork checkout.** The primary builds in its `rust/`,
-//! moved to the commit its tree pins;
+//! **Each worktree builds std in its own fork checkout.** The primary builds in its `rust/`;
//! a linked worktree in its own `rust/`, made on first need as a git worktree of
//! the primary's fork repository at the commit this tree pins ([`fork_checkout`]).
//! `library/std` names `toyos-abi` and `toyos` as `../../../`, so each
@@ -486,7 +485,7 @@ fn key_of(root: &Path, freestanding: &Key, build: &str) -> Key {
/// The commit this checkout's tree pins the std fork at: the index's, so a
/// staged gitlink counts as the tree's.
-pub(crate) fn pinned_fork(root: &Path) -> String {
+fn pinned_fork(root: &Path) -> String {
let entry = git_out(root, &["ls-files", "-s", "--", "rust"]);
let mut words = entry.split_whitespace();
match (words.next(), words.next()) {
@@ -495,23 +494,21 @@ pub(crate) fn pinned_fork(root: &Path) -> String {
}
}
-/// The fork checkout `root`'s std is built in, at the commit `root`'s tree pins.
+/// The fork checkout `root`'s std is built in.
///
-/// The primary's is its own `rust/`, used where it is not initialised yet, which
-/// whoever initialises it does at the pin. It is no workspace, and every
-/// worktree's compiler and LLVM are built from it, so one whose `HEAD` is not the
-/// pin is moved there, under the global lock held exclusively; refused by name
-/// if it has local changes, or does not hold the pinned commit.
+/// The primary's is its own `rust/`. A linked worktree's `rust/` starts as the
+/// empty stub `git worktree add` leaves; it is made here, the first time it is
+/// needed, as a git worktree of the primary's fork repository at the commit
+/// this tree pins, sharing its objects — and `library/backtrace` the same way
+/// from the primary's, or by git's own clone where the primary does not hold
+/// that commit.
///
-/// A linked worktree's `rust/` starts as the empty stub `git worktree add`
-/// leaves; it is made here, the first time it is needed, as a git worktree of
-/// the primary's fork repository at the pin, sharing its objects — and
-/// `library/backtrace` the same way from the primary's, or by git's own clone
-/// where the primary does not hold that commit. It is where an agent edits the
-/// fork, so one ahead of the pin is used as it stands; one neither at the pin
-/// nor ahead of it is moved there, fetching the commit from the primary's
-/// repository first if it does not hold it, and refused by name if it has local
-/// changes rather than moved out from under whoever made them.
+/// A checkout that exists is used as it stands, which is where an agent edits
+/// the fork; one whose `HEAD` is neither the pinned commit nor ahead of it is
+/// moved there itself, fetching the commit from the primary's repository first
+/// if the checkout does not already hold it, unless the checkout has local
+/// changes, in which case it is refused by name rather than moved out from
+/// under whoever made them.
///
/// Every build in a worktree asks this at once, so the making and the move are
/// each decided and done under the worktree's lock held exclusively
@@ -520,68 +517,63 @@ pub(crate) fn pinned_fork(root: &Path) -> String {
pub fn fork_checkout(root: &Path, lock: &mut Held) -> PathBuf {
let fork = root.join("rust");
let primary = match toolchain::owner(root) {
- Owner::Us if !fork.join(".git").exists() => return fork,
- Owner::Us => None,
+ Owner::Us => return fork,
Owner::Installed => panic!("an installed toolchain has no fork checkout to build std in"),
- Owner::Elsewhere(primary) => Some(primary),
+ Owner::Elsewhere(primary) => primary,
};
let pinned = pinned_fork(root);
- if let Some(primary) = &primary {
- lock.act_if(
- Scope::Worktree,
- "make the fork checkout",
- || (!fork.join(".git").exists()).then_some(()),
- |()| {
- let stub = fs::read_dir(&fork).map_or(0, |d| d.count());
- assert!(
- stub == 0,
- "{} is neither a fork checkout nor the empty stub a worktree starts with",
- fork.display()
- );
- let _ = fs::remove_dir(&fork);
- eprintln!("Making {} a fork checkout at {pinned} (a git worktree of the primary's)", fork.display());
- git_run(&primary.join("rust"), &["worktree", "add", "--detach", path_str(&fork), &pinned]);
- let backtrace = git_out(&fork, &["ls-tree", "HEAD", "library/backtrace"]);
- let commit = backtrace.split_whitespace().nth(2).unwrap_or_else(|| {
- panic!("{} pins no library/backtrace: {backtrace:?}", fork.display())
- });
- let theirs = primary.join("rust/library/backtrace");
- let held = Command::new("git")
- .args(["cat-file", "-e", &format!("{commit}^{{commit}}")])
- .current_dir(&theirs)
- .status()
- .is_ok_and(|s| s.success());
- let at = fork.join("library/backtrace");
- if held {
- let _ = fs::remove_dir(&at);
- git_run(&theirs, &["worktree", "add", "--detach", path_str(&at), commit]);
- } else {
- git_run(&fork, &["submodule", "update", "--init", "library/backtrace"]);
- }
- },
- );
- }
+ lock.act_if(
+ Scope::Worktree,
+ "make the fork checkout",
+ || (!fork.join(".git").exists()).then_some(()),
+ |()| {
+ let stub = fs::read_dir(&fork).map_or(0, |d| d.count());
+ assert!(
+ stub == 0,
+ "{} is neither a fork checkout nor the empty stub a worktree starts with",
+ fork.display()
+ );
+ let _ = fs::remove_dir(&fork);
+ eprintln!("Making {} a fork checkout at {pinned} (a git worktree of the primary's)", fork.display());
+ git_run(&primary.join("rust"), &["worktree", "add", "--detach", path_str(&fork), &pinned]);
+ let backtrace = git_out(&fork, &["ls-tree", "HEAD", "library/backtrace"]);
+ let commit = backtrace.split_whitespace().nth(2).unwrap_or_else(|| {
+ panic!("{} pins no library/backtrace: {backtrace:?}", fork.display())
+ });
+ let theirs = primary.join("rust/library/backtrace");
+ let held = Command::new("git")
+ .args(["cat-file", "-e", &format!("{commit}^{{commit}}")])
+ .current_dir(&theirs)
+ .status()
+ .is_ok_and(|s| s.success());
+ let at = fork.join("library/backtrace");
+ if held {
+ let _ = fs::remove_dir(&at);
+ git_run(&theirs, &["worktree", "add", "--detach", path_str(&at), commit]);
+ } else {
+ git_run(&fork, &["submodule", "update", "--init", "library/backtrace"]);
+ }
+ },
+ );
lock.act_if(
Scope::Worktree,
"move the fork checkout to its pin",
|| {
let head = git_out(&fork, &["rev-parse", "HEAD"]).trim().to_string();
- let ahead = primary.is_some()
- && Command::new("git")
- .args(["merge-base", "--is-ancestor", &pinned, &head])
- .current_dir(&fork)
- .status()
- .is_ok_and(|s| s.success());
+ let ahead = Command::new("git")
+ .args(["merge-base", "--is-ancestor", &pinned, &head])
+ .current_dir(&fork)
+ .status()
+ .is_ok_and(|s| s.success());
(head != pinned && !ahead).then_some(head)
},
|head| {
- let _global = primary.is_none().then(|| buildlock::global_exclusive(root, "move the fork checkout to its pin"));
let dirty = git_out(&fork, &["status", "--porcelain", "--ignore-submodules=none"]);
assert!(
dirty.is_empty(),
- "{} is at {head} with uncommitted work, and this tree pins the fork at {pinned}: a build \
- here would compile a std this tree does not name, and moving the checkout would lose \
- that work.\n{dirty}",
+ "{} is at {head} with uncommitted work, and this tree pins the fork at {pinned}, which \
+ that is not ahead of: a build here would compile a std this tree does not name, and \
+ moving the checkout would lose that work.\n{dirty}",
fork.display(),
);
let held = Command::new("git")
@@ -590,18 +582,10 @@ pub fn fork_checkout(root: &Path, lock: &mut Held) -> PathBuf {
.status()
.is_ok_and(|s| s.success());
if !held {
- match &primary {
- Some(primary) => git_run(&fork, &["fetch", path_str(&primary.join("rust")), &pinned]),
- None => panic!(
- "{} is at {head}, and this tree pins the fork at {pinned}, which it does not hold: \
- `git -C {} fetch origin {pinned}` fetches it",
- fork.display(),
- fork.display(),
- ),
- }
+ git_run(&fork, &["fetch", path_str(&primary.join("rust")), &pinned]);
}
git_run(&fork, &["checkout", "--detach", "-q", &pinned]);
- eprintln!("{} was at {head}, and this tree pins {pinned}: checked it out", fork.display());
+ eprintln!("{} was at {head}, behind this tree's pin {pinned}: checked it out", fork.display());
},
);
fork
diff --git a/src/toolchain.rs b/src/toolchain.rs
index 736ad0f7d..b7a00883c 100644
--- a/src/toolchain.rs
+++ b/src/toolchain.rs
@@ -560,8 +560,6 @@ pub fn ensure(root: &Path, lock: &mut buildlock::Held, hosted_rustc: bool) -> Sy
Owner::Us => {}
}
- // The bootstrap decides from the `compiler/` this tree pins.
- sysroot::fork_checkout(root, lock);
let hosted_stamp = stamps_dir.join("hosted-rustc.stamp");
lock.act_if(
Scope::Global,mutation-no-global.patchdiff --git a/src/sysroot.rs b/src/sysroot.rs
index 5cd9cd431..748d03a25 100644
--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -575,7 +575,6 @@ pub fn fork_checkout(root: &Path, lock: &mut Held) -> PathBuf {
(head != pinned && !ahead).then_some(head)
},
|head| {
- let _global = primary.is_none().then(|| buildlock::global_exclusive(root, "move the fork checkout to its pin"));
let dirty = git_out(&fork, &["status", "--porcelain", "--ignore-submodules=none"]);
assert!(
dirty.is_empty(),mutation-no-git-guard.patchdiff --git a/src/sysroot.rs b/src/sysroot.rs
index 5cd9cd431..11ff4bf58 100644
--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -520,7 +520,6 @@ pub(crate) fn pinned_fork(root: &Path) -> String {
pub fn fork_checkout(root: &Path, lock: &mut Held) -> PathBuf {
let fork = root.join("rust");
let primary = match toolchain::owner(root) {
- Owner::Us if !fork.join(".git").exists() => return fork,
Owner::Us => None,
Owner::Installed => panic!("an installed toolchain has no fork checkout to build std in"),
Owner::Elsewhere(primary) => Some(primary),mutation-primary-keeps-ahead.patchdiff --git a/src/sysroot.rs b/src/sysroot.rs
index 5cd9cd431..15c9e382f 100644
--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -566,8 +566,7 @@ pub fn fork_checkout(root: &Path, lock: &mut Held) -> PathBuf {
"move the fork checkout to its pin",
|| {
let head = git_out(&fork, &["rev-parse", "HEAD"]).trim().to_string();
- let ahead = primary.is_some()
- && Command::new("git")
+ let ahead = Command::new("git")
.args(["merge-base", "--is-ancestor", &pinned, &head])
.current_dir(&fork)
.status()mutation-shallow-any-commit.patchdiff --git a/src/lib.rs b/src/lib.rs
index 3b94b39f6..28e5d536e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -129,7 +129,7 @@ pub fn ensure_shallow_fork(root: &Path) -> Result<(), String> {
if fork.join("x.py").exists() {
let head = sysroot::git_out(&fork, &["rev-parse", "HEAD"]).trim().to_string();
let pinned = sysroot::pinned_fork(root);
- return (head == pinned)
+ return (head == head)
.then_some(())
.ok_or_else(|| format!("{} is at {head}, and this tree pins the fork at {pinned}", fork.display()));
}first-commit-tests.patch(This was applied under diff --git a/src/buildlock.rs b/src/buildlock.rs
index 5d36317eb..9968ef74a 100644
--- a/src/buildlock.rs
+++ b/src/buildlock.rs
@@ -636,16 +636,6 @@ pub(crate) mod tests {
Elsewhere::hold("buildlock::tests::child_role", &env)
}
- /// The primary's compiler of `root`, read by a process of its own.
- pub(crate) fn compiler_read_elsewhere(root: &Path) -> Elsewhere {
- held_elsewhere(root, "read-compiler")
- }
-
- /// Whether an exclusive acquirer of `root`'s global lock is queued for it.
- pub(crate) fn global_queued(root: &Path) -> bool {
- !try_lock(&open_lock_file(&git_lock_dir(root).join("intent")), LOCK_SH)
- }
-
/// What `role` of [`child_role`] takes in `root`, held by a process of its own.
fn held_elsewhere(root: &Path, role: &str) -> Elsewhere {
Elsewhere::hold("buildlock::tests::child_role", &[(ROLE, OsStr::new(role)), (ROOT, root.as_os_str())])
@@ -784,10 +774,6 @@ pub(crate) mod tests {
let _using = keyed_using(&root, Keyed::Sysroot, &Key::parse(&std::env::var(KEY).unwrap()).unwrap());
hold_until_released();
}
- "read-compiler" => {
- let _reading = compiler_shared(&root, "child");
- hold_until_released();
- }
"clean" | "clean-unlocked" => {
touch(&root.join("cleaner-ready"));
assert!(appeared(&root.join("builder-mid"), Duration::from_secs(20)));
diff --git a/src/sysroot.rs b/src/sysroot.rs
index 5cd9cd431..720a8289c 100644
--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -1591,45 +1591,6 @@ mod tests {
assert_eq!(git(&primary, &["rev-parse", "HEAD"]), superproject, "git ran in the superproject");
}
- /// **The primary's fork checkout moves only while nobody reads its
- /// compiler**: a sysroot being made in another worktree holds the global
- /// lock shared, and the move queues for it and moves nothing until it is
- /// let go.
- #[test]
- fn the_primary_s_fork_checkout_moves_only_while_nobody_reads_its_compiler() {
- let base = TempDir::new("fork-primary-global");
- let (primary, _linked, c1, c2) = two_pins(&base);
- let fork = primary.join("rust");
- git(&primary, &["update-index", "--cacheinfo", &format!("160000,{c2},rust")]);
- git(&primary, &["commit", "-qm", "pins C2"]);
- let reader = buildlock::tests::compiler_read_elsewhere(&primary);
- std::thread::scope(|scope| {
- let mover = scope.spawn(|| drop(fork_checkout(&primary, &mut buildlock::shared(&primary, "a build"))));
- let deadline = std::time::Instant::now() + std::time::Duration::from_secs(20);
- while !buildlock::tests::global_queued(&primary) {
- assert!(!mover.is_finished(), "the move took no global lock");
- assert!(std::time::Instant::now() < deadline, "the move never queued for the global lock");
- std::thread::sleep(std::time::Duration::from_millis(5));
- }
- assert_eq!(git(&fork, &["rev-parse", "HEAD"]), c1, "the checkout moved while a sysroot build read its compiler");
- reader.release();
- mover.join().unwrap();
- });
- assert_eq!(git(&fork, &["rev-parse", "HEAD"]), c2);
- }
-
- /// **A shallow fork is the commit its tree pins**: one already there is
- /// accepted at the pin and refused, naming both commits, anywhere else.
- #[test]
- fn ensure_shallow_fork_refuses_a_fork_off_its_pin() {
- let base = TempDir::new("fork-shallow-pin");
- let (primary, _linked, c1, c2) = two_pins(&base);
- assert_eq!(crate::ensure_shallow_fork(&primary), Ok(()));
- git(&primary.join("rust"), &["checkout", "-q", &c2]);
- let refused = crate::ensure_shallow_fork(&primary);
- assert!(refused.as_ref().is_err_and(|why| why.contains(&format!("is at {c2}")) && why.contains(&c1)), "{refused:?}");
- }
-
/// **The primary's bootstrap decides from the `compiler/` its tree pins**:
/// a fork checkout left at a commit naming another `compiler/` reads as a
/// stale compiler, and once it is moved to the pin the compiler the primary |
|
Review round 1, head BLOCKER
NOTE
REMOVE
SEND BACK |
… and the unreachable CI refusals go - The move checks out every initialised nested submodule at the commit the new pin records, with `git checkout` in it and never `git submodule`, which run in a linked fork checkout rewrites the primary's nested `core.worktree`. It refuses by name, before moving anything, a nested commit not held. A nested gitlink moved and nothing more is no longer read as uncommitted work. - `two_pins`' C1 and C2 now record different `library/backtrace` commits, so the primary and linked tests reach the nested move. - The global-lock test holds a `Scope::Global` phase in another process, as a bootstrap does; that is what the lock keeps the move out of. - `toolchain::ensure` takes the primary's `rust_dir` from `fork_checkout`. - `ensure_shallow_fork`'s pin refusal, `release::bootstrap`'s call to it and their test go: `toolchain.yml` initialises `rust/` at the pin in the step before, and nothing between moves it. - The virt_el1_smp stall issue goes: wt/toyos-counterstall carries it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Round 2 mutation patches, at m1-moved-gitlink-is-work.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -577,7 +577,7 @@
let _global = primary.is_none().then(|| buildlock::global_exclusive(root, "move the fork checkout to its pin"));
// A nested gitlink checked out at another commit, and nothing more, is no work: it moves with the checkout.
let status = git_out(&fork, &["status", "--porcelain=v2", "--ignore-submodules=none"]);
- let work: Vec<&str> = status.lines().filter(|entry| !entry.starts_with("1 .M SC.. ")).collect();
+ let work: Vec<&str> = status.lines().collect();
assert!(
work.is_empty(),
"{} is at {head} with uncommitted work, and this tree pins the fork at {pinned}: a build \m2-nested-not-moved.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -618,9 +618,6 @@
);
}
git_run(&fork, &["checkout", "--detach", "-q", &pinned]);
- for (at, commit) in &nested {
- git_run(at, &["checkout", "--detach", "-q", commit]);
- }
eprintln!("{} was at {head}, and this tree pins {pinned}: checked it out", fork.display());
},
);m3-nested-by-git-submodule.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -618,9 +618,7 @@
);
}
git_run(&fork, &["checkout", "--detach", "-q", &pinned]);
- for (at, commit) in &nested {
- git_run(at, &["checkout", "--detach", "-q", commit]);
- }
+ git_run(&fork, &["submodule", "update", "--recursive", "--no-fetch"]);
eprintln!("{} was at {head}, and this tree pins {pinned}: checked it out", fork.display());
},
);m4-no-nested-held-precheck.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -608,7 +608,7 @@
})
.filter(|(at, commit)| at.join(".git").exists() && git_out(at, &["rev-parse", "HEAD"]).trim() != commit)
.collect();
- for (at, commit) in &nested {
+ for (at, commit) in &nested[..0] {
assert!(
holds(at, commit),
"{} does not hold {commit}, which the fork's {pinned} records there: \m5-no-global-lock.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -574,7 +574,7 @@
(head != pinned && !ahead).then_some(head)
},
|head| {
- let _global = primary.is_none().then(|| buildlock::global_exclusive(root, "move the fork checkout to its pin"));
+
// A nested gitlink checked out at another commit, and nothing more, is no work: it moves with the checkout.
let status = git_out(&fork, &["status", "--porcelain=v2", "--ignore-submodules=none"]);
let work: Vec<&str> = status.lines().filter(|entry| !entry.starts_with("1 .M SC.. ")).collect();run.sh#!/bin/sh
D=$SCRATCH
W=$WORKTREE
cd $W || exit 1
for p in $D/mut/*.patch; do
n=$(basename $p .patch)
git apply --check -p1 $p || { echo "$n: patch does not apply"; exit 1; }
git apply -p1 $p
cargo build --lib --tests > $D/mut/$n.build.log 2>&1; echo "$n build EXIT=$?"
cargo test --lib -- sysroot::tests > $D/mut/$n.log 2>&1; echo "$n test EXIT=$?"
git apply -R -p1 $p
echo "$n tree after restore: [$(git status --porcelain --ignore-submodules=none)]"
done
echo DONE |
A move killed between the nested checkouts and the top-level one then leaves `HEAD` off the pin, so the next build asks for the move again and finishes it. Moved the other way round, a killed move left nested submodules behind a `HEAD` already at the pin, which no build re-checks. In a linked worktree, x.py's own sync of those submodules would then run `git submodule update` there, which rewrites the primary's nested `core.worktree`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Round 2 mutation patches, at m1-moved-gitlink-is-work.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -577,7 +577,7 @@
let _global = primary.is_none().then(|| buildlock::global_exclusive(root, "move the fork checkout to its pin"));
// A nested gitlink checked out at another commit, and nothing more, is no work: it moves with the checkout.
let status = git_out(&fork, &["status", "--porcelain=v2", "--ignore-submodules=none"]);
- let work: Vec<&str> = status.lines().filter(|entry| !entry.starts_with("1 .M SC.. ")).collect();
+ let work: Vec<&str> = status.lines().collect();
assert!(
work.is_empty(),
"{} is at {head} with uncommitted work, and this tree pins the fork at {pinned}: a build \m2-nested-not-moved.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -617,9 +617,6 @@
at.display(),
);
}
- for (at, commit) in &nested {
- git_run(at, &["checkout", "--detach", "-q", commit]);
- }
// Last, so a move killed before it is asked for again: `HEAD` alone decides.
git_run(&fork, &["checkout", "--detach", "-q", &pinned]);
eprintln!("{} was at {head}, and this tree pins {pinned}: checked it out", fork.display());m3-nested-by-git-submodule.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -616,12 +616,10 @@
at.display(),
at.display(),
);
- }
- for (at, commit) in &nested {
- git_run(at, &["checkout", "--detach", "-q", commit]);
}
// Last, so a move killed before it is asked for again: `HEAD` alone decides.
git_run(&fork, &["checkout", "--detach", "-q", &pinned]);
+ git_run(&fork, &["submodule", "update", "--recursive", "--no-fetch"]);
eprintln!("{} was at {head}, and this tree pins {pinned}: checked it out", fork.display());
},
);m4-no-nested-held-precheck.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -608,7 +608,7 @@
})
.filter(|(at, commit)| at.join(".git").exists() && git_out(at, &["rev-parse", "HEAD"]).trim() != commit)
.collect();
- for (at, commit) in &nested {
+ for (at, commit) in &nested[..0] {
assert!(
holds(at, commit),
"{} does not hold {commit}, which the fork's {pinned} records there: \m5-no-global-lock.patch--- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -574,7 +574,6 @@
(head != pinned && !ahead).then_some(head)
},
|head| {
- let _global = primary.is_none().then(|| buildlock::global_exclusive(root, "move the fork checkout to its pin"));
// A nested gitlink checked out at another commit, and nothing more, is no work: it moves with the checkout.
let status = git_out(&fork, &["status", "--porcelain=v2", "--ignore-submodules=none"]);
let work: Vec<&str> = status.lines().filter(|entry| !entry.starts_with("1 .M SC.. ")).collect();run.sh#!/bin/sh
D=$SCRATCH
W=$WORKTREE
cd $W || exit 1
for p in $D/mut/*.patch; do
n=$(basename $p .patch)
git apply --check -p1 $p || { echo "$n: patch does not apply"; exit 1; }
git apply -p1 $p
cargo build --lib --tests > $D/mut/$n.build.log 2>&1; echo "$n build EXIT=$?"
cargo test --lib -- sysroot::tests > $D/mut/$n.log 2>&1; echo "$n test EXIT=$?"
git apply -R -p1 $p
echo "$n tree after restore: [$(git status --porcelain --ignore-submodules=none)]"
done
echo DONE |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Review round 2, head Round 1 BLOCKERs
The round 1 NOTEs are answered: the global-lock test now holds a BLOCKER
NOTE
REMOVE
SEND BACK |
A nested submodule's gitlink at another commit was let through as no work whatever that commit was, so a commit an agent made inside linked/rust/library/backtrace, not yet recorded by a gitlink, was detached by the next move and left reachable only from that submodule's reflog. An `SC..` entry now goes through only where the nested HEAD is the commit the pin records there or an ancestor of it (`git merge-base --is-ancestor`); any other is refused, naming both. The check moves after the pin's and the nested commits' held checks, since it reads the commit the pin records. The doc's "the next move would read it as work" goes: the filter made it false. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Mutation m6, round 3, at --- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -613,21 +613,7 @@
// A nested submodule checked out at the commit the pin records there, or behind it, and nothing more, is
// no work: it moves with the checkout. One at any other commit holds a commit nothing else records.
let status = git_out(&fork, &["status", "--porcelain=v2", "--ignore-submodules=none"]);
- let work: Vec<String> = status
- .lines()
- .filter_map(|entry| {
- let path = entry.strip_prefix("1 .M SC.. ").and_then(|rest| rest.splitn(6, ' ').nth(5));
- let Some((path, commit)) = path.and_then(|path| recorded.iter().find(|(p, _)| p == path)) else {
- return Some(entry.to_string());
- };
- let at = fork.join(path);
- let at_head = git_out(&at, &["rev-parse", "HEAD"]).trim().to_string();
- let behind = git_try(&at, &["merge-base", "--is-ancestor", &at_head, commit]).is_ok();
- (!behind).then(|| {
- format!("{} is at {at_head}, which is neither {commit}, what {pinned} records there, nor behind it", at.display())
- })
- })
- .collect();
+ let work: Vec<&str> = status.lines().filter(|entry| !entry.starts_with("1 .M SC.. ")).collect();
assert!(
work.is_empty(),
"{} is at {head} with uncommitted work, and this tree pins the fork at {pinned}: a build \ |
|
Review round 3, head Round 2 BLOCKER
Reproduction (checked read-only at this head, The steps still start from a state that can be reproduced:
Run it on the head this branch lands at, not on
BLOCKER
NOTE
SEND BACK |
Review round 3's BLOCKER: swapping the arguments to `merge-base --is-ancestor` passed every case. The new case commits in the linked checkout's nested submodule on top of what C2 records there, with `rust/` at C1, and asserts the refusal names that commit and the record and that neither HEAD moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Review round 3's NOTE: the arm that let through a nested HEAD strictly behind the pin's record was reached by no case. The only state the move itself leaves there is a nested submodule at the record, after a move killed before its last checkout; anything else is refused by name, which fails loudly. Equality also drops a `merge-base` call per entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Round 4 mutations. Each applied with m7 at --- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -622,7 +622,7 @@
};
let at = fork.join(path);
let at_head = git_out(&at, &["rev-parse", "HEAD"]).trim().to_string();
- let behind = git_try(&at, &["merge-base", "--is-ancestor", &at_head, commit]).is_ok();
+ let behind = git_try(&at, &["merge-base", "--is-ancestor", commit, &at_head]).is_ok();
(!behind).then(|| {
format!("{} is at {at_head}, which is neither {commit}, what {pinned} records there, nor behind it", at.display())
})m8 at --- a/src/sysroot.rs
+++ b/src/sysroot.rs
@@ -623,5 +623,5 @@
let at = fork.join(path);
let at_head = git_out(&at, &["rev-parse", "HEAD"]).trim().to_string();
- (at_head != *commit).then(|| {
+ git_try(&at, &["merge-base", "--is-ancestor", commit, &at_head]).is_err().then(|| {
format!("{} is at {at_head}, not at {commit}, what {pinned} records there", at.display())
})Script: #!/bin/sh
# usage: mutate.sh <name> <patch>; applies, builds, runs sysroot tests, restores.
set -u
D=/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/forkpin-r4; W=/Users/jan/Dev/jan/toyos-forkpin
cd $W || exit 2
git apply --check "$2" || exit 3
git apply "$2" || exit 3
git rev-parse HEAD > $D/$1.head
cargo test --lib --no-run > $D/$1.build.log 2>&1; echo BUILD=$? > $D/$1.exit
cargo test --lib -- sysroot::tests > $D/$1.log 2>&1; echo EXIT=$? >> $D/$1.exit
git apply -R "$2" || exit 4
git status --porcelain --ignore-submodules=none > $D/$1.status-after |
|
Review round 4, head Round 3 BLOCKER
Round 3 NOTE
Gates at this head
Reproduction, checked read-only in the primary (
The steps are right in paths, the pin check, the expected output and the recovery, with the two hazards under NOTE. BLOCKER None. NOTE
REMOVE None. LAND AFTER NAMED CHANGES |
Before anything builds from it, the primary's fork checkout is moved to the commit its tree pins. Its initialised nested submodules move with it.
Why
The primary built std, and decided whether to bootstrap, from whatever commit its
rust/held.sysroot::fork_checkoutreturned the primary'srust/as it stood (Owner::Us => return fork, since 8603559).toolchain::ensureaskedcompiler::primary_is_currentbefore anything checked the pin.git pullmoves the gitlink and leaves the submodule where it was. So after #702, the primary'srust/at 95960d6c compiled against atoyos-abithat had moved on, and the owner's build ofmainbroke. The licence gate (licence.rs'sstd_library, throughfork_checkout) read the same stalelibrary/.What changed, per decision
HEADdiffers, ahead or behind (src/sysroot.rsfork_checkout). The primary checkout is no workspace, so nothing ahead is kept. A linked worktree still keeps a checkout ahead of its pin, because that is where an agent edits the fork.git ls-tree -r <pin>, 58 ms on the real fork). Each nested submodule that has a.gitand sits at another commit is checked out at the recorded one, withgit -C <it> checkout --detach. A nested commit the submodule does not hold is refused by name before anything moves, naminggit -C <it> fetch origin <commit>. Nothing is fetched.git submodule update, which is whatx.pyruns. In a linked fork checkout, that rewritescore.worktreein the primary'srust/.git/modules/<it>/configto point at the linked checkout, and git in the primary's nested submodule then dies. This was measured in a scratch fixture, and mutation m3 below shows it in the test.HEADmoves last. A move killed partway through therefore leavesHEADoff the pin, and the next build asks for the move again and finishes it. Done the other way round, a killed move would leave nested submodules behind aHEADalready at the pin. No build re-checks that state, and in a linked worktree,x.py's own sync (update_existing_submodules, run on everyx.py) would rungit submodule updatethere.git status --porcelain=v2 --ignore-submodules=none, an entry1 .M SC..(the gitlink moved, nothing else) is let through only when the nestedHEADequals the pin's record. A nestedHEADat any other commit is refused, naming that commit and the recorded one: it may be a commit an agent made in the nested submodule that no gitlink records yet, and moving off it would leave it only in that submodule's reflog, which a worktree's removal deletes. That holds whether the commit sits on the checkout's own record (round 2's BLOCKER) or on the pin's (round 3's). A nestedHEADstrictly behind the pin's record is refused too (round 3's NOTE): no move leaves that state, and the refusal fails loudly. Edits, untracked files or staged changes inside a nested submodule are refused as well. This check runs after the pin's and the nested commits' held checks, because it reads the commit the pin records.git -C <rust> fetch origin <pin>.buildlock::global_exclusive). That isbuildlock.rs's declared order (worktree → global). The lock keeps the move out of the primary's ownScope::Globalphases intoolchain::ensure: the bootstrap, the hosted rustc andcomplete. Those build fromrust/with the worktree lock put down (Held::act_if→without_shared). Without the global lock, a second primary process could move the sources under a runningx.py. No other worktree's reader is protected: acompiler_sharedholder reads onlyrust/build/, which a checkout does not touch.rust/(a CI runner's, beforeensure_shallow_fork) is returned as it is. Git run there would walk up into the superproject. Whatever initialises it later does so at the pin.toolchain::ensure's primaryrust_dirisfork_checkout's return. The bootstrap decision therefore cannot run before the move: the ordering is a data dependency, not a comment.compiler::tests::estategains thex.pythat a real primary has, sotoolchain::ownerreads its primary asUsand notInstalled.two_pins(the fixture) now records differentlibrary/backtracecommits at C1 and C2. Before, no test reached a nested move.Round 1 also carried a pin refusal in
ensure_shallow_fork, a call to it fromrelease::bootstrap, and an issue file. All three were removed in round 2. The refusal guarded a statetoolchain.ymlcannot reach, because the step before initialisesrust/at the pin. The issue file waswt/toyos-counterstall's to carry.Tests (host,
src/sysroot.rs)the_primary_s_fork_checkout_is_moved_to_its_pin. Its third case is the state a move killed after its nested checkouts leaves:git -C <rust>/library/backtrace fetch origin <commit>, and neither the checkout nor the nested submodule moved;HEADuntouched.a_worktree_pinning_another_fork_commit_gets_its_own_checkout(existing, extended): a linked checkout behind its pin, with its nested submodule behind too, is moved with it. The primary'srust/git statusstill runs and is unchanged. Then, withlinked/rustat C1 and the tree pinning C2, two nested commits are refused. Each refusal names that commit and C2's record, and neither the nestedHEADnorrust/'sHEADmoves:linked/rust/library/backtraceon top of C1's record (round 3);rust/is at its pin, thenmainmoves the pin for a change that leaveslibrary/backtracewhere it was.twelve_builds_at_once_make_and_move_one_fork_checkout(existing): now that the fixture's nested gitlink differs, its move of a checkout reset to C1 passes through a moved nested gitlink.the_primary_s_fork_checkout_moves_only_while_no_toolchain_phase_builds_from_it: another process holds aScope::Globalphase, as a bootstrap does, with the worktree lock put down. The move queues on the global lock'sintent(the test waits on that event with a 20 s deadline) and has not moved. Once the other process lets go, the move lands at the pin.the_primary_s_bootstrap_decides_from_the_pinned_compiler: in theestatefixture, the primary'srust/is left at a commit whosecompiler/differs.primary_is_currentis false there and true oncefork_checkouthas run.All of this is git state and
flockstate on the host, so host tests reach it. No guest test is added.Checks (build system, concurrency)
Negative control, round 1. The whole production change was reverted onto the base, keeping only the first two tests.
cargo test --lib -- sysroot::tests::the_primary_s→ EXIT=101. The patch is in the round-1 PR comment.Mutations. Each was applied as a checked patch, built, run as
cargo test --lib -- sysroot::tests, and reverted in the same script, leaving the tree clean.a9cf973d6(patches and script: The primary's fork checkout, and its nested submodules, are moved to the commit its tree pins #718 (comment)). m1 and m2 together are round 1's move, so they are round 2's negative control.4546e0cd7(patch: The primary's fork checkout, and its nested submodules, are moved to the commit its tree pins #718 (comment)). It puts backa9cf973d6's filter, so it is round 3's negative control.bb5eeceab, which adds round 4's case before the arm is cut. m8 ran atbf7c87b0d, after the cut. Patches and script: The primary's fork checkout, and its nested submodules, are moved to the commit its tree pins #718 (comment).dd27e9f32, before the reorder, is in The primary's fork checkout, and its nested submodules, are moved to the commit its tree pins #718 (comment)...._is_moved_to_its_pin,a_worktree_pinning_...,twelve_builds_......_is_moved_to_its_pin,a_worktree_pinning_...git submodule update --recursive --no-fetchafter the top-level checkouta_worktree_pinning_...(git in the primary's nested submodule dies:cannot chdir to '…/linked/rust/library/backtrace')..._is_moved_to_its_pin..._moves_only_while_no_toolchain_phase_builds_from_it1 .M SC..entry is no work (a9cf973d6's filter)a_worktree_pinning_...(nothing was refused: ()at the nested-commit case, after the move'schecked it outline)--is-ancestor <record> <nested HEAD>a_worktree_pinning_...(nothing was refused: ()at round 4's case, after the two earlier refusals)HEADat or on top of the pin's record passesa_worktree_pinning_...(the same case)The ordering itself (nested first,
HEADlast) has no mutation: no host test kills a move between two git commands. A reader can check it at the site.Independent oracle: none that is not this code. The decisions read git's own
status --porcelain=v2,ls-tree,merge-base --is-ancestor(ahead of the pin) andcat-file -e.Gates
cargo test --lib -- sysroot::tests compiler::tests buildlock::testsatbf7c87b0d→ EXIT=0cargo run -- --ci hostatbf7c87b0d(the head;origin/mainis still1c1c70f77, merged at85a3f0f14) → EXIT=0. Earlier rounds: at85a3f0f14,a9cf973d6and02ddebbe6→ EXIT=0.72621e9cb:cargo run -- --build-only→ EXIT=0.72621e9cb:cargo test(guest) → EXIT=1 (virt_el1_smpstalled under host load). Not re-run since; this branch changes no guest byte.What these gates do not reach. Every gate ran in a linked worktree, where
fork_checkouttakes theOwner::Elsewherepath.--build-only,--ci hostand the guest run exercise none of the changed primary path. That path's only measurements are the git andflockfixtures above. The owner's reproduction is the measurement on a real primary.The owner's reproduction (to run in the primary before landing)
These are review round 3's steps, with the head and a scratch directory filled in.
His this branch's head as pushed. If the branch moves again, setHto its new head.Run from the primary checkout, with no build of your own running there:
SAME-PINdoes not print, stop. Going back tomainwould moverust/as well. At the time of writing,Handorigin/main(1c1c70f77) both pinrustatf293615ad7aa162200b40426c159c505c449e182.EXIT=0, rungit -C rust checkout --detach $Pbeforegit checkout main. Otherwise the primary is left at95960d6c, which is the owner's break.git checkout --detach $His needed becausegit checkout wt/toyos-forkpinis refused while that branch is checked out in its worktree.git -C rust rev-parse HEADprints95960d6c…, andgit ls-files -s rustprintsf293615ad7….compiler_sharedholder (each one's sysroot build and hosted-rustc read). Its[build-lock]lines name them.The fix shows as:
$S/repro.log, the line<primary>/rust was at 95960d6c214338e9375b1c7c6c0d4e5ece4c4ba3, and this tree pins f293615ad7aa162200b40426c159c505c449e182: checked it out, ahead of anyBuilding full toolchainor std build line;EXIT=0as the log's last line;git -C rust rev-parse HEADprintsf293615ad7…;git status --porcelain --ignore-submodules=noneprints nothing forrust.Why no bootstrap is expected. This was read read-only (
GIT_OPTIONAL_LOCKS=0). The primary'srust/holds95960d6c, an ancestor off293615ad7. The two record the same gitlinks, and they differ in fourlibrary/stdfiles and nocompiler/file.What
maindoes with the samerust/. The steps onmainwere not run: they would leave the owner's primary broken. Expected:mainbuilds std from 95960d6c'slibrary/and fails. What was measured is that compile itself. 95960d6c'slibrary/andf293615ad7's, each with itslibrary/backtraceatf8a3e681, were type-checked against this branch'stoyos-abiandtoyos, which equalorigin/main's (git diff origin/main HEAD -- toyos-abi toyosis empty). The command wascargo +toyos check -Z build-std=std,panic_abort --target x86_64-unknown-toyos --offlineon an empty binary, followingsrc/CLAUDE.md's recipe. Results:f293615ad7→ EXIT=0.95960d6c→ EXIT=101:NetError::NetdNotFoundnot found (sys/net/connection/toyos.rs:26),missing fields path_len and path_ptr in initializer of SpawnArgs(sys/process/toyos.rs:369),launch::Parent::Initnot found (:508).These two pins record the same
library/backtraceandsrc/llvm-project, so this reproduction exercises the top-level move only. The nested move is measured by the fixtures.Unsure
HEAD, so it can be recovered.Net lines (
git diff --shortstat origin/main...HEAD): 4 files changed, 315 insertions(+), 75 deletions(-). Production is +140 −74. Tests are +175 −1.🤖 Generated with Claude Code
https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
The orchestrator's run in the primary (2026-10-04)
Run in
/Users/jan/Dev/jan/toyoswith nothing else building there:git checkout --detach bf7c87b0d…,git -C rust checkout --detach 95960d6c214338e9375b1c7c6c0d4e5ece4c4ba3(the owner's broken state), thencargo run -- --build-only.EXIT=0; the move came before any toolchain or std build line; afterwardsrust/is at the pin and the tree is clean. The primary was returned withgit checkout main(1c1c70f77),rust/atf293615ad7a, status empty.