Files
RedBear-OS/local/scripts/check-fork-drift.sh
T
vasilito 4e88d1915f build system: add upstream drift detection and fork upgrade tooling
- check-fork-drift.sh: compares each submodule HEAD against upstream/master,
  reports behind/ahead counts, exits 1 if any fork exceeds threshold
- upgrade-forks.sh: fetches latest upstream, saves RB commits, resets to
  upstream, cherry-picks RB patches with conflict handling and backups
- build-preflight.sh: wired drift check into preflight (threshold=50,
  suppressible via REDBEAR_SKIP_DRIFT_CHECK=1)
- bootloader submodule pointer updated to latest upstream rebase
2026-07-11 11:20:29 +03:00

166 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# check-fork-drift.sh — Detect upstream drift in Red Bear OS local forks.
#
# For each submodule in local/sources/, fetches upstream and reports:
# - Commits behind upstream (new upstream commits we don't have)
# - Commits ahead of upstream (our Red Bear patches)
# - Upstream HEAD date vs our HEAD date
#
# Exits 1 if any fork is behind upstream beyond the threshold.
#
# Usage:
# ./local/scripts/check-fork-drift.sh # Report all
# ./local/scripts/check-fork-drift.sh --threshold=20 # Fail if any fork >20 behind
# ./local/scripts/check-fork-drift.sh --fetch-depth=100 # Deeper fetch (default: 200)
# ./local/scripts/check-fork-drift.sh --no-fetch # Skip network fetch (use cached refs)
# ./local/scripts/check-fork-drift.sh --json Machine-readable output
# ./local/scripts/check-fork-drift.sh --quiet Only print violations
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
THRESHOLD=10
FETCH_DEPTH=200
NO_FETCH=0
JSON=0
QUIET=0
while [[ $# -gt 0 ]]; do
case "$1" in
--threshold=*) THRESHOLD="${1#*=}" ;;
--fetch-depth=*) FETCH_DEPTH="${1#*=}" ;;
--no-fetch) NO_FETCH=1 ;;
--json) JSON=1 ;;
--quiet) QUIET=1 ;;
-h|--help)
echo "Usage: $0 [--threshold=N] [--fetch-depth=N] [--no-fetch] [--json] [--quiet]"
exit 0
;;
*) echo "Unknown: $1" >&2; exit 1 ;;
esac
shift
done
cd "$PROJECT_ROOT"
# Submodules that have upstream remotes
declare -a FORKS=()
declare -A FORK_PATHS
while IFS= read -r line; do
name=$(basename "$line")
FORKS+=("$name")
FORK_PATHS["$name"]="$line"
done < <(find local/sources -maxdepth 1 -mindepth 1 -type d -o -type l 2>/dev/null | sort)
if [[ ${#FORKS[@]} -eq 0 ]]; then
echo "No local forks found under local/sources/" >&2
exit 0
fi
VIOLATIONS=0
declare -a JSON_LINES=()
if [[ "$JSON" -eq 0 && "$QUIET" -eq 0 ]]; then
printf "%-16s %-8s %-8s %-12s %-12s %s\n" "FORK" "BEHIND" "AHEAD" "UPSTREAM" "OUR_HEAD" "STATUS"
printf "%-16s %-8s %-8s %-12s %-12s %s\n" "----" "-------" "------" "--------" "--------" "------"
fi
for fork in "${FORKS[@]}"; do
fork_dir="${FORK_PATHS[$fork]}"
if [[ ! -d "$fork_dir/.git" ]]; then
continue
fi
upstream_remote=$(cd "$fork_dir" && git remote get-url upstream 2>/dev/null || true)
if [[ -z "$upstream_remote" ]]; then
continue
fi
# Determine upstream branch (master or main)
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
# Need to fetch first
if [[ "$NO_FETCH" -eq 0 ]]; then
(cd "$fork_dir" && git fetch upstream --depth="$FETCH_DEPTH" --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="$FETCH_DEPTH" --quiet 2>/dev/null) || true
fi
ref="upstream/$upstream_branch"
behind=$(cd "$fork_dir" && git log --oneline "HEAD..$ref" 2>/dev/null | wc -l)
ahead=$(cd "$fork_dir" && git log --oneline "$ref..HEAD" 2>/dev/null | wc -l)
upstream_sha=$(cd "$fork_dir" && git rev-parse --short "$ref" 2>/dev/null || echo "?")
our_sha=$(cd "$fork_dir" && git rev-parse --short HEAD 2>/dev/null || echo "?")
upstream_date=$(cd "$fork_dir" && git log -1 --format='%ci' "$ref" 2>/dev/null | cut -d' ' -f1 || echo "?")
our_date=$(cd "$fork_dir" && git log -1 --format='%ci' HEAD 2>/dev/null | cut -d' ' -f1 || echo "?")
status="OK"
if [[ "$behind" -gt "$THRESHOLD" ]]; then
status="DRIFT"
VIOLATIONS=$((VIOLATIONS + 1))
elif [[ "$behind" -gt 0 ]]; then
status="MINOR"
fi
if [[ "$JSON" -eq 1 ]]; then
json_line=$(printf '{"fork":"%s","behind":%d,"ahead":%d,"upstream_sha":"%s","our_sha":"%s","upstream_date":"%s","our_date":"%s","status":"%s"}' \
"$fork" "$behind" "$ahead" "$upstream_sha" "$our_sha" "$upstream_date" "$our_date" "$status")
JSON_LINES+=("$json_line")
elif [[ "$QUIET" -eq 0 ]] || [[ "$status" != "OK" ]]; then
if [[ "$status" == "DRIFT" ]]; then
printf "%-16s \033[1;31m%-8s\033[0m %-8s %-12s %-12s %s\n" "$fork" "$behind" "$ahead" "$upstream_sha" "$our_sha" "$status"
elif [[ "$status" == "MINOR" ]]; then
printf "%-16s \033[1;33m%-8s\033[0m %-8s %-12s %-12s %s\n" "$fork" "$behind" "$ahead" "$upstream_sha" "$our_sha" "$status"
else
printf "%-16s \033[1;32m%-8s\033[0m %-8s %-12s %-12s %s\n" "$fork" "$behind" "$ahead" "$upstream_sha" "$our_sha" "$status"
fi
fi
done
if [[ "$JSON" -eq 1 ]]; then
echo "["
for i in "${!JSON_LINES[@]}"; do
if [[ $i -lt $((${#JSON_LINES[@]} - 1)) ]]; then
echo " ${JSON_LINES[$i]},"
else
echo " ${JSON_LINES[$i]}"
fi
done
echo "]"
else
echo ""
if [[ "$VIOLATIONS" -gt 0 ]]; then
echo -e "\033[1;31mDRIFT DETECTED: $VIOLATIONS fork(s) exceed threshold ($THRESHOLD commits behind upstream).\033[0m"
echo ""
echo "To upgrade forks to latest upstream:"
echo " ./local/scripts/upgrade-forks.sh"
echo ""
echo "To suppress this check in builds:"
echo " REDBEAR_SKIP_DRIFT_CHECK=1"
else
echo -e "\033[1;32mAll forks within drift threshold ($THRESHOLD commits).\033[0m"
fi
fi
if [[ "$VIOLATIONS" -gt 0 ]]; then
exit 1
fi
exit 0