Check / compile (pull_request) Successful in 7m47s
- settings.gradle, stonecutter.gradle and the root build.gradle become .kts. Loom's version for the Stonecutter versions moves to the root gradle.properties (loom_version), read by pluginManagement, since the Kotlin plugins block can't read properties. The old mc-<v> projects keep their Groovy scripts until they're folded in. - buildAll ran all eight remapJar tasks at once. Each loads a Minecraft classpath and remaps a ~50 MB jar, which doesn't fit in the 3 GB daemon heap: a cold build stalled in garbage collection for 40+ minutes, and earlier runs failed. A shared build service now lets two remaps run together; a cold buildAll takes about 2 minutes. - port.sh and VERSION_GUIDE follow the .kts file names. The 1.21.11 jar is the same as with the Groovy scripts. Co-Authored-By: Claude Opus 5.5 <[email protected]>
185 lines
6.3 KiB
Bash
Executable File
185 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Port a change made in one Minecraft version's code to the other versions.
|
|
#
|
|
# Takes the diff under one version's source folder and applies it to the others
|
|
# with `git apply --3way`: it applies cleanly where the files match, and leaves
|
|
# normal merge conflicts only where a version really differs. Only source is
|
|
# ported; build.gradle and gradle.properties are version-specific on purpose.
|
|
#
|
|
# Versions moved to Stonecutter (stonecutterVersions in settings.gradle.kts)
|
|
# share the top-level src/, which counts as one version: the active one set in
|
|
# stonecutter.gradle.kts. The others keep their own copy in versions/<v>/src.
|
|
#
|
|
# Usage:
|
|
# scripts/port.sh [--from VERSION] [--to V1,V2,...] [--commit REV] [--dry-run]
|
|
#
|
|
# --from version the change was made in. Default: the only version with
|
|
# uncommitted changes (or changed by --commit).
|
|
# --to comma-separated versions to port to. Default: all others.
|
|
# --commit port the changes a commit made instead of uncommitted changes.
|
|
# --dry-run report what would happen without changing anything.
|
|
#
|
|
# Exit status: 0 if every version applied cleanly (or already had the change),
|
|
# 1 if any version has conflicts or failed.
|
|
|
|
set -euo pipefail
|
|
|
|
cd "$(git rev-parse --show-toplevel)"
|
|
|
|
from=""
|
|
to=""
|
|
commit=""
|
|
dry_run=false
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--from) from="$2"; shift 2 ;;
|
|
--to) to="$2"; shift 2 ;;
|
|
--commit) commit="$2"; shift 2 ;;
|
|
--dry-run) dry_run=true; shift ;;
|
|
-h|--help) sed -n '2,23p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
*) echo "Unknown option: $1 (see --help)" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
# Versions sharing the Stonecutter src/, and the one that stands for them.
|
|
stonecutter_versions=$(sed -n 's/^val stonecutterVersions = listOf(\(.*\))/\1/p' settings.gradle.kts | tr -d '" ' | tr ',' ' ')
|
|
stonecutter_active=$(sed -n 's/^stonecutter active "\(.*\)"/\1/p' stonecutter.gradle.kts 2>/dev/null || true)
|
|
|
|
is_stonecutter() {
|
|
printf '%s\n' $stonecutter_versions | grep -qx "$1"
|
|
}
|
|
|
|
# Where a version's source lives.
|
|
src_of() {
|
|
if is_stonecutter "$1"; then echo "src"; else echo "versions/$1/src"; fi
|
|
}
|
|
|
|
old_versions=$(for d in versions/*/; do [ -d "$d/src" ] && basename "$d"; done)
|
|
all_versions=$(printf '%s\n' $old_versions $stonecutter_active | grep . | sort -t. -k1,1n -k2,2n -k3,3n)
|
|
|
|
version_exists() {
|
|
[ -d "$(src_of "$1")" ]
|
|
}
|
|
|
|
# Versions with changes, from the working tree or from the given commit.
|
|
changed_versions() {
|
|
if [ -n "$commit" ]; then
|
|
git show --name-only --format= "$commit" -- versions/ src/
|
|
else
|
|
git diff HEAD --name-only -- versions/ src/
|
|
git ls-files --others --exclude-standard -- versions/ src/
|
|
fi | sed -n -e 's#^versions/\([^/]*\)/src/.*#\1#p' -e "s#^src/.*#${stonecutter_active}#p" | sort -u
|
|
}
|
|
|
|
if [ -z "$from" ]; then
|
|
candidates=$(changed_versions)
|
|
count=$(printf '%s\n' "$candidates" | grep -c . || true)
|
|
if [ "$count" -ne 1 ]; then
|
|
echo "Changes found in ${count} versions (${candidates//$'\n'/, }); pass --from to choose one." >&2
|
|
exit 2
|
|
fi
|
|
from="$candidates"
|
|
fi
|
|
|
|
version_exists "$from" || { echo "No such version: $from" >&2; exit 2; }
|
|
if is_stonecutter "$from"; then
|
|
from="$stonecutter_active"
|
|
fi
|
|
from_src=$(src_of "$from")
|
|
|
|
if [ -z "$to" ]; then
|
|
to=$(printf '%s\n' $all_versions | grep -vx "$from" | paste -sd, -)
|
|
fi
|
|
|
|
# New files are only included in `git diff HEAD` once git knows about them.
|
|
if [ -z "$commit" ]; then
|
|
untracked=$(git ls-files --others --exclude-standard -- "$from_src")
|
|
if [ -n "$untracked" ]; then
|
|
if $dry_run; then
|
|
echo "note: new files in $from_src are untracked and won't be ported in a dry run:" >&2
|
|
printf ' %s\n' $untracked >&2
|
|
else
|
|
printf '%s\n' "$untracked" | xargs git add --intent-to-add --
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# --full-index gives --3way the real blob ids to merge against.
|
|
if [ -n "$commit" ]; then
|
|
patch=$(git show --full-index --binary --format= "$commit" -- "$from_src")
|
|
else
|
|
patch=$(git diff HEAD --full-index --binary -- "$from_src")
|
|
fi
|
|
|
|
if [ -z "$patch" ]; then
|
|
echo "No changes under $from_src to port." >&2
|
|
exit 2
|
|
fi
|
|
|
|
files=$(printf '%s\n' "$patch" | grep -c '^diff --git' || true)
|
|
echo "Porting $files file(s) from $from${commit:+ (commit $commit)}"
|
|
|
|
from_re=$(printf '%s' "$from_src" | sed 's/\./\\./g')
|
|
tmp=$(mktemp -d)
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
|
|
status=0
|
|
IFS=',' read -r -a targets <<< "$to"
|
|
|
|
for target in "${targets[@]}"; do
|
|
if [ "$target" = "$from" ]; then
|
|
continue
|
|
fi
|
|
if is_stonecutter "$target" && [ "$target" != "$stonecutter_active" ]; then
|
|
printf ' %-8s skipped: shares src/ with %s\n' "$target" "$stonecutter_active"
|
|
[ "$from" = "$stonecutter_active" ] || status=1
|
|
continue
|
|
fi
|
|
if ! version_exists "$target"; then
|
|
printf ' %-8s skipped: no such version\n' "$target"
|
|
status=1
|
|
continue
|
|
fi
|
|
|
|
# Point the patch's file headers at the target version's source folder.
|
|
target_src=$(src_of "$target")
|
|
printf '%s\n' "$patch" | sed -E "/^(diff --git |--- |\+\+\+ |rename (from|to) |copy (from|to) )/ s#([ab]/|(from|to) )${from_re}/#\1${target_src}/#g" > "$tmp/$target.patch"
|
|
|
|
if git apply --check --reverse "$tmp/$target.patch" 2>/dev/null; then
|
|
printf ' %-8s already has this change\n' "$target"
|
|
continue
|
|
fi
|
|
|
|
if $dry_run; then
|
|
if git apply --check "$tmp/$target.patch" 2>/dev/null; then
|
|
printf ' %-8s would apply cleanly\n' "$target"
|
|
else
|
|
printf ' %-8s would need a 3-way merge (possible conflicts)\n' "$target"
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
if output=$(git apply --3way "$tmp/$target.patch" 2>&1); then
|
|
printf ' %-8s applied\n' "$target"
|
|
else
|
|
conflicts=$(git diff --name-only --diff-filter=U -- "$target_src")
|
|
if [ -n "$conflicts" ]; then
|
|
printf ' %-8s CONFLICTS in:\n' "$target"
|
|
printf '%s\n' "$conflicts" | sed 's/^/ /'
|
|
else
|
|
printf ' %-8s FAILED:\n' "$target"
|
|
printf '%s\n' "$output" | sed 's/^/ /'
|
|
fi
|
|
status=1
|
|
fi
|
|
done
|
|
|
|
if ! $dry_run; then
|
|
echo
|
|
echo "Ported changes are staged in the target versions. Resolve any conflicts, then build"
|
|
echo "the affected versions, e.g. ./gradlew compileJava"
|
|
fi
|
|
|
|
exit $status
|