Skip to content

Commit

Permalink
If Git Diff (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg authored Jan 7, 2022
2 parents cb44aba + e72ad0c commit b9c4bd8
Show file tree
Hide file tree
Showing 10 changed files with 464 additions and 54 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- New plugin [`com.diffplug.if-git-diff`](IF_GIT_DIFF.md) ([#31](https://github.com/diffplug/spotless-changelog/pull/31)).

## [2.3.2] - 2021-11-29
### Fixed
Expand Down
69 changes: 69 additions & 0 deletions IF_GIT_DIFF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# If Git Diff
<!---freshmark shields
output = [
link(shield('Gradle plugin', 'plugins.gradle.org', 'com.diffplug.if-git-diff', 'blue'), 'https://plugins.gradle.org/plugin/com.diffplug.if-git-diff'),
link(shield('Maven central', 'mavencentral', 'available', 'blue'), 'https://search.maven.org/search?q=g:com.diffplug.spotless-changelog'),
link(shield('Apache 2.0', 'license', 'apache-2.0', 'blue'), 'https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)'),
'',
link(shield('Changelog', 'changelog', versionLast, 'brightgreen'), 'CHANGELOG.md'),
link(shield('Javadoc', 'javadoc', 'yes', 'brightgreen'), 'https://javadoc.jitpack.io/com/github/diffplug/spotless-changelog/spotless-changelog-agg/release~{{versionLast}}/javadoc/'),
link(shield('Live chat', 'gitter', 'chat', 'brightgreen'), 'https://gitter.im/diffplug/spotless-changelog'),
link(image('CircleCI', 'https://circleci.com/gh/diffplug/spotless-changelog.svg?style=shield'), 'https://circleci.com/gh/diffplug/spotless-changelog')
].join('\n');
-->
[![Gradle plugin](https://img.shields.io/badge/plugins.gradle.org-com.diffplug.if--git--diff-blue.svg)](https://plugins.gradle.org/plugin/com.diffplug.if-git-diff)
[![Maven central](https://img.shields.io/badge/mavencentral-available-blue.svg)](https://search.maven.org/search?q=g:com.diffplug.spotless-changelog)
[![Apache 2.0](https://img.shields.io/badge/license-apache--2.0-blue.svg)](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0))

[![Changelog](https://img.shields.io/badge/changelog-2.3.2-brightgreen.svg)](CHANGELOG.md)
[![Javadoc](https://img.shields.io/badge/javadoc-yes-brightgreen.svg)](https://javadoc.jitpack.io/com/github/diffplug/spotless-changelog/spotless-changelog-agg/release~2.3.2/javadoc/)
[![Live chat](https://img.shields.io/badge/gitter-chat-brightgreen.svg)](https://gitter.im/diffplug/spotless-changelog)
[![CircleCI](https://circleci.com/gh/diffplug/spotless-changelog.svg?style=shield)](https://circleci.com/gh/diffplug/spotless-changelog)
<!---freshmark /shields -->

This plugin can be applied in `settings.gradle` or `build.gradle`, and it lets you execute a block of code contingent on whether there are changes in the given folder relative to a given baseline git ref.

```gradle
plugins {
id 'com.diffplug.if-git-diff'
}
ifGitDiff {
baseline 'origin/main' // default value
inFolder 'a', { include 'a' }
inFolder 'b', { include 'b' }
}
```

## Limitations

This plugin does not work well with the configuration cache. Using the example above:

- run `gradlew test` on a clean checkout of `origin/main`, and you would see that `:test` ran but `:a:test` and `:b:test` did not; so far so good.
- now add a file `a/blah`
- now if you run `gradlew test`
- without configuration-cache -> `:test` and `:a:test` -> good!
- with configuration-cache -> only `:test` -> bad, cached configuration doesn't know that `a/blah` was added

A different approach which could work with configuration-cache is to mark tasks as up-to-date based on git status, see the [`GitDiffUpToDatePlugin`](https://github.com/thahnen/GitDiffUpToDatePlugin) for that.

## Roadmap

This plugin was built to solve [a fairly specific problem in the Spotless build](https://github.com/diffplug/spotless-changelog/issues/30). It is packaged with `spotless-changelog` because it's vaguely related, and it might make sense someday for `spotless-changelog` to assert "if files changed in X dir, then changelog Y must be updated".

## Reference

<!---freshmark version
output = prefixDelimiterReplace(input, "id 'com.diffplug.spotless-changelog' version '", "'", versionLast)
output = prefixDelimiterReplace(output, 'https://github.com/diffplug/spotless-changelog/blob/release/', '/spotless', versionLast)
output = prefixDelimiterReplace(output, 'https://javadoc.io/static/com.diffplug.spotless-changelog/spotless-changelog-plugin-gradle/', '/', versionLast)
-->

[Plugin DSL javadoc](https://javadoc.io/static/com.diffplug.spotless-changelog/spotless-changelog-plugin-gradle/2.3.2/com/diffplug/spotless/changelog/gradle/IfGitDiffExtension.html). For requirements see [spotless-changelog](https://github.com/diffplug/spotless-changelog#requirements).

<!---freshmark /version -->

## Acknowledgments

- Git stuff by [jgit](https://www.eclipse.org/jgit/).
- Built by [gradle](https://gradle.org/).
- Maintained by [DiffPlug](https://www.diffplug.com/).
7 changes: 6 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
license=apache
git_url=github.com/diffplug/spotless-changelog
plugin_tags=changelog keepachangelog version git
plugin_list=spotlessChangelog
plugin_list=spotlessChangelog ifGitDiff
plugin_spotlessChangelog_id=com.diffplug.spotless-changelog
plugin_spotlessChangelog_impl=com.diffplug.spotless.changelog.gradle.ChangelogPlugin
plugin_spotlessChangelog_name=Spotless Changelog
plugin_spotlessChangelog_desc=The changelog is cast, let the versions fall where they may.
plugin_ifGitDiff_id=com.diffplug.if-git-diff
plugin_ifGitDiff_impl=com.diffplug.spotless.changelog.gradle.IfGitDiffPlugin
plugin_ifGitDiff_name=If Git Diff
plugin_ifGitDiff_desc=Decide what to configure based on changes relative to `origin/main`

maven_group=com.diffplug.spotless-changelog
javadoc_links=\
https://docs.oracle.com/javase/8/docs/api/ \
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (C) 2022 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
*
* https://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.changelog.gradle;


import com.diffplug.common.base.Preconditions;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.initialization.Settings;

public abstract class IfGitDiffExtension<T> {
static final String NAME = "ifGitDiff";

public static class ForProject extends IfGitDiffExtension<Project> {
public ForProject(Project owner) {
super(owner);
}

@Override
protected File file(Object fileArg) {
return owner.file(fileArg);
}
}

public static class ForSettings extends IfGitDiffExtension<Settings> {
public ForSettings(Settings owner) {
super(owner);
}

@Override
protected File file(Object fileArg) {
if (fileArg instanceof File) {
return (File) fileArg;
} else if (fileArg instanceof String) {
return new File(owner.getRootDir(), (String) fileArg);
} else {
throw new IllegalArgumentException("We only support String or File, this was " + fileArg.getClass());
}
}
}

final T owner;

IfGitDiffExtension(T owner) {
this.owner = owner;
}

private String baseline = "origin/main";

public void setBaseline(String baseline) {
this.baseline = baseline;
}

public String getBaseline() {
return baseline;
}

protected abstract File file(Object fileArg);

private TreeFilter filterTo(Repository repo, File child) {
String rootAbs = repo.getWorkTree().getAbsolutePath();
String childAbs = child.getAbsolutePath();
if (rootAbs.equals(childAbs)) {
return TreeFilter.ALL;
} else if (childAbs.startsWith(rootAbs)) {
String filter = childAbs.substring(rootAbs.length()).replace('\\', '/');
Preconditions.checkState(filter.charAt(0) == '/');
return PathFilter.create(filter.substring(1));
} else {
throw new GradleException(childAbs + " is not contained within the git repo " + rootAbs);
}
}

public void inFolder(Object folder, Action<T> onChanged) {
try (Repository repo = new FileRepositoryBuilder()
.findGitDir(file(""))
.build()) {
ObjectId baselineSha = repo.resolve(baseline);
if (baselineSha == null) {
throw new GradleException("Unable to resolve " + baseline);
}

CanonicalTreeParser baselineTree = new CanonicalTreeParser();
try (ObjectReader reader = repo.newObjectReader()) {
RevWalk walk = new RevWalk(reader);
baselineTree.reset(reader, walk.parseCommit(baselineSha).getTree());
}
Git git = new Git(repo);
List<DiffEntry> changes = git.diff()
.setOldTree(baselineTree)
.setShowNameAndStatusOnly(true)
.setPathFilter(filterTo(repo, file(folder)))
.call();
if (!changes.isEmpty()) {
onChanged.execute(owner);
}
} catch (IOException e) {
throw new GradleException("Unable to find git repository", e);
} catch (GitAPIException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2019-2022 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
*
* https://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.changelog.gradle;


import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.initialization.Settings;
import org.gradle.api.plugins.ExtensionAware;

/** @see IfGitDiffExtension */
public class IfGitDiffPlugin implements Plugin<ExtensionAware> {
@Override
public void apply(ExtensionAware projectOrSettings) {
if (projectOrSettings instanceof Project) {
Project project = (Project) projectOrSettings;
projectOrSettings.getExtensions().create(IfGitDiffExtension.NAME, IfGitDiffExtension.ForProject.class, project);
} else if (projectOrSettings instanceof Settings) {
Settings settings = (Settings) projectOrSettings;
projectOrSettings.getExtensions().create(IfGitDiffExtension.NAME, IfGitDiffExtension.ForSettings.class, settings);
} else {
throw new IllegalArgumentException("We support build.gradle and settings.gradle, this was " + projectOrSettings.getClass());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2021 DiffPlug
* Copyright (C) 2019-2022 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 @@ -26,8 +26,8 @@ public class ChangelogPluginTest extends GradleHarness {
private static final String DATE_NOW = "2019-01-30";

private void writeSpotlessChangelog(String... lines) throws IOException {
write("settings.gradle", "rootProject.name='undertest'");
write("build.gradle",
setFile("settings.gradle").toContent("rootProject.name='undertest'");
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless-changelog'",
"}",
Expand Down Expand Up @@ -62,15 +62,15 @@ public void missingChangelog() throws IOException {
@Test
public void changelogCheck() throws IOException {
writeSpotlessChangelog();
write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"",
"## [1.0.0]");
assertFailOutput("changelogCheck")
.contains("CHANGELOG.md:5: '] - ' is missing from the expected '## [x.y.z] - yyyy-mm-dd");
.contains("CHANGELOG.md:4: '] - ' is missing from the expected '## [x.y.z] - yyyy-mm-dd");

write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"",
Expand All @@ -81,30 +81,30 @@ public void changelogCheck() throws IOException {
@Test
public void changelogPrint() throws IOException {
writeSpotlessChangelog();
write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"",
"## [1.0.0] - 2020-10-10");
assertOutput("changelogPrint")
.startsWith("> Task :changelogPrint\nundertest 1.0.0 (no unreleased changes)");
write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"Some minor change",
"",
"## [1.0.0] - 2020-10-10");
assertOutput("changelogPrint")
.startsWith("> Task :changelogPrint\nundertest 1.0.0 -> 1.0.1");
write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"### Added",
"",
"## [1.0.0] - 2020-10-10");
assertOutput("changelogPrint")
.startsWith("> Task :changelogPrint\nundertest 1.0.0 -> 1.1.0");
write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"**BREAKING**",
Expand All @@ -117,7 +117,7 @@ public void changelogPrint() throws IOException {
@Test
public void changelogBump() throws IOException {
writeSpotlessChangelog();
write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"",
Expand All @@ -126,7 +126,7 @@ public void changelogBump() throws IOException {
gradleRunner().withArguments("changelogBump").build();
assertFile("CHANGELOG.md").hasContent(noUnreleasedChanges);

write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"### Added",
Expand All @@ -147,7 +147,7 @@ public void changelogBump() throws IOException {
@Test
public void changelogBumpCustomNextVersionFunction() throws IOException {
writeSpotlessChangelog("versionSchema(com.diffplug.spotless.changelog.NextVersionFunction.SemverBrandPrefix)");
write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"",
Expand All @@ -156,7 +156,7 @@ public void changelogBumpCustomNextVersionFunction() throws IOException {
gradleRunner().withArguments("changelogBump").build();
assertFile("CHANGELOG.md").hasContent(noUnreleasedChanges);

write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"### Added",
Expand All @@ -177,7 +177,7 @@ public void changelogBumpCustomNextVersionFunction() throws IOException {
@Test
public void snapshot() throws IOException {
writeSpotlessChangelog("appendDashSnapshotUnless_dashPrelease=true");
write("CHANGELOG.md",
setFile("CHANGELOG.md").toLines(
"",
"## [Unreleased]",
"### Added",
Expand Down
Loading

0 comments on commit b9c4bd8

Please sign in to comment.