build system: remove working-tree stashing (data-loss risk); pre-cook CI=1; target-scoped diagnostics

Remove the stash-and-restore machinery entirely. It manipulated the operator's
working tree and had a fatal bug: modern git's 'stash push' does not print the
stash SHA on stdout, so the SHA was never recorded, the stash was never restored,
and with REDBEAR_ALLOW_DIRTY=1 the operator's dirty-fork WIP was silently
stranded in 'git stash list' on every build (base had accumulated 25 strands).
The build now cooks committed HEAD, or the working tree AS-IS under
REDBEAR_ALLOW_DIRTY=1 — it never touches the tree. A read-only startup advisory
surfaces any leftover redbear-build-* strands from the old code.

Recovered the valuable stranded work to local/recovered-stashes/ (netstack
proptest, relibc get_dns_server daemon-path + getnetbyaddr impl); originals
remain in each fork's git stash list. See local/recovered-stashes/README.md.

Also: pre-cook now runs 'repo cook' with CI=1 (matches make live), fixing the
'Entering raw terminal mode ... Inappropriate ioctl' noise; and the failure
diagnostics per-recipe dump is scoped to THIS build's artifacts (mtime >= build
start) so a bare/mini build no longer lists graphical packages left over from a
prior redbear-full build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-27 12:59:08 +09:00
parent d41c0fd163
commit 6fc0366abb
6 changed files with 798 additions and 97 deletions
+35 -97
View File
@@ -9,7 +9,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# REDBEAR_VERSION (which tracks the OS release derived from the git branch).
# Starts at 1.0 and is bumped AUTOMATICALLY on every change by the pre-commit
# git hook (local/scripts/bump-build-version.sh); do not edit the minor by hand.
BUILD_REDBEAR_VERSION="1.1"
BUILD_REDBEAR_VERSION="1.2"
# ── Colorized output ──────────────────────────────────
# Enabled only on a TTY with NO_COLOR unset, so redirected build logs and CI
@@ -78,13 +78,6 @@ export REDBEAR_BUILD_STATE_DIR="$(mktemp -d -t redbear-build-state.XXXXXX)"
REDBEAR_BUILD_LOGS_DIR="$REDBEAR_BUILD_STATE_DIR/logs"
mkdir -p "$REDBEAR_BUILD_LOGS_DIR"
# Stash-and-restore: every Red Bear fork source whose working tree is dirty
# is stashed before the build runs, then popped on EXIT (success or failure).
# This ensures pkgar fingerprints always reflect the committed HEAD, not
# transient WIP that could be rolled back between builds.
declare -ga REDBEAR_STASHED_REPOS=()
declare -gA REDBEAR_STASH_MAP=()
declare -ga REDBEAR_FORK_SOURCES=(
"relibc:$PROJECT_ROOT/local/sources/relibc"
"kernel:$PROJECT_ROOT/local/sources/kernel"
@@ -152,87 +145,6 @@ redbear_check_fork_branches() {
fi
}
redbear_stash_one() {
local label="$1"
local dir="$2"
if [ -n "${REDBEAR_STASH_MAP[$label]:-}" ]; then
return 0
fi
if ! redbear_is_fork_dirty "$dir"; then
echo " [clean] $label"
return 0
fi
rm -f "$dir/.git/index.lock"
local msg="redbear-build-$REDBEAR_BUILD_START_EPOCH-$label"
local sha
local stash_output
stash_output=$(git -C "$dir" stash push --include-untracked -m "$msg" 2>/dev/null)
sha=$(echo "$stash_output" | grep -E '^[0-9a-f]{40}$' | head -1)
if [ -n "$sha" ]; then
REDBEAR_STASH_MAP[$label]="$sha"
REDBEAR_STASHED_REPOS+=("$label:$dir")
echo " [stashed] $label: $sha"
else
# Stash produced no SHA — either nothing to stash or locale issue.
# If the fork is now clean, treat as success.
if ! redbear_is_fork_dirty "$dir"; then
echo " [clean] $label (stash noop)"
return 0
fi
echo " [FAIL] $label: git stash push failed" >&2
return 1
fi
}
redbear_unstash_one() {
local label="$1"
local dir="$2"
local sha="${REDBEAR_STASH_MAP[$label]:-}"
if [ -z "$sha" ]; then
return 0
fi
rm -f "$dir/.git/index.lock"
if git -C "$dir" stash pop --quiet 2>/dev/null; then
echo " [restored] $label"
else
echo " [WARN] $label: stash pop failed — stash preserved at $sha" >&2
echo " Run: git -C '$dir' stash pop to restore manually" >&2
fi
unset REDBEAR_STASH_MAP[$label]
}
redbear_stash_all_dirty_forks() {
echo "${C_INFO}>>>${C_RESET} Stashing dirty fork sources (restored at exit)..."
local any_dirty=0
local entry label dir
for entry in "${REDBEAR_FORK_SOURCES[@]}"; do
label="${entry%%:*}"
dir="${entry#*:}"
[ -d "$dir" ] || continue
redbear_is_fork_dirty "$dir" && any_dirty=1
redbear_stash_one "$label" "$dir" || {
echo "ERROR: failed to stash $label — aborting to avoid cooking from dirty tree" >&2
redbear_unstash_all_forks "stash-failure"
exit 1
}
done
if [ "$any_dirty" = "0" ]; then
echo " (all fork sources clean)"
fi
return 0
}
redbear_unstash_all_forks() {
local reason="${1:-exit}"
[ "${#REDBEAR_STASHED_REPOS[@]}" -eq 0 ] && return 0
echo "${C_INFO}>>>${C_RESET} Restoring stashed fork sources (reason: $reason)..."
local i
for ((i=${#REDBEAR_STASHED_REPOS[@]}-1; i>=0; i--)); do
local entry="${REDBEAR_STASHED_REPOS[$i]}"
redbear_unstash_one "${entry%%:*}" "${entry#*:}"
done
}
# Cookbook binary fingerprint: BLAKE3-hash (via sha256sum on this system)
# of all .rs files in src/. Used to detect when the cookbook needs to be
# rebuilt. The hash is written to target/release/.cookbook-src-fingerprint
@@ -477,18 +389,30 @@ if [ -z "${REDBEAR_RELEASE:-}" ]; then
echo ""
fi
redbear_stash_all_dirty_forks
# The build system no longer stashes your working tree (it cooks committed HEAD,
# or your tree AS-IS under REDBEAR_ALLOW_DIRTY=1). A prior version stashed dirty
# forks and, due to a git-output-parsing bug, sometimes failed to restore them,
# leaving "redbear-build-*" stashes behind. Surface any leftovers read-only so
# stranded WIP is never silently forgotten. This NEVER touches your tree.
_rb_leftover_total=0
for _entry in "${REDBEAR_FORK_SOURCES[@]}"; do
_d="${_entry#*:}"
[ -d "$_d/.git" ] || continue
_n=$(git -C "$_d" stash list 2>/dev/null | grep -c "redbear-build-" || true)
if [ "${_n:-0}" -gt 0 ]; then
echo "${C_WARN}>>> NOTE:${C_RESET} ${_entry%%:*}: $_n leftover 'redbear-build-*' stash(es) from an older build — recover: git -C '$_d' stash list" >&2
_rb_leftover_total=$((_rb_leftover_total + _n))
fi
done
[ "$_rb_leftover_total" -gt 0 ] && echo "${C_WARN}>>> NOTE:${C_RESET} $_rb_leftover_total stranded build-stash(es) total; see local/recovered-stashes/README.md" >&2
# EXIT trap: always restore stashes (success or failure), always dump
# diagnostics, always preserve the original exit code. The trap fires
# EXIT trap: always dump diagnostics and preserve the original exit code. The trap fires
# even on signals (SIGINT/SIGTERM/SIGHUP) because we don't override
# ERR/DEBUG and we use EXIT (the POSIX-standard pseudo-signal that fires
# for any exit path). The trap runs LAST in the shell's exit order, so
# any explicit `exit N` we do inside the script triggers it with $? = N.
redbear_exit_trap() {
local rc=$?
# Phase 1: unstash so the operator's working tree is restored.
redbear_unstash_all_forks "exit"
# Phase 2: dump failure diagnostics if the build failed.
if [ "$rc" -ne 0 ]; then
redbear_dump_failure_diagnostics "$rc"
@@ -552,7 +476,14 @@ redbear_dump_failure_diagnostics() {
# vs which have only stage.tmp (in-progress).
echo ""
echo "--- Per-recipe build state ---"
# Scope to THIS build only. The dump walks the whole recipe tree, so without
# a time filter a bare/mini build would list graphical packages (mesa, qt,
# kf6-*, sddm, ...) whose stage/stage.tmp are leftovers from an earlier
# redbear-full build — packages not even part of the target being built.
# Report an artifact only if it was modified during this build.
local recipe_dir target_arch
local _rb_start="${REDBEAR_BUILD_START_EPOCH:-0}"
local _rb_shown=0
for recipe_dir in "$PROJECT_ROOT"/recipes/*/ "$PROJECT_ROOT"/local/recipes/*/*/; do
[ -d "$recipe_dir" ] || continue
[ -f "$recipe_dir/recipe.toml" ] || continue
@@ -561,17 +492,24 @@ redbear_dump_failure_diagnostics() {
for target_arch in "$PROJECT_ROOT"/recipes/"$pkg"/target/*/ \
"$PROJECT_ROOT"/local/recipes/*/"$pkg"/target/*/; do
[ -d "$target_arch" ] || continue
local stage stage_tmp size
local stage stage_tmp size ts
stage="$target_arch/stage"
stage_tmp="$target_arch/stage.tmp"
if [ -d "$stage_tmp" ]; then
ts=$(stat -c %Y "$stage_tmp" 2>/dev/null || echo 0)
[ "$ts" -ge "$_rb_start" ] || continue
size=$(du -sh "$stage_tmp" 2>/dev/null | cut -f1)
echo " $pkg [$target_arch]: INCOMPLETE (stage.tmp: $size)"
_rb_shown=1
elif [ -d "$stage" ]; then
echo " $pkg [$target_arch]: complete"
ts=$(stat -c %Y "$stage" 2>/dev/null || echo 0)
[ "$ts" -ge "$_rb_start" ] || continue
echo " $pkg [$target_arch]: complete (this build)"
_rb_shown=1
fi
done
done
[ "$_rb_shown" = "0" ] && echo " (no recipe artifacts were modified during this build)"
echo ""
echo "========================================"
@@ -1023,7 +961,7 @@ for pkg in $PRECOOK_PKGS; do
if [ -n "${REDBEAR_RELEASE:-}" ]; then
PRECOOK_OFFLINE=true
fi
if ! REDBEAR_BUILD_PHASE=pre-cook COOKBOOK_OFFLINE="$PRECOOK_OFFLINE" \
if ! REDBEAR_BUILD_PHASE=pre-cook CI=1 COOKBOOK_OFFLINE="$PRECOOK_OFFLINE" \
"$PROJECT_ROOT/target/release/repo" cook "$pkg" >"$log" 2>&1; then
echo "WARNING: pre-cook of $pkg failed (non-fatal; will build during main phase). Tail of repo log:" >&2
tail -50 "$log" >&2