diff --git a/CHANGES.md b/CHANGES.md index 4515786d3f..abc9799ba6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `LineEnding.GIT_ATTRIBUTES` now creates a policy whose serialized state can be relocated from one machine to another. No user-visible change, but paves the way for remote build cache support in Gradle. ([#621](https://github.com/diffplug/spotless/pull/621)) ### Added * `prettier` will now autodetect the parser (and formatter) to use based on the filename, unless you override this using `config` or `configFile` with the option `parser` or `filepath`. ([#620](https://github.com/diffplug/spotless/pull/620)) +* `GitRatchet` now lives in `lib-extra`, and is shared across `plugin-gradle` and `plugin-maven` ([#626](https://github.com/diffplug/spotless/pull/626)). ### Changed * **BREAKING** `FileSignature` can no longer sign folders, only files. Signatures are now based only on filename (not path), size, and a content hash. It throws an error if a signature is attempted on a folder or on multiple files with different paths but the same filename - it never breaks silently. This change does not break any of Spotless' internal logic, so it is unlikely to affect any of Spotless' consumers either. ([#571](https://github.com/diffplug/spotless/pull/571)) * This change allows the maven plugin to cache classloaders across subprojects when loading config resources from the classpath (fixes [#559](https://github.com/diffplug/spotless/issues/559)). diff --git a/gradle.properties b/gradle.properties index 1333215701..9770f4eeb6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,7 +22,7 @@ VER_SLF4J=[1.6,2.0[ # Used in multiple places VER_DURIAN=1.2.0 -VER_JGIT=5.7.0.202003110725-r +VER_JGIT=5.8.0.202006091008-r VER_JUNIT=4.13 VER_ASSERTJ=3.15.0 VER_MOCKITO=3.3.3 diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java index fe06f43183..b71731b4e5 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; import javax.annotation.Nullable; @@ -31,6 +32,7 @@ import org.eclipse.jgit.lib.RepositoryCache; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator; @@ -195,13 +197,21 @@ public synchronized ObjectId rootTreeShaOf(Project project, String reference) { Repository repo = repositoryFor(project); ObjectId treeSha = rootTreeShaCache.get(repo, reference); if (treeSha == null) { - ObjectId commitSha = repo.resolve(reference); - if (commitSha == null) { - throw new IllegalArgumentException("No such reference '" + reference + "'"); - } try (RevWalk revWalk = new RevWalk(repo)) { - RevCommit revCommit = revWalk.parseCommit(commitSha); - treeSha = revCommit.getTree(); + ObjectId commitSha = repo.resolve(reference); + if (commitSha == null) { + throw new IllegalArgumentException("No such reference '" + reference + "'"); + } + + RevCommit ratchetFrom = revWalk.parseCommit(commitSha); + RevCommit head = revWalk.parseCommit(repo.resolve(Constants.HEAD)); + + revWalk.setRevFilter(RevFilter.MERGE_BASE); + revWalk.markStart(ratchetFrom); + revWalk.markStart(head); + + RevCommit mergeBase = revWalk.next(); + treeSha = Optional.ofNullable(mergeBase).orElse(ratchetFrom).getTree(); } rootTreeShaCache.put(repo, reference, treeSha); } diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/GitRachetMergeBaseTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/GitRachetMergeBaseTest.java new file mode 100644 index 0000000000..e13b40a2b2 --- /dev/null +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/GitRachetMergeBaseTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2020 DiffPlug + * + * 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. + */ +package com.diffplug.spotless.extra; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefDatabase; +import org.junit.Test; + +import com.diffplug.spotless.ResourceHarness; + +public class GitRachetMergeBaseTest extends ResourceHarness { + @Test + public void test() throws IllegalStateException, GitAPIException, IOException { + try (Git git = initRepo()) { + setFile("mine.txt").toContent("init"); + setFile("untouched.txt").toContent("init"); + addAndCommit(git, "init"); + + git.checkout().setCreateBranch(true).setName("rem-branch").call(); + // so at the point where we start work, we are clean relative to the remote + ratchetFrom("main", "rem-branch").allClean(); + + // but when the remote changes + setFile("untouched.txt").toContent("changed"); + addAndCommit(git, "remote"); + + // it shouldn't affect files that we haven't changed + git.checkout().setName("main").call(); + ratchetFrom("main", "rem-branch").allClean(); + + // and when we work, it should continue to only affect our own work + setFile("mine.txt").toContent("changed"); + ratchetFrom("main", "rem-branch").onlyDirty("mine.txt"); + } + } + + static class GitRatchetSimple extends GitRatchet { + @Override + protected File getDir(File project) { + return project; + } + + @Override + protected File getParent(File project) { + return project.getParentFile(); + } + } + + Asserter ratchetFrom(String... ratchetFroms) { + return new Asserter(ratchetFroms); + } + + class Asserter { + final GitRatchetSimple ratchet = new GitRatchetSimple(); + final String[] ratchetFroms; + final ObjectId[] shas; + + Asserter(String... ratchetFrom) { + this.ratchetFroms = ratchetFrom; + this.shas = Arrays.stream(ratchetFrom) + .map(from -> ratchet.rootTreeShaOf(rootFolder(), from)) + .toArray(ObjectId[]::new); + } + + private void assertClean(int i, String filename, boolean expected) throws IOException { + boolean actual = ratchet.isClean(rootFolder(), shas[i], newFile(filename)); + if (actual != expected) { + throw new AssertionError("Expected " + filename + " to be " + (expected ? "clean" : "dirty") + " relative to " + ratchetFroms[i]); + } + } + + public void allClean() throws IOException { + onlyDirty(); + } + + public void allDirty() throws IOException { + String[] filenames = Arrays.stream(rootFolder().listFiles()) + .filter(File::isFile) + .map(File::getName) + .toArray(String[]::new); + onlyDirty(filenames); + } + + public void onlyDirty(String... filenames) throws IOException { + List dirtyFiles = Arrays.asList(filenames); + for (File file : rootFolder().listFiles()) { + if (!file.isFile()) { + continue; + } + boolean expectedClean = !dirtyFiles.contains(file.getName()); + for (int i = 0; i < shas.length; ++i) { + assertClean(i, file.getName(), expectedClean); + } + } + } + } + + private Git initRepo() throws IllegalStateException, GitAPIException, IOException { + Git git = Git.init().setDirectory(rootFolder()).call(); + RefDatabase refDB = git.getRepository().getRefDatabase(); + refDB.newUpdate(Constants.R_HEADS + "main", false).setNewObjectId(ObjectId.zeroId()); + refDB.newUpdate(Constants.HEAD, false).link(Constants.R_HEADS + "main"); + refDB.newUpdate(Constants.R_HEADS + Constants.MASTER, false).delete(); + return git; + } + + private void addAndCommit(Git git, String message) throws GitAPIException { + git.add().addFilepattern(".").call(); + git.commit().setMessage(message).call(); + } +} diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 8e2124f016..fae34fcaee 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -10,6 +10,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `prettier` will now autodetect the parser (and formatter) to use based on the filename, unless you override this using `config()` or `configFile()` with the option `parser` or `filepath`. ([#620](https://github.com/diffplug/spotless/pull/620)) ### Fixed * LineEndings.GIT_ATTRIBUTES is now a bit more efficient, and paves the way for remote build cache support in Gradle. ([#621](https://github.com/diffplug/spotless/pull/621)) +* `ratchetFrom` now ratchets from the merge base of `HEAD` and the specified branch. This fixes the surprising behavior when a remote branch advanced ([#631](https://github.com/diffplug/spotless/pull/631) fixes [#627](https://github.com/diffplug/spotless/issues/627)). ## [4.4.0] - 2020-06-19 ### Added