From b4959325d5a066d890c3947b3484f51360e4a003 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 03:27:52 +0700 Subject: [PATCH 1/2] tri t27o-sweep --repo : sweep another checkout, one cache per repository, a missing seal names its path (Closes #10638) The listing (git -C ls-files) and the files come from ; the two seal keys keep coming from the t27 checkout the command runs in. A missing seal now says which absolute path it looked for and to run from a t27 checkout. The rules are in specs/tools/t27o/ledger.t27: repo_is_here (no --repo, empty or ".") and cache_path (target/t27o-sweep-.tsv, "." keeps target/t27o-sweep.tsv), with the seal paths and the hint as constants. Six new tests, three of them negative controls. main.rs is glue only and gains no hand line (11 added, 11 deleted). Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27o_T27oLedger.json | 22 +++-- cli/tri/src/main.rs | 22 ++--- gen/rust/tools/t27o/ledger.rs | 74 ++++++++++++++++- specs/tools/t27o/ledger.t27 | 123 ++++++++++++++++++++++++++++ 4 files changed, 220 insertions(+), 21 deletions(-) diff --git a/.trinity/seals/t27o_T27oLedger.json b/.trinity/seals/t27o_T27oLedger.json index 2fb9cdacc7..9dea130d86 100644 --- a/.trinity/seals/t27o_T27oLedger.json +++ b/.trinity/seals/t27o_T27oLedger.json @@ -8,30 +8,34 @@ { "spec": "specs/tri/reseal/seal.t27", "spec_hash": "sha256:58eed39e2ed5b40cca6c35c5f951e867a7f92cb260972ee0305278f1e0028a47" + }, + { + "spec": "specs/tri/t27b/macho.t27", + "spec_hash": "sha256:3d7b5211967649508eff9770e966bc7d8522d1f3d964e30cfc76a5ae75978af3" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:70b876438caf032fd5539af93b210cec8f661ab519bb418ffdfb33d7c9f55eac", - "gen_hash_rust": "sha256:81513af17b39ada3c99c5b4340270456c67cfb1ff592ff82c35e032541210e0a", - "gen_hash_verilog": "sha256:42e96be374322dd1a6a2dfd10d9e598eb6c670d338f6c80f6c3bc8403ce84b87", - "gen_hash_zig": "sha256:27cc3b986f364b51e0a64ec4ceb5334b57bb9eca2a1d698ca08f6c4a8ffb10a8", + "gen_hash_c": "sha256:d75c3b7403359012744ee9791d5f24603c538fbc993cd059275cbce85ca04633", + "gen_hash_rust": "sha256:02f9f45cec23ed1149a09edd668dfc48aef3b4d83d0ad7a1f0709a564fd6d3c5", + "gen_hash_verilog": "sha256:7d461b24959b4ee95bcb3617f2a41e5eaac048bf7640f8a467f7e60b6db1c3b8", + "gen_hash_zig": "sha256:362faa94a4b8c45abc3a5485b5eaf1bdcf30b0e70fe327c231eb745bbede4a69", "module": "T27oLedger", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T18:07:43Z", + "sealed_at": "2026-10-18T20:26:56Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:c8ced20f9d10b20c8cfe366b991b34d2053775d9ea26fb656aeacf70d8004574", + "spec_hash": "sha256:93762f5c3e0dc493071e3850a933960a657b651964ca1da095afa6472ef10432", "spec_path": "specs/tools/t27o/ledger.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 23, - "total": 23, + "passed": 29, + "total": 29, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/cli/tri/src/main.rs b/cli/tri/src/main.rs index 889e37da92..5fecdf712b 100644 --- a/cli/tri/src/main.rs +++ b/cli/tri/src/main.rs @@ -439,8 +439,8 @@ enum Commands { /// The t27c binary; default target/release/t27c, then t27c on PATH. #[arg(long)] t27c: Option, }, - /// t27o over a tree (#10543): every tracked .rs file converted in-process; a file's row is reused while its key holds (its SHA-256 and the seals of rust_convert.t27 and rust_std.t27); counts, coverage and the ranked first-refusal kinds (rules: specs/tools/t27o/ledger.t27). - T27oSweep { paths: Vec, #[arg(long, default_value_t = t27o_ledger::SWEEP_MAX_BYTES)] max_bytes: usize, #[arg(long, default_value = t27o_ledger::SWEEP_CACHE)] cache: String, #[arg(long, default_value_t = t27o_ledger::SWEEP_TOP)] top: usize }, + /// t27o over a tree (#10543): every tracked .rs file converted in-process; a file's row is reused while its key holds (its SHA-256 and the seals of rust_convert.t27 and rust_std.t27, read from the t27 checkout this runs in); `--repo ` sweeps another checkout, with a cache of its own (#10638); counts, coverage and the ranked first-refusal kinds (rules: specs/tools/t27o/ledger.t27). + T27oSweep { paths: Vec, #[arg(long, default_value = t27o_ledger::SWEEP_HERE)] repo: String, #[arg(long, default_value_t = t27o_ledger::SWEEP_MAX_BYTES)] max_bytes: usize, #[arg(long)] cache: Option, #[arg(long, default_value_t = t27o_ledger::SWEEP_TOP)] top: usize }, } #[derive(Subcommand)] @@ -1331,7 +1331,7 @@ fn main() -> Result<()> { Commands::Hot { t27c, action, rest } => std::process::exit(cmd_hot(action, rest, t27c.as_deref()) as i32), Commands::Payout { epoch, pool, json } => std::process::exit(payout::cmd(*epoch, *pool, *json)? as i32), Commands::T27o { file, name, oracle, check, t27c } => std::process::exit(cmd_t27o(file, name.as_deref(), *oracle, *check, t27c.as_deref())? as i32), - Commands::T27oSweep { paths, max_bytes, cache, top } => std::process::exit(cmd_t27o_sweep(paths, *max_bytes, cache, *top)? as i32), + Commands::T27oSweep { paths, repo, max_bytes, cache, top } => std::process::exit(cmd_t27o_sweep(paths, repo, *max_bytes, cache.as_deref(), *top)? as i32), } Ok(()) @@ -1370,14 +1370,15 @@ fn cmd_t27o(file: &str, name: Option<&str>, oracle: bool, check: bool, t27c: Opt } /// `tri t27o-sweep` (#10543): every rule is ledger.t27 (which files, whether a row is reusable, coverage, root vs cascade, the ranking); this lists, hashes, converts in-process and keeps the cache. -fn cmd_t27o_sweep(paths: &[String], max: usize, cache: &str, top: usize) -> Result { - let t0 = std::time::Instant::now(); let seal = |f: &str| -> Result { Ok(serde_json::from_slice::(&fs::read(f)?)?["spec_hash"].as_str().with_context(|| format!("{f}: no spec_hash"))?.to_string()) }; - let (conv, std) = (seal(".trinity/seals/t27o_T27oRust.json")?, seal(".trinity/seals/t27o_T27oRustStd.json")?); - let old: &'static str = fs::read_to_string(cache).unwrap_or_default().leak(); let rows: std::collections::HashMap<&'static str, Vec<&'static str>> = old.lines().map(|l| l.split('\t').collect::>()).filter(|f| f.len() == 10).map(|f| (f[0], f)).collect(); - let listing: &'static str = String::from_utf8(Command::new("git").args(["ls-files", "--"]).args(paths).output()?.stdout)?.leak(); +fn cmd_t27o_sweep(paths: &[String], repo: &str, max: usize, cache: Option<&str>, top: usize) -> Result { + let t0 = std::time::Instant::now(); let seal = |f: &str| -> Result { let p = std::env::current_dir()?.join(f); Ok(serde_json::from_slice::(&fs::read(&p).with_context(|| format!("{}: cannot read the seal; {}", p.display(), t27o_ledger::SEAL_HINT))?)?["spec_hash"].as_str().with_context(|| format!("{}: no spec_hash", p.display()))?.to_string()) }; + let (conv, std) = (seal(t27o_ledger::SEAL_CONVERTER)?, seal(t27o_ledger::SEAL_STD)?); let repo: &'static str = repo.to_string().leak(); // the keys come from this checkout; the listing, the files and the cache belong to `root` + let root: &'static str = if t27o_ledger::repo_is_here(repo) { t27o_ledger::SWEEP_HERE } else { fs::canonicalize(repo).with_context(|| format!("{repo}: not a directory"))?.to_string_lossy().into_owned().leak() }; let cache = cache.map_or_else(|| { let mut b = vec![0u8; 4096]; let n = t27o_ledger::cache_path(root, &mut b); String::from_utf8_lossy(&b[..n]).into_owned() }, str::to_string); + let old: &'static str = fs::read_to_string(&cache).unwrap_or_default().leak(); let rows: std::collections::HashMap<&'static str, Vec<&'static str>> = old.lines().map(|l| l.split('\t').collect::>()).filter(|f| f.len() == 10).map(|f| (f[0], f)).collect(); + let ls = Command::new("git").args(["-C", root, "ls-files", "--"]).args(paths).output()?; anyhow::ensure!(ls.status.success(), "{root}: git ls-files failed: {}", String::from_utf8_lossy(&ls.stderr).trim()); let listing: &'static str = String::from_utf8(ls.stdout)?.leak(); let (mut tab, mut kinds, mut tot, mut n) = (String::new(), std::collections::BTreeMap::<&'static str, usize>::new(), [0usize; 5], [0usize; 3]); for p in listing.lines().filter(|p| t27o_ledger::sweep_path(p)) { - let Ok(src) = fs::read(p) else { continue }; + let Ok(src) = fs::read(Path::new(root).join(p)) else { continue }; if t27o_ledger::over_cap(src.len(), max) { n[2] += 1; println!("{p} {} bytes skipped", src.len()); continue; } let (sha, r) = (format!("{:x}", Sha256::digest(&src)), rows.get(p)); let reuse = t27o_ledger::reuse_row(t27o_ledger::key_check(r.is_some(), r.is_some_and(|r| r[1] == sha), r.is_some_and(|r| r[2] == conv), r.is_some_and(|r| r[3] == std))); @@ -1396,8 +1397,7 @@ fn cmd_t27o_sweep(paths: &[String], max: usize, cache: &str, top: usize) -> Resu let (casc, mut root): (Vec<_>, Vec<_>) = kinds.into_iter().partition(|(k, _)| t27o_ledger::is_cascade(*k)); root.sort_by(|a, b| t27o_ledger::rank_cmp(a.1, a.0, b.1, b.0).cmp(&0)); root.iter().take(top).for_each(|(k, c)| println!("{c:>7} {k}")); println!("{:>7} cascade ({})\ncold {} / reused {} / skipped {} wall {:.1} s", casc.iter().map(|k| k.1).sum::(), t27o_ledger::KIND_CASCADE, n[0], n[1], n[2], t0.elapsed().as_secs_f64()); - if let Some(d) = Path::new(cache).parent() { fs::create_dir_all(d)?; } - fs::write(cache, tab)?; Ok(0) + if let Some(d) = Path::new(&cache).parent() { fs::create_dir_all(d)?; } fs::write(&cache, tab)?; Ok(0) } /// `tri lanes`: gh's open pull requests into the base, the clone's worktrees and branch times, and what specs/tri/lanes/lanes.t27 reads in them. diff --git a/gen/rust/tools/t27o/ledger.rs b/gen/rust/tools/t27o/ledger.rs index f8ddc90f16..7bd930bc18 100644 --- a/gen/rust/tools/t27o/ledger.rs +++ b/gen/rust/tools/t27o/ledger.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 23 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 29 test(s), 0 invariant(s), 0 function(s) with no body. // This backend emits declarations only. The spec's checks live in // the Zig and Verilog outputs; do not read this file as verified. @@ -226,6 +226,18 @@ pub const SWEEP_TOP: usize = 15; pub const SWEEP_CACHE: &'static str = "target/t27o-sweep.tsv"; +pub const SWEEP_CACHE_HEAD: &'static str = "target/t27o-sweep-"; + +pub const SWEEP_CACHE_TAIL: &'static str = ".tsv"; + +pub const SWEEP_HERE: &'static str = "."; + +pub const SEAL_CONVERTER: &'static str = ".trinity/seals/t27o_T27oRust.json"; + +pub const SEAL_STD: &'static str = ".trinity/seals/t27o_T27oRustStd.json"; + +pub const SEAL_HINT: &'static str = "run from a t27 checkout"; + pub const COVERAGE_UNKNOWN: usize = 1001; pub const KIND_CASCADE: &'static str = "uses refused"; @@ -262,6 +274,42 @@ pub fn over_cap(size: usize, cap: usize) -> bool { return (size > cap); } +pub fn repo_is_here(repo: &'static str) -> bool { + if (repo.len() == 0) { + return true; + } + let mut to: usize = repo.len(); + while ((to > 0) && (repo.as_bytes()[((to - 1)) as usize] == 47)) { + to = (to - 1); + } + return ((to == 1) && (repo.as_bytes()[0] == 46)); +} + +pub fn cache_path(repo: &'static str, out: &mut [u8]) -> usize { + if repo_is_here(repo) { + return put_text(out, 0, SWEEP_CACHE); + } + let mut to: usize = repo.len(); + while ((to > 0) && (repo.as_bytes()[((to - 1)) as usize] == 47)) { + to = (to - 1); + } + let mut from: usize = to; + while ((from > 0) && (repo.as_bytes()[((from - 1)) as usize] != 47)) { + from = (from - 1); + } + if (((SWEEP_CACHE_HEAD.len() + (to - from)) + SWEEP_CACHE_TAIL.len()) > out.len()) { + return 0; + } + let mut w: usize = put_text(out, 0, SWEEP_CACHE_HEAD); + let mut k: usize = from; + while (k < to) { + out[(w) as usize] = repo.as_bytes()[(k) as usize]; + w = (w + 1); + k = (k + 1); + } + return put_text(out, w, SWEEP_CACHE_TAIL); +} + pub fn coverage_permille(seen: usize, converted: usize) -> usize { if (seen == 0) { return COVERAGE_UNKNOWN; @@ -350,6 +398,21 @@ pub fn rank_cmp(count_a: usize, kind_a: &'static str, count_b: usize, kind_b: &' return text_order(kind_a, kind_b); } +pub fn out_is(out: impl AsRef<[u8]>, n: usize, want: &'static str) -> bool { + let out: &[u8] = out.as_ref(); + if ((n != want.len()) || (n > out.len())) { + return false; + } + let mut k: usize = 0; + while (k < n) { + if (out[(k) as usize] != want.as_bytes()[(k) as usize]) { + return false; + } + k = (k + 1); + } + return true; +} + pub const V_BEHIND: u8 = 2; pub const V_BLOCKED: u8 = 3; @@ -616,3 +679,12 @@ pub fn verdict(conflicted: bool, matches: bool, write: bool) -> u8 { return (base + 2); } +pub fn put_text(out: &mut [u8], at: usize, w: &'static str) -> usize { + let mut i: usize = 0; + while (i < w.len()) { + out[((at + i)) as usize] = w.as_bytes()[(i) as usize]; + i = (i + 1); + } + return (at + w.len()); +} + diff --git a/specs/tools/t27o/ledger.t27 b/specs/tools/t27o/ledger.t27 index 1b93b39c29..9e33079b12 100644 --- a/specs/tools/t27o/ledger.t27 +++ b/specs/tools/t27o/ledger.t27 @@ -207,10 +207,21 @@ pub fn key_state(recorded: bool, same_source: bool, converter_version: u32, std_ use tri::lab::receipt::span_is; use tri::reseal::seal::starts_at; +use tri::t27b::macho::put_text; pub const SWEEP_MAX_BYTES : usize = 300000; pub const SWEEP_TOP : usize = 15; +; The cache of the repository the command runs in, and the pieces of the cache of any other (cache_path). pub const SWEEP_CACHE : str = "target/t27o-sweep.tsv"; +pub const SWEEP_CACHE_HEAD : str = "target/t27o-sweep-"; +pub const SWEEP_CACHE_TAIL : str = ".tsv"; +; --repo when it is not given: the checkout the command runs in. +pub const SWEEP_HERE : str = "."; +; The two seals the keys come from, relative to the t27 checkout the command runs in (never to the +; swept repository), and what a missing one is told to do. +pub const SEAL_CONVERTER : str = ".trinity/seals/t27o_T27oRust.json"; +pub const SEAL_STD : str = ".trinity/seals/t27o_T27oRustStd.json"; +pub const SEAL_HINT : str = "run from a t27 checkout"; ; seen == 0 has no share: unknown, one above 100.0 percent, never a number inside the scale. pub const COVERAGE_UNKNOWN : usize = 1001; ; The kind (see kind_to) of a refusal whose reason is another refusal. @@ -245,6 +256,39 @@ pub fn over_cap(size: usize, cap: usize) -> bool { return size > cap; } +; --- `tri t27o-sweep --repo ` (#10638): one cache per swept repository --------------------- +; A row is keyed by its path, so a cache shared by two repositories would evict the other's rows on +; every run. The default cache of a repository is named after its directory. + +; Is --repo this checkout: not given, empty, or "." (with any trailing slashes). Anything else is +; another directory, "/" and ".." included. +pub fn repo_is_here(repo: string) -> bool { + if (repo.len == 0) { return true; } + var to : usize = repo.len; + while (to > 0 and repo[to - 1] == 47) { to = to - 1; } + return to == 1 and repo[0] == 46; +} + +; The default cache of a repository written into out, its length: SWEEP_CACHE for this checkout, else +; target/t27o-sweep-.tsv with the last component of the directory, trailing slashes +; ignored. 0 when the path would not fit out: nothing is written then. +pub fn cache_path(repo: string, out: []u8) -> usize { + if (repo_is_here(repo)) { return put_text(out, 0, SWEEP_CACHE); } + var to : usize = repo.len; + while (to > 0 and repo[to - 1] == 47) { to = to - 1; } + var from : usize = to; + while (from > 0 and repo[from - 1] != 47) { from = from - 1; } + if (SWEEP_CACHE_HEAD.len + (to - from) + SWEEP_CACHE_TAIL.len > out.len) { return 0; } + var w : usize = put_text(out, 0, SWEEP_CACHE_HEAD); + var k : usize = from; + while (k < to) { + out[w] = repo[k]; + w = w + 1; + k = k + 1; + } + return put_text(out, w, SWEEP_CACHE_TAIL); +} + ; The share of functions converted, per mille. No function seen is unknown, never 1000; a count of ; converted above the count seen does not add up and is unknown too. pub fn coverage_permille(seen: usize, converted: usize) -> usize { @@ -515,3 +559,82 @@ test the_ranking_puts_more_refusals_first_and_equal_counts_in_text_order { assert(text_order("type", "types") == 0 - 1); assert(text_order("types", "type") == 1); } + +; --- Tests: `tri t27o-sweep --repo ` (#10638) ------------------------------------------------- + +; out[0..n) holds exactly the bytes of want. +pub fn out_is(out: []u8, n: usize, want: str) -> bool { + if (n != want.len or n > out.len) { return false; } + var k : usize = 0; + while (k < n) { + if (out[k] != want[k]) { return false; } + k = k + 1; + } + return true; +} + +test this_checkout_is_not_given_empty_or_a_dot_and_nothing_else_is { + assert(repo_is_here(SWEEP_HERE)); + assert(repo_is_here("")); + assert(repo_is_here("./")); + assert(repo_is_here(".//")); + assert(repo_is_here("/Users/playom/trios") == false); + assert(repo_is_here("trios") == false); + assert(repo_is_here("..") == false); + assert(repo_is_here("../") == false); + assert(repo_is_here("/") == false); + assert(repo_is_here("./trios") == false); + assert(repo_is_here("trios/.") == false); +} + +test the_cache_of_this_checkout_is_the_path_it_always_had { + var out : [128]u8 = undefined; + assert(cache_path(SWEEP_HERE, out[0 .. 128]) == SWEEP_CACHE.len); + assert(out_is(out[0 .. 128], SWEEP_CACHE.len, "target/t27o-sweep.tsv")); + assert(cache_path("", out[0 .. 128]) == SWEEP_CACHE.len); + assert(out_is(out[0 .. 128], SWEEP_CACHE.len, SWEEP_CACHE)); +} + +test the_cache_of_another_repository_is_named_after_its_directory { + var out : [128]u8 = undefined; + assert(cache_path("/Users/playom/trios", out[0 .. 128]) == 27); + assert(out_is(out[0 .. 128], 27, "target/t27o-sweep-trios.tsv")); + assert(cache_path("/Users/playom/999-multibots-telegraf", out[0 .. 128]) == 44); + assert(out_is(out[0 .. 128], 44, "target/t27o-sweep-999-multibots-telegraf.tsv")); + assert(cache_path("trinity-fpga", out[0 .. 128]) == 34); + assert(out_is(out[0 .. 128], 34, "target/t27o-sweep-trinity-fpga.tsv")); +} + +test negative_control_two_repositories_never_share_a_cache_and_a_slash_changes_nothing { + var a : [128]u8 = undefined; + var b : [128]u8 = undefined; + var na : usize = cache_path("/Users/playom/trios", a[0 .. 128]); + var nb : usize = cache_path("/Users/playom/trinity", b[0 .. 128]); + assert(out_is(a[0 .. 128], na, "target/t27o-sweep-trinity.tsv") == false); + assert(out_is(b[0 .. 128], nb, "target/t27o-sweep-trios.tsv") == false); + assert(out_is(a[0 .. 128], na, SWEEP_CACHE) == false); + assert(cache_path("/Users/playom/trios/", b[0 .. 128]) == na); + assert(out_is(b[0 .. 128], na, "target/t27o-sweep-trios.tsv")); + assert(cache_path("/Users/playom/trios//", b[0 .. 128]) == na); + assert(cache_path("/", b[0 .. 128]) == 22); + assert(out_is(b[0 .. 128], 22, "target/t27o-sweep-.tsv")); +} + +test a_cache_path_that_does_not_fit_is_not_written { + var out : [24]u8 = undefined; + out[0] = 0; + assert(cache_path("/Users/playom/trios", out[0 .. 24]) == 0); + assert(out[0] == 0); + var fits : [27]u8 = undefined; + assert(cache_path("/Users/playom/trios", fits[0 .. 27]) == 27); + assert(cache_path(SWEEP_HERE, out[0 .. 24]) == SWEEP_CACHE.len); +} + +test the_seals_are_read_from_the_checkout_and_a_missing_one_says_so { + assert(span_is(SEAL_CONVERTER, 0, SEAL_CONVERTER.len, ".trinity/seals/t27o_T27oRust.json")); + assert(span_is(SEAL_STD, 0, SEAL_STD.len, ".trinity/seals/t27o_T27oRustStd.json")); + assert(starts_at(SEAL_CONVERTER, 0, ".trinity/seals/")); + assert(starts_at(SEAL_STD, 0, ".trinity/seals/")); + assert(span_is(SEAL_HINT, 0, SEAL_HINT.len, "run from a t27 checkout")); + assert(SEAL_CONVERTER.len != SEAL_STD.len); +} From 271dc03cd163238c2ad79b4614b05637c021f5ed Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 03:30:47 +0700 Subject: [PATCH 2/2] tri t27o-sweep: name the failure when --repo does not exist (Closes #10638) The message said "not a directory" for a path that does not exist at all; it now says the repository cannot be opened and the cause follows. Co-Authored-By: Claude Opus 5.5 --- cli/tri/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/tri/src/main.rs b/cli/tri/src/main.rs index 5fecdf712b..1a567106a5 100644 --- a/cli/tri/src/main.rs +++ b/cli/tri/src/main.rs @@ -1373,7 +1373,7 @@ fn cmd_t27o(file: &str, name: Option<&str>, oracle: bool, check: bool, t27c: Opt fn cmd_t27o_sweep(paths: &[String], repo: &str, max: usize, cache: Option<&str>, top: usize) -> Result { let t0 = std::time::Instant::now(); let seal = |f: &str| -> Result { let p = std::env::current_dir()?.join(f); Ok(serde_json::from_slice::(&fs::read(&p).with_context(|| format!("{}: cannot read the seal; {}", p.display(), t27o_ledger::SEAL_HINT))?)?["spec_hash"].as_str().with_context(|| format!("{}: no spec_hash", p.display()))?.to_string()) }; let (conv, std) = (seal(t27o_ledger::SEAL_CONVERTER)?, seal(t27o_ledger::SEAL_STD)?); let repo: &'static str = repo.to_string().leak(); // the keys come from this checkout; the listing, the files and the cache belong to `root` - let root: &'static str = if t27o_ledger::repo_is_here(repo) { t27o_ledger::SWEEP_HERE } else { fs::canonicalize(repo).with_context(|| format!("{repo}: not a directory"))?.to_string_lossy().into_owned().leak() }; let cache = cache.map_or_else(|| { let mut b = vec![0u8; 4096]; let n = t27o_ledger::cache_path(root, &mut b); String::from_utf8_lossy(&b[..n]).into_owned() }, str::to_string); + let root: &'static str = if t27o_ledger::repo_is_here(repo) { t27o_ledger::SWEEP_HERE } else { fs::canonicalize(repo).with_context(|| format!("{repo}: cannot open the repository"))?.to_string_lossy().into_owned().leak() }; let cache = cache.map_or_else(|| { let mut b = vec![0u8; 4096]; let n = t27o_ledger::cache_path(root, &mut b); String::from_utf8_lossy(&b[..n]).into_owned() }, str::to_string); let old: &'static str = fs::read_to_string(&cache).unwrap_or_default().leak(); let rows: std::collections::HashMap<&'static str, Vec<&'static str>> = old.lines().map(|l| l.split('\t').collect::>()).filter(|f| f.len() == 10).map(|f| (f[0], f)).collect(); let ls = Command::new("git").args(["-C", root, "ls-files", "--"]).args(paths).output()?; anyhow::ensure!(ls.status.success(), "{root}: git ls-files failed: {}", String::from_utf8_lossy(&ls.stderr).trim()); let listing: &'static str = String::from_utf8(ls.stdout)?.leak(); let (mut tab, mut kinds, mut tot, mut n) = (String::new(), std::collections::BTreeMap::<&'static str, usize>::new(), [0usize; 5], [0usize; 3]);