Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
status: open
kind: tooling
opened: 2026-10-03
---

# A fork checkout a killed `git worktree add` left is taken as made

`sysroot::fork_checkout` (`src/sysroot.rs`) takes a linked worktree's `rust/` as
made where `rust/.git` exists and `HEAD` is the pinned commit or ahead of it. A
`git worktree add` killed while it writes its files leaves both, so every build
after it is handed a checkout with files missing. Git records the state itself:
the worktree stays `locked` with the reason `initializing`, beside a stale
`index.lock`.

## Measured

In a fixture whose fork checks its last file, `x.py`, out through a filter that
waits, `git worktree add --detach` was killed with SIGKILL once `rust/.git`,
`HEAD` and the files before `x.py` were written. `fork_checkout` returned what
it left: `rust/.git`, `HEAD` at the pin, no `x.py`, `locked` reading
`initializing`. `ensure_shallow_fork` refuses it by name. The licence gate
reaches that refusal only for a kill before `library/Cargo.toml` is written,
a file this fixture's fork never has: `licence::std_library` calls
`ensure_shallow_fork` only without it, and past it the gate reads what
`library/` holds and runs no `git submodule`.

In a fixture fork of 30,004 files the same kill left 253 of them,
`library/Cargo.toml` among them. Afterwards `git worktree add` at that path
exits 128 on `already exists`, `git worktree remove --force` exits 128 on the
lock, and `git worktree remove --force --force` exits 255 on
`Directory not empty` and unregisters the worktree.

## Read, not measured

Both measurements kill git. The kill `src/buildlock.rs`'s header calls routine
is the builder's alone. Then the kernel frees the worktree's lock at once, and
`git worktree add` goes on writing: it is the builder's child and holds no
descriptor of the lock, `git_run` being `Command::status` and the lock file
opened close-on-exec. A build that starts then finds `rust/.git`, `HEAD` at the
pin and the lock free. When that git ends, the checkout has no
`library/backtrace`, the state
`issues/build/the-fork-checkout-runs-git-submodule-in-a-linked-worktree.md`
records of a `fork_checkout` that stopped before adding it.

## Owner

The toolchain item of
`issues/build/the-tooling-is-a-review-prompt-and-three-workflows.md`, whose
stores are published by an atomic rename.

**Exit**: a build that finds a fork checkout git records as `locked` makes it
again or refuses it by name.

This file was deleted.

This file was deleted.

9 changes: 5 additions & 4 deletions src/buildlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
//! - **shared** — "I am building against the state as it stands". Any number
//! at once.
//! - **exclusive** — "I am replacing it": the rust bootstrap, this worktree's
//! std build, the `cargo clean`s. One at a time, and never while a build
//! holds the shared mode.
//! std build, the `cargo clean`s, the making or moving of its fork checkout.
//! One at a time, and never while a build holds the shared mode.
//!
//! And two [`Scope`]s: a crate target directory is shared by the builds in one
//! worktree, while the primary's `rust/build` — the compiler every sysroot is
Expand Down Expand Up @@ -94,8 +94,9 @@ pub enum Scope {
/// State every worktree shares: the primary's `rust/` build tree — the
/// compiler every sysroot is made with — and the machine-global rustup link.
Global,
/// State this worktree alone owns — its crate target directories. Two
/// worktrees cleaning their own have nothing to say to each other.
/// State this worktree alone owns — its crate target directories and its
/// fork checkout. Two worktrees cleaning their own have nothing to say to
/// each other.
Worktree,
}

Expand Down
10 changes: 9 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,19 @@ 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.
/// 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> {
if root.join("rust/x.py").exists() {
return Ok(());
}
if let toolchain::Owner::Elsewhere(_) = toolchain::owner(root) {
return Err(format!(
"{} is a linked worktree's fork checkout and is not whole: no submodule is initialised there",
root.join("rust").display()
));
}
let status = Command::new("git")
.args(["submodule", "update", "--init", "--depth", "1", "rust"])
.current_dir(root)
Expand Down
3 changes: 2 additions & 1 deletion src/licence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1174,7 +1174,8 @@ fn metadata(
/// whose `rust/` was never initialised — a CI runner's — fetches that commit
/// alone.
fn std_library(root: &Path) -> Result<PathBuf, String> {
let fork = crate::sysroot::fork_checkout(root);
let mut lock = crate::buildlock::shared(root, "the licences of what ships");
let fork = crate::sysroot::fork_checkout(root, &mut lock);
if !fork.join("library/Cargo.toml").exists() {
crate::ensure_shallow_fork(root)?;
}
Expand Down
Loading
Loading