Files
newgrok/scripts/apply-patches.sh
T
iceBear67andClaude Opus 5 d269c07396 Make scripts shellcheck-clean
No behaviour change. Explicit `cd || die` (set -e already covered it),
split declare/assign for PROTOC, if-blocks instead of `A && B || C`,
and array-length instead of a counter loop in count_patches. The
lib.sh constants get disable=SC2034 since they are consumed by the
scripts that source it, which shellcheck cannot see per-file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 04:54:45 +00:00

110 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Rebuild work/ from scratch: pristine upstream@upstream.rev + every patch in
# patches/, each replayed as its own commit.
#
# This is the CraftBukkit/Spigot `applyPatches.sh` step. work/ is a DERIVED
# artifact -- anything in it that is not a commit is discarded.
#
# usage: apply-patches.sh [-f|--force]
# -f discard uncommitted changes in work/ without asking
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
FORCE="${FORCE:-0}"
case "${1:-}" in
-f|--force) FORCE=1 ;;
"") ;;
*) die "unknown argument: $1" ;;
esac
ensure_upstream
REV="$(read_rev)"
if [[ ! -d "$WORK_DIR/.git" ]]; then
info "Creating work/ from upstream/"
git clone --origin upstream --no-checkout "$UPSTREAM_DIR" "$WORK_DIR"
else
# Refuse to nuke work the developer has not saved anywhere. patches/ is the
# source of truth, so anything only present in work/ is about to be lost.
if [[ "$FORCE" != "1" ]]; then
assert_no_am_in_progress
if [[ -n "$(git -C "$WORK_DIR" status --porcelain)" ]]; then
die "work/ has uncommitted changes; they would be destroyed.
Commit them (cd work && git add -A && git commit) then 'make rebuild',
or re-run with: make apply FORCE=1"
fi
if git -C "$WORK_DIR" rev-parse -q --verify "refs/tags/$BASE_TAG" >/dev/null; then
have="$(git -C "$WORK_DIR" rev-list --count "$BASE_TAG..HEAD" 2>/dev/null || echo 0)"
want="$(count_patches)"
if [[ "$have" != "$want" ]]; then
die "work/ has $have commit(s) above $BASE_TAG but patches/ has $want patch file(s).
Either you committed in work/ without running 'make rebuild' (export it:
make rebuild), or you added/removed patch files on purpose -- in which case
discard the work/ commits and replay patches/ as-is: make apply FORCE=1"
fi
fi
fi
step "Fetching into work/"
git -C "$WORK_DIR" remote set-url upstream "$UPSTREAM_DIR"
git -C "$WORK_DIR" fetch --tags --prune upstream
fi
cd "$WORK_DIR" || die "cannot enter $WORK_DIR"
git cat-file -e "${REV}^{commit}" 2>/dev/null \
|| die "pinned revision $REV not found in upstream. Run 'make update' to re-pin."
# Abandon any half-finished am from a previous run before resetting.
gitdir="$(git rev-parse --git-dir)"
if [[ -d "$gitdir/rebase-apply" ]]; then
git am --abort >/dev/null 2>&1 || true
fi
info "Resetting work/ to upstream $(git rev-parse --short "$REV")"
# -f is required: without it checkout refuses to run against a dirty tree, which
# is precisely the case FORCE=1 exists to handle. The tree is already known
# clean on the non-forced path, so forcing costs nothing there.
git checkout -q -f -B "$WORK_BRANCH" "$REV"
git reset -q --hard "$REV"
# -x removes ignored files too, which is what we want for a pristine tree --
# EXCEPT target/, which holds the Rust incremental cache. Wiping it would turn
# every apply into a 30-minute cold rebuild.
git clean -qfdx -e /target
# The boundary between "upstream" and "ours". rebuild-patches.sh formats
# everything after this tag.
git tag -f "$BASE_TAG" HEAD >/dev/null
n="$(count_patches)"
if [[ "$n" -eq 0 ]]; then
info "No patches to apply -- work/ is pristine upstream."
exit 0
fi
info "Applying $n patch(es)"
if ! git am --3way --keep-cr --whitespace=nowarn "$PATCHES_DIR"/*.patch; then
cat >&2 <<EOF
${C_RED}${C_BOLD}==> patch application failed${C_RESET}
This is normal after an upstream sync that touched the same code.
Resolve it the same way you would a rebase:
cd work
git status # see the conflicting files
\$EDITOR <conflicted files> # fix the conflict markers
git add -A
git am --continue # or: git am --skip / git am --abort
When 'git am' finishes cleanly, write the result back to patches/:
make rebuild
EOF
exit 1
fi
applied="$(git rev-list --count "$BASE_TAG..HEAD")"
info "work/ is ready: upstream $(git rev-parse --short "$BASE_TAG") + $applied patch(es)"