Harden apply-patches.sh and sync-upstream.sh against local/ data loss

apply-patches.sh: add --dry-run flag, make patch conflicts fail loudly instead of silently skipping, back up WIP directories instead of rm -rf, refuse to overwrite existing config files. sync-upstream.sh: add --force flag, abort on uncommitted local/ changes unless forced, stash with -u for untracked file protection, add pre-rebase overlay integrity check, improve nuclear option and stash pop guidance.
This commit is contained in:
2026-04-22 22:00:17 +01:00
parent 07c97ebaf8
commit 9820a860ac
2 changed files with 179 additions and 20 deletions
+82 -12
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# apply-patches.sh — Apply all Red Bear OS overlays on top of upstream Redox build system.
#
# Usage: ./local/scripts/apply-patches.sh [--force]
# Usage: ./local/scripts/apply-patches.sh [--force] [--dry-run]
#
# This script:
# 1. Applies build-system patches (rebranding, cookbook fixes, config, docs)
@@ -13,6 +13,7 @@
# instead. This script therefore treats the local overlay as the durable source of truth.
#
# With --force: reapplies even if patches appear already applied.
# With --dry-run: shows what would change without making changes.
#
# SAFE: does not touch local/ directory. Only modifies upstream files.
@@ -21,11 +22,37 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PATCHES_DIR="$REPO_ROOT/local/patches"
FORCE="${1:-}"
FORCE=""
DRY_RUN=0
cd "$REPO_ROOT"
# ── Helper ──────────────────────────────────────────────────────────
usage() {
cat <<'EOF'
Usage: ./local/scripts/apply-patches.sh [--force] [--dry-run]
Options:
--force attempt re-application even when checks fail
--dry-run show what would change without modifying files
EOF
}
for arg in "$@"; do
case "$arg" in
--force) FORCE="--force" ;;
--dry-run) DRY_RUN=1 ;;
-h|--help)
usage
exit 0
;;
*)
usage
exit 1
;;
esac
done
symlink() {
local target="$1" link="$2"
if [ -L "$link" ]; then
@@ -34,32 +61,56 @@ symlink() {
return 0 # already correct
fi
fi
if [ "$DRY_RUN" = "1" ]; then
dry_run_msg "would remove and relink $link -> $target"
return 0
fi
rm -f "$link"
ln -s "$target" "$link"
echo " linked $link -> $target"
}
dry_run_msg() {
if [ "$DRY_RUN" = "1" ]; then
echo " [dry-run] $*"
fi
}
# ── 1. Build-system patches ─────────────────────────────────────────
echo "==> Applying build-system patches..."
for patch_file in "$PATCHES_DIR"/build-system/[0-9]*.patch; do
[ -f "$patch_file" ] || continue
patch_name="$(basename "$patch_file")"
# Check if already applied (skip unless --force)
if [ "$FORCE" != "--force" ]; then
if git apply --check "$patch_file" 2>/dev/null; then
: # patch applies cleanly, apply it
else
echo " SKIP $patch_name (already applied or conflicts)"
echo " Use --force to attempt re-application"
# Check if patch applies cleanly
if ! git apply --check "$patch_file" 2>/dev/null; then
# Patch does NOT apply cleanly — check if it's already applied
if git apply --reverse --check "$patch_file" 2>/dev/null; then
echo " SKIP $patch_name (already applied)"
continue
fi
# Patch conflicts and is NOT already applied — this is a real problem
echo " FAIL $patch_name — conflicts with current tree"
echo " This likely means upstream has changed the target files."
echo " Patch file: $patch_file"
echo " Options:"
echo " 1. Resolve conflicts: edit the patch file to match new upstream"
echo " 2. Use --force to attempt re-application (may fail)"
if [ "$FORCE" != "--force" ]; then
echo " ABORT: unresolved patch conflict (use --force to override)"
exit 1
fi
fi
if [ "$DRY_RUN" = "1" ]; then
dry_run_msg "would apply $patch_name"
continue
fi
if git apply --whitespace=nowarn "$patch_file"; then
echo " OK $patch_name"
else
echo " FAIL $patch_nameresolve conflicts manually"
echo " FAIL $patch_nameapply failed (check conflicts above)"
echo " Patch file: $patch_file"
exit 1
fi
@@ -138,7 +189,13 @@ symlink "../../local/recipes/core/grub" "recipes/core/grub"
# so redirect the entire directory to our local overlay to ensure
# COOKBOOK_RECIPE resolves to a directory that contains grub.cfg
if [ -d "recipes/wip/services/grub" ] && [ ! -L "recipes/wip/services/grub" ]; then
rm -rf "recipes/wip/services/grub"
backup_name="recipes/wip/services/grub.upstream-backup-$(date +%Y%m%d-%H%M%S)"
if [ "$DRY_RUN" = "1" ]; then
dry_run_msg "would backup recipes/wip/services/grub to $backup_name and replace with symlink"
else
echo " backing up upstream recipes/wip/services/grub -> $backup_name"
mv "recipes/wip/services/grub" "$backup_name"
fi
fi
if [ ! -e "recipes/wip/services/grub" ]; then
symlink "../../../local/recipes/core/grub" "recipes/wip/services/grub"
@@ -200,6 +257,9 @@ echo "==> Ensuring Red Bear OS-specific files exist..."
# redbear.ipxe (network boot)
if [ ! -f redbear.ipxe ] && [ ! -L redbear.ipxe ]; then
if [ "$DRY_RUN" = "1" ]; then
dry_run_msg "would create redbear.ipxe"
else
cat > redbear.ipxe <<'IPXE'
#!ipxe
@@ -209,15 +269,19 @@ boot
IPXE
echo " created redbear.ipxe"
fi
fi
# redbear-full config (not in upstream)
if [ ! -f config/redbear-full.toml ] && [ ! -L config/redbear-full.toml ]; then
if [ "$DRY_RUN" = "1" ]; then
dry_run_msg "would create config/redbear-full.toml"
else
cat > config/redbear-full.toml <<'TOML'
# Red Bear OS Full Configuration
# Complete desktop + all Red Bear OS custom drivers and tools
#
# Build: make all CONFIG_NAME=redbear-full
# Live: make live CONFIG_NAME=redbear-full
# Live: make live CONFIG_NAME=redbear-live-full
include = ["desktop.toml"]
@@ -251,7 +315,13 @@ redbear-meta = {}
TOML
echo " created config/redbear-full.toml"
fi
else
echo " config/redbear-full.toml already exists — not overwriting"
fi
echo ""
echo "==> All Red Bear OS patches applied. Ready to build."
if [ "$DRY_RUN" = "1" ]; then
echo " [dry-run mode — no changes were made]"
fi
echo " make all CONFIG_NAME=redbear-full"
+93 -4
View File
@@ -23,17 +23,20 @@ UPSTREAM_REMOTE="upstream-redox"
UPSTREAM_BRANCH="${UPSTREAM_BRANCH:-master}"
DRY_RUN=0
NO_MERGE=0
FORCE=0
usage() {
echo "Usage: $0 [--dry-run] [--no-merge]"
echo "Usage: $0 [--dry-run] [--no-merge] [--force]"
echo " --dry-run Show what would happen without making changes"
echo " --no-merge Only fetch and check patch conflicts"
echo " --force Skip safety checks (uncommitted local/ changes)"
}
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=1 ;;
--no-merge) NO_MERGE=1 ;;
--force) FORCE=1 ;;
--help|-h)
usage
exit 0
@@ -112,16 +115,88 @@ if [ "$DRY_RUN" = "1" ]; then
exit 0
fi
# ── 3.5. Check for uncommitted local/ changes ──────────────────────
if [ "$NO_MERGE" = "0" ] && [ "$DRY_RUN" = "0" ]; then
LOCAL_CHANGES=""
LOCAL_UNTRACKED=""
if [ -d "local" ]; then
LOCAL_CHANGES=$(cd local && git diff --name-only HEAD 2>/dev/null || true)
LOCAL_UNTRACKED=$(cd local && git ls-files --others --exclude-standard 2>/dev/null || true)
fi
# Also check for uncommitted changes to tracked local/ files from repo root
ROOT_LOCAL_CHANGES=$(git diff --name-only HEAD -- local/ 2>/dev/null || true)
if [ -n "$LOCAL_CHANGES" ] || [ -n "$LOCAL_UNTRACKED" ] || [ -n "$ROOT_LOCAL_CHANGES" ]; then
echo ""
echo "!! WARNING: Uncommitted changes detected in local/"
if [ -n "$ROOT_LOCAL_CHANGES" ]; then
echo " Modified files:"
echo "$ROOT_LOCAL_CHANGES" | head -10 | while read -r f; do echo " $f"; done
TOTAL=$(echo "$ROOT_LOCAL_CHANGES" | grep -c .)
[ "$TOTAL" -gt 10 ] && echo " ... and $((TOTAL - 10)) more"
fi
if [ -n "$LOCAL_UNTRACKED" ]; then
echo " Untracked files (NOT protected by stash):"
echo "$LOCAL_UNTRACKED" | head -5 | while read -r f; do echo " $f"; done
TOTAL=$(echo "$LOCAL_UNTRACKED" | grep -c .)
[ "$TOTAL" -gt 5 ] && echo " ... and $((TOTAL - 5)) more"
fi
echo ""
echo " git stash does NOT protect untracked files."
echo " Commit your local/ changes before syncing, or use --force to proceed anyway."
if [ "$FORCE" = "0" ]; then
echo ""
echo " ABORT: Uncommitted local/ changes detected. Use --force to override."
exit 1
else
echo " --force specified, proceeding anyway..."
fi
fi
fi
# ── 4. Stash uncommitted changes ────────────────────────────────────
STASHED=0
if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
echo "==> Stashing uncommitted changes..."
git stash push -m "redbear-sync-$(date +%Y%m%d-%H%M%S)"
git stash push -u -m "redbear-sync-$(date +%Y%m%d-%H%M%S)"
STASHED=1
fi
PREV_HEAD=$(git rev-parse HEAD)
# ── 4.5. Verify overlay integrity before rebase ────────────────────
echo "==> Verifying Red Bear overlay integrity before rebase..."
BROKEN_SYMLINKS=""
while IFS= read -r link; do
if [ ! -e "$link" ]; then
BROKEN_SYMLINKS="$BROKEN_SYMLINKS
$link -> $(readlink "$link")"
fi
done < <(find recipes -maxdepth 3 -type l 2>/dev/null)
if [ -n "$BROKEN_SYMLINKS" ]; then
echo "!! WARNING: Broken symlinks detected in recipes/:"
echo "$BROKEN_SYMLINKS" | head -20
TOTAL=$(echo "$BROKEN_SYMLINKS" | grep -c .)
[ "$TOTAL" -gt 20 ] && echo " ... and $((TOTAL - 20)) more"
echo ""
echo " These symlinks may break further during rebase."
echo " Run ./local/scripts/apply-patches.sh after rebase to recreate them."
fi
# Check that key local/patches exist
for patch_file in local/patches/kernel/redox.patch local/patches/base/redox.patch local/patches/relibc/redox.patch; do
if [ ! -f "$patch_file" ]; then
echo "!! CRITICAL: Missing patch file: $patch_file"
echo " Cannot recover from rebase failure without this patch."
if [ "$FORCE" = "0" ]; then
exit 1
fi
fi
done
# ── 5. Rebase ───────────────────────────────────────────────────────
echo ""
echo "==> Rebasing Red Bear OS commits onto $UPSTREAM_REF..."
@@ -135,20 +210,34 @@ else
echo "!! Rebase conflict. Options:"
echo " 1. Resolve conflicts: edit files, git add, git rebase --continue"
echo " 2. Abort: git rebase --abort"
echo " 3. Nuclear option:"
echo " 3. Nuclear option (DESTRUCTIVE — loses uncommitted work):"
echo " git rebase --abort"
echo " git reset --hard $UPSTREAM_REF"
echo " ./local/scripts/apply-patches.sh --force"
echo ""
echo " Patches for recovery: local/patches/build-system/"
echo " Previous HEAD: $PREV_HEAD"
echo ""
echo " IMPORTANT: Before using the nuclear option, ensure all local/ changes"
echo " are committed. The nuclear option does NOT preserve uncommitted work."
echo " To recover to previous state: git reset --hard $PREV_HEAD"
exit 1
fi
# ── 6. Restore stash ────────────────────────────────────────────────
if [ "$STASHED" = 1 ]; then
echo "==> Restoring stashed changes..."
git stash pop || echo " (stash pop had conflicts — resolve manually)"
if git stash pop; then
echo " Stash restored successfully."
else
echo "!! Stash pop had conflicts."
echo " Your changes are preserved in the stash."
echo " Options:"
echo " 1. Resolve conflicts in the working tree"
echo " 2. git checkout --theirs . && git stash drop"
echo " 3. git reset --hard && git stash pop (try again on clean tree)"
echo " List stashes: git stash list"
fi
fi
# ── 7. Verify symlinks ─────────────────────────────────────────────