Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
090e5132b2 | ||
|
|
bbe6d02568 | ||
|
|
04e7064790 | ||
|
|
ff0bce4d24 | ||
|
|
2ecb4f80ea | ||
|
|
41f9be8595 | ||
|
|
f73f7e733c | ||
|
|
91b53daac9 | ||
|
|
5bcdc72b83 | ||
|
|
3f0670ea9d | ||
|
|
c54b5ae6d7 |
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env node
|
||||
// Publish the jars attached to a Gitea release to Modrinth.
|
||||
//
|
||||
// Creates one Modrinth version per jar (saturn-client-<version>+<mc>.jar), for
|
||||
// that Minecraft version, with Fabric API as a required dependency and the
|
||||
// Gitea release notes as the changelog. Versions that already exist on
|
||||
// Modrinth are skipped, so the script is safe to re-run.
|
||||
//
|
||||
// Runs on plain Node (>= 18: fetch, FormData, Blob) with no dependencies, since
|
||||
// every Gitea Actions runner has Node to run JavaScript actions.
|
||||
//
|
||||
// Environment:
|
||||
// TAG release tag, e.g. 0.1.1-beta
|
||||
// SERVER_URL public Gitea URL, e.g. https://git.selimaj.dev
|
||||
// REPO owner/name, e.g. saturnclientmc/saturnclient
|
||||
// MODRINTH_TOKEN Modrinth PAT with the "Create versions" scope
|
||||
// DRY_RUN "true" to check everything and print what would be
|
||||
// published without uploading (no token needed)
|
||||
|
||||
const MODRINTH_API = "https://api.modrinth.com/v2";
|
||||
const MODRINTH_PROJECT_ID = "i6JDSY9x"; // saturnclient
|
||||
const FABRIC_API_PROJECT_ID = "P7dR8mSH";
|
||||
const USER_AGENT =
|
||||
"saturnclientmc/saturnclient release workflow (https://git.selimaj.dev/saturnclientmc/saturnclient)";
|
||||
|
||||
const JAR_NAME = /^saturn-client-(?<version>.+)\+(?<mc>\d+\.\d+(?:\.\d+)?)\.jar$/;
|
||||
|
||||
function fail(message) {
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function http(url, options = {}) {
|
||||
const res = await fetch(url, {
|
||||
...options,
|
||||
headers: { "User-Agent": USER_AGENT, ...options.headers },
|
||||
});
|
||||
if (!res.ok) {
|
||||
const error = new Error(`${options.method ?? "GET"} ${url} returned ${res.status}: ${await res.text()}`);
|
||||
error.status = res.status;
|
||||
throw error;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async function getJson(url) {
|
||||
return (await http(url)).json();
|
||||
}
|
||||
|
||||
function versionType(tag) {
|
||||
const t = tag.toLowerCase();
|
||||
if (t.includes("alpha")) return "alpha";
|
||||
if (t.includes("beta") || t.includes("rc") || t.includes("pre")) return "beta";
|
||||
return "release";
|
||||
}
|
||||
|
||||
function compareMc(a, b) {
|
||||
const pa = a.split(".").map(Number);
|
||||
const pb = b.split(".").map(Number);
|
||||
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
||||
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
||||
if (diff !== 0) return diff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { TAG: tag, SERVER_URL, REPO: repo } = process.env;
|
||||
const serverUrl = (SERVER_URL ?? "").replace(/\/+$/, "");
|
||||
const dryRun = (process.env.DRY_RUN ?? "").toLowerCase() === "true";
|
||||
const token = process.env.MODRINTH_TOKEN ?? "";
|
||||
|
||||
if (!tag || !serverUrl || !repo) fail("TAG, SERVER_URL and REPO must be set.");
|
||||
if (!token && !dryRun) {
|
||||
fail("MODRINTH_TOKEN is not set. Add it as a repository secret (Settings → Actions → Secrets).");
|
||||
}
|
||||
|
||||
let release;
|
||||
try {
|
||||
release = await getJson(`${serverUrl}/api/v1/repos/${repo}/releases/tags/${encodeURIComponent(tag)}`);
|
||||
} catch (e) {
|
||||
if (e.status === 404) fail(`No Gitea release with tag ${tag}.`);
|
||||
throw e;
|
||||
}
|
||||
const changelog = release.body ?? "";
|
||||
|
||||
const jars = (release.assets ?? [])
|
||||
.map((asset) => ({ name: asset.name, match: JAR_NAME.exec(asset.name) }))
|
||||
.filter(({ match }) => match)
|
||||
.map(({ name, match }) => ({ name, version: match.groups.version, mc: match.groups.mc }))
|
||||
.sort((a, b) => compareMc(a.mc, b.mc));
|
||||
|
||||
if (jars.length === 0) fail(`Release ${tag} has no saturn-client-*.jar assets. Run the Release workflow first.`);
|
||||
|
||||
for (const jar of jars) {
|
||||
if (jar.version !== tag.replace(/^v/, "")) fail(`${jar.name} is version ${jar.version}, but the release tag is ${tag}.`);
|
||||
}
|
||||
|
||||
const knownGameVersions = new Set((await getJson(`${MODRINTH_API}/tag/game_version`)).map((g) => g.version));
|
||||
const unknown = [...new Set(jars.map((j) => j.mc))].filter((mc) => !knownGameVersions.has(mc));
|
||||
if (unknown.length > 0) fail(`Modrinth doesn't know these Minecraft versions: ${unknown.join(", ")}`);
|
||||
|
||||
const existing = new Set(
|
||||
(await getJson(`${MODRINTH_API}/project/${MODRINTH_PROJECT_ID}/version`)).map((v) => v.version_number),
|
||||
);
|
||||
const kind = versionType(tag);
|
||||
const failed = [];
|
||||
|
||||
for (const { name, version, mc } of jars) {
|
||||
const versionNumber = `${version}+${mc}`;
|
||||
|
||||
if (existing.has(versionNumber)) {
|
||||
console.log(`skip ${versionNumber}: already on Modrinth`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gitea's own download URLs use its internal ROOT_URL, so build a public one.
|
||||
const downloadUrl = `${serverUrl}/${repo}/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(name)}`;
|
||||
|
||||
const data = {
|
||||
project_id: MODRINTH_PROJECT_ID,
|
||||
name: `Saturn Client ${versionNumber}`,
|
||||
version_number: versionNumber,
|
||||
changelog,
|
||||
dependencies: [{ project_id: FABRIC_API_PROJECT_ID, dependency_type: "required" }],
|
||||
game_versions: [mc],
|
||||
version_type: kind,
|
||||
loaders: ["fabric"],
|
||||
featured: false,
|
||||
status: "listed",
|
||||
file_parts: ["file"],
|
||||
primary_file: "file",
|
||||
};
|
||||
|
||||
if (dryRun) {
|
||||
const probe = await http(downloadUrl, { headers: { Range: "bytes=0-0" } });
|
||||
await probe.arrayBuffer();
|
||||
console.log(`would publish ${versionNumber} (${kind}, Minecraft ${mc}) from ${downloadUrl} [reachable]`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const jarBytes = await (await http(downloadUrl)).arrayBuffer();
|
||||
|
||||
const form = new FormData();
|
||||
form.append("data", JSON.stringify(data));
|
||||
form.append("file", new Blob([jarBytes], { type: "application/java-archive" }), name);
|
||||
|
||||
await http(`${MODRINTH_API}/version`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: token },
|
||||
body: form,
|
||||
});
|
||||
console.log(`publish ${versionNumber}: done (${(jarBytes.byteLength / 1_000_000).toFixed(1)} MB)`);
|
||||
} catch (e) {
|
||||
console.error(`publish ${versionNumber}: FAILED ${e.message}`);
|
||||
failed.push(versionNumber);
|
||||
}
|
||||
}
|
||||
|
||||
if (failed.length > 0) fail(`Failed to publish: ${failed.join(", ")}`);
|
||||
}
|
||||
|
||||
main().catch((e) => fail(e.stack ?? String(e)));
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Check
|
||||
|
||||
# Compiles every Minecraft version on pull requests, so a change that breaks
|
||||
# one version (e.g. a missed or bad port) fails before it's merged instead of
|
||||
# in the release build. Merges to master aren't re-checked: the PR already
|
||||
# checked that code.
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
# The JDK comes from the runner's tool cache. `cache: gradle` stores Gradle's
|
||||
# dependencies, Loom's Minecraft cache and the build cache in the runner's
|
||||
# actions cache.
|
||||
- name: Set up Java 21
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
cache: gradle
|
||||
|
||||
- name: Compile all versions
|
||||
run: |
|
||||
chmod +x gradlew
|
||||
# --continue reports every version that fails, not just the first.
|
||||
./gradlew compileJava --no-daemon --stacktrace --continue
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Publish to Modrinth
|
||||
|
||||
# Publishes the jars already attached to a Gitea release (built by the Release
|
||||
# workflow) to Modrinth, one version per Minecraft version. Run it manually
|
||||
# after smoke-testing the release. It doesn't rebuild anything, and versions
|
||||
# already on Modrinth are skipped, so re-running is safe.
|
||||
#
|
||||
# Needs a MODRINTH_TOKEN repository secret: a Modrinth PAT with the
|
||||
# "Create versions" scope.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Tag of the Gitea release to publish
|
||||
required: true
|
||||
dry_run:
|
||||
description: Only check and print what would be published
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Publish to Modrinth
|
||||
env:
|
||||
TAG: ${{ inputs.tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
SERVER_URL: ${{ github.server_url }}
|
||||
REPO: ${{ github.repository }}
|
||||
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
|
||||
# Node is always available on Gitea runners (it runs JavaScript actions).
|
||||
run: node .gitea/scripts/publish_modrinth.mjs
|
||||
@@ -44,8 +44,11 @@ jobs:
|
||||
fi
|
||||
echo "Building Saturn Client $MOD_VERSION"
|
||||
|
||||
# The JDK comes from the runner's tool cache. `cache: gradle` stores Gradle's
|
||||
# dependencies, Loom's Minecraft cache and the build cache in the runner's
|
||||
# actions cache.
|
||||
- name: Set up Java 21
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.github.com/saturnclientmc/common/master/src/main/resources/assets/saturnclient/textures/logo/logo.png" alt="Saturn Client Logo" width="150px"/>
|
||||
<img src="logo.png" alt="Saturn Client Logo" width="150px"/>
|
||||
</p>
|
||||
|
||||
<h1 align="center">Saturn Client</h1>
|
||||
@@ -13,27 +13,47 @@
|
||||
<img src="screenshot.png" alt="Saturn Client Screenshot"/>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## ✨ About
|
||||
## About
|
||||
|
||||
Saturn Client is a free, open-source Minecraft client designed to enhance the gameplay experience while staying lightweight and customizable.
|
||||
Our goal is to build a clean, optimized client that the community can contribute to and improve together.
|
||||
|
||||
Saturn is currently trying to support multiple versions of minecraft, so please report any issues you are experiencing.
|
||||
Saturn runs on Fabric and supports **Minecraft 1.21.4 to 1.21.11**, with one build per version. If something doesn't work on your version, please open an issue.
|
||||
|
||||
---
|
||||
## Features
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Free cosmetics that don't require real money
|
||||
- Free cosmetics that don't require real money: cloaks, including animated ones, and hats
|
||||
- 18 built-in mods: Armor Display, Auto Sprint, Clock, Coordinates, Crosshair, Day Counter, FPS Display, Freelook, Fullbright, Health Display, Keystrokes, Nametags, No Fog, Ping Display, Speedometer, Status Effects, TPS Display and Zoom
|
||||
- Clean and modern UI with animations
|
||||
- Ongoing performance improvements
|
||||
- Open-source and community-driven
|
||||
|
||||
---
|
||||
## Installation
|
||||
|
||||
## 🤝 Contributing
|
||||
1. Install [Fabric Loader](https://fabricmc.net/use/) and [Fabric API](https://modrinth.com/mod/fabric-api) for your Minecraft version.
|
||||
2. Download the Saturn Client jar for the same version from [Modrinth](https://modrinth.com/mod/saturnclient) or the [releases page](https://git.selimaj.dev/saturnclientmc/saturnclient/releases).
|
||||
3. Put it in your `mods` folder.
|
||||
|
||||
### Network use
|
||||
|
||||
For cosmetics, Saturn Client connects to the Saturn server (`saturn-server.selimaj.dev`). You're signed in the same way as on an online-mode Minecraft server: your client proves who you are to Mojang's session server, and the Saturn server asks Mojang to confirm it. **Your Minecraft access token is only ever sent to Mojang, never to the Saturn server.**
|
||||
|
||||
## Building from source
|
||||
|
||||
The version-independent code lives in the `common` submodule, so clone with submodules:
|
||||
|
||||
```sh
|
||||
git clone --recurse-submodules https://git.selimaj.dev/saturnclientmc/saturnclient.git
|
||||
cd saturnclient
|
||||
|
||||
./gradlew buildAll # every version → build/allJars/
|
||||
./gradlew :mc-1.21.11:build # a single version
|
||||
./gradlew :mc-1.21.11:runClient # launch a development client
|
||||
```
|
||||
|
||||
Building needs Java 21.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions of all kinds:
|
||||
|
||||
@@ -43,11 +63,11 @@ We welcome contributions of all kinds:
|
||||
- Cosmetic designs
|
||||
- Suggestions and feedback
|
||||
|
||||
If you're an artist or designer interested in helping with cosmetics or visual assets, we’d love to collaborate.
|
||||
If you're an artist or designer interested in helping with cosmetics or visual assets, we'd love to collaborate.
|
||||
|
||||
---
|
||||
Most features belong in `common`, which every Minecraft version shares. For code that differs between versions, see the [version guide](VERSION_GUIDE.md): it covers how the versions are laid out, porting a change to all of them with `scripts/port.sh`, and adding a new Minecraft version. Every pull request is compiled for all versions automatically.
|
||||
|
||||
## 🙌 Credits
|
||||
## Credits
|
||||
|
||||
- **Cloak Rendering:** `IIpho3nix` & Saturn
|
||||
- **Who Am I:** `FokshaWasTaken`
|
||||
@@ -57,13 +77,9 @@ If you're an artist or designer interested in helping with cosmetics or visual a
|
||||
If any code or assets belong to you and you would like them removed, please open an issue.
|
||||
We aim to properly credit all contributors and respect original creators of their respective art.
|
||||
|
||||
---
|
||||
## License
|
||||
|
||||
## 📜 License
|
||||
|
||||
Saturn Client is free and open-source using **Apache-2.0**
|
||||
|
||||
---
|
||||
Saturn Client is free and open-source under the **Apache-2.0** license.
|
||||
|
||||
<p align="center">
|
||||
Made with ❤️ by the Saturn Client team
|
||||
|
||||
+42
-1
@@ -1,3 +1,44 @@
|
||||
# Version guide
|
||||
|
||||
This guide will help you to add or edit minecraft versions.
|
||||
How Saturn Client supports several Minecraft versions, and how to change or add one.
|
||||
|
||||
## Layout
|
||||
|
||||
- `common/`: all version-independent code (mods, UI, cosmetics, the server client). It has no Minecraft dependency and reaches the game only through the interfaces in `org.saturnclient.common`.
|
||||
- `versions/<mc>/`: one module per Minecraft version, implementing those interfaces (providers, refs, mixins). Each is a full copy, and they differ only where Minecraft's API differs.
|
||||
- `gradle.properties`: the Minecraft, Yarn, Fabric Loader and Fabric API versions.
|
||||
- `src/`: the version-specific code and the three mixin configs.
|
||||
|
||||
`settings.gradle` includes every folder under `versions/` automatically, as the Gradle project `:mc-<mc>`.
|
||||
|
||||
## Porting a change to the other versions
|
||||
|
||||
Make the change in one version, usually the newest, then run:
|
||||
|
||||
```sh
|
||||
scripts/port.sh # port uncommitted changes to every other version
|
||||
scripts/port.sh --dry-run # see what would apply cleanly first
|
||||
scripts/port.sh --to 1.21.9,1.21.10 # only some versions
|
||||
scripts/port.sh --commit <rev> # port what a commit changed instead
|
||||
```
|
||||
|
||||
The script applies the diff under `versions/<from>/src` to each other version with `git apply --3way`:
|
||||
|
||||
- **applied**: the files matched, and nothing else is needed.
|
||||
- **CONFLICTS**: that version's code really differs. The file gets normal conflict markers, with the version's own code in `ours` and the ported change in `theirs`. Adapt the change to that version's API and resolve.
|
||||
- **already has this change**: nothing to do, which is safe to see when re-running.
|
||||
|
||||
Only `src/` is ported. `build.gradle` and `gradle.properties` are version-specific on purpose. Then build everything:
|
||||
|
||||
```sh
|
||||
./gradlew compileJava
|
||||
```
|
||||
|
||||
The **Check** workflow runs the same compile on every pull request, so a version that was missed or mis-ported fails before merging.
|
||||
|
||||
## Adding a Minecraft version
|
||||
|
||||
1. Copy the closest existing version: `cp -r versions/1.21.11 versions/<new>`. Delete its `build/`, `.gradle/` and `run/` folders if present.
|
||||
2. In `versions/<new>/gradle.properties`, set `minecraft_version`, `yarn_mappings`, `loader_version` and `fabric_api_version`. The right values are listed at <https://fabricmc.net/develop>.
|
||||
3. Build it with `./gradlew :mc-<new>:compileJava` and fix what Minecraft changed. Check the mixin configs in `src/main/resources/*.mixins.json` as well: a mixin whose target changed fails at startup, not at compile time, so launch it with `./gradlew :mc-<new>:runClient`.
|
||||
4. The release workflow builds every folder under `versions/`, and "Publish to Modrinth" takes the game version from each jar's name, so neither needs changing.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
org.gradle.parallel=true
|
||||
# Reuse task outputs (e.g. compiled classes) when inputs haven't changed.
|
||||
org.gradle.caching=true
|
||||
|
||||
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
|
||||
org.gradle.configuration-cache=false
|
||||
|
||||
Executable
+156
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env bash
|
||||
# Port a change made in one Minecraft version's code to the other versions.
|
||||
#
|
||||
# Takes the diff under versions/<from>/src and applies it to versions/<to>/src
|
||||
# with `git apply --3way`: it applies cleanly where the files match, and leaves
|
||||
# normal merge conflicts only where a version really differs. Only src/ is
|
||||
# ported; build.gradle and gradle.properties are version-specific on purpose.
|
||||
#
|
||||
# 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,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "Unknown option: $1 (see --help)" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
all_versions=$(ls -d versions/*/ | xargs -n1 basename | sort -t. -k1,1n -k2,2n -k3,3n)
|
||||
|
||||
version_exists() {
|
||||
[ -d "versions/$1/src" ]
|
||||
}
|
||||
|
||||
# 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/
|
||||
else
|
||||
git diff HEAD --name-only -- versions/
|
||||
git ls-files --others --exclude-standard -- versions/
|
||||
fi | sed -n 's#^versions/\([^/]*\)/src/.*#\1#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 [ -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 -- "versions/$from/src")
|
||||
if [ -n "$untracked" ]; then
|
||||
if $dry_run; then
|
||||
echo "note: new files in versions/$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" -- "versions/$from/src")
|
||||
else
|
||||
patch=$(git diff HEAD --full-index --binary -- "versions/$from/src")
|
||||
fi
|
||||
|
||||
if [ -z "$patch" ]; then
|
||||
echo "No changes under versions/$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" | 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 ! 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.
|
||||
printf '%s\n' "$patch" | sed -E "/^(diff --git |--- |\+\+\+ |rename (from|to) |copy (from|to) )/ s#versions/${from_re}/#versions/${target}/#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 -- "versions/$target")
|
||||
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
|
||||
Reference in New Issue
Block a user