Skip to content

Commit

Permalink
Merge pull request #603 from diffplug/feat/ratchetfrom-maven
Browse files Browse the repository at this point in the history
Add `ratchetFrom` to maven
  • Loading branch information
nedtwigg authored Jun 29, 2020
2 parents 07d5ab0 + a5d1453 commit 41ed32d
Show file tree
Hide file tree
Showing 15 changed files with 339 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Project> 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
Expand Down Expand Up @@ -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);
}
Expand All @@ -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)) {
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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 {
Expand All @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Project> {
@Override
protected File getDir(Project project) {
return project.getProjectDir();
}

@Override
protected @Nullable Project getParent(Project project) {
return project.getParent();
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 21 additions & 1 deletion plugin-maven/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a name="examples"></a>
<a name="ratchet"></a>

## 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
<configuration>
<ratchetFrom>origin/main</ratchetFrom> <!-- only format files which have changed since origin/main -->
<!-- ... define formats ... -->
</configuration>
```

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. `<configuration><java><ratchetFrom>...`).

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?

Expand All @@ -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.

<a name="examples"></a>

## Example configurations (from real-world projects)

- [Apache Avro](https://github.com/apache/avro/blob/8026c8ffe4ef67ab419dba73910636bf2c1a691c/lang/java/pom.xml#L307-L334)
Expand Down
1 change: 1 addition & 0 deletions plugin-maven/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -110,21 +120,28 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo {
@Parameter(property = "spotlessFiles")
private String filePatterns;

protected abstract void process(List<File> files, Formatter formatter) throws MojoExecutionException;
protected abstract void process(Iterable<File> files, Formatter formatter) throws MojoExecutionException;

@Override
public final void execute() throws MojoExecutionException {
List<FormatterFactory> formatterFactories = getFormatterFactories();

for (FormatterFactory formatterFactory : formatterFactories) {
execute(formatterFactory);
}
}

private void execute(FormatterFactory formatterFactory) throws MojoExecutionException {
List<File> files = collectFiles(formatterFactory);
try (Formatter formatter = formatterFactory.newFormatter(files, getFormatterConfig())) {
process(files, formatter);
FormatterConfig config = getFormatterConfig();
Optional<String> ratchetFrom = formatterFactory.ratchetFrom(config);
Iterable<File> 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);
}
}

Expand Down Expand Up @@ -172,7 +189,7 @@ private FormatterConfig getFormatterConfig() {
Provisioner provisioner = MavenProvisioner.create(resolver);
List<FormatterStepFactory> 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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -27,14 +28,16 @@ public class FormatterConfig {

private final String encoding;
private final LineEnding lineEndings;
private final Optional<String> ratchetFrom;
private final Provisioner provisioner;
private final FileLocator fileLocator;
private final List<FormatterStepFactory> globalStepFactories;

public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Provisioner provisioner,
public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Optional<String> ratchetFrom, Provisioner provisioner,
FileLocator fileLocator, List<FormatterStepFactory> globalStepFactories) {
this.encoding = encoding;
this.lineEndings = lineEndings;
this.ratchetFrom = ratchetFrom;
this.provisioner = provisioner;
this.fileLocator = fileLocator;
this.globalStepFactories = globalStepFactories;
Expand All @@ -48,6 +51,10 @@ public LineEnding getLineEndings() {
return lineEndings;
}

public Optional<String> getRatchetFrom() {
return ratchetFrom;
}

public Provisioner getProvisioner() {
return provisioner;
}
Expand Down
Loading

0 comments on commit 41ed32d

Please sign in to comment.