Skip to content

Commit b9c4bd8

Browse files
authored
If Git Diff (#31)
2 parents cb44aba + e72ad0c commit b9c4bd8

File tree

10 files changed

+464
-54
lines changed

10 files changed

+464
-54
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

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

911
## [2.3.2] - 2021-11-29
1012
### Fixed

IF_GIT_DIFF.md

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# If Git Diff
2+
<!---freshmark shields
3+
output = [
4+
link(shield('Gradle plugin', 'plugins.gradle.org', 'com.diffplug.if-git-diff', 'blue'), 'https://plugins.gradle.org/plugin/com.diffplug.if-git-diff'),
5+
link(shield('Maven central', 'mavencentral', 'available', 'blue'), 'https://search.maven.org/search?q=g:com.diffplug.spotless-changelog'),
6+
link(shield('Apache 2.0', 'license', 'apache-2.0', 'blue'), 'https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)'),
7+
'',
8+
link(shield('Changelog', 'changelog', versionLast, 'brightgreen'), 'CHANGELOG.md'),
9+
link(shield('Javadoc', 'javadoc', 'yes', 'brightgreen'), 'https://javadoc.jitpack.io/com/github/diffplug/spotless-changelog/spotless-changelog-agg/release~{{versionLast}}/javadoc/'),
10+
link(shield('Live chat', 'gitter', 'chat', 'brightgreen'), 'https://gitter.im/diffplug/spotless-changelog'),
11+
link(image('CircleCI', 'https://circleci.com/gh/diffplug/spotless-changelog.svg?style=shield'), 'https://circleci.com/gh/diffplug/spotless-changelog')
12+
].join('\n');
13+
-->
14+
[![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)
15+
[![Maven central](https://img.shields.io/badge/mavencentral-available-blue.svg)](https://search.maven.org/search?q=g:com.diffplug.spotless-changelog)
16+
[![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))
17+
18+
[![Changelog](https://img.shields.io/badge/changelog-2.3.2-brightgreen.svg)](CHANGELOG.md)
19+
[![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/)
20+
[![Live chat](https://img.shields.io/badge/gitter-chat-brightgreen.svg)](https://gitter.im/diffplug/spotless-changelog)
21+
[![CircleCI](https://circleci.com/gh/diffplug/spotless-changelog.svg?style=shield)](https://circleci.com/gh/diffplug/spotless-changelog)
22+
<!---freshmark /shields -->
23+
24+
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.
25+
26+
```gradle
27+
plugins {
28+
id 'com.diffplug.if-git-diff'
29+
}
30+
ifGitDiff {
31+
baseline 'origin/main' // default value
32+
inFolder 'a', { include 'a' }
33+
inFolder 'b', { include 'b' }
34+
}
35+
```
36+
37+
## Limitations
38+
39+
This plugin does not work well with the configuration cache. Using the example above:
40+
41+
- 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.
42+
- now add a file `a/blah`
43+
- now if you run `gradlew test`
44+
- without configuration-cache -> `:test` and `:a:test` -> good!
45+
- with configuration-cache -> only `:test` -> bad, cached configuration doesn't know that `a/blah` was added
46+
47+
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.
48+
49+
## Roadmap
50+
51+
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".
52+
53+
## Reference
54+
55+
<!---freshmark version
56+
output = prefixDelimiterReplace(input, "id 'com.diffplug.spotless-changelog' version '", "'", versionLast)
57+
output = prefixDelimiterReplace(output, 'https://github.com/diffplug/spotless-changelog/blob/release/', '/spotless', versionLast)
58+
output = prefixDelimiterReplace(output, 'https://javadoc.io/static/com.diffplug.spotless-changelog/spotless-changelog-plugin-gradle/', '/', versionLast)
59+
-->
60+
61+
[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).
62+
63+
<!---freshmark /version -->
64+
65+
## Acknowledgments
66+
67+
- Git stuff by [jgit](https://www.eclipse.org/jgit/).
68+
- Built by [gradle](https://gradle.org/).
69+
- Maintained by [DiffPlug](https://www.diffplug.com/).

gradle.properties

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
license=apache
22
git_url=github.com/diffplug/spotless-changelog
33
plugin_tags=changelog keepachangelog version git
4-
plugin_list=spotlessChangelog
4+
plugin_list=spotlessChangelog ifGitDiff
55
plugin_spotlessChangelog_id=com.diffplug.spotless-changelog
66
plugin_spotlessChangelog_impl=com.diffplug.spotless.changelog.gradle.ChangelogPlugin
77
plugin_spotlessChangelog_name=Spotless Changelog
88
plugin_spotlessChangelog_desc=The changelog is cast, let the versions fall where they may.
9+
plugin_ifGitDiff_id=com.diffplug.if-git-diff
10+
plugin_ifGitDiff_impl=com.diffplug.spotless.changelog.gradle.IfGitDiffPlugin
11+
plugin_ifGitDiff_name=If Git Diff
12+
plugin_ifGitDiff_desc=Decide what to configure based on changes relative to `origin/main`
13+
914
maven_group=com.diffplug.spotless-changelog
1015
javadoc_links=\
1116
https://docs.oracle.com/javase/8/docs/api/ \
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright (C) 2022 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.changelog.gradle;
17+
18+
19+
import com.diffplug.common.base.Preconditions;
20+
import java.io.File;
21+
import java.io.IOException;
22+
import java.util.List;
23+
import org.eclipse.jgit.api.Git;
24+
import org.eclipse.jgit.api.errors.GitAPIException;
25+
import org.eclipse.jgit.diff.DiffEntry;
26+
import org.eclipse.jgit.lib.ObjectId;
27+
import org.eclipse.jgit.lib.ObjectReader;
28+
import org.eclipse.jgit.lib.Repository;
29+
import org.eclipse.jgit.revwalk.RevWalk;
30+
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
31+
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
32+
import org.eclipse.jgit.treewalk.filter.PathFilter;
33+
import org.eclipse.jgit.treewalk.filter.TreeFilter;
34+
import org.gradle.api.Action;
35+
import org.gradle.api.GradleException;
36+
import org.gradle.api.Project;
37+
import org.gradle.api.initialization.Settings;
38+
39+
public abstract class IfGitDiffExtension<T> {
40+
static final String NAME = "ifGitDiff";
41+
42+
public static class ForProject extends IfGitDiffExtension<Project> {
43+
public ForProject(Project owner) {
44+
super(owner);
45+
}
46+
47+
@Override
48+
protected File file(Object fileArg) {
49+
return owner.file(fileArg);
50+
}
51+
}
52+
53+
public static class ForSettings extends IfGitDiffExtension<Settings> {
54+
public ForSettings(Settings owner) {
55+
super(owner);
56+
}
57+
58+
@Override
59+
protected File file(Object fileArg) {
60+
if (fileArg instanceof File) {
61+
return (File) fileArg;
62+
} else if (fileArg instanceof String) {
63+
return new File(owner.getRootDir(), (String) fileArg);
64+
} else {
65+
throw new IllegalArgumentException("We only support String or File, this was " + fileArg.getClass());
66+
}
67+
}
68+
}
69+
70+
final T owner;
71+
72+
IfGitDiffExtension(T owner) {
73+
this.owner = owner;
74+
}
75+
76+
private String baseline = "origin/main";
77+
78+
public void setBaseline(String baseline) {
79+
this.baseline = baseline;
80+
}
81+
82+
public String getBaseline() {
83+
return baseline;
84+
}
85+
86+
protected abstract File file(Object fileArg);
87+
88+
private TreeFilter filterTo(Repository repo, File child) {
89+
String rootAbs = repo.getWorkTree().getAbsolutePath();
90+
String childAbs = child.getAbsolutePath();
91+
if (rootAbs.equals(childAbs)) {
92+
return TreeFilter.ALL;
93+
} else if (childAbs.startsWith(rootAbs)) {
94+
String filter = childAbs.substring(rootAbs.length()).replace('\\', '/');
95+
Preconditions.checkState(filter.charAt(0) == '/');
96+
return PathFilter.create(filter.substring(1));
97+
} else {
98+
throw new GradleException(childAbs + " is not contained within the git repo " + rootAbs);
99+
}
100+
}
101+
102+
public void inFolder(Object folder, Action<T> onChanged) {
103+
try (Repository repo = new FileRepositoryBuilder()
104+
.findGitDir(file(""))
105+
.build()) {
106+
ObjectId baselineSha = repo.resolve(baseline);
107+
if (baselineSha == null) {
108+
throw new GradleException("Unable to resolve " + baseline);
109+
}
110+
111+
CanonicalTreeParser baselineTree = new CanonicalTreeParser();
112+
try (ObjectReader reader = repo.newObjectReader()) {
113+
RevWalk walk = new RevWalk(reader);
114+
baselineTree.reset(reader, walk.parseCommit(baselineSha).getTree());
115+
}
116+
Git git = new Git(repo);
117+
List<DiffEntry> changes = git.diff()
118+
.setOldTree(baselineTree)
119+
.setShowNameAndStatusOnly(true)
120+
.setPathFilter(filterTo(repo, file(folder)))
121+
.call();
122+
if (!changes.isEmpty()) {
123+
onChanged.execute(owner);
124+
}
125+
} catch (IOException e) {
126+
throw new GradleException("Unable to find git repository", e);
127+
} catch (GitAPIException e) {
128+
throw new RuntimeException(e);
129+
}
130+
}
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (C) 2019-2022 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.changelog.gradle;
17+
18+
19+
import org.gradle.api.Plugin;
20+
import org.gradle.api.Project;
21+
import org.gradle.api.initialization.Settings;
22+
import org.gradle.api.plugins.ExtensionAware;
23+
24+
/** @see IfGitDiffExtension */
25+
public class IfGitDiffPlugin implements Plugin<ExtensionAware> {
26+
@Override
27+
public void apply(ExtensionAware projectOrSettings) {
28+
if (projectOrSettings instanceof Project) {
29+
Project project = (Project) projectOrSettings;
30+
projectOrSettings.getExtensions().create(IfGitDiffExtension.NAME, IfGitDiffExtension.ForProject.class, project);
31+
} else if (projectOrSettings instanceof Settings) {
32+
Settings settings = (Settings) projectOrSettings;
33+
projectOrSettings.getExtensions().create(IfGitDiffExtension.NAME, IfGitDiffExtension.ForSettings.class, settings);
34+
} else {
35+
throw new IllegalArgumentException("We support build.gradle and settings.gradle, this was " + projectOrSettings.getClass());
36+
}
37+
}
38+
}

spotless-changelog-plugin-gradle/src/test/java/com/diffplug/spotless/changelog/gradle/ChangelogPluginTest.java

+15-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2021 DiffPlug
2+
* Copyright (C) 2019-2022 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,8 +26,8 @@ public class ChangelogPluginTest extends GradleHarness {
2626
private static final String DATE_NOW = "2019-01-30";
2727

2828
private void writeSpotlessChangelog(String... lines) throws IOException {
29-
write("settings.gradle", "rootProject.name='undertest'");
30-
write("build.gradle",
29+
setFile("settings.gradle").toContent("rootProject.name='undertest'");
30+
setFile("build.gradle").toLines(
3131
"plugins {",
3232
" id 'com.diffplug.spotless-changelog'",
3333
"}",
@@ -62,15 +62,15 @@ public void missingChangelog() throws IOException {
6262
@Test
6363
public void changelogCheck() throws IOException {
6464
writeSpotlessChangelog();
65-
write("CHANGELOG.md",
65+
setFile("CHANGELOG.md").toLines(
6666
"",
6767
"## [Unreleased]",
6868
"",
6969
"## [1.0.0]");
7070
assertFailOutput("changelogCheck")
71-
.contains("CHANGELOG.md:5: '] - ' is missing from the expected '## [x.y.z] - yyyy-mm-dd");
71+
.contains("CHANGELOG.md:4: '] - ' is missing from the expected '## [x.y.z] - yyyy-mm-dd");
7272

73-
write("CHANGELOG.md",
73+
setFile("CHANGELOG.md").toLines(
7474
"",
7575
"## [Unreleased]",
7676
"",
@@ -81,30 +81,30 @@ public void changelogCheck() throws IOException {
8181
@Test
8282
public void changelogPrint() throws IOException {
8383
writeSpotlessChangelog();
84-
write("CHANGELOG.md",
84+
setFile("CHANGELOG.md").toLines(
8585
"",
8686
"## [Unreleased]",
8787
"",
8888
"## [1.0.0] - 2020-10-10");
8989
assertOutput("changelogPrint")
9090
.startsWith("> Task :changelogPrint\nundertest 1.0.0 (no unreleased changes)");
91-
write("CHANGELOG.md",
91+
setFile("CHANGELOG.md").toLines(
9292
"",
9393
"## [Unreleased]",
9494
"Some minor change",
9595
"",
9696
"## [1.0.0] - 2020-10-10");
9797
assertOutput("changelogPrint")
9898
.startsWith("> Task :changelogPrint\nundertest 1.0.0 -> 1.0.1");
99-
write("CHANGELOG.md",
99+
setFile("CHANGELOG.md").toLines(
100100
"",
101101
"## [Unreleased]",
102102
"### Added",
103103
"",
104104
"## [1.0.0] - 2020-10-10");
105105
assertOutput("changelogPrint")
106106
.startsWith("> Task :changelogPrint\nundertest 1.0.0 -> 1.1.0");
107-
write("CHANGELOG.md",
107+
setFile("CHANGELOG.md").toLines(
108108
"",
109109
"## [Unreleased]",
110110
"**BREAKING**",
@@ -117,7 +117,7 @@ public void changelogPrint() throws IOException {
117117
@Test
118118
public void changelogBump() throws IOException {
119119
writeSpotlessChangelog();
120-
write("CHANGELOG.md",
120+
setFile("CHANGELOG.md").toLines(
121121
"",
122122
"## [Unreleased]",
123123
"",
@@ -126,7 +126,7 @@ public void changelogBump() throws IOException {
126126
gradleRunner().withArguments("changelogBump").build();
127127
assertFile("CHANGELOG.md").hasContent(noUnreleasedChanges);
128128

129-
write("CHANGELOG.md",
129+
setFile("CHANGELOG.md").toLines(
130130
"",
131131
"## [Unreleased]",
132132
"### Added",
@@ -147,7 +147,7 @@ public void changelogBump() throws IOException {
147147
@Test
148148
public void changelogBumpCustomNextVersionFunction() throws IOException {
149149
writeSpotlessChangelog("versionSchema(com.diffplug.spotless.changelog.NextVersionFunction.SemverBrandPrefix)");
150-
write("CHANGELOG.md",
150+
setFile("CHANGELOG.md").toLines(
151151
"",
152152
"## [Unreleased]",
153153
"",
@@ -156,7 +156,7 @@ public void changelogBumpCustomNextVersionFunction() throws IOException {
156156
gradleRunner().withArguments("changelogBump").build();
157157
assertFile("CHANGELOG.md").hasContent(noUnreleasedChanges);
158158

159-
write("CHANGELOG.md",
159+
setFile("CHANGELOG.md").toLines(
160160
"",
161161
"## [Unreleased]",
162162
"### Added",
@@ -177,7 +177,7 @@ public void changelogBumpCustomNextVersionFunction() throws IOException {
177177
@Test
178178
public void snapshot() throws IOException {
179179
writeSpotlessChangelog("appendDashSnapshotUnless_dashPrelease=true");
180-
write("CHANGELOG.md",
180+
setFile("CHANGELOG.md").toLines(
181181
"",
182182
"## [Unreleased]",
183183
"### Added",

0 commit comments

Comments
 (0)