#!/usr/bin/env bash set -euo pipefail # split-kernel-patch.sh # Split one big git-style patch into one patch file per touched file, then # optionally apply them one by one and stop at the first failure. # # Defaults match your ASK kernel patch workflow. # # Usage: # ./split-kernel-patch.sh split # ./split-kernel-patch.sh apply # ./split-kernel-patch.sh check # ./split-kernel-patch.sh reset-output # # Env overrides: # PATCH_FILE=/src/ASK/patches/kernel/002-mono-gateway-ask-kernel_linux_6_12.patch # LINUX_DIR=/src/linux # OUT_DIR=/src/ASK/patches/kernel/split-002 # FUZZ=0 # USE_PATCH=0 # 0 = git apply, 1 = patch utility # THREEWAY=0 # git apply --3way when USE_PATCH=0 # UPDATED_PATCH_DIR=/src/ASK/patches/kernel/updated-patch # # If a file with the same basename exists here, use it # # instead of the generated split fragment. PATCH_FILE="${PATCH_FILE:-/src/ASK/patches/kernel/002-mono-gateway-ask-kernel_linux_6_12.patch}" LINUX_DIR="${LINUX_DIR:-/src/linux}" OUT_DIR="${OUT_DIR:-/src/ASK/patches/kernel/split-002}" FUZZ="${FUZZ:-0}" USE_PATCH="${USE_PATCH:-0}" THREEWAY="${THREEWAY:-0}" UPDATED_PATCH_DIR="${UPDATED_PATCH_DIR:-$(dirname "$OUT_DIR")/updated-patch}" SERIES_FILE="${OUT_DIR}/series" LOG_FILE="${OUT_DIR}/apply.log" usage() { sed -n '1,35p' "$0" >&2 } need_file() { [ -f "$1" ] || { echo "ERROR: missing file: $1" >&2; exit 1; } } need_dir() { [ -d "$1" ] || { echo "ERROR: missing directory: $1" >&2; exit 1; } } resolve_patch_file() { # Generated split fragments are immutable-ish. During porting, put a # corrected replacement patch in UPDATED_PATCH_DIR with the exact same # basename, e.g.: # updated-patch/0005-drivers__net__ethernet__freescale__sdk_dpaa__dpaa_eth.h.patch # The apply/check loop will use that replacement instead. local generated="$1" local replacement="${UPDATED_PATCH_DIR}/$(basename "$generated")" if [ -f "$replacement" ]; then printf '%s\n' "$replacement" else printf '%s\n' "$generated" fi } patch_target_file() { # Best-effort target display for one split fragment. Prefer the b/ path # from the diff header, fall back to +++ if needed. awk ' /^diff --git / { p = $4 sub(/^b\//, "", p) print p exit } /^\+\+\+ / && $2 != "/dev/null" { p = $2 sub(/^b\//, "", p) print p exit } ' "$1" } reset_output() { rm -rf "$OUT_DIR" mkdir -p "$OUT_DIR" } split_patch() { need_file "$PATCH_FILE" reset_output # Lossless split: do NOT trim trailing whitespace and do NOT rewrite content. # Each output starts at a 'diff --git ...' boundary. awk -v outdir="$OUT_DIR" -v series="$SERIES_FILE" ' function sanitize(s) { sub(/^b\//, "", s) gsub(/^\"|\"$/, "", s) gsub(/\//, "__", s) gsub(/[^A-Za-z0-9._+-]/, "_", s) if (length(s) > 160) s = substr(s, 1, 160) return s } BEGIN { n = 0 out = "" } /^diff --git / { if (out != "") close(out) n++ path = $4 safe = sanitize(path) out = sprintf("%s/%04d-%s.patch", outdir, n, safe) print out >> series } { if (out != "") print $0 > out } END { if (out != "") close(out) if (n == 0) { print "ERROR: no diff --git sections found" > "/dev/stderr" exit 2 } print n > outdir "/count" } ' "$PATCH_FILE" echo "Split $(cat "$OUT_DIR/count") patch fragments into: $OUT_DIR" echo "Series file: $SERIES_FILE" } ensure_split_exists() { if [ ! -s "$SERIES_FILE" ]; then echo "=> No split series found, splitting first..." split_patch fi } apply_one_git() { patch_file="$1" args=(--verbose) if [ "$THREEWAY" = "1" ]; then args+=(--3way) fi git apply "${args[@]}" --check "$patch_file" git apply "${args[@]}" "$patch_file" } apply_one_patch_utility() { patch_file="$1" # --forward avoids reapplying already-applied hunks. # --reject leaves .rej files for manual whack-a-mole. patch -p1 --forward --batch --fuzz="$FUZZ" --dry-run < "$patch_file" patch -p1 --forward --batch --fuzz="$FUZZ" --reject < "$patch_file" } check_or_apply() { mode="$1" ensure_split_exists need_dir "$LINUX_DIR" : > "$LOG_FILE" cd "$LINUX_DIR" i=0 total=$(wc -l < "$SERIES_FILE" | tr -d ' ') while IFS= read -r patch_file; do i=$((i + 1)) generated_patch_file="$patch_file" patch_file=$(resolve_patch_file "$generated_patch_file") base=$(basename "$generated_patch_file") patching_target=$(patch_target_file "$patch_file") if [ -z "$patching_target" ]; then patching_target="unknown" fi if [ "$patch_file" != "$generated_patch_file" ]; then echo "=> [$i/$total] $base (using updated-patch override)" | tee -a "$LOG_FILE" echo " override: $patch_file" >>"$LOG_FILE" else echo "=> [$i/$total] $base" | tee -a "$LOG_FILE" fi if [ "$mode" = "check" ]; then if git apply --check "$patch_file" >>"$LOG_FILE" 2>&1; then echo " OK" | tee -a "$LOG_FILE" continue fi echo " FAIL: $patch_file" | tee -a "$LOG_FILE" echo "Stopped at: $patch_file" echo "Inspect log: $LOG_FILE" echo "Target file: $patching_target" echo "" exit 1 fi if [ "$USE_PATCH" = "1" ]; then if apply_one_patch_utility "$patch_file" >>"$LOG_FILE" 2>&1; then echo " applied" | tee -a "$LOG_FILE" continue fi else if apply_one_git "$patch_file" >>"$LOG_FILE" 2>&1; then echo " applied" | tee -a "$LOG_FILE" continue fi fi echo " FAILED: $patch_file" | tee -a "$LOG_FILE" echo "" echo "Stopped at: $patch_file" echo "Inspect log: $LOG_FILE" echo "Target file: $patching_target" echo "" echo "Useful next commands:" echo " cd $LINUX_DIR" echo " git diff" echo " git status --short" echo " ${USE_PATCH:+find . -name '*.rej' -o -name '*.orig'}" exit 1 done < "$SERIES_FILE" echo "All $total patch fragments ${mode}ed successfully." } cmd="${1:-split}" case "$cmd" in split) split_patch ;; check) check_or_apply check ;; apply) check_or_apply apply ;; reset-output) reset_output ;; *) usage exit 2 ;; esac