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:
@@ -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_name — resolve conflicts manually"
|
||||
echo " FAIL $patch_name — apply 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,24 +257,31 @@ echo "==> Ensuring Red Bear OS-specific files exist..."
|
||||
|
||||
# redbear.ipxe (network boot)
|
||||
if [ ! -f redbear.ipxe ] && [ ! -L redbear.ipxe ]; then
|
||||
cat > redbear.ipxe <<'IPXE'
|
||||
if [ "$DRY_RUN" = "1" ]; then
|
||||
dry_run_msg "would create redbear.ipxe"
|
||||
else
|
||||
cat > redbear.ipxe <<'IPXE'
|
||||
#!ipxe
|
||||
|
||||
kernel bootloader-live.efi
|
||||
initrd http://${next-server}:8080/redbear-live.iso
|
||||
boot
|
||||
IPXE
|
||||
echo " created redbear.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
|
||||
cat > config/redbear-full.toml <<'TOML'
|
||||
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"]
|
||||
|
||||
@@ -249,9 +313,15 @@ amdgpu = {}
|
||||
# Red Bear OS meta-package (dependencies, default config)
|
||||
redbear-meta = {}
|
||||
TOML
|
||||
echo " created config/redbear-full.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"
|
||||
|
||||
@@ -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 ─────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user