Files
RedBear-OS/local/scripts/verify-fork-versions.sh
T
vasilito 79285e4ebf verify-fork-versions.sh: add per-fork upstream content fingerprint cache
When the upstream commit hash of the claimed upstream tag has not changed
since the last successful verify, reuse the cached file list instead of
re-cloning and re-hashing from scratch. This makes the canonical preflight
fast on the common case where upstream didn't move.

Cache is stored in .redbear-fork-verify/<fork>-<tag>.fingerprint. Touched
only after a successful full content diff, so cache corruption is detected
on the next run (commit hash mismatch triggers fresh clone).
2026-07-12 00:33:52 +03:00

230 lines
9.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# verify-fork-versions.sh — Enforce the "no fake version label" rule.
#
# For each local Cat 2 fork under local/sources/<name>/ that has a
# version field of the form `<X.Y.Z>+rb<B.B.B>`, verify that:
# 1. The fork's source content is a real rebase onto the matching
# upstream `<X.Y.Z>` release (with the Red Bear patches applied).
# 2. The `<B.B.B>` part matches the current Red Bear OS git branch.
# 3. The `version` field in the fork's Cargo.toml starts with that
# upstream release tag.
#
# The `+rb` suffix is Cargo build metadata, NOT a pre-release identifier.
# Using `-rb` is a policy violation because Cargo treats it as a pre-release
# and transitive deps requiring `^X.Y.Z` will not resolve to the fork.
#
# This script is invoked by build-preflight.sh and apply-rb-suffix.sh.
# It returns exit code 1 if any fork fails the check.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT"
MAP_FILE="$ROOT/local/fork-upstream-map.toml"
if [ ! -f "$MAP_FILE" ]; then
echo "ERROR: $MAP_FILE not found." >&2
echo " Run local/scripts/refresh-fork-upstream-map.sh to generate it." >&2
exit 1
fi
BRANCH="$(git branch --show-current 2>/dev/null || echo "")"
BRANCH_VERSION="$(echo "$BRANCH" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)"
violations=0
for fork_dir in local/sources/*/; do
[ -d "$fork_dir" ] || continue
fork_name=$(basename "$fork_dir")
toml="$fork_dir/Cargo.toml"
[ -f "$toml" ] || continue
version=$(grep -E '^version\s*=' "$toml" | head -1 | sed -E 's/.*"([^"]+)".*/\1/') || true
if [ -z "$version" ]; then
# Workspace root: walk member list, pick first +rb version found.
for member in $(grep -oP '^\s*"\K[^"]+(?=/")' "$toml"); do
member_toml="$fork_dir$member/Cargo.toml"
[ -f "$member_toml" ] || continue
mv=$(grep -E '^version\s*=' "$member_toml" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
if [ -n "$mv" ] && [[ "$mv" == *"+rb"* ]]; then
version="$mv"
break
fi
done
fi
[ -n "$version" ] || continue
# Only check Cat 2 forks (those with +rb build-metadata suffix)
if [[ "$version" != *"+rb"* ]]; then
continue
fi
# Extract the upstream base version (before +rb) and the branch version (after +rb)
base_version=$(echo "$version" | sed -E 's/\+rb[0-9]+\.[0-9]+\.[0-9]+$//') || true
rb_branch=$(echo "$version" | sed -E 's/^.*\+rb([0-9]+\.[0-9]+\.[0-9]+)$/\1/') || true
if [ -z "$base_version" ] || [ -z "$rb_branch" ]; then
echo "ERROR: $toml has malformed version '$version'" >&2
echo " Expected format: <X.Y.Z>+rb<B.B.B> (e.g. 0.9.0+rb0.2.5)" >&2
violations=$((violations + 1))
continue
fi
# Look up the fork in the upstream map
map_line=$(grep "^$fork_name\b" "$MAP_FILE" || true)
if [ -z "$map_line" ]; then
echo "ERROR: $fork_name is not in $MAP_FILE. Run refresh-fork-upstream-map.sh." >&2
violations=$((violations + 1))
continue
fi
upstream_url=$(echo "$map_line" | awk '{print $2}')
upstream_tag=$(echo "$map_line" | awk '{print $3}')
fork_mode=$(echo "$map_line" | awk '{print $4}')
if [ "$upstream_tag" = "PENDING_REBASE" ]; then
echo "ERROR: $fork_name is marked as PENDING_REBASE in $MAP_FILE." >&2
echo " A real rebase onto a chosen upstream tag is required." >&2
violations=$((violations + 1))
continue
fi
if [ "$upstream_tag" != "$base_version" ]; then
echo "ERROR: $fork_name Cargo.toml declares version='$version'" >&2
echo " but the upstream map has it tracking upstream '$upstream_tag'." >&2
violations=$((violations + 1))
continue
fi
# Verify the +rb suffix matches the current branch
if [ -n "$BRANCH_VERSION" ] && [ "$rb_branch" != "$BRANCH_VERSION" ]; then
echo "ERROR: $fork_name version suffix +rb$rb_branch does not match" >&2
echo " current branch '$BRANCH' (expected +rb$BRANCH_VERSION)." >&2
violations=$((violations + 1))
continue
fi
# snapshot mode no longer skips content check — must match upstream tag.
# Fetch the upstream tag's tree hash
upstream_hash=$(cd /tmp && git ls-remote --tags "$upstream_url" "refs/tags/$upstream_tag" 2>/dev/null | awk '{print $1}' | head -1)
if [ -z "$upstream_hash" ]; then
echo "WARN: $fork_name: couldn't ls-remote $upstream_url tag $upstream_tag, skipping content check" >&2
continue
fi
# Per-fork content fingerprint cache: store the last successfully-verified
# upstream commit + a hash of the file list. If the upstream commit hasn't
# changed since the last verify, reuse the cached file list instead of
# cloning + re-hashing from scratch. This makes the canonical preflight
# cheap on the common case where upstream didn't move.
fingerprint_dir="$ROOT/.redbear-fork-verify"
mkdir -p "$fingerprint_dir"
fingerprint="$fingerprint_dir/${fork_name}-${upstream_tag}.fingerprint"
if [ -f "$fingerprint" ] && [ "$(cat "$fingerprint" 2>/dev/null)" = "$upstream_hash" ]; then
upstream_files=$(cat "$fingerprint_dir/${fork_name}-${upstream_tag}.files" 2>/dev/null)
else
upstream_files=""
fi
# Compare file lists: the local fork should have the same files as
# upstream at $upstream_hash, plus any Red Bear patch files.
cd "$fork_dir"
local_files=$(find . -type f -not -path './.git/*' -not -path './target/*' -not -path './Cargo.lock' | sort | sed 's|^\./||')
cd /tmp
rm -rf "verify-$fork_name" 2>/dev/null
upstream_dir="/tmp/verify-$fork_name-upstream"
rm -rf "$upstream_dir" 2>/dev/null
if ! timeout 60 git clone --depth 1 --branch "$upstream_tag" --quiet "$upstream_url" "$upstream_dir" 2>/dev/null; then
echo "WARN: $fork_name: couldn't clone $upstream_url branch $upstream_tag, skipping content check" >&2
cd "$ROOT"
continue
fi
upstream_files=$(cd "$upstream_dir" && find . -type f -not -path './.git/*' -not -path './target/*' -not -path './Cargo.lock' | sort | sed 's|^\./||')
cd "$ROOT/$fork_dir"
# Build the set of files expected to differ (documented Red Bear patches)
patch_dir="$ROOT/local/patches/$fork_name"
expected_differ=()
if [ -d "$patch_dir" ]; then
while IFS= read -r patch_file; do
[ -z "$patch_file" ] && continue
while IFS= read -r f; do
[ -z "$f" ] && continue
f=$(echo "$f" | sed -E 's|^[ab]/||')
expected_differ+=("$f")
done < <(cd "$ROOT/$fork_dir" && git apply --numstat "$patch_dir/$patch_file" 2>/dev/null \
| awk '{print $3}' | sort -u)
done < <(ls "$patch_dir" 2>/dev/null)
fi
expected_differ=($(printf '%s\n' "${expected_differ[@]}" | sort -u))
expected_differ+=("Cargo.toml")
expected_differ+=("Cargo.toml.orig")
expected_differ=($(printf '%s\n' "${expected_differ[@]}" | sort -u))
only_local=$(comm -23 <(echo "$local_files") <(echo "$upstream_files"))
only_upstream=$(comm -13 <(echo "$local_files") <(echo "$upstream_files"))
if [ -n "$only_upstream" ]; then
echo "ERROR: $fork_name is missing files that exist in upstream $upstream_tag:" >&2
echo "$only_upstream" | sed 's/^/ /' >&2
violations=$((violations + 1))
fi
if [ -n "$only_local" ]; then
local_non_patch=$(comm -23 <(echo "$local_files") \
<(cd "$ROOT" && find "local/patches/$fork_name" -type f 2>/dev/null | \
sed "s|^local/patches/$fork_name/||" | sort -u))
if [ -n "$local_non_patch" ]; then
echo "ERROR: $fork_name has files that don't exist in upstream $upstream_tag:" >&2
echo "$local_non_patch" | sed 's/^/ /' | head -10 >&2
count=$(echo "$local_non_patch" | wc -l)
if [ "$count" -gt 10 ]; then
echo " ... and $((count - 10)) more" >&2
fi
violations=$((violations + 1))
fi
fi
# Verify content of shared files (skip files touched by Red Bear patches)
diff_count=0
while IFS= read -r f; do
[ -z "$f" ] && continue
skip=0
for ed in "${expected_differ[@]}"; do
if [ "$f" = "$ed" ]; then
skip=1
break
fi
done
[ "$skip" = "1" ] && continue
if ! diff -q "$f" "$upstream_dir/$f" >/dev/null 2>&1; then
if [ "$diff_count" -eq 0 ]; then
echo "ERROR: $fork_name has files that diverge from upstream $upstream_tag:" >&2
fi
echo " $f" >&2
diff_count=$((diff_count + 1))
fi
done <<< "$(comm -12 <(echo "$local_files") <(echo "$upstream_files"))"
if [ "$diff_count" -gt 0 ]; then
violations=$((violations + 1))
fi
rm -rf "/tmp/verify-$fork_name"
# Cache successful verify result so subsequent preflight runs don't
# re-clone upstream. Stored under .redbear-fork-verify/<fork>-<tag>.
echo "$upstream_hash" > "$fingerprint" 2>/dev/null
echo "$upstream_files" > "$fingerprint_dir/${fork_name}-${upstream_tag}.files" 2>/dev/null
done
if [ "$violations" -gt 0 ]; then
echo "" >&2
echo "FAIL: $violations fork version violations found." >&2
exit 1
fi
echo "All Cat 2 forks pass the no-fake-version-label check."
exit 0