Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions scripts/patches/apply-override.sh
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,15 @@ if [[ ! -d "$PATCHED_SRC" ]]; then
exit 1
fi

# Find the first series file for quilt env
SERIES_FILE=$(find "$PATCHES_DIR" -maxdepth 1 -name "*.series" | head -1)
if [[ -z "$SERIES_FILE" ]]; then
echo "Error: No series files found in patches/" >&2
exit 1
# Find the series file for quilt env (honor a pre-set QUILT_SERIES)
if [[ -n "${QUILT_SERIES:-}" && -f "${QUILT_SERIES}" ]]; then
SERIES_FILE="$QUILT_SERIES"
else
SERIES_FILE=$(find "$PATCHES_DIR" -maxdepth 1 -name "*.series" | head -1)
if [[ -z "$SERIES_FILE" ]]; then
echo "Error: No series files found in patches/" >&2
exit 1
fi
fi

export QUILT_PATCHES="$PATCHES_DIR"
Expand Down
261 changes: 261 additions & 0 deletions scripts/patches/regenerate-override-patches.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
#!/usr/bin/env bash
#
# regenerate-override-patches.sh — Regenerate @generated override patches and
# heal the rest of the series.
#
# Usage:
# scripts/patches/regenerate-override-patches.sh \
# [--dry-run] [--target <target>] [--set 'PACKAGE=VERSION']... <patch-name>...
#
# <patch-name> — an existing @generated patch, e.g. common/finding-override-undici.diff
# --set — rewrite the version for PACKAGE in the named patches'
# @generator specs before regenerating (repeatable)
# --target — target whose series to rebase (default: code-editor-sagemaker-server)
# --dry-run — print the commands that would run without executing them
#
# Each named patch is regenerated by re-executing the apply-override.sh command
# stored in its @generator metadata line. Because regenerating an early patch
# changes the context of later @generated patches, the script then rebases the
# series and automatically regenerates any @generated patch that conflicts,
# repeating until the series applies cleanly.
#
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
PATCHED_SRC="${ROOT_DIR}/code-editor-src"
PATCHES_DIR="${ROOT_DIR}/patches"
MAX_ITERATIONS=25

# ---------------------------------------------------------------------------
# Parse CLI args
# ---------------------------------------------------------------------------
DRY_RUN=false
TARGET="code-editor-sagemaker-server"
SETS=()
PATCH_NAMES=()

while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=true; shift ;;
--target) TARGET="$2"; shift 2 ;;
--set) SETS+=("$2"); shift 2 ;;
-*) echo "Unknown option: $1" >&2; exit 1 ;;
*) PATCH_NAMES+=("$1"); shift ;;
esac
done

if [[ ${#PATCH_NAMES[@]} -eq 0 ]]; then
echo "Error: at least one patch name is required" >&2
exit 1
fi

CONFIG_FILE="${ROOT_DIR}/configuration/${TARGET}.json"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "Error: Configuration file not found: $CONFIG_FILE" >&2
exit 1
fi

for s in "${SETS[@]:-}"; do
if [[ -n "$s" && "$s" != *"="* ]]; then
echo "Error: --set expects PACKAGE=VERSION, got: $s" >&2
exit 1
fi
done

export QUILT_PATCHES="$PATCHES_DIR"
export QUILT_SERIES="${ROOT_DIR}/$(jq -r '.patches.path' "$CONFIG_FILE")"

# ---------------------------------------------------------------------------
# Read metadata from an @generated patch file
# ---------------------------------------------------------------------------
require_generated() {
local patch_file="$1"
if [[ ! -f "$patch_file" ]]; then
echo "Error: patch not found: $patch_file" >&2
exit 1
fi
if ! grep -q '^@generated$' "$patch_file"; then
echo "Error: not an @generated patch: $patch_file" >&2
exit 1
fi
if ! grep -q '^@generator: scripts/patches/apply-override.sh ' "$patch_file"; then
echo "Error: no usable @generator line in: $patch_file" >&2
exit 1
fi
}

# Sets PATCH_HEADER to the free-text header (everything before @generated,
# without trailing blank lines).
read_header() {
local patch_file="$1"
PATCH_HEADER=$(sed -n '1,/^@generated$/p' "$patch_file" | sed '$d' \
| sed -e :a -e '/^[[:space:]]*$/{$d;N;ba' -e '}')
}

# Sets GEN_PATCH and GEN_OVERRIDES from the @generator line.
read_generator() {
local patch_file="$1"
local gen_args
gen_args=$(grep -m1 '^@generator: scripts/patches/apply-override.sh ' "$patch_file" \
| sed 's|^@generator: scripts/patches/apply-override.sh ||')

GEN_PATCH=""
GEN_OVERRIDES=()
eval "set -- $gen_args"
while [[ $# -gt 0 ]]; do
case "$1" in
--patch) GEN_PATCH="$2"; shift 2 ;;
--override) GEN_OVERRIDES+=("$2"); shift 2 ;;
*) echo "Error: unexpected @generator argument '$1' in $patch_file" >&2; exit 1 ;;
esac
done

if [[ -z "$GEN_PATCH" || ${#GEN_OVERRIDES[@]} -eq 0 ]]; then
echo "Error: incomplete @generator line in $patch_file" >&2
exit 1
fi
}

# ---------------------------------------------------------------------------
# Apply --set rewrites to GEN_OVERRIDES and PATCH_HEADER
# ---------------------------------------------------------------------------
apply_sets() {
local s pkg new_ver old_ver old_bare new_bare i spec spec_pkg
for s in "${SETS[@]:-}"; do
[[ -z "$s" ]] && continue
pkg="${s%%=*}"
new_ver="${s#*=}"
for i in "${!GEN_OVERRIDES[@]}"; do
spec="${GEN_OVERRIDES[$i]}"
spec_pkg="${spec##*:}"
spec_pkg="${spec_pkg%%=*}"
if [[ "$spec_pkg" == "$pkg" ]]; then
old_ver="${spec##*=}"
GEN_OVERRIDES[$i]="${spec%=*}=${new_ver}"
# Update version references in the free-text header too
old_bare="${old_ver#"${old_ver%%[0-9]*}"}"
new_bare="${new_ver#"${new_ver%%[0-9]*}"}"
if [[ -n "$old_bare" && -n "$new_bare" ]]; then
PATCH_HEADER="${PATCH_HEADER//$old_bare/$new_bare}"
fi
PATCH_HEADER="${PATCH_HEADER//$old_ver/$new_ver}"
fi
done
done
}

# ---------------------------------------------------------------------------
# Command runners
# ---------------------------------------------------------------------------
run_cmd() {
if [[ "$DRY_RUN" == true ]]; then
printf '[dry-run] would run:'
printf ' %q' "$@"
printf '\n'
else
"$@"
fi
}

regenerate_patch() {
local patch_name="$1"; shift
local header="$1"; shift
local cmd=("$SCRIPT_DIR/apply-override.sh" --patch "$patch_name" --header "$header")
local o
for o in "$@"; do
cmd+=(--override "$o")
done
run_cmd "${cmd[@]}"
}

# ---------------------------------------------------------------------------
# Rebase the series, auto-regenerating conflicted @generated patches
# ---------------------------------------------------------------------------
ITERATIONS=0

rebase_and_heal() {
if [[ "$DRY_RUN" == true ]]; then
run_cmd ./scripts/prepare-src.sh --command rebase_patches "$TARGET"
echo "[dry-run] on conflict, would regenerate the conflicted @generated patch from its @generator line and rebase again (max $MAX_ITERATIONS iterations)"
return 0
fi

while true; do
ITERATIONS=$((ITERATIONS + 1))
if [[ $ITERATIONS -gt $MAX_ITERATIONS ]]; then
echo "Error: exceeded $MAX_ITERATIONS rebase iterations, giving up" >&2
exit 1
fi

echo ""
echo "=== Rebase iteration $ITERATIONS ==="
local output exit_code
set +e
output=$(cd "$ROOT_DIR" && ./scripts/prepare-src.sh --command rebase_patches "$TARGET" 2>&1)
exit_code=$?
set -e
echo "$output"

if [[ $exit_code -eq 0 ]]; then
return 0
fi

if ! grep -q "Required actions:" <<< "$output"; then
echo "Error: rebase failed for a reason other than a patch conflict (see output above)" >&2
exit 1
fi

local top rel
top=$(cd "$PATCHED_SRC" && quilt top)
rel="${top#"$PATCHES_DIR"/}"
local patch_file="${PATCHES_DIR}/${rel}"

if ! grep -q '^@generated$' "$patch_file"; then
echo ""
echo "Conflicted patch $rel is not @generated; resolve it manually:"
echo "1. Edit the files to resolve the conflicts"
echo "2. Run 'quilt refresh' to update the patch"
echo "3. Re-run this script to continue"
exit 1
fi

echo ""
echo "Regenerating conflicted @generated patch: $rel"
read_header "$patch_file"
read_generator "$patch_file"

# Drop the forced application and empty the stale patch so
# apply-override.sh can push it cleanly and refresh it from scratch.
(cd "$PATCHED_SRC" && quilt pop -f)
: > "$patch_file"
regenerate_patch "$GEN_PATCH" "$PATCH_HEADER" "${GEN_OVERRIDES[@]}"
done
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
for name in "${PATCH_NAMES[@]}"; do
require_generated "${PATCHES_DIR}/${name}"
done

echo "Target: $TARGET"
echo "Patches: ${PATCH_NAMES[*]}"

# Ensure the series is fully applied before regenerating anything
rebase_and_heal

for name in "${PATCH_NAMES[@]}"; do
echo ""
echo "=== Regenerating $name ==="
patch_file="${PATCHES_DIR}/${name}"
read_header "$patch_file"
read_generator "$patch_file"
apply_sets
regenerate_patch "$GEN_PATCH" "$PATCH_HEADER" "${GEN_OVERRIDES[@]}"
rebase_and_heal
done

echo ""
echo "All patches regenerated and series applies cleanly."