penguin/monok8s

k8s image for Mono Gateway Dev Kit

patches/split-kernel-patch.sh

raw ยท 7031 bytes

#!/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