#!/usr/bin/env bash # verify-fork-functions.sh — Verify that local forks retain ALL upstream functions. # # This is the FUNCTION-LEVEL verification that prevents the silent code-loss bug # that occurred when git merge conflict resolution dropped upstream functions # (kfdwrite, bulk_insert_files, resize, read_with_timeout, etc.) from the kernel fork. # # For each fork with an upstream remote, this script: # 1. Extracts all function definitions (fn ) from upstream source files # 2. Checks that each function exists in the local fork # 3. Reports any missing functions with file paths and line numbers # # This catches the class of bugs that file-level verification CANNOT detect: # when a file is touched by RB patches, file-level diff allows ANY difference, # including silently dropped upstream functions. # # Usage: # ./local/scripts/verify-fork-functions.sh # Check all forks # ./local/scripts/verify-fork-functions.sh kernel relibc # Check specific forks # ./local/scripts/verify-fork-functions.sh --no-fetch # Skip network fetch # ./local/scripts/verify-fork-functions.sh --quiet # Only print violations # # Exit codes: # 0 = All upstream functions present in all forks # 1 = One or more upstream functions are missing from forks set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" NO_FETCH=0 QUIET=0 declare -a TARGET_FORKS=() while [[ $# -gt 0 ]]; do case "$1" in --no-fetch) NO_FETCH=1 ;; --quiet) QUIET=1 ;; -h|--help) echo "Usage: $0 [--no-fetch] [--quiet] [fork1 fork2 ...]" echo "" echo "Verifies that local forks retain ALL upstream functions." echo "Catches silent code loss from bad merge conflict resolution." exit 0 ;; --*) echo "Unknown: $1" >&2; exit 1 ;; *) TARGET_FORKS+=("$1") ;; esac shift done cd "$PROJECT_ROOT" # Discover forks if not specified if [[ ${#TARGET_FORKS[@]} -eq 0 ]]; then while IFS= read -r line; do TARGET_FORKS+=("$line") done < <(for d in local/sources/*/; do [[ -d "$d/.git" ]] || continue name=$(basename "$d") git -C "$d" remote get-url upstream >/dev/null 2>&1 && echo "$name" done | sort) fi VIOLATIONS=0 TOTAL_MISSING=0 for fork in "${TARGET_FORKS[@]}"; do fork_dir="local/sources/${fork}" if [[ ! -d "$fork_dir/.git" ]]; then continue fi upstream_url=$(cd "$fork_dir" && git remote get-url upstream 2>/dev/null || true) if [[ -z "$upstream_url" ]]; then continue fi # Determine upstream branch upstream_branch="master" if ! (cd "$fork_dir" && git rev-parse --verify upstream/master >/dev/null 2>&1); then if (cd "$fork_dir" && git rev-parse --verify upstream/main >/dev/null 2>&1); then upstream_branch="main" else if [[ "$NO_FETCH" -eq 0 ]]; then (cd "$fork_dir" && git fetch upstream --depth=200 --quiet 2>/dev/null) || true fi if (cd "$fork_dir" && git rev-parse --verify upstream/master >/dev/null 2>&1); then upstream_branch="master" elif (cd "$fork_dir" && git rev-parse --verify upstream/main >/dev/null 2>&1); then upstream_branch="main" else continue fi fi fi # Fetch if needed if [[ "$NO_FETCH" -eq 0 ]]; then (cd "$fork_dir" && git fetch upstream --depth=200 --quiet 2>/dev/null) || true fi upstream_ref="upstream/$upstream_branch" # Get list of source files that exist in BOTH upstream and our fork upstream_files=$(cd "$fork_dir" && git ls-tree -r --name-only "$upstream_ref" 2>/dev/null | grep '\.rs$' | sort) local_files=$(cd "$fork_dir" && find . -name '*.rs' -not -path './.git/*' -not -path './target/*' -not -path './stage/*' | sed 's|^\./||' | sort) mapfile -t shared_files < <(comm -12 <(echo "$upstream_files") <(echo "$local_files")) fork_missing=0 declare -a missing_details=() for f in "${shared_files[@]}"; do # Extract function names from upstream version of the file upstream_fns=$(cd "$fork_dir" && git show "${upstream_ref}:${f}" 2>/dev/null | \ grep -oP '(?:pub )?(?:async )?(?:unsafe )?fn \w+' | \ sed 's/fn //' | sed 's/ *$//' | sort -u) [[ -z "$upstream_fns" ]] && continue # Extract function names from our version of the file local_fns=$(cd "$fork_dir" && grep -oP '(?:pub )?(?:async )?(?:unsafe )?fn \w+' "$f" 2>/dev/null | \ sed 's/fn //' | sed 's/ *$//' | sort -u) # Load per-fork exclusion list for intentionally removed/replaced functions exclude_file="${fork_dir}/.verify-fork-functions.exclude" declare -A excluded=() if [[ -f "$exclude_file" ]]; then while IFS= read -r line; do [[ "$line" =~ ^# ]] && continue [[ -z "$line" ]] && continue excluded["$line"]=1 done < "$exclude_file" fi # Find functions in upstream but not in our fork missing=$(comm -23 <(echo "$upstream_fns") <(echo "$local_fns") 2>/dev/null) if [[ -n "$missing" ]]; then while IFS= read -r fn; do [[ -z "$fn" ]] && continue # Check exclusion list: "$f:$fn" if [[ -n "${excluded["$f:$fn"]:-}" ]]; then [[ "$QUIET" -eq 0 ]] && missing_details+=(" $f: fn $fn → EXCLUDED (RB intentional)") continue fi # Strip modifiers to get bare function name for cross-file search bare_fn=$(echo "$fn" | sed -E 's/^(pub |async |unsafe )+//') found_elsewhere=$(cd "$fork_dir" && \ grep -rlP "(?:pub )?(?:async )?(?:unsafe )?fn ${bare_fn}\b" \ --include='*.rs' --exclude-dir='.git' --exclude-dir='target' --exclude-dir='stage' . 2>/dev/null | \ grep -v "^\./${f}$" | head -1 | sed 's|^\./||') if [[ -n "$found_elsewhere" ]]; then [[ "$QUIET" -eq 0 ]] && missing_details+=(" $f: fn $fn → MOVED to $found_elsewhere") else missing_details+=(" $f: fn $fn") fork_missing=$((fork_missing + 1)) TOTAL_MISSING=$((TOTAL_MISSING + 1)) fi done <<< "$missing" fi done if [[ "$fork_missing" -gt 0 ]]; then if [[ "$QUIET" -eq 0 ]] || [[ "$fork_missing" -gt 0 ]]; then echo "" echo -e "\033[1;31mFAIL: $fork is missing $fork_missing upstream function(s):\033[0m" for detail in "${missing_details[@]}"; do echo "$detail" done echo "" echo " These functions exist in upstream $upstream_ref but are MISSING from the local fork." echo " This likely means a bad merge or cherry-pick dropped them silently." echo " Fix: ./local/scripts/upgrade-forks.sh $fork" fi VIOLATIONS=$((VIOLATIONS + 1)) elif [[ "$QUIET" -eq 0 ]]; then echo -e "\033[1;32mOK: $fork — all upstream functions present\033[0m" fi done echo "" if [[ "$VIOLATIONS" -gt 0 ]]; then echo -e "\033[1;31mFUNCTION VERIFICATION FAILED: $VIOLATIONS fork(s) missing $TOTAL_MISSING upstream function(s).\033[0m" exit 1 fi echo -e "\033[1;32mAll forks retain all upstream functions.\033[0m" exit 0