From 6aedc2af5dc6d85c9229725260778d9bbe2bd5b4 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Tue, 5 Dec 2023 21:46:41 -0700
Subject: [PATCH] Improve update helper task

---
 build.gradle.kts                              | 88 ++++++++++++++-----
 .../server/Add-BlockBreakBlockEvent.patch     |  0
 .../Add-back-EntityPortalExitEvent.patch      | 10 +--
 ...d-missing-team-sidebar-display-slots.patch |  0
 ...target-without-changing-other-things.patch |  0
 .../Clear-bucket-NBT-after-dispense.patch     |  0
 ...tem-frame-map-cursor-update-interval.patch |  0
 .../Make-EntityUnleashEvent-cancellable.patch |  0
 .../server/More-CommandBlock-API.patch        |  0
 ...prevent-NBT-copy-in-smithing-recipes.patch |  0
 10 files changed, 72 insertions(+), 26 deletions(-)
 rename patches/{unapplied => }/server/Add-BlockBreakBlockEvent.patch (100%)
 rename patches/{unapplied => }/server/Add-back-EntityPortalExitEvent.patch (91%)
 rename patches/{unapplied => }/server/Add-missing-team-sidebar-display-slots.patch (100%)
 rename patches/{unapplied => }/server/Change-EnderEye-target-without-changing-other-things.patch (100%)
 rename patches/{unapplied => }/server/Clear-bucket-NBT-after-dispense.patch (100%)
 rename patches/{unapplied => }/server/Configurable-item-frame-map-cursor-update-interval.patch (100%)
 rename patches/{unapplied => }/server/Make-EntityUnleashEvent-cancellable.patch (100%)
 rename patches/{unapplied => }/server/More-CommandBlock-API.patch (100%)
 rename patches/{unapplied => }/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch (100%)

diff --git a/build.gradle.kts b/build.gradle.kts
index cb9e7e93b8..5449518dba 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,6 +4,7 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat
 import org.gradle.api.tasks.testing.logging.TestLogEvent
 import java.io.ByteArrayOutputStream
 import java.nio.file.Path
+import java.util.regex.Pattern
 import kotlin.io.path.*
 
 plugins {
@@ -177,37 +178,82 @@ abstract class RebasePatches : BaseTask() {
     private fun unapplied(): List<Path> =
         unappliedPatches.path.listDirectoryEntries("*.patch").sortedBy { it.name }
 
+    private fun appliedLoc(patch: Path): Path = appliedPatches.path.resolve(unappliedPatches.path.relativize(patch))
+
+    companion object {
+        val regex = Pattern.compile("Patch failed at ([0-9]{4}) (.*)")
+        const val subjectPrefix = "Subject: [PATCH] "
+    }
+
     @TaskAction
     fun run() {
-        for (patch in unapplied()) {
-            val appliedLoc = appliedPatches.path.resolve(unappliedPatches.path.relativize(patch))
-            patch.copyTo(appliedLoc)
+        val unapplied = unapplied()
+        for (patch in unapplied) {
+            patch.copyTo(appliedLoc(patch))
+        }
 
-            val out = ByteArrayOutputStream()
+        val out = ByteArrayOutputStream()
+        val proc = ProcessBuilder()
+            .directory(projectDir.path)
+            .command("./gradlew", "applyServerPatches")
+            .redirectErrorStream(true)
+            .start()
 
-            val proc = ProcessBuilder()
+        redirect(proc.inputStream, out)
+
+        val exit = proc.waitFor()
+
+        if (exit != 0) {
+            val outStr = String(out.toByteArray())
+            val matcher = regex.matcher(outStr)
+            if (!matcher.find()) error("Could not determine failure point")
+            val failedSubjectFragment = matcher.group(2)
+            val failed = unapplied.single { p ->
+                p.useLines { lines ->
+                    val subjectLine = lines.single { it.startsWith(subjectPrefix) }
+                        .substringAfter(subjectPrefix)
+                    subjectLine.startsWith(failedSubjectFragment)
+                }
+            }
+
+            // delete successful & failure point from unapplied patches dir
+            for (path in unapplied) {
+                path.deleteIfExists()
+                if (path == failed) {
+                    break
+                }
+            }
+
+            // delete failed from patches dir
+            var started = false
+            for (path in unapplied) {
+                if (path == failed) {
+                    started = true
+                    continue
+                }
+                if (started) {
+                    appliedLoc(path).deleteIfExists()
+                }
+            }
+
+            // Apply again to reset the am session (so it ends on the failed patch, to allow us to rebuild after fixing it)
+            val apply2 = ProcessBuilder()
                 .directory(projectDir.path)
                 .command("./gradlew", "applyServerPatches")
                 .redirectErrorStream(true)
                 .start()
 
-            redirect(proc.inputStream, out)
+            redirect(apply2.inputStream, System.out)
 
-            val exit = proc.waitFor()
-
-            patch.deleteIfExists()
-
-            if (exit != 0) {
-                logger.lifecycle("Patch failed at $patch; Git output:")
-                logger.lifecycle(String(out.toByteArray()))
-                break
-            }
-
-            val git = Git(projectDir.path)
-            git("add", appliedPatches.path.toString() + "/*").runSilently()
-            git("add", unappliedPatches.path.toString() + "/*").runSilently()
-
-            logger.lifecycle("Applied $patch")
+            logger.lifecycle(outStr)
+            logger.lifecycle("Patch failed at $failed; See Git output above.")
+        } else {
+            unapplied.forEach { it.deleteIfExists() }
+            logger.lifecycle("All patches applied!")
         }
+
+        val git = Git(projectDir.path)
+        git("add", appliedPatches.path.toString() + "/*").runSilently()
+        git("add", unappliedPatches.path.toString() + "/*").runSilently()
     }
 }
diff --git a/patches/unapplied/server/Add-BlockBreakBlockEvent.patch b/patches/server/Add-BlockBreakBlockEvent.patch
similarity index 100%
rename from patches/unapplied/server/Add-BlockBreakBlockEvent.patch
rename to patches/server/Add-BlockBreakBlockEvent.patch
diff --git a/patches/unapplied/server/Add-back-EntityPortalExitEvent.patch b/patches/server/Add-back-EntityPortalExitEvent.patch
similarity index 91%
rename from patches/unapplied/server/Add-back-EntityPortalExitEvent.patch
rename to patches/server/Add-back-EntityPortalExitEvent.patch
index 74271818d7..0b46d5061a 100644
--- a/patches/unapplied/server/Add-back-EntityPortalExitEvent.patch
+++ b/patches/server/Add-back-EntityPortalExitEvent.patch
@@ -8,7 +8,7 @@ diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/jav
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/world/entity/Entity.java
 +++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
              } else {
                  // CraftBukkit start
                  worldserver = shapedetectorshape.world;
@@ -37,7 +37,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  if (worldserver == this.level) {
                      // SPIGOT-6782: Just move the entity if a plugin changed the world to the one the entity is already in
                      this.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, shapedetectorshape.xRot);
-@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
  
                  if (entity != null) {
                      entity.restoreFrom(this);
@@ -45,6 +45,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -                    entity.setDeltaMovement(shapedetectorshape.speed);
 +                    entity.moveTo(position.x, position.y, position.z, yaw, pitch); // Paper - use EntityPortalExitEvent values
 +                    entity.setDeltaMovement(velocity); // Paper - use EntityPortalExitEvent values
-                     worldserver.addDuringTeleport(entity);
-                     if (worldserver.getTypeKey() == LevelStem.END) { // CraftBukkit
-                         ServerLevel.makeObsidianPlatform(worldserver, this); // CraftBukkit
+                     // CraftBukkit start - Don't spawn the new entity if the current entity isn't spawned
+                     if (this.inWorld) {
+                         worldserver.addDuringTeleport(entity);
diff --git a/patches/unapplied/server/Add-missing-team-sidebar-display-slots.patch b/patches/server/Add-missing-team-sidebar-display-slots.patch
similarity index 100%
rename from patches/unapplied/server/Add-missing-team-sidebar-display-slots.patch
rename to patches/server/Add-missing-team-sidebar-display-slots.patch
diff --git a/patches/unapplied/server/Change-EnderEye-target-without-changing-other-things.patch b/patches/server/Change-EnderEye-target-without-changing-other-things.patch
similarity index 100%
rename from patches/unapplied/server/Change-EnderEye-target-without-changing-other-things.patch
rename to patches/server/Change-EnderEye-target-without-changing-other-things.patch
diff --git a/patches/unapplied/server/Clear-bucket-NBT-after-dispense.patch b/patches/server/Clear-bucket-NBT-after-dispense.patch
similarity index 100%
rename from patches/unapplied/server/Clear-bucket-NBT-after-dispense.patch
rename to patches/server/Clear-bucket-NBT-after-dispense.patch
diff --git a/patches/unapplied/server/Configurable-item-frame-map-cursor-update-interval.patch b/patches/server/Configurable-item-frame-map-cursor-update-interval.patch
similarity index 100%
rename from patches/unapplied/server/Configurable-item-frame-map-cursor-update-interval.patch
rename to patches/server/Configurable-item-frame-map-cursor-update-interval.patch
diff --git a/patches/unapplied/server/Make-EntityUnleashEvent-cancellable.patch b/patches/server/Make-EntityUnleashEvent-cancellable.patch
similarity index 100%
rename from patches/unapplied/server/Make-EntityUnleashEvent-cancellable.patch
rename to patches/server/Make-EntityUnleashEvent-cancellable.patch
diff --git a/patches/unapplied/server/More-CommandBlock-API.patch b/patches/server/More-CommandBlock-API.patch
similarity index 100%
rename from patches/unapplied/server/More-CommandBlock-API.patch
rename to patches/server/More-CommandBlock-API.patch
diff --git a/patches/unapplied/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch b/patches/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch
similarity index 100%
rename from patches/unapplied/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch
rename to patches/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch