Skip to content

Commit

Permalink
Allow to ignore certain files from the Git repo change detection
Browse files Browse the repository at this point in the history
This includes refactoring those methods that accept a ton of mojo
configuration parameters. There is now a single class that wraps all of
those.

Fixes danielflower#77
  • Loading branch information
marcelstoer committed Sep 24, 2019
1 parent 1c661aa commit 25f3aed
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 68 deletions.
6 changes: 2 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.github.danielflower.mavenplugins</groupId>
<artifactId>multi-module-maven-release-plugin</artifactId>
<version>3.3-SNAPSHOT</version> <!-- When changing also update scaffolding.TestProject.PLUGIN_VERSION_FOR_TESTS and add to src/site/markdown/changelog.md -->
<version>3.4-SNAPSHOT</version> <!-- When changing also update scaffolding.TestProject.PLUGIN_VERSION_FOR_TESTS and add to src/site/markdown/changelog.md -->

<name>The Multi Module Maven Release Plugin</name>
<description>A maven release plugin built for multi-maven-module git repositories allowing continuous deployment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.apache.maven.model.Scm;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
Expand All @@ -19,15 +18,17 @@
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;

import java.util.List;
import java.util.Set;

import static java.lang.String.format;
import static java.util.Arrays.asList;

/**
* @author Roland Hauser [email protected]
*
*/
public abstract class BaseMojo extends AbstractMojo {
private PluginConfiguration pluginConfiguration;

/**
* The Maven Project.
*/
Expand Down Expand Up @@ -185,6 +186,23 @@ public void setBuildNumber(String buildNumber) {
@Parameter(property = "arguments")
public String arguments;

/**
* <p>List of relative file system paths to ignore when detecting changes in the project(s).</p>
* <p>The primarily purpose is to skip creating new releases if only "infrastructure" files such as
* <code>.gitignore</code>, <code>.editorconfig</code> and the like changed. Very basic wild cards are supported as
* follows:
* <ul>
* <li><code>foo.txt</code> - matches <code>foo.txt</code> in the root of the top-level project</li>
* <li><code>bar/foo.txt</code> - matches <code>foo.txt</code> in the root of the <code>bar</code> directory</li>
* <li><code>bar</code> - matches the <code>foo</code> directory and ignores everything below</li>
* <li><code>**.txt</code> - matches all paths ending in <code>.txt</code> (suffix match)</li>
* <li><code>**.editorconfig</code> - matches all <code>.editorconfig</code> files in all (sub)directories; a special case of suffix matching</li>
* <ul/>
* <p/>
*/
@Parameter(property = "ignoredPaths")
Set<String> ignoredPaths;

final void setSettings(final Settings settings) {
this.settings = settings;
}
Expand Down Expand Up @@ -275,4 +293,54 @@ protected static String getRemoteUrlOrNullIfNoneSet(Scm originalScm, Scm actualS
}
return GitHelper.scmUrlToRemote(remote);
}

PluginConfiguration getPluginConfiguration() {
if (pluginConfiguration == null) {
pluginConfiguration = new PluginConfiguration(project, projects, buildNumber, versionNamer,
modulesToRelease, modulesToForceRelease, noChangesAction, factory, artifactResolver, remoteRepositories,
localRepository, settings, pullTags, arguments, ignoredPaths);
}
return pluginConfiguration;
}

static class PluginConfiguration {
final MavenProject rootProject;
final List<MavenProject> projects;
final String buildNumber;
final VersionNamer versionNamer;
final List<String> modulesToRelease;
final List<String> modulesToForceRelease;
final NoChangesAction noChangesAction;
final ArtifactFactory factory;
final ArtifactResolver artifactResolver;
final List remoteRepositories;
final ArtifactRepository localRepository;
final Settings settings;
final boolean pullTags;
final String arguments;
final Set<String> ignoredPaths;

PluginConfiguration(MavenProject rootProject, List<MavenProject> projects, String buildNumber,
VersionNamer versionNamer, List<String> modulesToRelease,
List<String> modulesToForceRelease, NoChangesAction noChangesAction,
ArtifactFactory factory, ArtifactResolver artifactResolver, List remoteRepositories,
ArtifactRepository localRepository, Settings settings, boolean pullTags, String arguments,
Set<String> ignoredPaths) {
this.rootProject = rootProject;
this.projects = projects;
this.buildNumber = buildNumber;
this.versionNamer = versionNamer;
this.modulesToRelease = modulesToRelease;
this.modulesToForceRelease = modulesToForceRelease;
this.noChangesAction = noChangesAction;
this.factory = factory;
this.artifactResolver = artifactResolver;
this.remoteRepositories = remoteRepositories;
this.localRepository = localRepository;
this.settings = settings;
this.pullTags = pullTags;
this.arguments = arguments;
this.ignoredPaths = ignoredPaths;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ public void execute() throws MojoExecutionException, MojoFailureException {
project.getModel().getScm()))
.credentialsProvider(getCredentialsProvider(log))
.buildFromCurrentDir();
ResolverWrapper resolverWrapper = new ResolverWrapper(factory, artifactResolver, remoteRepositories, localRepository);
Reactor reactor = Reactor.fromProjects(log, repo, project, projects, buildNumber, modulesToForceRelease, noChangesAction, resolverWrapper, versionNamer);
ResolverWrapper resolverWrapper = new ResolverWrapper(getPluginConfiguration());
Reactor reactor = Reactor.fromProjects(repo, getPluginConfiguration(), resolverWrapper, noChangesAction, log);
if (reactor == null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ public List<ReleasableModule> getModulesInBuildOrder() {
return modulesInBuildOrder;
}

public static Reactor fromProjects(Log log, LocalGitRepo gitRepo, MavenProject rootProject, List<MavenProject> projects, String buildNumber, List<String> modulesToForceRelease, NoChangesAction actionWhenNoChangesDetected, ResolverWrapper resolverWrapper, VersionNamer versionNamer) throws ValidationException, GitAPIException, MojoExecutionException {
DiffDetector detector = new TreeWalkingDiffDetector(gitRepo.git.getRepository());
List<ReleasableModule> modules = new ArrayList<ReleasableModule>();
static Reactor fromProjects(LocalGitRepo gitRepo, BaseMojo.PluginConfiguration pluginConfiguration, ResolverWrapper resolverWrapper, NoChangesAction noChangesAction, Log log) throws ValidationException, GitAPIException, MojoExecutionException {
DiffDetector detector = new TreeWalkingDiffDetector(gitRepo.git.getRepository(), pluginConfiguration.ignoredPaths);
List<ReleasableModule> modules = new ArrayList<>();

resolveVersionsDefinedThroughProperties(projects);
resolveVersionsDefinedThroughProperties(pluginConfiguration.projects);

VersionNamer versionNamer = pluginConfiguration.versionNamer;
AnnotatedTagFinder annotatedTagFinder = new AnnotatedTagFinder(versionNamer);
for (MavenProject project : projects) {
String relativePathToModule = calculateModulePath(rootProject, project);
for (MavenProject project : pluginConfiguration.projects) {
String relativePathToModule = calculateModulePath(pluginConfiguration.rootProject, project);
String artifactId = project.getArtifactId();
String versionWithoutBuildNumber = project.getVersion().replace("-SNAPSHOT", "");
List<AnnotatedTag> previousTagsForThisModule = annotatedTagFinder.tagsForVersion(gitRepo.git, artifactId, versionWithoutBuildNumber);
Expand All @@ -53,7 +54,7 @@ public static Reactor fromProjects(Log log, LocalGitRepo gitRepo, MavenProject r
Collection<String> remoteBuildNumbers = getRemoteBuildNumbers(gitRepo, artifactId, versionWithoutBuildNumber, versionNamer);
previousBuildNumbers.addAll(remoteBuildNumbers);

VersionName newVersion = versionNamer.name(project.getVersion(), buildNumber, previousBuildNumbers);
VersionName newVersion = versionNamer.name(project.getVersion(), pluginConfiguration.buildNumber, previousBuildNumbers);

boolean oneOfTheDependenciesHasChanged = false;
String changedDependency = null;
Expand Down Expand Up @@ -90,7 +91,7 @@ public static Reactor fromProjects(Log log, LocalGitRepo gitRepo, MavenProject r

String equivalentVersion = null;

if(modulesToForceRelease != null && modulesToForceRelease.contains(artifactId)) {
if(pluginConfiguration.modulesToForceRelease != null && pluginConfiguration.modulesToForceRelease.contains(artifactId)) {
log.info("Releasing " + artifactId + " " + newVersion.releaseVersion() + " as we was asked to forced release.");
} else if (oneOfTheDependenciesHasChanged) {
log.info("Releasing " + artifactId + " " + newVersion.releaseVersion() + " as " + changedDependency + " has changed.");
Expand All @@ -114,15 +115,15 @@ public static Reactor fromProjects(Log log, LocalGitRepo gitRepo, MavenProject r
}

if (!atLeastOneBeingReleased(modules)) {
switch (actionWhenNoChangesDetected) {
switch (noChangesAction) {
case ReleaseNone:
log.warn("No changes have been detected in any modules so will not perform release");
return null;
case FailBuild:
throw new MojoExecutionException("No module changes have been detected");
default:
log.warn("No changes have been detected in any modules so will re-release them all");
List<ReleasableModule> newList = new ArrayList<ReleasableModule>();
List<ReleasableModule> newList = new ArrayList<>();
for (ReleasableModule module : modules) {
newList.add(module.createReleasableVersion());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ public void execute() throws MojoExecutionException, MojoFailureException {
.buildFromCurrentDir();
repo.errorIfNotClean();

ResolverWrapper resolverWrapper = new ResolverWrapper(factory, artifactResolver, remoteRepositories, localRepository);
Reactor reactor = Reactor.fromProjects(log, repo, project, projects, buildNumber, modulesToForceRelease, noChangesAction, resolverWrapper, versionNamer);
ResolverWrapper resolverWrapper = new ResolverWrapper(getPluginConfiguration());
Reactor reactor = Reactor.fromProjects(repo, getPluginConfiguration(), resolverWrapper, noChangesAction, log);
if (reactor == null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,21 @@

import java.util.List;

/**
*
*/
public class ResolverWrapper {
class ResolverWrapper {

private final ArtifactFactory factory;

private final ArtifactResolver artifactResolver;

private final List remoteRepositories;

private final ArtifactRepository localRepository;

public ResolverWrapper(ArtifactFactory factory, ArtifactResolver artifactResolver, List remoteRepositories, ArtifactRepository localRepository) {
this.factory = factory;
this.artifactResolver = artifactResolver;
this.remoteRepositories = remoteRepositories;
this.localRepository = localRepository;
}

public ArtifactFactory getFactory() {
return factory;
}

public ArtifactResolver getArtifactResolver() {
return artifactResolver;
}

public List getRemoteRepositories() {
return remoteRepositories;
}

public ArtifactRepository getLocalRepository() {
return localRepository;
ResolverWrapper(BaseMojo.PluginConfiguration pluginConfiguration) {
this.factory = pluginConfiguration.factory;
this.artifactResolver = pluginConfiguration.artifactResolver;
this.remoteRepositories = pluginConfiguration.remoteRepositories;
this.localRepository = pluginConfiguration.localRepository;
}

public boolean isResolvable(String groupId, String artifactId, String version, String type, Log log) {
boolean isResolvable(String groupId, String artifactId, String version, String type, Log log) {
try {
Artifact pomArtifact = this.factory.createArtifact(groupId, artifactId, version, "", type);
artifactResolver.resolve(pomArtifact, remoteRepositories, localRepository);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,75 @@
package com.github.danielflower.mavenplugins.release;

import org.codehaus.plexus.util.CollectionUtils;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class TreeWalkingDiffDetector implements DiffDetector {

private final Repository repo;
private final Set<String> ignoredPaths;

public TreeWalkingDiffDetector(Repository repo) {
TreeWalkingDiffDetector(Repository repo, Set<String> ignoredPaths) {
this.repo = repo;
this.ignoredPaths = ignoredPaths;
}

public boolean hasChangedSince(String modulePath, java.util.List<String> childModules, Collection<AnnotatedTag> tags) throws IOException {
TreeWalkingDiffDetector(Repository repository) {
this(repository, Collections.emptySet());
}

public boolean hasChangedSince(String modulePath, List<String> childModules, Collection<AnnotatedTag> tags) throws IOException {
RevWalk walk = new RevWalk(repo);
try {
walk.setRetainBody(false);
walk.markStart(walk.parseCommit(repo.getRefDatabase().findRef("HEAD").getObjectId()));
filterOutOtherModulesChanges(modulePath, childModules, walk);

List<TreeFilter> treeFilters = createTreeFiltersForOtherModulesChanges(modulePath, childModules);
treeFilters.addAll(createTreeFiltersForIgnoredPaths());
walk.setTreeFilter(treeFilters.size() == 1 ? treeFilters.get(0) : AndTreeFilter.create(treeFilters));
stopWalkingWhenTheTagsAreHit(tags, walk);

return walk.iterator().hasNext();
} finally {
walk.dispose();
}
}

private Collection<TreeFilter> createTreeFiltersForIgnoredPaths() {
List<TreeFilter> treeFilters = new ArrayList<>();
if (ignoredPaths != null) {
treeFilters.addAll(
ignoredPaths.stream()
// To differentiate path suffix filters from path filters in the configuration there is the special
// "**" prefix.
// foo.txt -> path filter that matches foo.txt in the root of the top-level project
// bar/foo.txt -> path filter that matches foo.txt in the root of the bar directory
// bar -> path filter that matches everything in/under the bar directory
// **.txt -> path suffix filter that matches all paths ending in .txt (suffix match)
// **.editorconfig -> path suffix filter that matches all .editorconfig files in all (sub)directories; a special case of suffix matching
.map(p -> p.startsWith("**") ? PathSuffixFilter.create(p.substring(2)): PathFilter.create(p))
// tree filters define what to include, yet the users define what to IGNORE -> negate the filter
.map(TreeFilter::negate)
.collect(Collectors.toList())
);
}
return treeFilters;
}

private static void stopWalkingWhenTheTagsAreHit(Collection<AnnotatedTag> tags, RevWalk walk) throws IOException {
for (AnnotatedTag tag : tags) {
ObjectId commitId = tag.ref().getTarget().getObjectId();
Expand All @@ -42,7 +78,7 @@ private static void stopWalkingWhenTheTagsAreHit(Collection<AnnotatedTag> tags,
}
}

private void filterOutOtherModulesChanges(String modulePath, List<String> childModules, RevWalk walk) {
private List<TreeFilter> createTreeFiltersForOtherModulesChanges(String modulePath, List<String> childModules) {
boolean isRootModule = ".".equals(modulePath);
boolean isMultiModuleProject = !isRootModule || !childModules.isEmpty();
List<TreeFilter> treeFilters = new ArrayList<>();
Expand All @@ -60,7 +96,6 @@ private void filterOutOtherModulesChanges(String modulePath, List<String> childM
}

}
TreeFilter treeFilter = treeFilters.size() == 1 ? treeFilters.get(0) : AndTreeFilter.create(treeFilters);
walk.setTreeFilter(treeFilter);
return treeFilters;
}
}
4 changes: 4 additions & 0 deletions src/site/markdown/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changelog
---------

### 3.4.0

* Allow to define paths which should be ignored when detecting changes (`.gitignore` et.al.), #77

### 3.3.0

* Allow build numbers to be arbitrary strings or null, #75
Expand Down
Loading

0 comments on commit 25f3aed

Please sign in to comment.