From fcd1886556a030274adff1dc132ef6e614e9ac1a Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 4 Jun 2020 14:14:16 -0700 Subject: [PATCH 1/5] Make it easy to run plugin-gradle integration tests from eclipse. --- plugin-gradle/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin-gradle/build.gradle b/plugin-gradle/build.gradle index d858645fe8..ff4f4d2901 100644 --- a/plugin-gradle/build.gradle +++ b/plugin-gradle/build.gradle @@ -41,6 +41,9 @@ task npmTest(type: Test) { } } +// make it easy for eclipse to run against latest build +tasks.eclipse.dependsOn(pluginUnderTestMetadata) + ////////////////////////// // GRADLE PLUGIN PORTAL // ////////////////////////// From cbe77132814d2e0608ca8936cb9258b69f38be47 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 4 Jun 2020 13:48:00 -0700 Subject: [PATCH 2/5] Add a multi-project test which exercises up-to-dateness as the baseline changes. --- .../gradle/spotless/RatchetFromTest.java | 268 ++++++++++++++---- 1 file changed, 205 insertions(+), 63 deletions(-) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java index 16a3f3661c..3308e5ce1e 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java @@ -15,7 +15,20 @@ */ package com.diffplug.gradle.spotless; +import java.util.Date; +import java.util.Objects; +import java.util.TimeZone; + +import org.assertj.core.api.Assertions; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.TaskOutcome; import org.junit.Test; public class RatchetFromTest extends GradleIntegrationHarness { @@ -23,75 +36,74 @@ public class RatchetFromTest extends GradleIntegrationHarness { @Test public void singleProjectExhaustive() throws Exception { - Git git = Git.init().setDirectory(rootFolder()).call(); - setFile("build.gradle").toLines( - "plugins {", - " id 'com.diffplug.gradle.spotless'", - "}", - "spotless {", - " ratchetFrom 'baseline'", - " format 'misc', {", - " target 'src/markdown/*.md'", - " custom 'lowercase', { str -> str.toLowerCase() }", - " bumpThisNumberIfACustomStepChanges(1)", - " }", - "}"); - setFile(TEST_PATH).toContent("HELLO"); - git.add().addFilepattern(TEST_PATH).call(); - git.commit().setMessage("Initial state").call(); - // tag this initial state as the baseline for spotless to ratchet from - git.tag().setName("baseline").call(); - - // so at this point we have test.md, and it would normally be dirty, - // but because it is unchanged, spotless says it is clean - assertClean(); - - // but if we change it so that it is not clean, spotless will now say it is dirty - setFile(TEST_PATH).toContent("HELLO WORLD"); - assertDirty(); - gradleRunner().withArguments("spotlessApply").build(); - assertFile(TEST_PATH).hasContent("hello world"); - - // but if we make it unchanged again, it goes back to being clean - setFile(TEST_PATH).toContent("HELLO"); - assertClean(); - - // and if we make the index dirty - setFile(TEST_PATH).toContent("HELLO WORLD"); - git.add().addFilepattern(TEST_PATH).call(); - { - // and the content dirty in the same way, then it's dirty - assertDirty(); - // if we make the content something else dirty, then it's dirty - setFile(TEST_PATH).toContent("HELLO MOM"); - assertDirty(); - // if we make the content unchanged, even though index it and index are dirty, then it's clean + try (Git git = Git.init().setDirectory(rootFolder()).call()) { + setFile("build.gradle").toLines( + "plugins { id 'com.diffplug.gradle.spotless' }", + "spotless {", + " ratchetFrom 'baseline'", + " format 'misc', {", + " target 'src/markdown/*.md'", + " custom 'lowercase', { str -> str.toLowerCase() }", + " bumpThisNumberIfACustomStepChanges(1)", + " }", + "}"); setFile(TEST_PATH).toContent("HELLO"); + git.add().addFilepattern(TEST_PATH).call(); + git.commit().setMessage("Initial state").call(); + // tag this initial state as the baseline for spotless to ratchet from + git.tag().setName("baseline").call(); + + // so at this point we have test.md, and it would normally be dirty, + // but because it is unchanged, spotless says it is clean assertClean(); - // if we delete the file, but it's still in the index, then it's clean - setFile(TEST_PATH).deleted(); - assertClean(); - } - // if we remove the file from the index - git.rm().addFilepattern(TEST_PATH).setCached(true).call(); - { - // and it's gone in real life too, then it's clean - assertClean(); - // if the content is there and unchanged, then it's clean + + // but if we change it so that it is not clean, spotless will now say it is dirty + setFile(TEST_PATH).toContent("HELLO WORLD"); + assertDirty(); + gradleRunner().withArguments("spotlessApply").build(); + assertFile(TEST_PATH).hasContent("hello world"); + + // but if we make it unchanged again, it goes back to being clean setFile(TEST_PATH).toContent("HELLO"); assertClean(); - // if the content is dirty, then it's dirty + + // and if we make the index dirty setFile(TEST_PATH).toContent("HELLO WORLD"); - assertDirty(); - } + git.add().addFilepattern(TEST_PATH).call(); + { + // and the content dirty in the same way, then it's dirty + assertDirty(); + // if we make the content something else dirty, then it's dirty + setFile(TEST_PATH).toContent("HELLO MOM"); + assertDirty(); + // if we make the content unchanged, even though index it and index are dirty, then it's clean + setFile(TEST_PATH).toContent("HELLO"); + assertClean(); + // if we delete the file, but it's still in the index, then it's clean + setFile(TEST_PATH).deleted(); + assertClean(); + } + // if we remove the file from the index + git.rm().addFilepattern(TEST_PATH).setCached(true).call(); + { + // and it's gone in real life too, then it's clean + assertClean(); + // if the content is there and unchanged, then it's clean + setFile(TEST_PATH).toContent("HELLO"); + assertClean(); + // if the content is dirty, then it's dirty + setFile(TEST_PATH).toContent("HELLO WORLD"); + assertDirty(); + } - // new files always get checked - setFile("new.md").toContent("HELLO"); - { - assertDirty(); - // even if they are added - git.add().addFilepattern("new.md").call(); - assertDirty(); + // new files always get checked + setFile("new.md").toContent("HELLO"); + { + assertDirty(); + // even if they are added + git.add().addFilepattern("new.md").call(); + assertDirty(); + } } } @@ -102,4 +114,134 @@ private void assertClean() throws Exception { private void assertDirty() throws Exception { gradleRunner().withArguments("spotlessCheck").buildAndFail(); } + + private BuildResultAssertion assertPass(String... tasks) throws Exception { + return new BuildResultAssertion(gradleRunner().withGradleVersion("6.0").withArguments(tasks).build()); + } + + private BuildResultAssertion assertFail(String... tasks) throws Exception { + return new BuildResultAssertion(gradleRunner().withGradleVersion("6.0").withArguments(tasks).buildAndFail()); + } + + private static final String BASELINE_ROOT = "ebb03d6940ee0254010e71917735efa203c27e16"; + private static final String BASELINE_CLEAN = "65fdd75c1ae00c0646f6487d68c44ddca51f0841"; + private static final String BASELINE_DIRTY = "4cfc3358ccbf186738b82a60276b1e5306bc3870"; + + @Test + public void multiProject() throws Exception { + try (Git git = Git.init().setDirectory(rootFolder()).call()) { + setFile("settings.gradle").toLines( + "plugins {", + " id 'com.diffplug.gradle.spotless' apply false", + "}", + "include 'clean'", + "include 'dirty'", + "include 'added'"); + setFile("spotless.gradle").toLines( + "apply plugin: 'com.diffplug.gradle.spotless'", + "spotless {", + " ratchetFrom 'master'", + " format 'misc', {", + " target 'src/markdown/*.md'", + " custom 'lowercase', { str -> str.toLowerCase() }", + " bumpThisNumberIfACustomStepChanges(1)", + " }", + "}"); + setFile(".gitignore").toContent("build/\n.gradle\n"); + setFile("build.gradle").toContent("apply from: rootProject.file('spotless.gradle') // root"); + setFile(TEST_PATH).toContent("HELLO"); + setFile("clean/build.gradle").toContent("apply from: rootProject.file('spotless.gradle') // clean"); + setFile("clean/" + TEST_PATH).toContent("HELLO"); + setFile("dirty/build.gradle").toContent("apply from: rootProject.file('spotless.gradle') // dirty"); + setFile("dirty/" + TEST_PATH).toContent("HELLO"); + RevCommit baseline = addAndCommit(git); + + ObjectId cleanFolder = TreeWalk.forPath(git.getRepository(), "clean", baseline.getTree()).getObjectId(0); + ObjectId dirtyFolder = TreeWalk.forPath(git.getRepository(), "dirty", baseline.getTree()).getObjectId(0); + + Assertions.assertThat(baseline.getTree().toObjectId()).isEqualTo(ObjectId.fromString(BASELINE_ROOT)); + Assertions.assertThat(cleanFolder).isEqualTo(ObjectId.fromString(BASELINE_CLEAN)); + Assertions.assertThat(dirtyFolder).isEqualTo(ObjectId.fromString(BASELINE_DIRTY)); + + assertPass("spotlessCheck") + .outcome(":spotlessCheck", TaskOutcome.SUCCESS) + .outcome(":clean:spotlessCheck", TaskOutcome.SUCCESS) + .outcome(":dirty:spotlessCheck", TaskOutcome.SUCCESS); + + setFile("added/build.gradle").toContent("apply from: rootProject.file('spotless.gradle') // added"); + setFile("added/" + TEST_PATH).toContent("HELLO"); + + TreeWalk isNull = TreeWalk.forPath(git.getRepository(), "added", baseline.getTree()); + Assertions.assertThat(isNull).isNull(); + + assertPass("spotlessMisc") + .outcome(":spotlessMisc", TaskOutcome.UP_TO_DATE) + .outcome(":clean:spotlessMisc", TaskOutcome.UP_TO_DATE) + .outcome(":dirty:spotlessMisc", TaskOutcome.UP_TO_DATE) + .outcome(":added:spotlessMisc", TaskOutcome.SUCCESS); + assertFail(":added:spotlessCheck"); + assertPass(":added:spotlessApply"); + + // now dirty is "git dirty" and "format dirty" + setFile("dirty/" + TEST_PATH).toContent("HELLO WORLD"); + assertFail(":dirty:spotlessCheck") + .outcome(":dirty:spotlessMisc", TaskOutcome.SUCCESS); + assertPass("spotlessApply") + .outcome(":dirty:spotlessMisc", TaskOutcome.UP_TO_DATE); + // now it is "git dirty" but "format clean" + assertPass("spotlessCheck"); + // and every single task is up-to-date + assertPass("spotlessCheck") + .outcome(":spotlessMisc", TaskOutcome.UP_TO_DATE) + .outcome(":clean:spotlessMisc", TaskOutcome.UP_TO_DATE) + .outcome(":dirty:spotlessMisc", TaskOutcome.UP_TO_DATE) + .outcome(":added:spotlessMisc", TaskOutcome.UP_TO_DATE); + + RevCommit next = addAndCommit(git); + Assertions.assertThat(next.getTree().toObjectId()).isNotEqualTo(baseline.getTree().toObjectId()); + // if we commit to master (the baseline), then tasks will be out of date only because the baseline changed + // TO REPEAAT: + // - everything was up-to-date + // - we pressed "commit", which didn't change the files, just the baseline + // - and that causes spotless to be out-of-date on all tasks + + ObjectId nextCleanFolder = TreeWalk.forPath(git.getRepository(), "clean", next.getTree()).getObjectId(0); + ObjectId nextDirtyFolder = TreeWalk.forPath(git.getRepository(), "dirty", next.getTree()).getObjectId(0); + Assertions.assertThat(nextCleanFolder).isEqualTo(cleanFolder); // which is too bad, becuase the baseline for clean didn't change + Assertions.assertThat(nextDirtyFolder).isNotEqualTo(dirtyFolder); // only the baseline for dirty + + // check will still pass, but the tasks are all out of date + assertPass("spotlessCheck") + .outcome(":spotlessMisc", TaskOutcome.SUCCESS) + .outcome(":clean:spotlessMisc", TaskOutcome.SUCCESS) // with up-to-dateness based on subtree, this could be UP-TO-DATE + .outcome(":dirty:spotlessMisc", TaskOutcome.SUCCESS) + .outcome(":added:spotlessMisc", TaskOutcome.SUCCESS); + } + } + + public static class BuildResultAssertion { + BuildResult result; + + BuildResultAssertion(BuildResult result) { + this.result = Objects.requireNonNull(result); + } + + public BuildResultAssertion outcome(String taskPath, TaskOutcome expected) { + TaskOutcome actual = result.getTasks().stream() + .filter(task -> task.getPath().equals(taskPath)) + .findAny().get().getOutcome(); + Assertions.assertThat(actual).isEqualTo(expected); + return this; + } + } + + private RevCommit addAndCommit(Git git) throws NoFilepatternException, GitAPIException { + PersonIdent emptyPerson = new PersonIdent("jane doe", "jane@doe.com", new Date(0), TimeZone.getTimeZone("UTC")); + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("baseline") + .setCommitter(emptyPerson) + .setAuthor(emptyPerson) + .call(); + return commit; + } } From 75e743ab6156282217baf0f07b138b0f52de1074 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 3 Jun 2020 12:12:28 -0700 Subject: [PATCH 3/5] Optimize GitRatchet for the only-one-changed subproject case. --- .../gradle/spotless/FormatExtension.java | 3 +- .../diffplug/gradle/spotless/GitRatchet.java | 41 +++++++++++++++---- .../com/diffplug/gradle/spotless/IdeHook.java | 2 +- .../gradle/spotless/SpotlessTask.java | 29 ++++++++++--- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index cb72ef7e36..c0195ab831 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -637,8 +637,7 @@ protected void setupTask(SpotlessTask task) { spotless.registerDependenciesTask.hookSubprojectTask(task); } if (spotless.getRatchetFrom() != null) { - task.ratchet = spotless.registerDependenciesTask.gitRatchet; - task.treeSha = task.ratchet.treeShaOf(spotless.project, spotless.getRatchetFrom()); + task.setupRatchet(spotless.registerDependenciesTask.gitRatchet, spotless.getRatchetFrom()); } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchet.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchet.java index 2da3e9bafb..b15daacf61 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchet.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchet.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * 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. @@ -17,8 +17,9 @@ import java.io.File; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; -import java.util.TreeMap; import javax.annotation.Nullable; @@ -117,8 +118,9 @@ private static boolean worktreeIsCleanCheckout(TreeWalk treeWalk) { private final static int INDEX = 1; private final static int WORKDIR = 2; - TreeMap gitRoots = new TreeMap<>(); - Table shaCache = HashBasedTable.create(); + Map gitRoots = new HashMap<>(); + Table rootTreeShaCache = HashBasedTable.create(); + Map subtreeShaCache = new HashMap<>(); /** * The first part of making this fast is finding the appropriate git repository quickly. Because of composite @@ -175,17 +177,17 @@ static Repository createRepo(File dir) throws IOException { * is the only method which can trigger any changes, and it is only called during project evaluation. That means our state * is final/read-only during task execution, so we don't need any locks during the heavy lifting. */ - public synchronized ObjectId treeShaOf(Project project, String reference) { + public synchronized ObjectId rootTreeShaOf(Project project, String reference) { try { Repository repo = repositoryFor(project); - ObjectId treeSha = shaCache.get(repo, reference); + ObjectId treeSha = rootTreeShaCache.get(repo, reference); if (treeSha == null) { ObjectId commitSha = repo.resolve(reference); try (RevWalk revWalk = new RevWalk(repo)) { RevCommit revCommit = revWalk.parseCommit(commitSha); treeSha = revCommit.getTree(); } - shaCache.put(repo, reference, treeSha); + rootTreeShaCache.put(repo, reference, treeSha); } return treeSha; } catch (Exception e) { @@ -193,6 +195,31 @@ public synchronized ObjectId treeShaOf(Project project, String reference) { } } + /** + * Returns the sha of the git subtree which represents the root of the given project, or {@link ObjectId#zeroId()} + * if there is no git subtree at the project root. + */ + public synchronized ObjectId subtreeShaOf(Project project, ObjectId rootTreeSha) { + try { + ObjectId subtreeSha = subtreeShaCache.get(project); + if (subtreeSha == null) { + Repository repo = repositoryFor(project); + File directory = project.getProjectDir(); + if (repo.getWorkTree().equals(directory)) { + subtreeSha = rootTreeSha; + } else { + String subpath = FileSignature.pathNativeToUnix(repo.getWorkTree().toPath().relativize(directory.toPath()).toString()); + TreeWalk treeWalk = TreeWalk.forPath(repo, subpath, rootTreeSha); + subtreeSha = treeWalk == null ? ObjectId.zeroId() : treeWalk.getObjectId(0); + } + subtreeShaCache.put(project, subtreeSha); + } + return subtreeSha; + } catch (Exception e) { + throw Errors.asRuntime(e); + } + } + @Override public void close() { gitRoots.values().stream() diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/IdeHook.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/IdeHook.java index 85e3836bb3..a83184c528 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/IdeHook.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/IdeHook.java @@ -43,7 +43,7 @@ static void performHook(SpotlessTask spotlessTask) { if (spotlessTask.getTarget().contains(file)) { try (Formatter formatter = spotlessTask.buildFormatter()) { if (spotlessTask.ratchet != null) { - if (spotlessTask.ratchet.isClean(spotlessTask.getProject(), spotlessTask.treeSha, file)) { + if (spotlessTask.ratchet.isClean(spotlessTask.getProject(), spotlessTask.rootTreeSha, file)) { dumpIsClean(); return; } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java index a921e2630c..7033e11924 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java @@ -91,18 +91,37 @@ public void setLineEndingsPolicy(LineEnding.Policy lineEndingsPolicy) { this.lineEndingsPolicy = Objects.requireNonNull(lineEndingsPolicy); } + /*** API which performs git up-to-date tasks. */ @Nullable GitRatchet ratchet; - ObjectId treeSha = ObjectId.zeroId(); + /** The sha of the tree at repository root, used for determining if an individual *file* is clean according to git. */ + ObjectId rootTreeSha; + /** + * The sha of the tree at the root of *this project*, used to determine if the git baseline has changed within this folder. + * Using a more fine-grained tree (rather than the project root) allows Gradle to mark more subprojects as up-to-date + * compared to using the project root. + */ + private ObjectId subtreeSha = ObjectId.zeroId(); + + public void setupRatchet(GitRatchet gitRatchet, String ratchetFrom) { + ratchet = gitRatchet; + rootTreeSha = gitRatchet.rootTreeShaOf(getProject(), ratchetFrom); + subtreeSha = gitRatchet.subtreeShaOf(getProject(), rootTreeSha); + } @Internal - public GitRatchet getRatchet() { + GitRatchet getRatchet() { return ratchet; } + @Internal + ObjectId getRootTreeSha() { + return rootTreeSha; + } + @Input public ObjectId getRatchetSha() { - return treeSha; + return subtreeSha; } @Deprecated @@ -202,7 +221,7 @@ public void performAction(IncrementalTaskInputs inputs) throws Exception { if (this.filePatterns.isEmpty()) { shouldInclude = file -> true; } else { - Preconditions.checkArgument(treeSha == ObjectId.zeroId(), + Preconditions.checkArgument(ratchet == null, "Cannot use 'ratchetFrom' and '-PspotlessFiles' at the same time"); // a list of files has been passed in via project property @@ -245,7 +264,7 @@ private void processInputFile(Formatter formatter, File input) throws IOExceptio File output = getOutputFile(input); getLogger().debug("Applying format to " + input + " and writing to " + output); PaddedCell.DirtyState dirtyState; - if (ratchet != null && ratchet.isClean(getProject(), treeSha, input)) { + if (ratchet != null && ratchet.isClean(getProject(), rootTreeSha, input)) { dirtyState = PaddedCell.isClean(); } else { dirtyState = PaddedCell.calculateDirtyState(formatter, input); From f66dc8de137a34d14768e83ab3cbff5344539b56 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 4 Jun 2020 14:33:45 -0700 Subject: [PATCH 4/5] Now that GitRatchet does up-to-dateness based on subtree, our test must be more specific. --- .../java/com/diffplug/gradle/spotless/RatchetFromTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java index 3308e5ce1e..1910a2aec6 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java @@ -203,17 +203,17 @@ public void multiProject() throws Exception { // TO REPEAAT: // - everything was up-to-date // - we pressed "commit", which didn't change the files, just the baseline - // - and that causes spotless to be out-of-date on all tasks + // - and that causes spotless to be out-of-date ObjectId nextCleanFolder = TreeWalk.forPath(git.getRepository(), "clean", next.getTree()).getObjectId(0); ObjectId nextDirtyFolder = TreeWalk.forPath(git.getRepository(), "dirty", next.getTree()).getObjectId(0); - Assertions.assertThat(nextCleanFolder).isEqualTo(cleanFolder); // which is too bad, becuase the baseline for clean didn't change + Assertions.assertThat(nextCleanFolder).isEqualTo(cleanFolder); // the baseline for 'clean' didn't change Assertions.assertThat(nextDirtyFolder).isNotEqualTo(dirtyFolder); // only the baseline for dirty // check will still pass, but the tasks are all out of date assertPass("spotlessCheck") .outcome(":spotlessMisc", TaskOutcome.SUCCESS) - .outcome(":clean:spotlessMisc", TaskOutcome.SUCCESS) // with up-to-dateness based on subtree, this could be UP-TO-DATE + .outcome(":clean:spotlessMisc", TaskOutcome.UP_TO_DATE) // with up-to-dateness based on subtree, this is UP-TO-DATE .outcome(":dirty:spotlessMisc", TaskOutcome.SUCCESS) .outcome(":added:spotlessMisc", TaskOutcome.SUCCESS); } From 10f229bd066a0109f5616feb37973a4b90ddebfb Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 4 Jun 2020 14:38:26 -0700 Subject: [PATCH 5/5] Update changelog. --- plugin-gradle/CHANGES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 0fe164d934..0cc07cd30c 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -4,7 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Fixed -* `ratchetFrom` incorrectly marked every file as though it were clean on Windows. +* `ratchetFrom` incorrectly marked every file as though it were clean on Windows. ([#596](https://github.com/diffplug/spotless/pull/596)) + * Also large [performance improvement (win and unix) for multiproject builds](https://github.com/diffplug/spotless/pull/597/commits/f66dc8de137a34d14768e83ab3cbff5344539b56). ([#597](https://github.com/diffplug/spotless/pull/597)) * Improved the warning message for `paddedCell` deprecation, along with many API-invisible fixes and cleanup. ([#592](https://github.com/diffplug/spotless/pull/592)) ## [4.2.0] - 2020-06-03