feat: Allow checking out repo PRs

This commit is contained in:
Linnea Gräf
2025-03-18 19:28:15 +01:00
parent 3ca60f0daa
commit 40ce4f8567
3 changed files with 105 additions and 89 deletions

View File

@@ -1,6 +1,7 @@
package moe.nea.firmament.commands package moe.nea.firmament.commands
import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.IntegerArgumentType
import com.mojang.brigadier.arguments.StringArgumentType.string import com.mojang.brigadier.arguments.StringArgumentType.string
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
@@ -130,6 +131,15 @@ fun firmamentCommand() = literal("firmament") {
} }
} }
thenLiteral("repo") { thenLiteral("repo") {
thenLiteral("checkpr") {
thenArgument("prnum", IntegerArgumentType.integer(1)) { prnum ->
thenExecute {
val prnum = this[prnum]
source.sendFeedback(tr("firmament.repo.reload.pr", "Temporarily reloading repo from PR #${prnum}."))
RepoManager.downloadOverridenBranch("refs/pull/$prnum/head")
}
}
}
thenLiteral("reload") { thenLiteral("reload") {
thenLiteral("fetch") { thenLiteral("fetch") {
thenExecute { thenExecute {

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.repo package moe.nea.firmament.repo
import io.ktor.client.call.body import io.ktor.client.call.body
@@ -28,101 +26,102 @@ import moe.nea.firmament.util.iterate
object RepoDownloadManager { object RepoDownloadManager {
val repoSavedLocation = Firmament.DATA_DIR.resolve("repo-extracted") val repoSavedLocation = Firmament.DATA_DIR.resolve("repo-extracted")
val repoMetadataLocation = Firmament.DATA_DIR.resolve("loaded-repo-sha.txt") val repoMetadataLocation = Firmament.DATA_DIR.resolve("loaded-repo-sha.txt")
private fun loadSavedVersionHash(): String? = private fun loadSavedVersionHash(): String? =
if (repoSavedLocation.exists()) { if (repoSavedLocation.exists()) {
if (repoMetadataLocation.exists()) { if (repoMetadataLocation.exists()) {
try { try {
repoMetadataLocation.readText().trim() repoMetadataLocation.readText().trim()
} catch (e: IOException) { } catch (e: IOException) {
null null
} }
} else { } else {
null null
} }
} else null } else null
private fun saveVersionHash(versionHash: String) { private fun saveVersionHash(versionHash: String) {
latestSavedVersionHash = versionHash latestSavedVersionHash = versionHash
repoMetadataLocation.writeText(versionHash) repoMetadataLocation.writeText(versionHash)
} }
var latestSavedVersionHash: String? = loadSavedVersionHash() var latestSavedVersionHash: String? = loadSavedVersionHash()
private set private set
@Serializable @Serializable
private class GithubCommitsResponse(val sha: String) private class GithubCommitsResponse(val sha: String)
private suspend fun requestLatestGithubSha(): String? { private suspend fun requestLatestGithubSha(branchOverride: String?): String? {
if (RepoManager.Config.branch == "prerelease") { if (RepoManager.Config.branch == "prerelease") {
RepoManager.Config.branch = "master" RepoManager.Config.branch = "master"
} }
val response = val response =
Firmament.httpClient.get("https://api.github.com/repos/${RepoManager.Config.username}/${RepoManager.Config.reponame}/commits/${RepoManager.Config.branch}") Firmament.httpClient.get("https://api.github.com/repos/${RepoManager.Config.username}/${RepoManager.Config.reponame}/commits/${branchOverride ?: RepoManager.Config.branch}")
if (response.status.value != 200) { if (response.status.value != 200) {
return null return null
} }
return response.body<GithubCommitsResponse>().sha return response.body<GithubCommitsResponse>().sha
} }
private suspend fun downloadGithubArchive(url: String): Path = withContext(IO) { private suspend fun downloadGithubArchive(url: String): Path = withContext(IO) {
val response = Firmament.httpClient.get(url) val response = Firmament.httpClient.get(url)
val targetFile = Files.createTempFile("firmament-repo", ".zip") val targetFile = Files.createTempFile("firmament-repo", ".zip")
val outputChannel = Files.newByteChannel(targetFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE) val outputChannel = Files.newByteChannel(targetFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE)
response.bodyAsChannel().copyTo(outputChannel) response.bodyAsChannel().copyTo(outputChannel)
targetFile targetFile
} }
/** /**
* Downloads the latest repository from github, setting [latestSavedVersionHash]. * Downloads the latest repository from github, setting [latestSavedVersionHash].
* @return true, if an update was performed, false, otherwise (no update needed, or wasn't able to complete update) * @return true, if an update was performed, false, otherwise (no update needed, or wasn't able to complete update)
*/ */
suspend fun downloadUpdate(force: Boolean): Boolean = withContext(CoroutineName("Repo Update Check")) { suspend fun downloadUpdate(force: Boolean, branch: String? = null): Boolean =
val latestSha = requestLatestGithubSha() withContext(CoroutineName("Repo Update Check")) {
if (latestSha == null) { val latestSha = requestLatestGithubSha(branch)
logger.warn("Could not request github API to retrieve latest REPO sha.") if (latestSha == null) {
return@withContext false logger.warn("Could not request github API to retrieve latest REPO sha.")
} return@withContext false
val currentSha = loadSavedVersionHash() }
if (latestSha != currentSha || force) { val currentSha = loadSavedVersionHash()
val requestUrl = if (latestSha != currentSha || force) {
"https://github.com/${RepoManager.Config.username}/${RepoManager.Config.reponame}/archive/$latestSha.zip" val requestUrl =
logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl") "https://github.com/${RepoManager.Config.username}/${RepoManager.Config.reponame}/archive/$latestSha.zip"
val zipFile = downloadGithubArchive(requestUrl) logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl")
logger.info("Download repository zip file to $zipFile. Deleting old repository") val zipFile = downloadGithubArchive(requestUrl)
withContext(IO) { repoSavedLocation.toFile().deleteRecursively() } logger.info("Download repository zip file to $zipFile. Deleting old repository")
logger.info("Extracting new repository") withContext(IO) { repoSavedLocation.toFile().deleteRecursively() }
withContext(IO) { extractNewRepository(zipFile) } logger.info("Extracting new repository")
logger.info("Repository loaded on disk.") withContext(IO) { extractNewRepository(zipFile) }
saveVersionHash(latestSha) logger.info("Repository loaded on disk.")
return@withContext true saveVersionHash(latestSha)
} else { return@withContext true
logger.debug("Repository on latest sha $currentSha. Not performing update") } else {
return@withContext false logger.debug("Repository on latest sha $currentSha. Not performing update")
} return@withContext false
} }
}
private fun extractNewRepository(zipFile: Path) { private fun extractNewRepository(zipFile: Path) {
repoSavedLocation.createDirectories() repoSavedLocation.createDirectories()
ZipInputStream(zipFile.inputStream()).use { cis -> ZipInputStream(zipFile.inputStream()).use { cis ->
while (true) { while (true) {
val entry = cis.nextEntry ?: break val entry = cis.nextEntry ?: break
if (entry.isDirectory) continue if (entry.isDirectory) continue
val extractedLocation = val extractedLocation =
repoSavedLocation.resolve( repoSavedLocation.resolve(
entry.name.substringAfter('/', missingDelimiterValue = "") entry.name.substringAfter('/', missingDelimiterValue = "")
) )
if (repoSavedLocation !in extractedLocation.iterate { it.parent }) { if (repoSavedLocation !in extractedLocation.iterate { it.parent }) {
logger.error("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.") logger.error("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.")
throw RuntimeException("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.") throw RuntimeException("Firmament detected an invalid zip file. This is a potential security risk, please report this in the Firmament discord.")
} }
extractedLocation.parent.createDirectories() extractedLocation.parent.createDirectories()
extractedLocation.outputStream().use { cis.copyTo(it) } extractedLocation.outputStream().use { cis.copyTo(it) }
} }
} }
} }
} }

View File

@@ -102,6 +102,13 @@ object RepoManager {
fun getNEUItem(skyblockId: SkyblockId): NEUItem? = neuRepo.items.getItemBySkyblockId(skyblockId.neuItem) fun getNEUItem(skyblockId: SkyblockId): NEUItem? = neuRepo.items.getItemBySkyblockId(skyblockId.neuItem)
fun downloadOverridenBranch(branch: String) {
Firmament.coroutineScope.launch {
RepoDownloadManager.downloadUpdate(true, branch)
reload()
}
}
fun launchAsyncUpdate(force: Boolean = false) { fun launchAsyncUpdate(force: Boolean = false) {
Firmament.coroutineScope.launch { Firmament.coroutineScope.launch {
RepoDownloadManager.downloadUpdate(force) RepoDownloadManager.downloadUpdate(force)