xtask/src/vendor_patch.rs
//! The guard on `[patch.crates-io] gix-parallel = { path = "vendor/gix-parallel" }`.
//!
//! **Since gix 0.90 (row GIX-090, 2026-10-31) the fork is `vendor/gix-parallel`**: gix 0.90
//! split gix-features, its `parallel` module is the crate gix-parallel, nothing in the graph
//! names gix-features any more, and fix 2 moved with `Stepwise` (see
//! vendor/gix-parallel/PATCH.md). The history below is the gix-features fork's and is kept
//! because the measurements are the record; the guard itself checks gix-parallel.
//!
//! # What is being guarded, and why a note was not enough
//!
//! `vendor/gix-features` is gix-features **0.50.0** (rebased from 0.49.1 on
//! 2026-10-02) with **one** fix left;
//! `PATCH.md` there carries the diff and the measurements.
//!
//! * **Fix 1** — the watcher that slept out a whole 50 ms interval on every
//! `index_pack`. 8-object pack **50.22 → 2.99 ms**; against stock git,
//! 20.6–24.2× slower → **0.34–0.90×**. Filed as gitoxide PR **#2877**, merged
//! upstream 2026-08-05 (`c08cdc81`) and **RELEASED in `gix-features 0.49.1`**,
//! carrying the patch verbatim — diffed. It is no longer ours and is no longer
//! in this fork's diff; you get it from the registry.
//! * **Fix 2** — `Stepwise` spawning two threads at `thread_limit: Some(1)`.
//! `clone3` per served request **2 → 0**; per-thread CPU attribution
//! 0.097–0.356 → **0.982–1.000**. Filed as issue **2881**, not merged. Still
//! ours, and the whole remaining reason this directory and both patch tables
//! exist.
//!
//! The fork was additionally **published as `gix-features-gunnar 0.49.0`**
//! (2026-08-23, `8952778`) so the diffs are citable off this checkout. That did
//! **not** lift the publish hold and could not: cargo refuses a
//! `[patch.crates-io]` entry pointing back at crates.io ("patches must point to
//! different sources"), so this remains a **PATH patch** and
//! `nornir_release_core::model::patch_fork_blockers` still classifies it a
//! foreign fork. The published crate is a citation, not a mechanism.
//!
//! So the patch table stays. What it needed was a guard, because the way it goes
//! wrong is silent: on 2026-08-05 a `cargo publish` verify build printed
//! `Compiling gix-features v0.49.0` with **no path suffix** — the registry copy
//! — meaning a publish today ships stock gix-features with **zero signal that it
//! did**. Losing fix 2 costs the serving tier its "stays serial" property and every
//! per-thread instrument in the repository.
//!
//! (The patch table does **not** block `cargo publish`. That was a misdiagnosis
//! and it was disproven twice — `gunnar-pktline` and `gunnar-delta`, both dry-run
//! exit 0.)
//!
//! # THE TWO WAYS A PATH PATCH SILENTLY STOPS BINDING
//!
//! Both were reached in this repository and both were MEASURED on 2026-08-25 by
//! making each one wrong and reading what cargo said. A `[patch.crates-io]` PATH
//! entry binds on the vendored crate's **name** and on its **version**, and on
//! nothing else — not on the key in the table, not on `[lib] name`.
//!
//! 1. **The name.** `8952778` renamed the vendored `[package] name` to
//! `gix-features-gunnar` and put `package = "gix-features-gunnar"` in both
//! tables. `package =` renames the crate the patch is **for**, not the copy it
//! supplies, so cargo went looking for a `gix-features-gunnar` in the graph,
//! found none, and said `warning: patch gix-features-gunnar v0.49.0 was not
//! used in the crate graph` while stock `gix-pack` re-resolved `gix-features`
//! from the registry. Removing the `package =` and keeping the renamed
//! vendored crate hard-errors the other way: `patch location
//! .../vendor/gix-features does not contain packages matching gix-features`.
//! 2. **The version.** A patch is a **candidate, not an override**: the resolver
//! still takes the greatest compatible version, so the pin at `0.49.0` lost to
//! registry `0.49.1` the day 0.49.1 shipped, with the same `patch ... was not
//! used` warning. The fork therefore has to be rebased onto the newest
//! published version rather than left behind it — which is why it is 0.50.0
//! (the newest, 2026-10-02) carrying fix 2 alone.
//!
//! # Why THIS check, and why the file reads are not enough on their own
//!
//! Three were weighed. `index_floor` measures the *consequence*, but only after a
//! real release build and only for fix 1, and it is a timing assertion on a loaded
//! box. Checking the released version against the merge needs crates.io on every
//! run, and a tool polling that index got the account throttled for 45 minutes.
//! **Asserting the resolved copy** costs a file read, covers the fixes, and fires
//! *before* the damage rather than after it.
//!
//! But a file read alone is how this guard **went blind for two days**. It read a
//! checked-in `Cargo.lock` and never asked whether that lock still described what
//! cargo would resolve. `8952778` changed both manifests and regenerated neither
//! lock, so the locks kept saying `name = "gix-features"` with no `source` — the
//! shape of a patch that applied — for a patch that had stopped applying entirely.
//! All five tests here were **green on that tree**, verified by running the built
//! test binary against the committed locks while `cargo metadata --locked` in the
//! same checkout printed `patch ... was not used in the crate graph`.
//!
//! So the checks are in two layers and the second one is the one that cannot be
//! fossilised:
//!
//! * [`check_texts`] — pure, from three strings. It keys on the **vendored
//! `[package] name`**, refuses a `package =` rename outright, and requires the
//! lock's version to equal the vendored manifest's. That is enough to catch
//! `8952778` from the files alone, on the day it lands.
//! * [`tests::cargo_itself_says_the_patch_bound_and_the_lock_is_current`] — asks
//! **cargo**, with `--locked`, per patched root. A stale lock is then an error
//! rather than a silent rewrite, `patch ... was not used` on stderr is a
//! failure, and the resolved graph must actually contain the
//! `path+file://…/vendor/gix-features` package id. No lock can lie to this arm,
//! because it does not read one.
//!
//! It also answers the opposite question for free. The day a released
//! `gix-features` carries fix 2 as well, the patch becomes unnecessary; the same
//! arms fire and a human looks.
use std::path::{Path, PathBuf};
/// The crate the patch table replaces.
///
/// A PATH patch binds on the vendored crate's `[package] name`, so this is also
/// the name `vendor/gix-parallel/Cargo.toml` must carry — see [`check_texts`].
pub const PATCHED_CRATE: &str = "gix-parallel";
/// The two workspace roots that resolve it, relative to the repository root.
///
/// Cargo does **not** inherit a `[patch]` table through a path dependency, so
/// `bench/` needs its own. Those are the only two: `installtest/` names no gix
/// crate at all, and the old znippy engine crate never indexes a pack — which is
/// why its lock legitimately carries a registry copy, and why that lock is the
/// negative control in the tests below.
pub const PATCHED_ROOTS: &[&str] = &["", "bench"];
/// The vendored fork, relative to the repository root.
pub const VENDOR_REL: &str = "vendor/gix-parallel";
fn is_key_char(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '-' || c == '_'
}
/// `key = "value"` out of one inline table or one line, `key` matched WHOLE.
///
/// Hand-parsed rather than pulled through a TOML crate because `xtask` has ZERO
/// dependencies on purpose.
///
/// Whole-key matching is the point. The previous reader took
/// `t.starts_with(name)` and `t.split("path").nth(1)`, so `gix-features` matched
/// a line that began `gix-features-gunnar = …` — the exact rename that broke this
/// patch would have been read as the patch still being there.
pub fn inline_field(entry: &str, key: &str) -> Option<String> {
let mut from = 0usize;
while let Some(i) = entry[from..].find(key) {
let at = from + i;
from = at + key.len();
let before_ok = entry[..at]
.chars()
.next_back()
.is_none_or(|c| !is_key_char(c));
let after = &entry[at + key.len()..];
if !before_ok || after.chars().next().is_some_and(is_key_char) {
continue;
}
let Some(rest) = after.trim_start().strip_prefix('=') else {
continue;
};
let Some(rest) = rest.trim_start().strip_prefix('"') else {
continue;
};
let Some(end) = rest.find('"') else { continue };
return Some(rest[..end].to_string());
}
None
}
/// The right-hand side of `name = …` inside `[patch.crates-io]`, if any.
///
/// `name` must be the WHOLE key: `gix-features` does not match
/// `gix-features-gunnar = { … }`.
pub fn patch_entry(manifest: &str, name: &str) -> Option<String> {
let mut in_table = false;
for line in manifest.lines() {
let t = line.trim();
if t.starts_with('#') {
continue;
}
if t.starts_with('[') {
in_table = t == "[patch.crates-io]";
continue;
}
if !in_table {
continue;
}
let Some(rest) = t.strip_prefix(name) else {
continue;
};
let Some(rest) = rest.trim_start().strip_prefix('=') else {
continue;
};
return Some(rest.trim().to_string());
}
None
}
/// The `path = "…"` a workspace root's `[patch.crates-io]` gives `name`, if any.
pub fn patch_path(manifest: &str, name: &str) -> Option<String> {
inline_field(&patch_entry(manifest, name)?, "path")
}
/// The `package = "…"` rename a patch entry carries, if any.
///
/// There must never be one here. It renames the crate the patch is FOR, so it
/// unbinds the table rather than aliasing the replacement — see the module docs.
pub fn patch_rename(manifest: &str, name: &str) -> Option<String> {
inline_field(&patch_entry(manifest, name)?, "package")
}
/// A field of a manifest's `[package]` table.
///
/// Comment lines are skipped, so the prose above `name =` in the vendored
/// manifest — which quotes the wrong name on purpose — cannot be read as the
/// value.
pub fn package_field(manifest: &str, key: &str) -> Option<String> {
let mut in_table = false;
for line in manifest.lines() {
let t = line.trim();
if t.starts_with('#') {
continue;
}
if t.starts_with('[') {
in_table = t == "[package]";
continue;
}
if !in_table {
continue;
}
if let Some(v) = inline_field(t, key) {
return Some(v);
}
}
None
}
/// How a `Cargo.lock` resolved `name`: `(version, source)`.
///
/// `None` — the lock holds no such package at all.
/// `Some((v, None))` — it is there with **no `source` line**, which is what a
/// `path` override looks like: the patch applied.
/// `Some((v, Some(src)))` — it came from `src`, i.e. the registry: the patch did
/// not apply, and whatever links this lock is running stock gix-features.
pub fn lock_fields(lock: &str, name: &str) -> Option<(String, Option<String>)> {
let mut in_it = false;
let mut source: Option<String> = None;
let mut version = String::new();
let mut found = false;
for line in lock.lines() {
let t = line.trim();
if t == "[[package]]" {
if found {
break;
}
in_it = false;
source = None;
continue;
}
if t == format!("name = \"{name}\"") {
in_it = true;
found = true;
continue;
}
if in_it {
if let Some(rest) = t.strip_prefix("source = \"") {
source = rest.strip_suffix('"').map(str::to_string);
}
if let Some(rest) = t.strip_prefix("version = \"") {
version = rest.trim_end_matches('"').to_string();
}
}
}
if found {
Some((version, source))
} else {
None
}
}
/// [`lock_fields`] without the version, kept because the source is what most of
/// the prose in this module is about.
#[allow(clippy::option_option)]
pub fn lock_source(lock: &str, name: &str) -> Option<Option<String>> {
lock_fields(lock, name).map(|(_, src)| src)
}
/// The source with every `//` comment stripped.
///
/// The first version of the fix marker below asserted `contains("park_timeout")`
/// against the whole file, and the red probe for it came back **green**: the
/// vendored `in_parallel.rs` names `park_timeout` in a comment two lines above
/// the call, so replacing the call with `std::thread::sleep` left the marker
/// satisfied. A guard that a comment can hold up is not a guard.
pub fn code_only(src: &str) -> String {
src.lines()
.map(|l| match l.find("//") {
Some(i) => &l[..i],
None => l,
})
.collect::<Vec<_>>()
.join("\n")
}
/// `(manifest, lock)` for a workspace root under `repo`.
pub fn manifest_and_lock(repo: &Path, rel: &str) -> (PathBuf, PathBuf) {
let dir = if rel.is_empty() {
repo.to_path_buf()
} else {
repo.join(rel)
};
(dir.join("Cargo.toml"), dir.join("Cargo.lock"))
}
/// **The pure half of the guard**, decided from three texts and nothing else:
/// one workspace root's `Cargo.toml`, the vendored crate's `Cargo.toml`, and that
/// root's `Cargo.lock`. `Ok` carries the `path = "…"` the table declared.
///
/// It is a free function rather than assertions inside a `#[test]` so that the
/// non-vacuity arm below can hand it the exact shape that fooled the old guard
/// and require an `Err`. A guard whose only test is that it passes is how this
/// went blind.
pub fn check_texts(manifest: &str, vendored: &str, lock: &str) -> Result<String, String> {
let entry = patch_entry(manifest, PATCHED_CRATE).ok_or_else(|| {
format!(
"declares no `[patch.crates-io] {PATCHED_CRATE}`. Fix 2 (Stepwise spawning two \
threads at thread_limit Some(1), gitoxide issue 2881) is unmerged and still ours. \
Without the table this workspace links stock gix-parallel and the serving tier \
stops being serial."
)
})?;
let path = inline_field(&entry, "path").ok_or_else(|| {
format!("`[patch.crates-io] {PATCHED_CRATE} = {entry}` names no `path = \"…\"`")
})?;
if let Some(pkg) = inline_field(&entry, "package") {
return Err(format!(
"`[patch.crates-io] {PATCHED_CRATE} = {entry}` carries `package = \"{pkg}\"`. That \
renames the crate the patch is FOR, not the copy it supplies: cargo then patches \
`{pkg}`, which nothing in the graph depends on, prints `warning: patch {pkg} was \
not used in the crate graph`, and lets stock gix-pack resolve {PATCHED_CRATE} from \
the registry. MEASURED — that is what 8952778 did, and it went unnoticed for two \
days because the locks were never regenerated."
));
}
let vendored_name = package_field(vendored, "name")
.ok_or_else(|| format!("{path}/Cargo.toml has no `[package] name`"))?;
if vendored_name != PATCHED_CRATE {
return Err(format!(
"{path}/Cargo.toml is `[package] name = \"{vendored_name}\"`, not \
\"{PATCHED_CRATE}\". A PATH patch binds on the vendored package name and on \
nothing else — not on the key in the table and not on `[lib] name` — so cargo \
refuses this outright: `patch location …/{path} does not contain packages matching \
{PATCHED_CRATE}`."
));
}
let vendored_version = package_field(vendored, "version")
.ok_or_else(|| format!("{path}/Cargo.toml has no `[package] version`"))?;
match lock_fields(lock, PATCHED_CRATE) {
None => Err(format!(
"the lock holds no {PATCHED_CRATE} at all, so this root no longer resolves the \
crate the patch table is for and the table is unverifiable"
)),
Some((v, Some(src))) => Err(format!(
"the lock resolved {PATCHED_CRATE} {v} from {src}, NOT from {path}. The patch was \
bypassed — or a newer release outranks the vendored {vendored_version} and the \
patch has stopped applying, a patch being a CANDIDATE and not an override. Either \
way this build links stock gix-parallel and says nothing about it: that is exactly \
what `cargo publish`'s verify step did on 2026-08-05, printing `Compiling \
{PATCHED_CRATE} v0.49.0` with no path suffix."
)),
Some((v, None)) if v != vendored_version => Err(format!(
"the lock resolved {PATCHED_CRATE} {v} from a path, but {path} is version \
{vendored_version}. THE LOCK IS A FOSSIL: it was written against a different \
vendored crate and has not been regenerated since, so every claim made from it \
about this patch is void. Run cargo in this root and commit the lock it writes."
)),
Some(_) => Ok(path),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn repo() -> PathBuf {
crate::guard::workspace_root()
}
/// A patch table exactly as `8952778` left it, and the vendored manifest it
/// pointed at. This is the shape the old guard called green.
const RENAMED_TABLE: &str =
"[patch.crates-io]\ngix-parallel = { path = \"vendor/gix-parallel\", \
package = \"gix-parallel-gunnar\" }\n";
const GOOD_TABLE: &str =
"[patch.crates-io]\ngix-parallel = { path = \"vendor/gix-parallel\" }\n";
const GOOD_VENDOR: &str = "[package]\nname = \"gix-parallel\"\nversion = \"0.49.1\"\n";
const RENAMED_VENDOR: &str =
"[package]\nname = \"gix-parallel-gunnar\"\nversion = \"0.49.0\"\n";
/// The fossil: a lock written before the rename, still shaped like a patch
/// that applied.
const FOSSIL_LOCK: &str =
"[[package]]\nname = \"gix-parallel\"\nversion = \"0.49.0\"\ndependencies = [\n \"libc\",\n]\n";
const GOOD_LOCK: &str =
"[[package]]\nname = \"gix-parallel\"\nversion = \"0.49.1\"\ndependencies = [\n \"libc\",\n]\n";
/// **The guard, pure half.** Both workspace roots that resolve
/// `gix-parallel` must declare the patch with no rename, point at a vendored
/// crate that carries the patched name, and hold a lock that agrees with it.
#[test]
fn gix_parallel_resolves_to_the_vendored_copy_in_every_root_that_patches_it() {
let vendored_manifest = std::fs::read_to_string(repo().join(VENDOR_REL).join("Cargo.toml"))
.expect("the vendored manifest");
let mut checked = 0usize;
for rel in PATCHED_ROOTS {
let (manifest_path, lock_path) = manifest_and_lock(&repo(), rel);
let where_ = if rel.is_empty() {
"the repository root"
} else {
rel
};
let manifest = std::fs::read_to_string(&manifest_path)
.unwrap_or_else(|e| panic!("{}: {e}", manifest_path.display()));
let lock = std::fs::read_to_string(&lock_path)
.unwrap_or_else(|e| panic!("{}: {e}", lock_path.display()));
let path = check_texts(&manifest, &vendored_manifest, &lock)
.unwrap_or_else(|why| panic!("{where_} ({}): {why}", manifest_path.display()));
let vendored = manifest_path
.parent()
.expect("a workspace directory")
.join(&path);
assert!(
vendored.join("Cargo.toml").is_file(),
"{where_} patches {PATCHED_CRATE} to {}, and there is no crate there",
vendored.display()
);
assert_eq!(
vendored.canonicalize().ok(),
repo().join(VENDOR_REL).canonicalize().ok(),
"{where_} patches {PATCHED_CRATE} to {}, which is not {VENDOR_REL} — the \
vendored manifest this test checked is not the one it would resolve",
vendored.display()
);
checked += 1;
}
// The control: a loop over an empty list is green.
assert_eq!(
checked,
PATCHED_ROOTS.len(),
"{} of {} patched roots were actually checked",
checked,
PATCHED_ROOTS.len()
);
assert!(checked >= 2, "only {checked} workspace roots were checked");
}
/// **NON-VACUITY.** The guard above is worth exactly as much as its ability
/// to go red, and it spent 2026-08-23 to 2026-08-25 unable to. Every way the
/// patch is known to come unbound is fed to [`check_texts`] here and must
/// come back `Err`.
#[test]
fn check_texts_goes_red_on_every_known_way_the_patch_comes_unbound() {
// The control: the shape that is actually correct is accepted, or the
// reds below prove nothing.
assert_eq!(
check_texts(GOOD_TABLE, GOOD_VENDOR, GOOD_LOCK).as_deref(),
Ok("vendor/gix-parallel")
);
let red = |manifest, vendored, lock, because: &str| {
let got = check_texts(manifest, vendored, lock);
assert!(
got.is_err(),
"{because}: check_texts said {got:?}, and this is how the guard went blind"
);
got.unwrap_err()
};
// 1. 8952778, exactly: `package =` in the table, renamed vendored crate,
// and a lock nobody regenerated. THE OLD GUARD PASSED THIS.
let why = red(
RENAMED_TABLE,
RENAMED_VENDOR,
FOSSIL_LOCK,
"the 8952778 rename with its fossil lock",
);
assert!(why.contains("package"), "{why}");
// 2. The rename with the `package =` dropped — cargo hard-errors here,
// and so must this.
let why = red(
GOOD_TABLE,
RENAMED_VENDOR,
FOSSIL_LOCK,
"a vendored crate published under another name",
);
assert!(why.contains("gix-parallel-gunnar"), "{why}");
// 3. The fossil on its own: table and vendored crate both right, lock
// left at a version the vendored crate no longer is.
let why = red(
GOOD_TABLE,
GOOD_VENDOR,
FOSSIL_LOCK,
"a lock left at 0.49.0 against a 0.49.1 vendored crate",
);
assert!(why.contains("FOSSIL"), "{why}");
// 4. The patch table deleted outright.
red(
"[dependencies]\ngix-parallel = \"0.49\"\n",
GOOD_VENDOR,
GOOD_LOCK,
"no patch table at all",
);
// 5. The lock re-resolved from the registry — the version pin outranked,
// or the patch simply bypassed.
let why = red(
GOOD_TABLE,
GOOD_VENDOR,
"[[package]]\nname = \"gix-parallel\"\nversion = \"0.49.1\"\n\
source = \"registry+https://github.com/rust-lang/crates.io-index\"\n",
"a lock that resolved the crate from crates.io",
);
assert!(why.contains("registry+"), "{why}");
// 6. The crate gone from the lock entirely.
red(
GOOD_TABLE,
GOOD_VENDOR,
"[[package]]\nname = \"something-else\"\nversion = \"1.0.0\"\n",
"a lock with no gix-parallel in it",
);
}
/// **The arm no lock can lie to.** Ask cargo, with `--locked`, in each
/// patched root.
///
/// This exists because everything above reads files that a `cargo build`
/// rewrites, and between 2026-08-23 and 2026-08-25 both locks in this tree
/// were fossils describing a patch that had stopped binding. Three things are
/// required, and each one failed at some point during that window:
///
/// * exit 0 under `--locked` — a lock cargo would have to rewrite is stale,
/// and a stale lock makes every claim the pure arms make void;
/// * no `was not used in the crate graph` on stderr — that is the exact and
/// only warning cargo gives for an unbound patch;
/// * the resolved graph names `path+file://…/vendor/gix-parallel`.
#[test]
fn cargo_itself_says_the_patch_bound_and_the_lock_is_current() {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let vendored = repo()
.join(VENDOR_REL)
.canonicalize()
.expect("the vendored crate is on disk");
let want_id = format!("path+file://{}", vendored.display());
for rel in PATCHED_ROOTS {
let dir = if rel.is_empty() {
repo()
} else {
repo().join(rel)
};
let where_ = if rel.is_empty() {
"the repository root"
} else {
rel
};
let out = std::process::Command::new(&cargo)
.current_dir(&dir)
.args(["metadata", "--format-version", "1", "--locked"])
.output()
.unwrap_or_else(|e| panic!("{where_}: running `{cargo} metadata`: {e}"));
let err = String::from_utf8_lossy(&out.stderr);
assert!(
out.status.success(),
"{where_}: `cargo metadata --locked` failed, so its Cargo.lock is NOT what \
cargo would resolve today and nothing read out of it means anything. That is \
how this guard was green from 2026-08-23 to 2026-08-25.\n{err}"
);
// Only THIS patch's warning counts: an estate workspace's own sibling
// patches (G-B7) may name a crate it does not link on every target, and
// cargo reports that one unused by design.
assert!(
!err.lines()
.any(|l| l.contains("was not used in the crate graph")
&& l.contains("patch `gix-parallel ")),
"{where_}: cargo says the patch did not bind. A PATH patch binds on the \
vendored `[package] name` and on its version — `package =` in the table \
redirects it at a crate nobody depends on, and a version behind the newest \
published one simply loses the resolve.\n{err}"
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains(&want_id),
"{where_}: the resolved graph contains no `{want_id}` package, so whatever \
{PATCHED_CRATE} this root links, it is not the vendored fork"
);
}
}
/// **The identity-value trap, closed.** A lock reader that returned
/// `Some(None)` for everything would satisfy the guard above forever.
///
/// The `gix_porcelain_in_server` fixture lock resolves `gix-features` from
/// the registry and is the only lock left in the tree that does — every
/// workspace that indexes a pack patches it.
///
/// It used to be a crate's own lock, which was better because it was not a
/// fixture. That crate was deleted on 2026-08-10 and the test then read a
/// path that does not exist, so it was red for a reason that had nothing to
/// do with what it guards. A fixture that really is checked in still fails
/// if the lock format stops looking like this, which is the property that
/// matters; it just no longer doubles as a claim about a live workspace.
#[test]
fn the_lock_reader_reports_a_registry_copy_as_a_registry_copy() {
let lock_path = repo().join("xtask/tests/fixtures/gix_porcelain_in_server/Cargo.lock");
let lock = std::fs::read_to_string(&lock_path)
.unwrap_or_else(|e| panic!("{}: {e}", lock_path.display()));
// The fixture predates gix 0.90 and resolves gix-features (the crate the fork
// patched then); the reader is name-agnostic, which is what this control is for.
match lock_source(&lock, "gix-features") {
Some(Some(src)) => assert!(
src.starts_with("registry+"),
"an unpatched lock resolved gix-features from {src}"
),
other => panic!(
"{} is the unpatched control and the reader called it {other:?}. It cannot tell \
a path override from a registry copy, so the guard above proves nothing.",
lock_path.display()
),
}
// And a name that is not in the lock is not quietly a pass either.
assert_eq!(lock_source(&lock, "no-such-crate-anywhere"), None);
}
/// The other way the patch stops working: the directory is still patched in
/// and the **fix is gone from it** — a re-vendor from the registry copy
/// looks identical from the lock's side.
///
/// Red-first, seen: swap the `Source::Inline` arm out of `reduce.rs` and this
/// fails naming the file. The fix-1 markers are kept even though 0.49.1 ships
/// fix 1 itself: they now assert that the version this fork is rebased onto
/// really is one that carries it, so a re-vendor backwards is red too.
#[test]
fn the_vendored_copy_still_carries_fix_two_on_a_base_that_has_fix_one() {
let vendor = repo().join(VENDOR_REL);
let one = code_only(
&std::fs::read_to_string(vendor.join("src/in_parallel.rs"))
.expect("the vendored in_parallel.rs"),
);
assert!(
one.contains("std::thread::park_timeout("),
"vendor/gix-parallel/src/in_parallel.rs does not CALL \
`std::thread::park_timeout`, so the watcher is sleeping out its whole 50 ms \
interval again (fix 1, PR #2877, upstream since gix-features 0.49.1). index_floor \
then fails at 50.97 ms against its 25 ms ceiling."
);
// The defect itself, named: stock gix-features 0.49.0 had
// `std::thread::sleep(duration)` there and fix 1 is precisely its removal.
assert!(
!one.contains("std::thread::sleep("),
"vendor/gix-parallel/src/in_parallel.rs calls `std::thread::sleep` \
again — that IS the defect fix 1 removes: an uninterruptible wait appended to \
every `index_pack`, 50.22 ms on an 8-object pack"
);
assert!(
one.contains("watcher.unpark()"),
"the watcher is parked and never unparked, which is fix 1 applied halfway: \
`in_parallel_with_slice` would then block until the interval elapses anyway"
);
let two = code_only(
&std::fs::read_to_string(vendor.join("src/reduce.rs")).expect("the vendored reduce.rs"),
);
assert!(
two.contains("Source::Inline"),
"vendor/gix-parallel/src/reduce.rs has no `Inline` source, so `Stepwise` \
spawns two threads at thread_limit Some(1) again (fix 2, gitoxide issue 2881): \
2 clone3 per served request instead of 0. THIS IS THE ONLY FIX IN THIS FORK and \
therefore its only justification."
);
// The version the diff is taken against. It has to track the newest published
// gix-parallel, not trail it: a patch is a candidate and the resolver takes the
// greatest compatible version, so a fork left behind a release stops binding.
let manifest =
std::fs::read_to_string(vendor.join("Cargo.toml")).expect("the vendored manifest");
assert_eq!(
package_field(&manifest, "version").as_deref(),
Some("0.50.1"),
"vendor/gix-parallel is no longer 0.50.1, which is the version the fix 2 diff in \
vendor/gix-parallel/PATCH.md is taken against (moved from gix-features 0.50.0 on \
2026-10-31, gix 0.90)"
);
}
/// The comment stripper, because the marker guard is only as good as it.
///
/// This is the exact shape that made the first red probe come back green:
/// the call replaced, the word still present in a comment.
#[test]
fn a_marker_that_survives_only_in_a_comment_does_not_count() {
let patched = " // an unpark before the park_timeout is remembered\n\
std::thread::park_timeout(remaining);\n";
let reverted = " // an unpark before the park_timeout is remembered\n\
std::thread::sleep(remaining);\n";
assert!(code_only(patched).contains("std::thread::park_timeout("));
assert!(
!code_only(reverted).contains("std::thread::park_timeout("),
"the comment held the marker up, which is how this guard was blind at first"
);
assert!(code_only(reverted).contains("std::thread::sleep("));
// And it must not eat code that merely mentions a URL-shaped string.
assert!(code_only("let x = 1; // https://example.com").contains("let x = 1;"));
}
/// The parser, on shapes rather than on the tree, so a red above is a red
/// about the repository and not about the reading of it.
#[test]
fn the_parser_tells_the_two_lock_shapes_apart() {
let patched = "[[package]]\nname = \"gix-features\"\nversion = \"0.49.1\"\n\
dependencies = [\n \"libc\",\n]\n";
let registry = "[[package]]\nname = \"gix-features\"\nversion = \"0.49.1\"\n\
source = \"registry+https://github.com/rust-lang/crates.io-index\"\n\
checksum = \"20aa\"\n";
assert_eq!(lock_source(patched, "gix-features"), Some(None));
assert_eq!(
lock_fields(patched, "gix-features"),
Some(("0.49.1".into(), None))
);
assert_eq!(
lock_source(registry, "gix-features"),
Some(Some(
"registry+https://github.com/rust-lang/crates.io-index".into()
))
);
// A `source` belonging to the NEXT package must not be attributed to
// this one — that would report every patched lock as a registry copy.
let then_another = format!("{patched}{registry}");
assert_eq!(lock_source(&then_another, "gix-features"), Some(None));
assert_eq!(
patch_path(GOOD_TABLE, PATCHED_CRATE).as_deref(),
Some("vendor/gix-parallel")
);
assert_eq!(patch_rename(GOOD_TABLE, PATCHED_CRATE), None);
assert_eq!(
patch_rename(RENAMED_TABLE, PATCHED_CRATE).as_deref(),
Some("gix-parallel-gunnar")
);
// A path dependency in an ordinary `[dependencies]` table is not a
// patch, and reading it as one would make the guard permanently green.
assert_eq!(
patch_path(
"[dependencies]\ngix-features = { path = \"vendor/gix-features\" }\n",
"gix-features"
),
None
);
assert_eq!(patch_path("[patch.crates-io]\n", "gix-features"), None);
// A COMMENTED-OUT patch entry is not a patch entry. The tables in this
// repository carry a dozen lines of prose above them.
assert_eq!(
patch_path(
"[patch.crates-io]\n# gix-features = { path = \"vendor/gix-features\" }\n",
"gix-features"
),
None
);
}
/// Whole-key matching, which is what the old reader did not do.
///
/// `t.starts_with(name)` plus `split("path")` meant `gix-features` matched a
/// line beginning `gix-features-gunnar = { path = … }`. The guard would then
/// have read the renamed entry as the patch still being present — the single
/// change that unbound this patch, read as proof that it had not.
#[test]
fn a_longer_crate_name_is_not_the_crate_name() {
let renamed_key = "[patch.crates-io]\ngix-features-gunnar = { path = \"vendor/x\" }\n";
assert_eq!(patch_entry(renamed_key, "gix-features"), None);
assert_eq!(patch_path(renamed_key, "gix-features"), None);
assert_eq!(
patch_path(renamed_key, "gix-features-gunnar").as_deref(),
Some("vendor/x")
);
// `inline_field` on shapes: whole key, both sides.
assert_eq!(
inline_field("{ path = \"a\", package = \"b\" }", "package").as_deref(),
Some("b")
);
assert_eq!(inline_field("{ path = \"a\" }", "package"), None);
// `rust-version` must not answer for `version`, or every vendored
// manifest reads as 1.85.
assert_eq!(
package_field(
"[package]\nrust-version = \"1.85\"\nversion = \"0.49.1\"\n",
"version"
)
.as_deref(),
Some("0.49.1")
);
// `[lib] name` must not answer for `[package] name`. Keeping `[lib] name
// = "gix_features"` while renaming the package is exactly what 8952778
// did, and it is what made the rename look harmless.
assert_eq!(
package_field(
"[package]\nname = \"gix-features-gunnar\"\n[lib]\nname = \"gix_features\"\n",
"name"
)
.as_deref(),
Some("gix-features-gunnar")
);
// And a comment quoting the wrong name is not the value.
assert_eq!(
package_field(
"[package]\n# it was once name = \"gix-features-gunnar\", see PATCH.md\n\
name = \"gix-features\"\n",
"name"
)
.as_deref(),
Some("gix-features")
);
}
}