build server: the remote checkout's clean spares a lane's scratch (AP-H1, the lost-scratch class)
remote-run.sh checkout_tree ran `git clean -fd` on the box mirror before every build from any agent, so the attack rows lost attack-f3/, attack-f1-venv/ and tools/attack/*/target to each other's builds (7 October 2026, 09:2x UK). The clean now also spares the fixed prefixes attack-*, scratch-*, target-attack-*, .build-remote.log and every glob in the mirror-local .igneum-scratch-spare (one per line, # comments, the file itself spared), keeps the target and stamp excludes and still runs without -x. The clean-tree test asks `git clean -nd` with the same excludes instead of filtering the status list, so a spared dir is not "not clean". --self-test: a fixed-prefix dir at the root and nested, a declared dir and the spare file survive, an undeclared dir is removed. tools/ci/scratch-spare-check.sh in the pre-push gate fails when the clean line loses the spare arguments, spare_args stops reading the file, a fixed prefix goes, or -x appears. docs/plans/build-server.md R4a says how a lane declares its prefix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
d91fe9bbb3
commit
210084b0e5
4 changed files with 98 additions and 4 deletions
|
|
@ -79,6 +79,7 @@ Also logged for context: the Mac's Linux cross-build with zig (`infra/cross/buil
|
|||
| R2 | Windows exes come from `tools/cross-remote.sh` (fork worktree: igneumd.exe, igneum-miner.exe; app/igneum-app: igneum-app.exe and the two tools). The PC `build` job stays as the second source until two releases have shipped from the box. |
|
||||
| R3 | The Mac keeps what only it can do: aarch64-apple-darwin binaries (the DMG, nodes that agents run locally), tests that need Metal (proto-metal, the Metal worker), and measurements. Those still use `tools/lock/with-lock.sh` and the Mac's build slots. |
|
||||
| R4 | A worktree builds once on the box per commit plus overlay; the next build is incremental in `/srv/builds/<worktree>/.../target`. Nobody deletes another worktree's target dir on the box. |
|
||||
| R4a | A lane's scratch on the box mirror survives other lanes' builds (7 October 2026, after the attack rows lost `attack-f3/`, `attack-f1-venv/` and `tools/attack/*/target` to each other's builds, hazard AP-H1). The checkout's `git clean` spares `attack-*`, `scratch-*`, `target-attack-*` and `.build-remote.log` at any depth, plus every glob in the mirror-local file `/srv/builds/<worktree>/.igneum-scratch-spare` (one glob per line, gitignore syntax, `#` comments; the file itself is spared). **To declare a prefix**: before the first build that must leave it alone, append one line named for the lane (`bs-<name>`, `r03xx-ship`, the scratchpad prefix rule of 7 October): `ssh -i ~/.ssh/igneum_ed25519 build@188.40.146.49 "printf 'bs-mylane-*\n' >> /srv/builds/<worktree>/.igneum-scratch-spare"`. Keep scratch outside the crate directories the overlay syncs (`bs_overlay_dir` rsyncs those with `--delete`, so an undeclared dir inside one goes the Mac's way regardless; a `target-attack-*` name is safe there too, rsync protects its excluded `target-*/`). `remote-run.sh --self-test` shows a declared and a fixed-prefix dir surviving and an undeclared one removed; `tools/ci/scratch-spare-check.sh` (pre-push gate) fails when the clean line loses the mechanism or gains `-x`. |
|
||||
| R5 | `RUST_TOOLCHAIN` in provision.sh is bumped in the same commit as the Mac's `rustup update`; build-remote.sh refuses a mismatch. Add a `rust-toolchain.toml` to the fork and the repo (none exists today) so both sides pin from one file. |
|
||||
| R6 | The box is never a node host for the live devnet and never holds a secret (no `~/.config/igneum` there). The Devnet 2 seed on it runs under its own unit with `--devnet --devnet-suffix=<n>` on 26611 (ufw already open) when that work starts. |
|
||||
| R6a | ONE recorded exception to R6 (main's ruling, 6 October 2026, 20:0x UK): the three Discord webhook URLs live at `/srv/discord-hooks/env` (mode 600, owner build), installed by `infra/build-server/discord-hooks/install.sh` with `IGNEUM_SECRET_ON_BOX_OK=1`, because the Mac sleeps and the bot's timer must not. The reasoning: a webhook URL signs no release, moves no funds and reaches none of the devnet's hands; whoever holds it can only post as the bot, and a leaked one is deleted in Discord in one click and the file replaced. No other secret joins it; `check` prints key names, never values. |
|
||||
|
|
@ -96,6 +97,7 @@ Also logged for context: the Mac's Linux cross-build with zig (`infra/cross/buil
|
|||
| A path dependency inside a vendor repo (the shipper's proving build, 6 Oct 2026) | proving/igneum-prove depends on vendor/igneum-node-exec/igneum/evm-types, a MEMBER of the fork's workspace (it inherits `thiserror` from the fork's root manifest); the first design synced that one directory, so cargo found no workspace root on the box ("failed to load manifest for workspace member"), and the shipper cross-built the Linux prove-host on the Mac with cargo-zigbuild meanwhile | lib.sh groups path dependencies by git top level: one under vendor/ is a whole repository, pushed to its mirror (a fork worktree such as igneum-node-exec goes to /srv/igneum-node.git, which already held its branch; a repository of its own gets /srv/<name>.git, created on first use) and checked out whole at /srv/builds/<worktree>/vendor/<name>; run-from-mac.sh wires every vendor repo the Cargo.toml files reach (today only igneum-node-exec). The detector's first version tested "under BS_TOP" before "own repository" and missed it, since vendor/ lies under the igneum top level on disk. Then `libprotobuf-dev` was missing (sp1-prover-types's build script imports google/protobuf/empty.proto); added to provision.sh. Proof: `cd proving/igneum-prove && tools/build-remote.sh -- build --release -p igneum-prove-host`: igneum-prove-host 71,943,192 B, sha256 e9213e3a6c979512d7859f6d8e848105bab53f4355e99fb0d30fb4a72c2d5714, ELF x86-64, 1 min 05 s warm (the cold run compiled 605 crates in 55 s before protoc stopped it). A Mac worktree has no vendor/ of its own, so a worktree that builds proving needs `git -C vendor/igneum-node worktree add <wt>/vendor/igneum-node-exec execution-layer` first, as the fork worktrees do |
|
||||
| One remote build lost its ssh session after 75 s (18:30:50 UTC, the first full proving build) | the remote bash died with it (slot line left behind, no JSONL line); no OOM, no reboot, the retry a minute later passed | the dashboard collector's one-pass unit finished within a second of the drop, so it was tested: a 90 s remote session through the same ControlMaster path survived two collector passes triggered by hand; the collector only reads (/proc, lock files, `flock -n`, `sccache --show-stats`, `kill(pid, 0)`). One event, no cause in the journal, the retry passed. A build that must survive a dropped connection would need the remote command under setsid with the Mac reconnecting to wait; not done, open if it happens again |
|
||||
| A stamp file at a nested path failed every checkout of /srv/builds/igneum (18:31 to 18:5x UTC, the shipper's 18:52 run) | a repo-kind crate keeps its `.build-remote-sha-<target>` and `target/` inside the crate dir; the checkout mode's clean-tree test only excused them at the tree root | the test is depth-agnostic and uses `--untracked-files=all` (a wholly untracked directory is otherwise collapsed to `?? dir/`); the self-test carries a nested stamp, a nested target dir and a stale `.git/index.lock`, which the mode now removes when no git runs there |
|
||||
| The attack rows lost scratch dirs to each other's builds (7 Oct 2026, 09:2x UK, hazard AP-H1 from the attack-pass lane) | checkout_tree's `git clean -fd` ran on the shared mirror before every build from any agent and took every untracked directory: `attack-f3/`, `attack-f1-venv/`, `tools/attack/*/target` | the clean spares the fixed prefixes and the globs in `.igneum-scratch-spare` (R4a), still without `-x` so `.git/info/exclude` applies; the clean-tree test asks `git clean -nd` with the same excludes instead of filtering the status list, so a spared dir no longer reads as "not clean"; the self-test carries a fixed-prefix dir at the root and nested, a declared dir, the spare file and an undeclared dir; `tools/ci/scratch-spare-check.sh` in the gate |
|
||||
| Two runs on one worktree at once (the shipper, 18:48:56Z) | the second run's checkout replaced the first's sources mid-cargo; both died | lib.sh takes a per-worktree lock on the box (`/srv/builds/_locks/wt-<worktree>`, mkdir-atomic, holder line) across sync, build and fetch; a second run waits up to 2 h (a line every minute), a lock older than 3 h is taken over; released on EXIT. The build slot (`build-<k>`) is unchanged |
|
||||
| The 0.3.15 prover pair for the PCs needs `--features igneum-prove-host/cuda` (the PCs run SP1_PROVER=cuda) | my first proving build named no feature | built on the box from master e1b5bc9: igneum-prove-host 73,161,528 B sha256 71bc2438856bb141f6cad3d18489f708568144fad5a06a002fbadefb9ce256f9, igneum-prove-export 3,609,360 B sha256 263bf4cef70af4a13a45b2e79b8dbab373282ea4c571f624d02ddb5791935361 (52 s warm, no CUDA needed at build time); handed to the shipper |
|
||||
| Let's Encrypt saw NXDOMAIN for build.igneum.network | the deSEC record was minutes old; Ubuntu's Caddy then fell back to ZeroSSL and failed with HTTP 422 for ever | issuer pinned to Let's Encrypt; the retry got the certificate |
|
||||
|
|
|
|||
|
|
@ -51,6 +51,26 @@ _slots_env="${IGNEUM_BUILD_SLOTS_DIR:-}"; _log_env="${IGNEUM_BUILD_LOG_DIR:-}"
|
|||
[ -f /etc/profile.d/igneum-build.sh ] && . /etc/profile.d/igneum-build.sh
|
||||
[ -n "$_slots_env" ] && IGNEUM_BUILD_SLOTS_DIR="$_slots_env"; [ -n "$_log_env" ] && IGNEUM_BUILD_LOG_DIR="$_log_env"
|
||||
|
||||
# What the clean spares beyond target dirs and stamps: a lane's scratch. The fixed prefixes, plus every glob in the mirror-local
|
||||
# file `.igneum-scratch-spare` (one per line, # comments; the file itself is spared). spare_args <dir> fills SPARE_ARGS with
|
||||
# the `-e <glob>` arguments; it is never empty (the fixed list), so bash 3.2's unbound-empty-array rule cannot bite the self-test.
|
||||
SPARE_FIXED=('attack-*' 'scratch-*' 'target-attack-*' '.build-remote.log' '.igneum-scratch-spare')
|
||||
spare_args() {
|
||||
local p
|
||||
SPARE_ARGS=()
|
||||
for p in "${SPARE_FIXED[@]}"; do SPARE_ARGS+=(-e "$p"); done
|
||||
[ -f "$1/.igneum-scratch-spare" ] || return 0
|
||||
while IFS= read -r p || [ -n "$p" ]; do
|
||||
p="${p%%#*}"; p="${p#"${p%%[![:space:]]*}"}"; p="${p%"${p##*[![:space:]]}"}"
|
||||
[ -n "$p" ] && SPARE_ARGS+=(-e "$p")
|
||||
done < "$1/.igneum-scratch-spare"
|
||||
return 0
|
||||
}
|
||||
# the untracked paths the clean would still take, up to three (empty = the tree is clean); the same excludes as the clean line
|
||||
tree_left() {
|
||||
git -C "$1" clean -nd -e target -e 'target-*' -e '.build-remote-sha-*' -e '.cross-remote-sha-*' -e sccache "${SPARE_ARGS[@]}" | head -3
|
||||
}
|
||||
|
||||
# discard the previous overlay (tracked edits and untracked files; target dirs, the sha stamps and anything ignored are kept),
|
||||
# fetch, then the branch at the commit. Runs in BR_CO_DIR, clones it from BR_CO_MIRROR when it has no .git.
|
||||
checkout_tree() {
|
||||
|
|
@ -68,7 +88,12 @@ checkout_tree() {
|
|||
if [ "$lock_age" -gt 30 ]; then rm -f .git/index.lock; echo "checkout: removed a stale .git/index.lock (${lock_age}s old) in $dir" >&2; fi
|
||||
fi
|
||||
git checkout -q -- . 2>/dev/null || true
|
||||
git clean -qfd -e target -e 'target-*' -e '.build-remote-sha-*' -e '.cross-remote-sha-*' -e sccache
|
||||
# 7 October 2026: the attack rows lost their scratch dirs (attack-f3/, attack-f1-venv/, tools/attack/*/target) to each
|
||||
# other's builds, because this clean ran on the shared mirror before every build from any agent and took every untracked
|
||||
# directory. A lane's scratch is now spared: the fixed prefixes and the mirror-local .igneum-scratch-spare (SPARE_ARGS,
|
||||
# see spare_args above). Never -x here: .git/info/exclude still applies. tools/ci/scratch-spare-check.sh guards this line.
|
||||
spare_args "$dir"
|
||||
git clean -qfd -e target -e 'target-*' -e '.build-remote-sha-*' -e '.cross-remote-sha-*' -e sccache "${SPARE_ARGS[@]}"
|
||||
git fetch -q origin '+refs/heads/*:refs/remotes/origin/*'
|
||||
git checkout -q -B "$branch" "$sha"
|
||||
git reset -q --hard "$sha"
|
||||
|
|
@ -78,7 +103,9 @@ checkout_tree() {
|
|||
# ... at any depth: a repo-kind crate (pool/, igneum-pow/, app/igneum-app/) writes its stamp in its own directory, and the
|
||||
# root-anchored pattern of the first version failed the second build of every such crate (6 October 2026, 18:51 UTC, the
|
||||
# pool build: "tree not clean after reset: ?? pool/.build-remote-sha-target"; the self-test has the subdirectory case now)
|
||||
local left; left=$(git status --porcelain --untracked-files=all | grep -vE '^\?\? (.*/)?(target|target-|sccache|\.build-remote-sha-|\.cross-remote-sha-)' | head -3 || true)
|
||||
# ... and since 7 October 2026 a lane's spared scratch, so the test asks the clean itself what it would still remove
|
||||
# (tree_left: `git clean -nd` with the same excludes; a spared directory is no longer "not clean")
|
||||
local left; left=$(tree_left "$dir" || true)
|
||||
[ -z "$left" ] || { echo "checkout: tree not clean after reset at $dir: $left" >&2; return 1; }
|
||||
set +e
|
||||
}
|
||||
|
|
@ -95,6 +122,11 @@ if [ "${1:-}" = --self-test ]; then
|
|||
echo edited > "$t/box/a.txt"; echo new > "$t/box/b.txt"; mkdir -p "$t/box/target/release"; echo bin > "$t/box/target/release/x"; echo "$sha1" > "$t/box/.build-remote-sha-target"
|
||||
# a crate in a subdirectory: its stamp and target dir must survive, its untracked overlay file must not
|
||||
mkdir -p "$t/box/sub/target/release"; echo bin > "$t/box/sub/target/release/y"; echo "$sha1" > "$t/box/sub/.build-remote-sha-target"; echo new > "$t/box/sub/c.txt"
|
||||
# a lane's scratch (7 October 2026): a fixed-prefix dir at the root and nested, a dir declared in .igneum-scratch-spare
|
||||
# (comments and blank lines in the file), and an undeclared dir that the clean must still take
|
||||
mkdir -p "$t/box/attack-f3" "$t/box/sub/attack-f1-venv" "$t/box/bs-lane-1" "$t/box/undeclared-1"
|
||||
echo x > "$t/box/attack-f3/x"; echo x > "$t/box/sub/attack-f1-venv/x"; echo x > "$t/box/bs-lane-1/x"; echo x > "$t/box/undeclared-1/x"
|
||||
printf '# the build-server lane\n\n bs-lane-* # trailing comment\n' > "$t/box/.igneum-scratch-spare"
|
||||
: > "$t/box/.git/index.lock" # a run killed mid-git leaves this; the checkout mode removes it once it is older than 30 s
|
||||
touch -t "$(date -d '-2 min' +%Y%m%d%H%M.%S 2>/dev/null || date -v-2M +%Y%m%d%H%M.%S)" "$t/box/.git/index.lock" # backdated two minutes (GNU date, then BSD date)
|
||||
[ $(( $(date +%s) - $(stat -c %Y "$t/box/.git/index.lock" 2>/dev/null || stat -f %m "$t/box/.git/index.lock") )) -gt 30 ] || { echo "self-test: could not backdate the fixture lock"; exit 1; }
|
||||
|
|
@ -111,12 +143,21 @@ if [ "${1:-}" = --self-test ]; then
|
|||
[ -f "$t/box/sub/.build-remote-sha-target" ] || { echo "self-test: a subdirectory crate's sha stamp was cleaned"; exit 1; }
|
||||
[ -f "$t/box/sub/target/release/y" ] || { echo "self-test: a subdirectory crate's target dir was cleaned"; exit 1; }
|
||||
[ ! -e "$t/box/sub/c.txt" ] || { echo "self-test: a subdirectory's untracked overlay file survived"; exit 1; }
|
||||
# a lane's scratch (7 October 2026): fixed prefixes at any depth and a declared glob survive, the spare file survives, an
|
||||
# undeclared dir is removed
|
||||
[ -f "$t/box/attack-f3/x" ] || { echo "self-test: a fixed-prefix scratch dir (attack-*) was cleaned"; exit 1; }
|
||||
[ -f "$t/box/sub/attack-f1-venv/x" ] || { echo "self-test: a nested fixed-prefix scratch dir was cleaned"; exit 1; }
|
||||
[ -f "$t/box/bs-lane-1/x" ] || { echo "self-test: a scratch dir declared in .igneum-scratch-spare was cleaned"; exit 1; }
|
||||
[ -f "$t/box/.igneum-scratch-spare" ] || { echo "self-test: the .igneum-scratch-spare file itself was cleaned"; exit 1; }
|
||||
[ ! -e "$t/box/undeclared-1" ] || { echo "self-test: an undeclared scratch dir survived the clean"; exit 1; }
|
||||
# the known-failed case: an untracked file the overlay left that no rule keeps must fail the check
|
||||
echo stray > "$t/box/sub/stray.txt"
|
||||
if (cd "$t/box" && left=$(git status --porcelain --untracked-files=all | grep -vE '^\?\? (.*/)?(target|target-|sccache|\.build-remote-sha-|\.cross-remote-sha-)' | head -3); [ -n "$left" ]); then :; else echo "self-test: the clean-tree check did NOT fire on a stray untracked file"; exit 1; fi
|
||||
spare_args "$t/box"; left=$(tree_left "$t/box"); [ -n "$left" ] || { echo "self-test: the clean-tree check did NOT fire on a stray untracked file"; exit 1; }
|
||||
rm -f "$t/box/sub/stray.txt"
|
||||
# ... and a spared directory alone must NOT fire it (the check asks the clean, not the status list)
|
||||
left=$(tree_left "$t/box"); [ -z "$left" ] || { echo "self-test: the clean-tree check fired on spared scratch: $left"; exit 1; }
|
||||
[ ! -f "$t/box/.git/index.lock" ] || { echo "self-test: the stale index.lock survived"; exit 1; }
|
||||
echo "self-test: checkout mode lands on the new commit with a clean tree, target dirs and sha stamps kept at any depth, stale index.lock removed, and fires on a stray file"; exit 0
|
||||
echo "self-test: checkout mode lands on the new commit with a clean tree, target dirs and sha stamps kept at any depth, declared and fixed-prefix scratch kept, an undeclared dir removed, stale index.lock removed, and fires on a stray file"; exit 0
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = --self-test-slots ]; then
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ tree_checks() {
|
|||
run "commit-string gate self-test" bash tools/ci/commit-string-check.sh --self-test
|
||||
run "build server remote checkout self-test" bash infra/build-server/remote-run.sh --self-test
|
||||
run "the remote checkout resets the mirror's tree before the branch checkout (the stale-overlay class)" bash -c 'bash tools/ci/mirror-reset-check.sh --self-test && bash tools/ci/mirror-reset-check.sh'
|
||||
run "the remote checkout's clean spares a lane's scratch (.igneum-scratch-spare, the fixed prefixes, never -x; the lost-scratch class)" bash -c 'bash tools/ci/scratch-spare-check.sh --self-test && bash tools/ci/scratch-spare-check.sh'
|
||||
run "long-running tools keep their body in one parsed block (the edited-while-running class)" bash -c 'bash tools/ci/whole-body-check.sh --self-test && bash tools/ci/whole-body-check.sh'
|
||||
run "no shell assignment hides behind a trailing comment (the swallowed-defaults class)" bash -c 'bash tools/ci/defaults-line-check.sh --self-test && bash tools/ci/defaults-line-check.sh'
|
||||
run "no script kills or finds a process by a plain name or a file name (pgrep/pkill -f literals, ps | grep)" bash -c 'bash tools/ci/kill-by-name-check.sh --self-test && bash tools/ci/kill-by-name-check.sh'
|
||||
|
|
|
|||
50
tools/ci/scratch-spare-check.sh
Executable file
50
tools/ci/scratch-spare-check.sh
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
# The lost-scratch class (7 October 2026, 09:2x UK, the attack-pass lane's hazard AP-H1): infra/build-server/remote-run.sh
|
||||
# checkout_tree() runs `git clean -fd` on the box mirror of a worktree before every build from any agent, so the attack rows'
|
||||
# untracked scratch (attack-f3/, attack-f1-venv/, tools/attack/*/target) was deleted by the next build from another row.
|
||||
# Rule: the clean spares a lane's scratch. It keeps the target and stamp excludes, carries the spare arguments (SPARE_ARGS,
|
||||
# filled by spare_args from the fixed prefixes attack-*, scratch-*, target-attack-*, .build-remote.log and every glob in the
|
||||
# mirror-local `.igneum-scratch-spare`), and never carries -x or -X (which would drop .git/info/exclude and the ignored files
|
||||
# with it). This check reads checkout_tree() and spare_args() and fails when any of that is missing.
|
||||
#
|
||||
# tools/ci/scratch-spare-check.sh # exit 1 with the reason
|
||||
# tools/ci/scratch-spare-check.sh --self-test # the real script passes; a clean line without SPARE_ARGS, one with -x, a script
|
||||
# # that no longer reads .igneum-scratch-spare, and one missing a fixed prefix fail
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
FIXED='attack-* scratch-* target-attack-* .build-remote.log'
|
||||
check() { # <file>: exit 0 when checkout_tree's clean keeps its excludes, spares declared scratch and carries no -x
|
||||
local f="$1" body line fixed p
|
||||
body=$(sed -n '/^checkout_tree()/,/^}/p' "$f")
|
||||
[ -n "$body" ] || { echo "scratch-spare: $f has no checkout_tree() function"; return 1; }
|
||||
line=$(printf '%s\n' "$body" | grep -E '^[[:space:]]*git clean ' | head -1)
|
||||
[ -n "$line" ] || { echo "scratch-spare: $f: no git clean line in checkout_tree"; return 1; }
|
||||
if printf '%s\n' "$line" | grep -qE '(^|[[:space:]])-[A-Za-z]*[xX]'; then echo "scratch-spare: $f: the clean carries -x/-X (ignored files and .git/info/exclude would go): $line"; return 1; fi
|
||||
for p in '-e target ' "-e 'target-*'" "-e '.build-remote-sha-*'" "-e '.cross-remote-sha-*'" '-e sccache'; do
|
||||
printf '%s \n' "$line" | grep -qF -- "$p" || { echo "scratch-spare: $f: the clean lost the exclude $p: $line"; return 1; }
|
||||
done
|
||||
printf '%s\n' "$line" | grep -qF -- '"${SPARE_ARGS[@]}"' || { echo "scratch-spare: $f: the clean does not carry the spare arguments (\"\${SPARE_ARGS[@]}\"): $line"; return 1; }
|
||||
printf '%s\n' "$body" | grep -qE '^[[:space:]]*spare_args ' || { echo "scratch-spare: $f: checkout_tree never calls spare_args before the clean"; return 1; }
|
||||
sed -n '/^spare_args()/,/^}/p' "$f" | grep -qF '.igneum-scratch-spare' || { echo "scratch-spare: $f: spare_args() does not read the mirror-local .igneum-scratch-spare file"; return 1; }
|
||||
fixed=$(grep -E '^SPARE_FIXED=' "$f" | head -1)
|
||||
[ -n "$fixed" ] || { echo "scratch-spare: $f: no SPARE_FIXED list"; return 1; }
|
||||
for p in $FIXED; do
|
||||
printf '%s\n' "$fixed" | grep -qF -- "'$p'" || { echo "scratch-spare: $f: the fixed spare list lacks '$p': $fixed"; return 1; }
|
||||
done
|
||||
echo "scratch-spare: $f: the clean keeps its excludes, spares the fixed prefixes and .igneum-scratch-spare, and carries no -x"
|
||||
}
|
||||
if [ "${1:-}" = --self-test ]; then
|
||||
t=$(mktemp -d); trap 'rm -rf "$t"' EXIT
|
||||
real=infra/build-server/remote-run.sh
|
||||
check "$real" >/dev/null || { echo "scratch-spare self-test: the real script FAILED the check"; exit 1; }
|
||||
sed -E '/^[[:space:]]*git clean /s/ "\$\{SPARE_ARGS\[@\]\}"//' "$real" > "$t/no-spare.sh"
|
||||
if check "$t/no-spare.sh" >/dev/null 2>&1; then echo "scratch-spare self-test: a clean without the spare arguments PASSED (the check is blind)"; exit 1; fi
|
||||
sed -E '/^[[:space:]]*git clean /s/-qfd /-qfdx /' "$real" > "$t/with-x.sh"
|
||||
if check "$t/with-x.sh" >/dev/null 2>&1; then echo "scratch-spare self-test: a clean with -x PASSED (the check is blind)"; exit 1; fi
|
||||
sed '/^spare_args()/,/^}/s/\.igneum-scratch-spare/.somewhere-else/' "$real" > "$t/no-file.sh"
|
||||
if check "$t/no-file.sh" >/dev/null 2>&1; then echo "scratch-spare self-test: a spare_args that ignores .igneum-scratch-spare PASSED (the check is blind)"; exit 1; fi
|
||||
sed -E "/^SPARE_FIXED=/s/'scratch-\*' //" "$real" > "$t/no-fixed.sh"
|
||||
if check "$t/no-fixed.sh" >/dev/null 2>&1; then echo "scratch-spare self-test: a fixed list without scratch-* PASSED (the check is blind)"; exit 1; fi
|
||||
echo "scratch-spare self-test: the real script passes; no spare arguments, -x, no spare file, and a missing fixed prefix each fail"; exit 0
|
||||
fi
|
||||
check infra/build-server/remote-run.sh
|
||||
Loading…
Reference in a new issue