From 76c250aa2f41d7ebcb91e9a27c2b19c9fa52b6b8 Mon Sep 17 00:00:00 2001 From: Felitendo Date: Tue, 15 Sep 2026 15:01:44 +0200 Subject: [PATCH] build: rebase patches onto new upstream releases --- README.md | 12 ++++---- patches/base.txt | 1 + scripts/common.sh | 13 ++++++++- scripts/export-patches.sh | 3 ++ scripts/prepare.sh | 61 ++++++++++++++++++++++++++++++++------- 5 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 patches/base.txt diff --git a/README.md b/README.md index 8c7eae8..944d763 100644 --- a/README.md +++ b/README.md @@ -181,10 +181,12 @@ cd ../.. scripts/export-patches.sh # rewrite patches/ from those commits ``` -Patches are applied with `git am --3way`, so small upstream movements around a hunk resolve by -themselves. When one genuinely conflicts, `scripts/prepare.sh` stops and leaves the conflict staged -in `build/upstream` to be resolved with `git am --continue`, after which `scripts/export-patches.sh` -writes the fixed series back. +Patches are applied to the release they were exported against (`patches/base.txt`), where they +always fit, and then rebased onto the release in `upstream.txt` when that is newer. The rebase +merges against the files the patches were written for, so upstream changes near a hunk resolve by +themselves. When one genuinely conflicts, `scripts/prepare.sh` stops with the rebase in progress in +`build/upstream`, to be resolved with `git rebase --continue`, after which +`scripts/export-patches.sh` writes the fixed series back against the new release. To move to a newer upstream release: @@ -199,7 +201,7 @@ scripts/prepare.sh reusable workflow the release job calls. It applies the patches, checks them, and builds on Linux, Windows and macOS. - **Upstream release** (`.github/workflows/upstream-release.yml`) runs daily. If Modrinth has - published a newer release than `upstream.txt`, it rebuilds against it and — only if every + published a newer release than `upstream.txt`, it rebases the patches onto it, rebuilds and — only if every platform built and every check passed — commits the bump, tags it with the upstream version and publishes a release with the installers. - **Revisions** of the same upstream release are published by running Upstream release by hand diff --git a/patches/base.txt b/patches/base.txt new file mode 100644 index 0000000..f198b15 --- /dev/null +++ b/patches/base.txt @@ -0,0 +1 @@ +v0.21.2 diff --git a/scripts/common.sh b/scripts/common.sh index d85a6c3..16eb8ca 100755 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -19,7 +19,18 @@ WORKTREE="${WORKTREE:-$REPO_ROOT/build/upstream}" # Branch the patches are applied on top of the upstream tag as. PATCH_BRANCH=enhanced -PATCH_DIR="$REPO_ROOT/patches" +PATCH_DIR="${PATCH_DIR:-$REPO_ROOT/patches}" + +# The upstream release the patches in patches/ were last exported against, +# written there by export-patches.sh. upstream.txt cannot say: a release bumps +# it without exporting the patches again. +patch_base() { + if [ -f "$PATCH_DIR/base.txt" ]; then + tr -d '[:space:]' <"$PATCH_DIR/base.txt" + else + printf '%s\n' "$UPSTREAM_REF" + fi +} log() { printf '\033[1;32m==>\033[0m %s\n' "$*" diff --git a/scripts/export-patches.sh b/scripts/export-patches.sh index ac8d29a..00638f4 100755 --- a/scripts/export-patches.sh +++ b/scripts/export-patches.sh @@ -26,5 +26,8 @@ git -C "$WORKTREE" format-patch \ --output-directory "$PATCH_DIR" \ "$UPSTREAM_REF..HEAD" +# So that prepare.sh applies them where they fit and rebases from there. +printf '%s\n' "$UPSTREAM_REF" >"$PATCH_DIR/base.txt" + log "patches/ now contains:" ls -1 "$PATCH_DIR" diff --git a/scripts/prepare.sh b/scripts/prepare.sh index 6ed1ee1..cedacfc 100755 --- a/scripts/prepare.sh +++ b/scripts/prepare.sh @@ -4,11 +4,23 @@ # The result lands in build/upstream on the `enhanced` branch and is what all # other scripts build from. Running this again always starts from a clean # upstream tree, so it is safe to repeat. +# +# The patches are applied to the release they were exported against, where they +# always fit, and rebased onto $UPSTREAM_REF when that is a different release. +# A rebase merges against the files the patches were written for, so upstream +# changing something near a patch resolves by itself. `git am` straight onto the +# new release cannot do that: a shallow checkout does not have those files. . "$(dirname "${BASH_SOURCE[0]}")/common.sh" +BASE_REF="$(patch_base)" + log "Upstream $UPSTREAM_REF from $UPSTREAM_REPO" +fetch_tag() { + git -C "$WORKTREE" fetch --depth 1 --force origin "refs/tags/$1:refs/tags/$1" +} + if [ ! -d "$WORKTREE/.git" ]; then log "Cloning into $WORKTREE" mkdir -p "$(dirname "$WORKTREE")" @@ -16,21 +28,27 @@ if [ ! -d "$WORKTREE/.git" ]; then else log "Fetching $UPSTREAM_REF into the existing checkout" git -C "$WORKTREE" remote set-url origin "$UPSTREAM_REPO" - git -C "$WORKTREE" fetch --depth 1 --force origin "refs/tags/$UPSTREAM_REF:refs/tags/$UPSTREAM_REF" + fetch_tag "$UPSTREAM_REF" fi -# `git am` refuses to run with a rebase or merge in progress, and a previous -# run may have stopped on a conflict. -git -C "$WORKTREE" am --abort 2>/dev/null || true +if [ "$BASE_REF" != "$UPSTREAM_REF" ]; then + log "Fetching $BASE_REF, which the patches were exported against" + fetch_tag "$BASE_REF" +fi -log "Resetting to $UPSTREAM_REF" -git -C "$WORKTREE" checkout --detach --force "$UPSTREAM_REF" -git -C "$WORKTREE" branch -f "$PATCH_BRANCH" "$UPSTREAM_REF" +# Neither `git am` nor `git rebase` runs while the other is in progress, and a +# previous run may have stopped on a conflict in either. +git -C "$WORKTREE" am --abort 2>/dev/null || true +git -C "$WORKTREE" rebase --abort 2>/dev/null || true + +log "Resetting to $BASE_REF" +git -C "$WORKTREE" checkout --detach --force "$BASE_REF" +git -C "$WORKTREE" branch -f "$PATCH_BRANCH" "$BASE_REF" git -C "$WORKTREE" checkout --force "$PATCH_BRANCH" -git -C "$WORKTREE" reset --hard "$UPSTREAM_REF" +git -C "$WORKTREE" reset --hard "$BASE_REF" git -C "$WORKTREE" clean -fdx -e node_modules -e target -# `git am` needs an identity for the commits it creates. +# `git am` and `git rebase` need an identity for the commits they create. git -C "$WORKTREE" config user.name "Modrinth Enhanced" git -C "$WORKTREE" config user.email "patches@modrinth-enhanced.invalid" git -C "$WORKTREE" config commit.gpgsign false @@ -41,11 +59,11 @@ shopt -u nullglob [ ${#patches[@]} -gt 0 ] || die "No patches found in $PATCH_DIR" -log "Applying ${#patches[@]} patches" +log "Applying ${#patches[@]} patches to $BASE_REF" if ! git -C "$WORKTREE" am --3way --whitespace=nowarn "${patches[@]}"; then cat >&2 <&2 <