#!/usr/bin/env bash # validate-patches.sh — Check that all recipe patches apply cleanly. # # Usage: ./local/scripts/validate-patches.sh [--rebase] [recipe...] # # Without arguments, validates all recipes with patches. # With --rebase, runs patch --dry-run -R to check if the patch can # be reverse-applied (meaning it IS currently applied). # With recipe names/paths, validates only those recipes. # # Exit code: number of failed patches (0 = all clean). set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" cd "$ROOT" MODE="apply" # apply | rebase FAILED=0 CHECKED=0 TOTAL_PATCHES=0 red() { printf "\033[1;31m%s\033[0m\n" "$*"; } green(){ printf "\033[1;32m%s\033[0m\n" "$*"; } cyan() { printf "\033[1;36m%s\033[0m\n" "$*"; } while [[ $# -gt 0 ]]; do case "$1" in --rebase) MODE="rebase"; shift ;; *) break ;; esac done RECIPE_DIRS=("$@") if [ ${#RECIPE_DIRS[@]} -eq 0 ]; then mapfile -t RECIPE_DIRS < <(find recipes -name "recipe.toml" -exec dirname {} \; | sort) fi validate_patch() { local recipe_dir="$1" local patch_name="$2" local patch_file="$3" local source_dir="$4" local flags="--strip=1 --batch --fuzz=0" if [ "$MODE" = "rebase" ]; then # Check if patch can be reverse-applied (it IS currently applied) if patch --dry-run --directory "$source_dir" $flags -R < "$patch_file" >/dev/null 2>&1; then return 0 # already applied → OK else echo " $(red NEEDS REBASE) $patch_name" FAILED=$((FAILED + 1)) return 1 fi fi # Forward-apply check if patch --dry-run --directory "$source_dir" $flags < "$patch_file" >/dev/null 2>&1; then return 0 fi # Show the actual error for diagnosis echo " $(red FAILED) $patch_name" patch --dry-run --directory "$source_dir" $flags < "$patch_file" 2>&1 | head -5 | sed 's/^/ /' FAILED=$((FAILED + 1)) return 1 } for recipe_dir in "${RECIPE_DIRS[@]}"; do recipe_toml="$recipe_dir/recipe.toml" [ -f "$recipe_toml" ] || continue # Extract patch list from recipe.toml patches=$(grep -o '"[^"]*\.patch"' "$recipe_toml" 2>/dev/null | tr -d '"') [ -n "$patches" ] || continue source_dir="$recipe_dir/source" if [ ! -d "$source_dir" ]; then echo "$(cyan SKIP) $recipe_dir — no source directory" continue fi recipe_failed=0 recipe_checked=0 for patch_name in $patches; do # Resolve patch file path (handle relative paths like ../../../local/patches/...) patch_file="$recipe_dir/$patch_name" if [ ! -f "$patch_file" ]; then echo " $(red MISSING) $patch_name ($patch_file)" FAILED=$((FAILED + 1)) continue fi recipe_checked=$((recipe_checked + 1)) TOTAL_PATCHES=$((TOTAL_PATCHES + 1)) validate_patch "$recipe_dir" "$patch_name" "$patch_file" "$source_dir" || true done if [ "$recipe_checked" -gt 0 ]; then CHECKED=$((CHECKED + 1)) if [ "$recipe_failed" -eq 0 ]; then echo "$(green OK) $recipe_dir — $recipe_checked patch(es) clean" fi fi done echo "" echo "──────────────────────────────────────────" echo " Recipes checked: $CHECKED" echo " Total patches: $TOTAL_PATCHES" echo " Failed: $FAILED" echo "──────────────────────────────────────────" if [ "$FAILED" -eq 0 ]; then echo "$(green All patches apply cleanly)" else red "$FAILED patch(es) need rebasing or fixing" fi exit $FAILED