b66a9e509e
The local bootloader fork claims to be upstream 1.0.0 + Red Bear
patches, but the patches in local/patches/bootloader/ do NOT
apply cleanly to upstream 1.0.0. The fork was originally a 0.1.0
baseline with substantial additions that pre-date the upstream-1.0.0
refactor.
Changes:
* local/fork-upstream-map.toml: set bootloader's upstream tag to
PENDING_REBASE. The verifier recognises this as a deliberate
state marker (a fork whose rebase is in progress) and refuses
the build with a clear error pointing the user to the rebase
procedure documented in the map.
* local/scripts/verify-fork-versions.sh: when a fork is marked
PENDING_REBASE in the map, the script reports a dedicated error
message instead of running the upstream content comparison (which
would always fail for a fork in this state).
The current state of the build is:
* 5 of 6 `-rb1` Cat 2 forks pass the no-fake-version-label
check (redoxfs, redox-scheme, kernel, installer, userutils).
* bootloader refuses to build until a real rebase onto a chosen
upstream tag is completed (the patches must apply cleanly with
--fuzz=0).
* installer has additional divergent content that may need a
rebase too (separate operational task).
This is the strict enforcement the user asked for. The build
cannot proceed silently with fake labels. The user must drive
the rebase work for bootloader (and installer) following the
procedure in fork-upstream-map.toml.
229 lines
10 KiB
Bash
Executable File
229 lines
10 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<N>`, 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 `version` field in the fork's Cargo.toml starts with that
|
|
# upstream release tag.
|
|
#
|
|
# This script is invoked by build-preflight.sh and apply-rb-suffix.sh.
|
|
# It returns exit code 1 if any fork fails the check, with a clear
|
|
# error message identifying the fork and the specific mismatch.
|
|
|
|
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
|
|
|
|
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
|
|
[ -n "$version" ] || continue
|
|
|
|
# Only check Cat 2 forks (those with -rb suffix)
|
|
if [[ "$version" != *"-rb"* ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Extract the base version (before -rb) and the rb counter
|
|
base_version=$(echo "$version" | sed -E 's/-rb[0-9]+$//') || true
|
|
rb_n=$(echo "$version" | sed -E 's/^.*-rb([0-9]+)$/\1/') || true
|
|
|
|
if [ -z "$base_version" ] || [ -z "$rb_n" ]; then
|
|
# If the base_version came out empty (e.g. version field is
|
|
# blank), we already detected & skipped in the empty-version
|
|
# case above. Reaching this branch with empty outputs means
|
|
# the version string is malformed (e.g. ends in `-rb` with
|
|
# no number). That is a fake-label failure.
|
|
echo "ERROR: $toml has malformed version '$version' (must be <X.Y.Z>-rb<N>)" >&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}')
|
|
|
|
# If the upstream tag is `PENDING_REBASE` (used to mark a fork
|
|
# whose rebase is in progress), the build cannot proceed. Refuse
|
|
# with a clear error pointing the user to the rebase work.
|
|
if [ "$upstream_tag" = "PENDING_REBASE" ]; then
|
|
echo "ERROR: $fork_name is marked as PENDING_REBASE in $MAP_FILE." >&2
|
|
echo " The local source does not match any single upstream" >&2
|
|
echo " release. A real rebase onto a chosen upstream tag is" >&2
|
|
echo " required. See the comment next to '$fork_name' in" >&2
|
|
echo " $MAP_FILE for the rebase procedure." >&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
|
|
echo " Either update the version field or update the map." >&2
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
|
|
# 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
|
|
|
|
# Compare file lists: the local fork should have the same files as
|
|
# upstream at $upstream_hash, plus any Red Bear patch files in
|
|
# local/patches/$fork_name/.
|
|
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
|
|
# Shallow-clone the specific tag directly. `--depth 1 --branch <tag>`
|
|
# makes the tag the initial HEAD, so we have the file tree without
|
|
# having to fetch a specific commit (which `git clone --depth 1`
|
|
# can't reach on shallow clones because the commit is past the
|
|
# shallow boundary).
|
|
upstream_dir="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
|
|
cd "$upstream_dir"
|
|
upstream_files=$(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 that are EXPECTED to differ from upstream
|
|
# because of documented Red Bear patches. A file is "expected to
|
|
# differ" if it is touched by a patch in local/patches/$fork_name/.
|
|
# The verifier EXCLUDES these from the content diff so that a
|
|
# well-structured fork (with documented Red Bear patches) passes
|
|
# verification.
|
|
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
|
|
# `git apply --stat` lists files touched by the patch.
|
|
# Use the local fork's working-tree state to interpret the
|
|
# patch paths (since they are relative to the fork root).
|
|
while IFS= read -r f; do
|
|
[ -z "$f" ] && continue
|
|
# Normalize: strip leading 'a/' or 'b/' if present
|
|
f=$(echo "$f" | sed -E 's|^[ab]/||')
|
|
expected_differ+=("$f")
|
|
done < <(cd "$ROOT/$fork_dir" && git apply --stat "$patch_dir/$patch_file" 2>/dev/null \
|
|
| tail -n +3 | head -n -2 | awk '{print $1}' | sort -u)
|
|
done < <(ls "$patch_dir" 2>/dev/null)
|
|
fi
|
|
# Deduplicate
|
|
expected_differ=($(printf '%s\n' "${expected_differ[@]}" | sort -u))
|
|
# Always expect Cargo.toml and Cargo.toml.orig to differ (version field
|
|
# bump and auto-generated Cargo.toml are Red Bear bookkeeping).
|
|
expected_differ+=("Cargo.toml")
|
|
expected_differ+=("Cargo.toml.orig")
|
|
expected_differ=($(printf '%s\n' "${expected_differ[@]}" | sort -u))
|
|
|
|
# Files in local but not in upstream: these are the Red Bear additions
|
|
# (tracked by the local fork) OR untracked working-tree files (which
|
|
# should not be present). Files in upstream but not in local: these
|
|
# are missing patches (unacceptable).
|
|
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
|
|
# Filter out files that are EXPECTED to be absent (e.g. deleted
|
|
# by a Red Bear patch). For now, we treat all upstream-only files
|
|
# as errors — a future enhancement would parse the patch diffs
|
|
# to find deletions.
|
|
echo "ERROR: $fork_name is missing files that exist in upstream $upstream_tag:" >&2
|
|
echo "$only_upstream" | sed 's/^/ /' >&2
|
|
echo " This fork claims to be '$upstream_tag' but is missing source." >&2
|
|
violations=$((violations + 1))
|
|
fi
|
|
|
|
if [ -n "$only_local" ]; then
|
|
# Filter out files in local/patches/$fork_name/ (patches
|
|
# themselves, README, etc.) — those are Red Bear bookkeeping,
|
|
# not source files.
|
|
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
|
|
echo " These must be deleted or moved to local/patches/$fork_name/ as documented Red Bear patches." >&2
|
|
violations=$((violations + 1))
|
|
fi
|
|
fi
|
|
|
|
# Verify content of shared files. Skip files in expected_differ
|
|
# (files touched by documented Red Bear patches).
|
|
diff_count=0
|
|
while IFS= read -r f; do
|
|
[ -z "$f" ] && continue
|
|
# Skip if this file is in expected_differ
|
|
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
|
|
echo " These must either be re-rebased onto $upstream_tag OR" >&2
|
|
echo " moved to local/patches/$fork_name/ as documented Red Bear patches." >&2
|
|
violations=$((violations + 1))
|
|
fi
|
|
|
|
rm -rf "/tmp/verify-$fork_name"
|
|
done
|
|
|
|
if [ "$violations" -gt 0 ]; then
|
|
echo "" >&2
|
|
echo "FAIL: $violations fork version violations found." >&2
|
|
echo " Run local/scripts/refresh-fork-upstream-map.sh and" >&2
|
|
echo " local/scripts/apply-rb-suffix.sh to fix the offending forks." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "All Cat 2 forks pass the no-fake-version-label check."
|
|
exit 0
|