diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchet.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java similarity index 88% rename from plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchet.java rename to lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java index 89cd3b05b7..fe06f43183 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchet.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.diffplug.gradle.spotless; +package com.diffplug.spotless.extra; import java.io.File; import java.io.IOException; @@ -40,14 +40,20 @@ import org.eclipse.jgit.treewalk.filter.IndexDiffFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.util.FS; -import org.gradle.api.Project; import com.diffplug.common.base.Errors; import com.diffplug.common.collect.HashBasedTable; import com.diffplug.common.collect.Table; import com.diffplug.spotless.FileSignature; -class GitRatchet implements AutoCloseable { +/** + * How to use: + * - For best performance, you should have one instance of GitRatchet, shared by all projects. + * - Use {@link #rootTreeShaOf(Object, String)} to turn `origin/master` into the SHA of the tree object at that reference + * - Use {@link #isClean(Object, ObjectId, File)} to see if the given file is "git clean" relative to that tree + * - If you have up-to-date checking and want the best possible performance, use {@link #subtreeShaOf(Object, ObjectId)} to optimize up-to-date checks on a per-project basis. + */ +public abstract class GitRatchet implements AutoCloseable { /** * This is the highest-level method, which all the others serve. Given the sha * of a git tree (not a commit!), and the file in question, this method returns @@ -130,17 +136,17 @@ private static boolean worktreeIsCleanCheckout(TreeWalk treeWalk) { private Repository repositoryFor(Project project) throws IOException { Repository repo = gitRoots.get(project); if (repo == null) { - if (isGitRoot(project.getProjectDir())) { - repo = createRepo(project.getProjectDir()); + if (isGitRoot(getDir(project))) { + repo = createRepo(getDir(project)); } else { - Project parentProj = project.getParent(); + Project parentProj = getParent(project); if (parentProj == null) { - repo = traverseParentsUntil(project.getProjectDir().getParentFile(), null); + repo = traverseParentsUntil(getDir(project).getParentFile(), null); if (repo == null) { throw new IllegalArgumentException("Cannot find git repository in any parent directory"); } } else { - repo = traverseParentsUntil(project.getProjectDir().getParentFile(), parentProj.getProjectDir()); + repo = traverseParentsUntil(getDir(project).getParentFile(), getDir(parentProj)); if (repo == null) { repo = repositoryFor(parentProj); } @@ -151,6 +157,10 @@ private Repository repositoryFor(Project project) throws IOException { return repo; } + protected abstract File getDir(Project project); + + protected abstract @Nullable Project getParent(Project project); + private static @Nullable Repository traverseParentsUntil(File startWith, File file) throws IOException { while (startWith != null) { if (isGitRoot(startWith)) { @@ -196,7 +206,7 @@ public synchronized ObjectId rootTreeShaOf(Project project, String reference) { rootTreeShaCache.put(repo, reference, treeSha); } return treeSha; - } catch (Exception e) { + } catch (IOException e) { throw Errors.asRuntime(e); } } @@ -210,7 +220,7 @@ public synchronized ObjectId subtreeShaOf(Project project, ObjectId rootTreeSha) ObjectId subtreeSha = subtreeShaCache.get(project); if (subtreeSha == null) { Repository repo = repositoryFor(project); - File directory = project.getProjectDir(); + File directory = getDir(project); if (repo.getWorkTree().equals(directory)) { subtreeSha = rootTreeSha; } else { @@ -221,7 +231,7 @@ public synchronized ObjectId subtreeShaOf(Project project, ObjectId rootTreeSha) subtreeShaCache.put(project, subtreeSha); } return subtreeSha; - } catch (Exception e) { + } catch (IOException e) { throw Errors.asRuntime(e); } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchetGradle.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchetGradle.java new file mode 100644 index 0000000000..bfa84258c1 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchetGradle.java @@ -0,0 +1,37 @@ +/* + * 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.gradle.spotless; + +import java.io.File; + +import javax.annotation.Nullable; + +import org.gradle.api.Project; + +import com.diffplug.spotless.extra.GitRatchet; + +/** Gradle implementation of GitRatchet. */ +final class GitRatchetGradle extends GitRatchet { + @Override + protected File getDir(Project project) { + return project.getProjectDir(); + } + + @Override + protected @Nullable Project getParent(Project project) { + return project.getParent(); + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RegisterDependenciesTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RegisterDependenciesTask.java index 4fd4c46c9a..b770e66627 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RegisterDependenciesTask.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RegisterDependenciesTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,10 +106,10 @@ public void trivialFunction() throws IOException { Files.write(Integer.toString(getSteps().size()), unitOutput, StandardCharsets.UTF_8); } - GitRatchet gitRatchet = new GitRatchet(); + GitRatchetGradle gitRatchet = new GitRatchetGradle(); @Internal - GitRatchet getGitRatchet() { + GitRatchetGradle getGitRatchet() { return gitRatchet; } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskBase.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskBase.java index 37e5a78341..3ec59fd725 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskBase.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskBase.java @@ -76,7 +76,7 @@ public void setLineEndingsPolicy(LineEnding.Policy lineEndingsPolicy) { /*** API which performs git up-to-date tasks. */ @Nullable - GitRatchet ratchet; + GitRatchetGradle ratchet; /** The sha of the tree at repository root, used for determining if an individual *file* is clean according to git. */ ObjectId rootTreeSha; /** @@ -86,14 +86,14 @@ public void setLineEndingsPolicy(LineEnding.Policy lineEndingsPolicy) { */ private ObjectId subtreeSha = ObjectId.zeroId(); - public void setupRatchet(GitRatchet gitRatchet, String ratchetFrom) { + public void setupRatchet(GitRatchetGradle gitRatchet, String ratchetFrom) { ratchet = gitRatchet; rootTreeSha = gitRatchet.rootTreeShaOf(getProject(), ratchetFrom); subtreeSha = gitRatchet.subtreeShaOf(getProject(), rootTreeSha); } @Internal - GitRatchet getRatchet() { + GitRatchetGradle getRatchet() { return ratchet; } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GitRatchetGradleTest.java similarity index 99% rename from plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java rename to plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GitRatchetGradleTest.java index 387df29dbe..010f8d2112 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RatchetFromTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GitRatchetGradleTest.java @@ -34,7 +34,7 @@ import org.gradle.testkit.runner.TaskOutcome; import org.junit.Test; -public class RatchetFromTest extends GradleIntegrationHarness { +public class GitRatchetGradleTest extends GradleIntegrationHarness { private static final String TEST_PATH = "src/markdown/test.md"; private Git initRepo() throws IllegalStateException, GitAPIException, IOException { diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 6aaebb196a..8e999a4897 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -4,8 +4,9 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### 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)). +* You can now ratchet a project's style by limiting Spotless only to files which have changed since a given [git reference](https://javadoc.io/static/org.eclipse.jgit/org.eclipse.jgit/5.6.1.202002131546-r/org/eclipse/jgit/lib/Repository.html#resolve-java.lang.String-), e.g. `ratchetFrom 'origin/main'`. ([#590](https://github.com/diffplug/spotless/pull/590)) * Huge speed improvement for multi-module projects thanks to improved cross-project classloader caching ([#571](https://github.com/diffplug/spotless/pull/571), fixes [#559](https://github.com/diffplug/spotless/issues/559)). +* `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)). ## [1.31.3] - 2020-06-17 ### Changed diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 66c2155209..bcba6f6e3b 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -594,7 +594,25 @@ By default, `spotless:check` is bound to the `verify` phase. You might want to - If you don't like what spotless did, `git reset --hard` - If you'd like to remove the "checkpoint" commit, `git reset --soft head~1` will make the checkpoint commit "disappear" from history, but keeps the changes in your working directory. - + + +## How can I enforce formatting gradually? + +If your project is not currently enforcing formatting, then it can be a noisy transition. Having a giant commit where every single file gets changed makes the history harder to read. To address this, you can use the `ratchet` feature: + +```xml + + origin/main + + +``` + +In this mode, Spotless will apply only to files which have changed since `origin/main`. You can ratchet from [any point you want](https://javadoc.io/static/org.eclipse.jgit/org.eclipse.jgit/5.6.1.202002131546-r/org/eclipse/jgit/lib/Repository.html#resolve-java.lang.String-), even `HEAD`. You can also set `ratchetFrom` per-format if you prefer (e.g. `...`). + +However, we strongly recommend that you use a non-local branch, such as a tag or `origin/main`. The problem with `HEAD` or any local branch is that as soon as you commit a file, that is now the canonical formatting, even if it was formatted incorrectly. By instead specifying `origin/main` or a tag, your CI server will fail unless every changed file is at least as good or better than it was before the change. + +This is especially helpful for injecting accurate copyright dates using the [license step](#license-header). + ## Can I apply Spotless to specific files? @@ -606,6 +624,8 @@ cmd> mvn spotless:apply -DspotlessFiles=my/file/pattern.java,more/generic/.*-pat The patterns are matched using `String#matches(String)` against the absolute file path. + + ## Example configurations (from real-world projects) - [Apache Avro](https://github.com/apache/avro/blob/8026c8ffe4ef67ab419dba73910636bf2c1a691c/lang/java/pom.xml#L307-L334) diff --git a/plugin-maven/build.gradle b/plugin-maven/build.gradle index a98948ff8b..62a945bd71 100644 --- a/plugin-maven/build.gradle +++ b/plugin-maven/build.gradle @@ -78,6 +78,7 @@ dependencies { implementation "com.diffplug.durian:durian-core:${VER_DURIAN}" implementation "com.diffplug.durian:durian-collect:${VER_DURIAN}" + implementation "org.eclipse.jgit:org.eclipse.jgit:${VER_JGIT}" testImplementation project(":testlib") testImplementation "junit:junit:${VER_JUNIT}" diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java index ede8e58389..d3f410a754 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,13 @@ import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -36,6 +42,7 @@ import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.RemoteRepository; +import com.diffplug.common.collect.Iterables; import com.diffplug.spotless.Formatter; import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.Provisioner; @@ -76,6 +83,9 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo { @Parameter(defaultValue = DEFAULT_LINE_ENDINGS) private LineEnding lineEndings; + @Parameter + private String ratchetFrom; + @Parameter private LicenseHeader licenseHeader; @@ -110,12 +120,11 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo { @Parameter(property = "spotlessFiles") private String filePatterns; - protected abstract void process(List files, Formatter formatter) throws MojoExecutionException; + protected abstract void process(Iterable files, Formatter formatter) throws MojoExecutionException; @Override public final void execute() throws MojoExecutionException { List formatterFactories = getFormatterFactories(); - for (FormatterFactory formatterFactory : formatterFactories) { execute(formatterFactory); } @@ -123,8 +132,16 @@ public final void execute() throws MojoExecutionException { private void execute(FormatterFactory formatterFactory) throws MojoExecutionException { List files = collectFiles(formatterFactory); - try (Formatter formatter = formatterFactory.newFormatter(files, getFormatterConfig())) { - process(files, formatter); + FormatterConfig config = getFormatterConfig(); + Optional ratchetFrom = formatterFactory.ratchetFrom(config); + Iterable toFormat; + if (!ratchetFrom.isPresent()) { + toFormat = files; + } else { + toFormat = Iterables.filter(files, GitRatchetMaven.instance().isGitDirty(baseDir, ratchetFrom.get())); + } + try (Formatter formatter = formatterFactory.newFormatter(files, config)) { + process(toFormat, formatter); } } @@ -172,7 +189,7 @@ private FormatterConfig getFormatterConfig() { Provisioner provisioner = MavenProvisioner.create(resolver); List formatterStepFactories = getFormatterStepFactories(); FileLocator fileLocator = getFileLocator(); - return new FormatterConfig(baseDir, encoding, lineEndings, provisioner, fileLocator, formatterStepFactories); + return new FormatterConfig(baseDir, encoding, lineEndings, Optional.ofNullable(ratchetFrom), provisioner, fileLocator, formatterStepFactories); } private FileLocator getFileLocator() { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java index bc2e5812a4..6127d93d58 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.io.File; import java.util.List; +import java.util.Optional; import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.Provisioner; @@ -27,14 +28,16 @@ public class FormatterConfig { private final String encoding; private final LineEnding lineEndings; + private final Optional ratchetFrom; private final Provisioner provisioner; private final FileLocator fileLocator; private final List globalStepFactories; - public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Provisioner provisioner, + public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Optional ratchetFrom, Provisioner provisioner, FileLocator fileLocator, List globalStepFactories) { this.encoding = encoding; this.lineEndings = lineEndings; + this.ratchetFrom = ratchetFrom; this.provisioner = provisioner; this.fileLocator = fileLocator; this.globalStepFactories = globalStepFactories; @@ -48,6 +51,10 @@ public LineEnding getLineEndings() { return lineEndings; } + public Optional getRatchetFrom() { + return ratchetFrom; + } + public Provisioner getProvisioner() { return provisioner; } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java index d3ad7062ca..bbed6ad1a4 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import org.apache.maven.plugins.annotations.Parameter; @@ -41,6 +42,12 @@ public abstract class FormatterFactory { @Parameter private LineEnding lineEndings; + /** Sentinel to distinguish between "don't ratchet this format" and "use spotless parent format". */ + private static final String RATCHETFROM_NOT_SET_AT_FORMAT_LEVEL = " not set at format level "; + + @Parameter(defaultValue = RATCHETFROM_NOT_SET_AT_FORMAT_LEVEL) + private String ratchetFrom; + @Parameter private String[] includes; @@ -128,6 +135,14 @@ private LineEnding lineEndings(FormatterConfig config) { return lineEndings == null ? config.getLineEndings() : lineEndings; } + Optional ratchetFrom(FormatterConfig config) { + if (ratchetFrom == RATCHETFROM_NOT_SET_AT_FORMAT_LEVEL) { + return config.getRatchetFrom(); + } else { + return Optional.ofNullable(ratchetFrom); + } + } + private FormatterStepConfig stepConfig(Charset encoding, FormatterConfig config) { return new FormatterStepConfig(encoding, licenseHeaderDelimiter(), config.getProvisioner(), config.getFileLocator()); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/GitRatchetMaven.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/GitRatchetMaven.java new file mode 100644 index 0000000000..a759f05b06 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/GitRatchetMaven.java @@ -0,0 +1,64 @@ +/* + * 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.maven; + +import java.io.File; +import java.io.IOException; +import java.util.function.Predicate; + +import org.eclipse.jgit.lib.ObjectId; + +import com.diffplug.common.base.Errors; +import com.diffplug.spotless.extra.GitRatchet; + +final class GitRatchetMaven extends GitRatchet { + private GitRatchetMaven() {} + + @Override + protected File getDir(File project) { + return project; + } + + @Override + protected File getParent(File project) { + return project.getParentFile(); + } + + private static volatile GitRatchetMaven instance = new GitRatchetMaven(); + + static GitRatchetMaven instance() { + if (instance == null) { + synchronized (GitRatchetMaven.class) { + if (instance == null) { + instance = new GitRatchetMaven(); + } + } + } + return instance; + } + + /** A predicate which returns only the "git dirty" files. */ + Predicate isGitDirty(File baseDir, String ratchetFrom) { + ObjectId sha = rootTreeShaOf(baseDir, ratchetFrom); + return file -> { + try { + return !isClean(baseDir, sha, file); + } catch (IOException e) { + throw Errors.asRuntime(e); + } + }; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java index 3d85c27ab2..893a70f3f4 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-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,7 +17,6 @@ import java.io.File; import java.io.IOException; -import java.util.List; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; @@ -35,7 +34,7 @@ public class SpotlessApplyMojo extends AbstractSpotlessMojo { private boolean skip; @Override - protected void process(List files, Formatter formatter) throws MojoExecutionException { + protected void process(Iterable files, Formatter formatter) throws MojoExecutionException { if (skip) { getLog().info("Spotless apply skipped"); return; diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java index 4457ed5bc6..6f555890ca 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ public class SpotlessCheckMojo extends AbstractSpotlessMojo { private boolean skip; @Override - protected void process(List files, Formatter formatter) throws MojoExecutionException { + protected void process(Iterable files, Formatter formatter) throws MojoExecutionException { if (skip) { getLog().info("Spotless check skipped"); return; diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/GitRatchetMavenTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/GitRatchetMavenTest.java new file mode 100644 index 0000000000..ae8cac7669 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/GitRatchetMavenTest.java @@ -0,0 +1,133 @@ +/* + * 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.maven; + +import java.io.IOException; + +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; + +public class GitRatchetMavenTest extends MavenIntegrationHarness { + private static final String TEST_PATH = "src/markdown/test.md"; + + 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; + } + + @Test + public void singleProjectExhaustive() throws Exception { + try (Git git = initRepo()) { + writePom( + "", + " ", + " baseline", + " ", + " src/markdown/*.md", + " ", + " ", + " Lowercase hello", + " HELLO", + " hello", + " ", + " ", + " Lowercase world", + " WORLD", + " world", + " ", + " ", + " Lowercase world", + " MOM", + " mom", + " ", + " ", + ""); + 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(); + mavenRunner().withArguments("spotless:apply").runNoError(); + 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 + 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(); + } + } + } + + private void assertClean() throws Exception { + mavenRunner().withArguments("spotless:check").runNoError(); + } + + private void assertDirty() throws Exception { + mavenRunner().withArguments("spotless:check").runHasError(); + } +}