From 07ea7e60fbf656641e6ca217d56372d0c46424f5 Mon Sep 17 00:00:00 2001 From: Laurettta Date: Tue, 8 Sep 2026 16:34:13 +0100 Subject: [PATCH] Switch patch application from java-diff-utils to JGit --- pom.xml | 34 ++++- src/it/apply-patch-existing-file/pom.xml | 50 +++++++ .../main/patches/0001-greeting-change.patch | 5 + .../src/main/resources/greeting.txt | 1 + .../apply-patch-existing-file/verify.groovy | 36 +++++ src/it/apply-patch-new-file/pom.xml | 49 +++++++ .../src/main/patches/0001-new-file-add.patch | 5 + .../src/main/resources/.gitkeep | 0 src/it/apply-patch-new-file/verify.groovy | 33 +++++ .../maven/plugins/patch/PatchMojo.java | 130 ++++++++++-------- 10 files changed, 283 insertions(+), 60 deletions(-) create mode 100644 src/it/apply-patch-existing-file/pom.xml create mode 100644 src/it/apply-patch-existing-file/src/main/patches/0001-greeting-change.patch create mode 100644 src/it/apply-patch-existing-file/src/main/resources/greeting.txt create mode 100644 src/it/apply-patch-existing-file/verify.groovy create mode 100644 src/it/apply-patch-new-file/pom.xml create mode 100644 src/it/apply-patch-new-file/src/main/patches/0001-new-file-add.patch create mode 100644 src/it/apply-patch-new-file/src/main/resources/.gitkeep create mode 100644 src/it/apply-patch-new-file/verify.groovy diff --git a/pom.xml b/pom.xml index af424fa..b43c962 100644 --- a/pom.xml +++ b/pom.xml @@ -83,9 +83,9 @@ provided - io.github.java-diff-utils - java-diff-utils - 4.12 + org.eclipse.jgit + org.eclipse.jgit + 5.13.5.202508271544-r @@ -133,6 +133,7 @@
apache-20-license.template.txt
apache-20-license.template.txt + src/it/apply-patch-existing-file/src/** @@ -291,6 +292,33 @@ true + + org.apache.maven.plugins + maven-invoker-plugin + + + org.junit.jupiter + junit-jupiter-api + 5.14.4 + + + + ${project.build.directory}/it + ${project.build.directory}/it-repo + verify + true + + + + integration-tests + + install + run + + + + + diff --git a/src/it/apply-patch-existing-file/pom.xml b/src/it/apply-patch-existing-file/pom.xml new file mode 100644 index 0000000..9d25611 --- /dev/null +++ b/src/it/apply-patch-existing-file/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + com.evolvedbinary.maven.plugins.it.patch-maven-plugin + apply-patch-existing-file + 1.0.0 + jar + + Test Applying a Patch to an Existing File + + + + + + com.evolvedbinary.maven.plugins + patch-maven-plugin + @project.version@ + + + apply-patch + process-resources + apply + + ${project.build.outputDirectory} + ${project.basedir}/src/main/patches + true + + + + + + + \ No newline at end of file diff --git a/src/it/apply-patch-existing-file/src/main/patches/0001-greeting-change.patch b/src/it/apply-patch-existing-file/src/main/patches/0001-greeting-change.patch new file mode 100644 index 0000000..8b6f024 --- /dev/null +++ b/src/it/apply-patch-existing-file/src/main/patches/0001-greeting-change.patch @@ -0,0 +1,5 @@ +--- a/greeting.txt ++++ b/greeting.txt +@@ -1 +1 @@ +-Hello, World! ++Hello, Elemental! \ No newline at end of file diff --git a/src/it/apply-patch-existing-file/src/main/resources/greeting.txt b/src/it/apply-patch-existing-file/src/main/resources/greeting.txt new file mode 100644 index 0000000..b45ef6f --- /dev/null +++ b/src/it/apply-patch-existing-file/src/main/resources/greeting.txt @@ -0,0 +1 @@ +Hello, World! \ No newline at end of file diff --git a/src/it/apply-patch-existing-file/verify.groovy b/src/it/apply-patch-existing-file/verify.groovy new file mode 100644 index 0000000..ffb80ee --- /dev/null +++ b/src/it/apply-patch-existing-file/verify.groovy @@ -0,0 +1,36 @@ +import java.nio.file.Files +import java.nio.file.Path + +import static org.junit.jupiter.api.Assertions.assertTrue +import static org.junit.jupiter.api.Assertions.fail + + +/* + * Copyright [2024] [Lukas Mansour] + * Modifications: Copyright (C) 2026, Evolved Binary Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +//open the file and check if the content(hello elemental) is truly there +final Path patchedFile = basedir.toPath().resolve("target/classes/greeting.txt") +if (!Files.exists(patchedFile)) { + fail("Patched file is missing: " + patchedFile) +} + +final String content = new String(Files.readAllBytes(patchedFile)) +assertTrue(content.contains("Hello, Elemental!"), "Expected content was not found in: " + patchedFile) +return true + diff --git a/src/it/apply-patch-new-file/pom.xml b/src/it/apply-patch-new-file/pom.xml new file mode 100644 index 0000000..a08a963 --- /dev/null +++ b/src/it/apply-patch-new-file/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + com.evolvedbinary.maven.plugins.it.patch-maven-plugin + apply-patch-new-file + 1.0.0 + jar + + Test Applying a Patch that Creates a New File + + + + + com.evolvedbinary.maven.plugins + patch-maven-plugin + @project.version@ + + + apply-patch + process-resources + apply + + ${project.build.outputDirectory} + ${project.basedir}/src/main/patches + true + + + + + + + \ No newline at end of file diff --git a/src/it/apply-patch-new-file/src/main/patches/0001-new-file-add.patch b/src/it/apply-patch-new-file/src/main/patches/0001-new-file-add.patch new file mode 100644 index 0000000..ddf2bb4 --- /dev/null +++ b/src/it/apply-patch-new-file/src/main/patches/0001-new-file-add.patch @@ -0,0 +1,5 @@ +--- /dev/null ++++ b/new-file.txt +@@ -0,0 +1,2 @@ ++Hello, this is a ++brand new file! \ No newline at end of file diff --git a/src/it/apply-patch-new-file/src/main/resources/.gitkeep b/src/it/apply-patch-new-file/src/main/resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/it/apply-patch-new-file/verify.groovy b/src/it/apply-patch-new-file/verify.groovy new file mode 100644 index 0000000..06592f3 --- /dev/null +++ b/src/it/apply-patch-new-file/verify.groovy @@ -0,0 +1,33 @@ +import java.nio.file.Files +import java.nio.file.Path + +import static org.junit.jupiter.api.Assertions.assertTrue +import static org.junit.jupiter.api.Assertions.fail + + +/* + * Copyright [2024] [Lukas Mansour] + * Modifications: Copyright (C) 2026, Evolved Binary Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +final Path createdFile = basedir.toPath().resolve("target/classes/new-file.txt") +if (!Files.exists(createdFile)) { + fail("New file was not created by the patch: " + createdFile) +} + +final String content = new String(Files.readAllBytes(createdFile)) +assertTrue(content.contains("brand new file"), "Expected content was not found in: " + createdFile) +return true \ No newline at end of file diff --git a/src/main/java/com/evolvedbinary/maven/plugins/patch/PatchMojo.java b/src/main/java/com/evolvedbinary/maven/plugins/patch/PatchMojo.java index d5c7d17..f7c4f49 100644 --- a/src/main/java/com/evolvedbinary/maven/plugins/patch/PatchMojo.java +++ b/src/main/java/com/evolvedbinary/maven/plugins/patch/PatchMojo.java @@ -16,25 +16,23 @@ */ package com.evolvedbinary.maven.plugins.patch; -import com.github.difflib.patch.PatchFailedException; -import com.github.difflib.unifieddiff.UnifiedDiff; -import com.github.difflib.unifieddiff.UnifiedDiffFile; -import com.github.difflib.unifieddiff.UnifiedDiffReader; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.DirectoryStream; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; + import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.eclipse.jgit.api.ApplyResult; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; /** * Applies unified diff patches. @@ -51,67 +49,85 @@ public class PatchMojo extends AbstractMojo { @Parameter(property = "patch.failOnFailedPatch", defaultValue = "false") private boolean failOnFailedPatch; + @Override public void execute() throws MojoExecutionException { - getLog().info(targetDirectory.toString()); + final Path targetDirectoryPath = targetDirectory.toPath(); + final Path patchDirectoryPath = patchDirectory.toPath(); + + getLog().info(targetDirectoryPath.toString()); getLog().info("Applying patches..."); - File[] patchFiles = patchDirectory.listFiles( - (dir, name) -> name.endsWith(".diff") || name.endsWith(".patch")); - if (patchFiles == null || !patchDirectory.isDirectory()) { + if (!Files.isDirectory(patchDirectoryPath)) { throw new MojoExecutionException("'patchDirectory' must be a directory."); } - if (!targetDirectory.isDirectory()) { + if (!Files.isDirectory(targetDirectoryPath)) { throw new MojoExecutionException("'targetDirectory' must be a directory."); } + final Path tempGitDir; + try { + tempGitDir = Files.createTempDirectory("jgit-temp-folder-"); + } catch (IOException e) { + throw new MojoExecutionException("Could not create a temporary directory for JGit", e); + } + try { - for (File patchFile : patchFiles) { - String patchFileName = patchFile.getName(); - getLog().info(String.format("Applying patch '%s'...", patchFile.getName())); - - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - new FileInputStream(patchFile)); - - Map> writeResults = new HashMap<>(); - for (UnifiedDiffFile file : diff.getFiles()) { - Path targetFile = targetDirectory.toPath().resolve(file.getToFile()); - String targetFileName = targetFile.getFileName().toString(); - - try { - List results = file.getPatch().applyTo( - Files.readAllLines(targetFile) - ); - - writeResults.put(targetFile, results); - - getLog().info( - String.format("Applied diff to '%s' successfully.", targetFileName)); - } catch (PatchFailedException pfe1) { - String failureMessage = String.format( - "Failed to apply patch file '%s' to file '%s'. (It may already have been applied!)", - patchFileName, targetFileName); - - if (failOnFailedPatch) { - throw new MojoExecutionException(failureMessage); - } else { - getLog().warn(failureMessage); + + try (Repository repository = new FileRepositoryBuilder() + .setGitDir(tempGitDir.toFile()) + .setWorkTree(targetDirectoryPath.toFile()) + .build()) { + + repository.create(); + + try (Git git = new Git(repository); + DirectoryStream patchFiles = Files.newDirectoryStream(patchDirectoryPath, "*.{diff,patch}")) { + for (final Path patchFile : patchFiles) { + final String patchFileName = patchFile.getFileName().toString(); + getLog().info("Apply: " + patchFileName); + + + try (final InputStream patchStream = Files.newInputStream(patchFile)) { + final ApplyResult result = git.apply() + .setPatch(patchStream) + .call(); + + for (final File updatedFile : result.getUpdatedFiles()) { + getLog().info("Applied diff successfully: " + updatedFile.getName()); + } + + } catch (GitAPIException | IOException applyException) { + + if (failOnFailedPatch) { + final String failureMessage = "Failed to apply patch file " + patchFileName + + " to directory " + targetDirectoryPath + " (It may already have been applied)"; + throw new MojoExecutionException(failureMessage, applyException); + } else { + getLog().warn("Failed to apply patch: " + patchFileName + " It may already have been applied."); + } } - break; + getLog().info("Finished applying patch: " + patchFileName); } - getLog().info(String.format("Finished applying diff to '%s'.", targetFileName)); } - for (Entry> entry : writeResults.entrySet()) { - Files.write(entry.getKey(), entry.getValue()); - } - getLog().info(String.format("Finished applying patch '%s'.", patchFileName)); + } catch (IOException e) { + throw new MojoExecutionException("Something went wrong with IO Operations. ", e); } + } finally { + deleteDirectoryQuietly(tempGitDir); + } + getLog().info("Finished applying patches"); + } - } catch (NoSuchFileException nsfe) { - throw new MojoExecutionException( - String.format("Could not find the file '%s' for patching. ", nsfe.getFile())); + private void deleteDirectoryQuietly(final Path directory) { + try (DirectoryStream children = Files.newDirectoryStream(directory)) { + for (final Path child : children) { + deleteDirectoryQuietly(child); + } + } catch (IOException e) { + } + try { + Files.delete(directory); } catch (IOException e) { - throw new MojoExecutionException("Something went wrong with IO Operations.", e); } - getLog().info("Finished applying patches."); } -} +} \ No newline at end of file