250 lines
6.9 KiB
Bash
250 lines
6.9 KiB
Bash
#!/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
|