7d134eef37
Context: A relibc fork check via cargo compare exposed that the fork labeled 0.6.0 (matching a 2020-12-23 upstream tag) was on a completely different codebase from upstream master. The fork was imported as a snapshot. The verify-fork-versions.sh tool allowed this because 'snapshot' mode skipped byte-for-byte content comparison. Changes: 1. Remove 'snapshot = skip content check' behavior. All Cat 2 forks must pass actual content comparison against their claimed upstream tag. 2. Add base to local/fork-upstream-map.toml so it's checked too (was missing). Effect: Future 'fake version label' divergences (Cargo.toml says version X but source content does not match upstream X) will be caught by the preflight check instead of slipping through build.
199 lines
7.8 KiB
Bash
Executable File
199 lines
7.8 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
|
|
[ -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
|
|
|
|
# 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"
|
|
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
|