Files
RedBear-OS/local/scripts/upgrade-forks.sh
T
vasilito 62d929d62a phase 17: guarantee upstream+RB patches on every build
upgrade-forks.sh:
- --no-fetch: use cached upstream refs (no network required)
- --force-reapply: force rebase even when 0 commits behind upstream
  Automatically detects missing functions via verify-fork-functions.sh
  and triggers reapply when RB cherry-picks dropped upstream code.

verify-fork-functions.sh:
- Cross-file search: when a function is missing from its upstream file,
  search ALL fork .rs files for renamed/moved equivalents (→ MOVED)
- Per-fork exclusion list: .verify-fork-functions.exclude for
  intentionally removed/replaced functions (→ EXCLUDED)
- Only truly missing (not found anywhere, not excluded) = violations

build-preflight.sh:
- Updated fix suggestion to --no-fetch --force-reapply

Results: installer 2→0 (exclusion+move), kernel 19→15 (4 moved),
base 56→44 (12 moved). Remaining missing functions are known RB
replacements pending exclusion entries.
2026-07-12 16:40:31 +03:00

329 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
# upgrade-forks.sh — Upgrade local forks to latest upstream + reapply RB patches.
#
# For each fork in local/sources/:
# 1. Fetch latest upstream (full depth for accurate merge base)
# 2. Identify Red Bear commits (ahead of upstream merge-base)
# 3. Save RB commits to a patch file
# 4. Reset to upstream/master (or upstream/main)
# 5. Reapply RB commits via cherry-pick (or patch fallback)
# 6. Verify no upstream functions were dropped (verify-fork-functions.sh)
# 7. Report success/failure per fork
#
# Usage:
# ./local/scripts/upgrade-forks.sh # Upgrade all forks
# ./local/scripts/upgrade-forks.sh kernel redoxfs # Upgrade specific forks
# ./local/scripts/upgrade-forks.sh --dry-run # Show plan, don't execute
# ./local/scripts/upgrade-forks.sh --force # Skip safety checks
# ./local/scripts/upgrade-forks.sh --no-fetch # Use cached upstream refs (no network)
# ./local/scripts/upgrade-forks.sh --force-reapply # Reapply even when 0 commits behind
#
# Safety:
# - Requires clean working tree in each fork (no uncommitted changes)
# - Creates backup branch before reset
# - Stops on first conflict (interactive resolution or --abort)
# - Post-upgrade: verifies all upstream functions are present
#
# Environment:
# REDBEAR_UPGRADE_FORCE=1 Same as --force
# REDBEAR_UPGRADE_NO_FETCH=1 Same as --no-fetch
# REDBEAR_UPGRADE_FORCE_REAPPLY=1 Same as --force-reapply
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
DRY_RUN=0
FORCE=0
NO_FETCH=0
FORCE_REAPPLY=0
declare -a TARGET_FORKS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=1 ;;
--force) FORCE=1 ;;
--no-fetch) NO_FETCH=1 ;;
--force-reapply) FORCE_REAPPLY=1 ;;
-h|--help)
echo "Usage: $0 [--dry-run] [--force] [--no-fetch] [--force-reapply] [fork1 fork2 ...]"
echo ""
echo "Upgrades local forks to latest upstream and reapplies Red Bear patches."
echo ""
echo "Options:"
echo " --dry-run Show what would happen without executing"
echo " --force Skip safety checks (dirty tree, etc.)"
echo " --no-fetch Use cached upstream refs (no network required)"
echo " --force-reapply Reapply even when 0 commits behind upstream"
echo ""
echo "Forks (default: all in local/sources/ with upstream remotes):"
for d in "$PROJECT_ROOT"/local/sources/*/; do
[[ -d "$d/.git" ]] && git -C "$d" remote get-url upstream >/dev/null 2>&1 && echo " $(basename "$d")"
done 2>/dev/null || true
exit 0
;;
--*) echo "Unknown: $1" >&2; exit 1 ;;
*) TARGET_FORKS+=("$1") ;;
esac
shift
done
[[ "${REDBEAR_UPGRADE_FORCE:-0}" == "1" ]] && FORCE=1
[[ "${REDBEAR_UPGRADE_NO_FETCH:-0}" == "1" ]] && NO_FETCH=1
[[ "${REDBEAR_UPGRADE_FORCE_REAPPLY:-0}" == "1" ]] && FORCE_REAPPLY=1
cd "$PROJECT_ROOT"
GREEN='\033[1;32m'
RED='\033[1;31m'
YELLOW='\033[1;33m'
BLUE='\033[1;34m'
NC='\033[0m'
# 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
if [[ ${#TARGET_FORKS[@]} -eq 0 ]]; then
echo -e "${RED}No forks with upstream remotes found.${NC}" >&2
exit 1
fi
echo -e "${BLUE}Red Bear OS — Fork Upgrade${NC}"
echo ""
if [[ "$DRY_RUN" -eq 1 ]]; then
echo -e "${YELLOW}DRY RUN — no changes will be made${NC}"
echo ""
fi
SUCCESS_COUNT=0
FAIL_COUNT=0
SKIP_COUNT=0
declare -a FAILED_FORKS=()
declare -a SUCCEEDED_FORKS=()
for fork in "${TARGET_FORKS[@]}"; do
fork_dir="local/sources/${fork}"
echo -e "${BLUE}=== ${fork} ===${NC}"
if [[ ! -d "$fork_dir/.git" ]]; then
echo -e " ${YELLOW}SKIP: not a git repo${NC}"
SKIP_COUNT=$((SKIP_COUNT + 1))
echo ""
continue
fi
upstream_url=$(cd "$fork_dir" && git remote get-url upstream 2>/dev/null || true)
if [[ -z "$upstream_url" ]]; then
echo -e " ${YELLOW}SKIP: no upstream remote${NC}"
SKIP_COUNT=$((SKIP_COUNT + 1))
echo ""
continue
fi
# Safety: check working tree is clean
if [[ "$FORCE" -eq 0 ]]; then
dirty=$(cd "$fork_dir" && git status --porcelain 2>/dev/null | wc -l)
if [[ "$dirty" -gt 0 ]]; then
echo -e " ${RED}FAIL: working tree has $dirty uncommitted change(s)${NC}"
echo -e " Commit or stash first, or use --force"
FAIL_COUNT=$((FAIL_COUNT + 1))
FAILED_FORKS+=("$fork")
echo ""
continue
fi
fi
# Determine upstream branch and ref
upstream_branch="master"
if [[ "$NO_FETCH" -eq 1 ]]; then
# Use cached upstream refs — verify they exist
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
echo -e " ${RED}FAIL: --no-fetch but no cached upstream/master or upstream/main${NC}"
FAIL_COUNT=$((FAIL_COUNT + 1))
FAILED_FORKS+=("$fork")
echo ""
continue
fi
fi
else
if ! (cd "$fork_dir" && git fetch upstream --quiet 2>&1); then
echo -e " ${RED}FAIL: git fetch upstream failed (network error?)${NC}"
echo -e " ${YELLOW}Use --no-fetch if upstream refs are already cached${NC}"
FAIL_COUNT=$((FAIL_COUNT + 1))
FAILED_FORKS+=("$fork")
echo ""
continue
fi
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
echo -e " ${RED}FAIL: cannot find upstream/master or upstream/main${NC}"
FAIL_COUNT=$((FAIL_COUNT + 1))
FAILED_FORKS+=("$fork")
echo ""
continue
fi
fi
fi
upstream_ref="upstream/$upstream_branch"
old_sha=$(cd "$fork_dir" && git rev-parse --short HEAD)
upstream_sha=$(cd "$fork_dir" && git rev-parse --short "$upstream_ref")
behind=$(cd "$fork_dir" && git log --oneline "HEAD..$upstream_ref" 2>/dev/null | wc -l)
ahead=$(cd "$fork_dir" && git log --oneline "$upstream_ref..HEAD" 2>/dev/null | wc -l)
echo " Current: $old_sha ($ahead commits ahead of upstream)"
echo " Upstream: $upstream_sha ($behind commits behind)"
if [[ "$behind" -eq 0 && "$FORCE_REAPPLY" -eq 0 ]]; then
# Up to date in git history — but check if RB commits dropped upstream functions
need_reapply=0
if [[ -x "$SCRIPT_DIR/verify-fork-functions.sh" ]]; then
if ! "$SCRIPT_DIR/verify-fork-functions.sh" "$fork" --no-fetch --quiet 2>/dev/null; then
need_reapply=1
echo -e " ${YELLOW}Upstream functions are MISSING (RB cherry-pick dropped code)${NC}"
echo -e " ${YELLOW}Forcing reapply to restore missing functions...${NC}"
fi
fi
if [[ "$need_reapply" -eq 0 ]]; then
echo -e " ${GREEN}Already up to date${NC}"
SKIP_COUNT=$((SKIP_COUNT + 1))
echo ""
continue
fi
elif [[ "$behind" -eq 0 && "$FORCE_REAPPLY" -eq 1 ]]; then
echo -e " ${YELLOW}--force-reapply: reapplying RB commits on clean upstream${NC}"
fi
if [[ "$ahead" -eq 0 ]]; then
echo " No RB patches — fast-forward to upstream"
if [[ "$DRY_RUN" -eq 0 ]]; then
(cd "$fork_dir" && git merge --ff-only "$upstream_ref" 2>/dev/null) || {
echo -e " ${RED}FAIL: fast-forward failed${NC}"
FAIL_COUNT=$((FAIL_COUNT + 1))
FAILED_FORKS+=("$fork")
echo ""
continue
}
fi
echo -e " ${GREEN}DONE: fast-forwarded to $upstream_sha${NC}"
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
SUCCEEDED_FORKS+=("$fork")
echo ""
continue
fi
# Has RB patches — need to rebase
# Find merge base
merge_base=$(cd "$fork_dir" && git merge-base HEAD "$upstream_ref" 2>/dev/null || echo "")
if [[ -z "$merge_base" ]]; then
echo -e " ${RED}FAIL: cannot find merge base (unrelated histories)${NC}"
echo -e " ${YELLOW}This fork may need manual migration (snapshot import)${NC}"
FAIL_COUNT=$((FAIL_COUNT + 1))
FAILED_FORKS+=("$fork")
echo ""
continue
fi
merge_base_short=$(cd "$fork_dir" && git rev-parse --short "$merge_base")
echo " Merge base: $merge_base_short"
echo " RB commits: $ahead"
# Collect RB commit list (oldest first for cherry-pick)
mapfile -t rb_commits < <(cd "$fork_dir" && git log --reverse --format='%H' "${upstream_ref}..HEAD" 2>/dev/null)
if [[ "$DRY_RUN" -eq 1 ]]; then
echo -e " ${YELLOW}PLAN: reset to $upstream_sha, cherry-pick $ahead commits${NC}"
for c in "${rb_commits[@]}"; do
short=$(cd "$fork_dir" && git log -1 --format='%h %s' "$c")
echo " + $short"
done
echo ""
continue
fi
# Create backup branch
backup_branch="rb-backup/${fork}-$(date +%Y%m%d-%H%M%S)-$$"
(cd "$fork_dir" && git branch "$backup_branch" HEAD 2>/dev/null)
echo " Backup: $backup_branch"
# Reset to upstream
(cd "$fork_dir" && git reset --hard "$upstream_ref" 2>/dev/null)
echo " Reset to: $upstream_sha"
# Cherry-pick RB commits
pick_fail=0
for c in "${rb_commits[@]}"; do
short=$(cd "$fork_dir" && git log -1 --format='%h %s' "$c")
if (cd "$fork_dir" && git cherry-pick "$c" 2>/dev/null); then
echo "$short"
else
echo -e " ${RED}✗ CONFLICT: $short${NC}"
(cd "$fork_dir" && git cherry-pick --abort 2>/dev/null) || true
pick_fail=1
break
fi
done
if [[ "$pick_fail" -ne 0 ]]; then
echo -e " ${RED}FAIL: cherry-pick conflict, restored from backup${NC}"
echo -e " ${YELLOW}Manual rebase needed: cd $fork_dir && git rebase $upstream_ref $backup_branch${NC}"
(cd "$fork_dir" && git reset --hard "$backup_branch" 2>/dev/null)
FAIL_COUNT=$((FAIL_COUNT + 1))
FAILED_FORKS+=("$fork")
echo ""
continue
fi
new_sha=$(cd "$fork_dir" && git rev-parse --short HEAD)
echo -e " ${GREEN}DONE: $old_sha$new_sha ($ahead patches reapplied)${NC}"
echo -e " ${YELLOW}Backup at $backup_branch${NC}"
# Post-upgrade verification: check no upstream functions were dropped
if [[ -x "$SCRIPT_DIR/verify-fork-functions.sh" ]]; then
if ! "$SCRIPT_DIR/verify-fork-functions.sh" "$fork" --no-fetch --quiet 2>/dev/null; then
echo -e " ${RED}VERIFICATION FAILED: upstream functions missing after upgrade!${NC}"
"$SCRIPT_DIR/verify-fork-functions.sh" "$fork" --no-fetch 2>&1 | grep -E "^ " | head -20
echo -e " ${YELLOW}Restoring from backup — manual rebase needed${NC}"
(cd "$fork_dir" && git reset --hard "$backup_branch" 2>/dev/null)
FAIL_COUNT=$((FAIL_COUNT + 1))
FAILED_FORKS+=("$fork")
echo ""
continue
else
echo -e " ${GREEN}VERIFIED: all upstream functions present${NC}"
fi
fi
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
SUCCEEDED_FORKS+=("$fork")
echo ""
done
echo ""
echo -e "${BLUE}=== SUMMARY ===${NC}"
echo -e " ${GREEN}Upgraded: $SUCCESS_COUNT${NC}" $([[ ${#SUCCEEDED_FORKS[@]} -gt 0 ]] && echo "(${SUCCEEDED_FORKS[*]})")
echo -e " ${RED}Failed: $FAIL_COUNT${NC}" $([[ ${#FAILED_FORKS[@]} -gt 0 ]] && echo "(${FAILED_FORKS[*]})")
echo " Skipped: $SKIP_COUNT"
if [[ "$FAIL_COUNT" -gt 0 ]]; then
exit 1
fi
exit 0