- * The build number to use in the release version. Given a snapshot version
- * of "1.0-SNAPSHOT" and a buildNumber value of "2", the actual released
- * version will be "1.0.2".
- *
- *
- * By default, the plugin will automatically find a suitable build number.
- * It will start at version 0 and increment this with each release.
- *
- *
- * This can be specified using a command line parameter ("-DbuildNumber=2")
- * or in this plugin's configuration.
- *
+ * The build number to use in the release version. Given a snapshot version
+ * of "1.0-SNAPSHOT" and a buildNumber value of "2", the actual released
+ * version will be "1.0.2".
+ *
+ *
+ * By default, the plugin will automatically find a suitable build number.
+ * It will start at version 0 and increment this with each release.
+ *
+ *
+ * This can be specified using a command line parameter ("-DbuildNumber=2")
+ * or in this plugin's configuration.
+ *
+ */
+ @Parameter(property = "buildNumber")
+ protected Long buildNumber;
/**
@@ -61,20 +63,20 @@ public abstract class BaseMojo extends AbstractMojo {
@Parameter(property = "versionNamer")
protected VersionNamer versionNamer = new VersionNamer(".");
- /**
- * The modules to release, or no value to to release the project from the
- * root pom, which is the default. The selected module plus any other
- * modules it needs will be built and released also. When run from the
- * command line, this can be a comma-separated list of module names.
- */
- @Parameter(alias = "modulesToRelease", property = "modulesToRelease")
- protected List modulesToRelease;
-
- /**
- * A module to force release on, even if no changes has been detected.
- */
- @Parameter(alias = "forceRelease", property = "forceRelease")
- protected List modulesToForceRelease;
+ /**
+ * The modules to release, or no value to to release the project from the
+ * root pom, which is the default. The selected module plus any other
+ * modules it needs will be built and released also. When run from the
+ * command line, this can be a comma-separated list of module names.
+ */
+ @Parameter(alias = "modulesToRelease", property = "modulesToRelease")
+ protected List modulesToRelease;
+
+ /**
+ * A module to force release on, even if no changes has been detected.
+ */
+ @Parameter(alias = "forceRelease", property = "forceRelease")
+ protected List modulesToForceRelease;
/**
* Determines the action to take when no module changes are detected. Possible values:
@@ -108,40 +110,40 @@ public abstract class BaseMojo extends AbstractMojo {
@Parameter(property = "localRepository", required = true, readonly = true, defaultValue = "${localRepository}")
protected ArtifactRepository localRepository;
- @Parameter(property = "disableSshAgent")
- private boolean disableSshAgent;
+ @Parameter(property = "disableSshAgent")
+ private boolean disableSshAgent;
- @Parameter(defaultValue = "${settings}", readonly = true, required = true)
- private Settings settings;
+ @Parameter(defaultValue = "${settings}", readonly = true, required = true)
+ private Settings settings;
- /**
- *
If set, the identityFile and passphrase will be read from the Maven settings file.
+ /**
+ *
If set, the identityFile and passphrase will be read from the Maven settings file.
- */
- @Parameter(property = "serverId")
- private String serverId;
-
- /**
- * If set, this file will be used to specify the known_hosts. This will
- * override any default value.
- */
- @Parameter(property = "knownHosts")
- private String knownHosts;
-
- /**
- * Specifies the private key to be used for SSH URLs. By default it will use ~/.ssh/id_rsa
- */
- @Parameter(property = "privateKey")
- private String privateKey;
-
- /**
- *
Specifies the passphrase to be used with the identityFile specified for SSH where the private key requires a pass phrase.
+ */
+ @Parameter(property = "serverId")
+ private String serverId;
+
+ /**
+ * If set, this file will be used to specify the known_hosts. This will
+ * override any default value.
+ */
+ @Parameter(property = "knownHosts")
+ private String knownHosts;
+
+ /**
+ * Specifies the private key to be used for SSH URLs. By default it will use ~/.ssh/id_rsa
+ */
+ @Parameter(property = "privateKey")
+ private String privateKey;
+
+ /**
+ *
Specifies the passphrase to be used with the identityFile specified for SSH where the private key requires a pass phrase.
*
To avoid specifying a passphrase in your pom, you could instead specify a server in your
* maven settings file and then set the serverId property.
- */
- @Parameter(property = "passphrase")
- private String passphrase;
+ */
+ @Parameter(property = "passphrase")
+ private String passphrase;
/**
* Fetch tags from remote repository to determine the next build number. If
@@ -161,35 +163,52 @@ public abstract class BaseMojo extends AbstractMojo {
@Parameter(property = "arguments")
public String arguments;
+ /**
+ *
List of relative file system paths to ignore when detecting changes in the project(s).
+ *
The primary purpose is to skip creating new releases if only "infrastructure" files such as
+ * .gitignore, .editorconfig and the like changed. Very basic wild cards are supported as
+ * follows:
+ *
+ *
foo.txt - matches foo.txt in the root of the top-level project
+ *
bar/foo.txt - matches foo.txt in the root of the bar directory
+ *
bar - matches the foo directory and ignores everything below
+ *
**.txt - matches all paths ending in .txt (suffix match)
+ *
**.editorconfig - matches all .editorconfig files in all (sub)directories; a special case of suffix matching
+ *
+ *
+ */
+ @Parameter(property = "ignoredPaths")
+ Set ignoredPaths;
+
final void setSettings(final Settings settings) {
- this.settings = settings;
- }
+ this.settings = settings;
+ }
final Settings getSettings() {
return settings;
}
- final void setServerId(final String serverId) {
- this.serverId = serverId;
- }
+ final void setServerId(final String serverId) {
+ this.serverId = serverId;
+ }
- final void setKnownHosts(final String knownHosts) {
- this.knownHosts = knownHosts;
- }
+ final void setKnownHosts(final String knownHosts) {
+ this.knownHosts = knownHosts;
+ }
- final void setPrivateKey(final String privateKey) {
- this.privateKey = privateKey;
- }
+ final void setPrivateKey(final String privateKey) {
+ this.privateKey = privateKey;
+ }
- final void setPassphrase(final String passphrase) {
- this.passphrase = passphrase;
- }
+ final void setPassphrase(final String passphrase) {
+ this.passphrase = passphrase;
+ }
- final void disableSshAgent() {
- disableSshAgent = true;
- }
+ final void disableSshAgent() {
+ disableSshAgent = true;
+ }
- protected CredentialsProvider getCredentialsProvider(final Log log) throws ValidationException {
+ protected CredentialsProvider getCredentialsProvider(final Log log) throws ValidationException {
if (serverId != null) {
Server server = settings.getServer(serverId);
if (server == null) {
@@ -204,21 +223,21 @@ protected CredentialsProvider getCredentialsProvider(final Log log) throws Valid
return null;
}
- protected final void configureJsch(final Log log) {
- if (!disableSshAgent) {
- if (serverId != null) {
- final Server server = settings.getServer(serverId);
- if (server != null) {
- privateKey = privateKey == null ? server.getPrivateKey() : privateKey;
- passphrase = passphrase == null ? server.getPassphrase() : passphrase;
- } else {
- log.warn(format("No server configuration in Maven settings found with id %s", serverId));
- }
- }
-
- JschConfigSessionFactory.setInstance(new SshAgentSessionFactory(log, knownHosts, privateKey, passphrase));
- }
- }
+ protected final void configureJsch(final Log log) {
+ if (!disableSshAgent) {
+ if (serverId != null) {
+ final Server server = settings.getServer(serverId);
+ if (server != null) {
+ privateKey = privateKey == null ? server.getPrivateKey() : privateKey;
+ passphrase = passphrase == null ? server.getPassphrase() : passphrase;
+ } else {
+ log.warn(format("No server configuration in Maven settings found with id %s", serverId));
+ }
+ }
+
+ JschConfigSessionFactory.setInstance(new SshAgentSessionFactory(log, knownHosts, privateKey, passphrase));
+ }
+ }
static void printBigErrorMessageAndThrow(Log log, String terseMessage, List linesToLog) throws MojoExecutionException {
log.error("");
diff --git a/src/main/java/com/github/danielflower/mavenplugins/release/NextMojo.java b/src/main/java/com/github/danielflower/mavenplugins/release/NextMojo.java
index de41f73f..a1cc705d 100644
--- a/src/main/java/com/github/danielflower/mavenplugins/release/NextMojo.java
+++ b/src/main/java/com/github/danielflower/mavenplugins/release/NextMojo.java
@@ -46,7 +46,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
.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);
+ Reactor reactor = Reactor.fromProjects(log, repo, project, projects, buildNumber, modulesToForceRelease, noChangesAction, resolverWrapper, versionNamer, ignoredPaths);
if (reactor == null) {
return;
}
diff --git a/src/main/java/com/github/danielflower/mavenplugins/release/Reactor.java b/src/main/java/com/github/danielflower/mavenplugins/release/Reactor.java
index 3ef17308..0049e0d6 100644
--- a/src/main/java/com/github/danielflower/mavenplugins/release/Reactor.java
+++ b/src/main/java/com/github/danielflower/mavenplugins/release/Reactor.java
@@ -13,6 +13,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import static com.github.danielflower.mavenplugins.release.MavenVersionResolver.resolveVersionsDefinedThroughProperties;
@@ -28,8 +29,8 @@ public List getModulesInBuildOrder() {
return modulesInBuildOrder;
}
- public static Reactor fromProjects(Log log, LocalGitRepo gitRepo, MavenProject rootProject, List projects, Long buildNumber, List modulesToForceRelease, NoChangesAction actionWhenNoChangesDetected, ResolverWrapper resolverWrapper, VersionNamer versionNamer) throws ValidationException, GitAPIException, MojoExecutionException {
- DiffDetector detector = new TreeWalkingDiffDetector(gitRepo.git.getRepository());
+ public static Reactor fromProjects(Log log, LocalGitRepo gitRepo, MavenProject rootProject, List projects, Long buildNumber, List modulesToForceRelease, NoChangesAction actionWhenNoChangesDetected, ResolverWrapper resolverWrapper, VersionNamer versionNamer, Set ignoredPaths) throws ValidationException, GitAPIException, MojoExecutionException {
+ DiffDetector detector = new TreeWalkingDiffDetector(gitRepo.git.getRepository(), ignoredPaths);
List modules = new ArrayList();
resolveVersionsDefinedThroughProperties(projects);
@@ -121,7 +122,7 @@ public static Reactor fromProjects(Log log, LocalGitRepo gitRepo, MavenProject r
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 newList = new ArrayList();
+ List newList = new ArrayList<>();
for (ReleasableModule module : modules) {
newList.add(module.createReleasableVersion());
}
diff --git a/src/main/java/com/github/danielflower/mavenplugins/release/ReleaseMojo.java b/src/main/java/com/github/danielflower/mavenplugins/release/ReleaseMojo.java
index 2cc6da85..f7829b1a 100644
--- a/src/main/java/com/github/danielflower/mavenplugins/release/ReleaseMojo.java
+++ b/src/main/java/com/github/danielflower/mavenplugins/release/ReleaseMojo.java
@@ -69,27 +69,27 @@ public class ReleaseMojo extends BaseMojo {
*/
@Parameter(alias = "skipTests", defaultValue = "false", property = "skipTests")
private boolean skipTests;
-
- /**
- * Specifies a custom, user specific Maven settings file to be used during the release build.
+
+ /**
+ * Specifies a custom, user specific Maven settings file to be used during the release build.
*
* @deprecated In versions prior to 2.1, if the plugin was run with custom user settings the settings were ignored
* during the release phase. Now that custom settings are inherited, setting this value is no longer needed.
* Please use the '-s' command line parameter to set custom user settings.
- */
- @Parameter(alias = "userSettings")
- private File userSettings;
+ */
+ @Parameter(alias = "userSettings")
+ private File userSettings;
- /**
- * Specifies a custom, global Maven settings file to be used during the release build.
+ /**
+ * Specifies a custom, global Maven settings file to be used during the release build.
*
* @deprecated In versions prior to 2.1, if the plugin was run with custom global settings the settings were ignored
* during the release phase. Now that custom settings are inherited, setting this value is no longer needed.
* Please use the '-gs' command line parameter to set custom global settings.
*/
- @Parameter(alias = "globalSettings")
- private File globalSettings;
-
+ @Parameter(alias = "globalSettings")
+ private File globalSettings;
+
/**
* Push tags to remote repository as they are created.
*/
@@ -159,7 +159,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
repo.errorIfNotClean(ignoredUntrackedPaths);
ResolverWrapper resolverWrapper = new ResolverWrapper(factory, artifactResolver, remoteRepositories, localRepository);
- Reactor reactor = Reactor.fromProjects(log, repo, project, projects, buildNumber, modulesToForceRelease, noChangesAction, resolverWrapper, versionNamer);
+ Reactor reactor = Reactor.fromProjects(log, repo, project, projects, buildNumber, modulesToForceRelease, noChangesAction, resolverWrapper, versionNamer, ignoredPaths);
if (reactor == null) {
return;
}
@@ -180,8 +180,8 @@ public void execute() throws MojoExecutionException, MojoFailureException {
tagAndPushRepo(log, repo, proposedTags);
try {
- final ReleaseInvoker invoker = new ReleaseInvoker(getLog(), project);
- invoker.setGlobalSettings(globalSettings);
+ final ReleaseInvoker invoker = new ReleaseInvoker(getLog(), project);
+ invoker.setGlobalSettings(globalSettings);
if (userSettings != null) {
invoker.setUserSettings(userSettings);
} else if (getSettings() != null) {
@@ -190,11 +190,11 @@ public void execute() throws MojoExecutionException, MojoFailureException {
new DefaultSettingsWriter().write(settingsFile, null, getSettings());
invoker.setUserSettings(settingsFile);
}
- invoker.setGoals(goals);
- invoker.setModulesToRelease(modulesToRelease);
- invoker.setReleaseProfiles(releaseProfiles);
- invoker.setSkipTests(skipTests);
- invoker.setArguments(arguments);
+ invoker.setGoals(goals);
+ invoker.setModulesToRelease(modulesToRelease);
+ invoker.setReleaseProfiles(releaseProfiles);
+ invoker.setSkipTests(skipTests);
+ invoker.setArguments(arguments);
invoker.runMavenBuild(reactor);
revertChanges(log, repo, changedFiles, true); // throw if you can't revert as that is the root problem
diff --git a/src/main/java/com/github/danielflower/mavenplugins/release/TreeWalkingDiffDetector.java b/src/main/java/com/github/danielflower/mavenplugins/release/TreeWalkingDiffDetector.java
index 81fd260c..ba1e6c67 100644
--- a/src/main/java/com/github/danielflower/mavenplugins/release/TreeWalkingDiffDetector.java
+++ b/src/main/java/com/github/danielflower/mavenplugins/release/TreeWalkingDiffDetector.java
@@ -6,34 +6,70 @@
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.Iterator;
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 ignoredPaths;
- public TreeWalkingDiffDetector(Repository repo) {
+ TreeWalkingDiffDetector(Repository repo, Set ignoredPaths) {
this.repo = repo;
+ this.ignoredPaths = ignoredPaths;
}
- public boolean hasChangedSince(String modulePath, java.util.List childModules, Collection tags) throws IOException {
+ TreeWalkingDiffDetector(Repository repository) {
+ this(repository, Collections.emptySet());
+ }
+
+ public boolean hasChangedSince(String modulePath, List childModules, Collection 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 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 createTreeFiltersForIgnoredPaths() {
+ List 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 tags, RevWalk walk) throws IOException {
for (AnnotatedTag tag : tags) {
ObjectId commitId = tag.ref().getTarget().getObjectId();
@@ -42,7 +78,7 @@ private static void stopWalkingWhenTheTagsAreHit(Collection tags,
}
}
- private void filterOutOtherModulesChanges(String modulePath, List childModules, RevWalk walk) {
+ private List createTreeFiltersForOtherModulesChanges(String modulePath, List childModules) {
boolean isRootModule = ".".equals(modulePath);
boolean isMultiModuleProject = !isRootModule || !childModules.isEmpty();
List treeFilters = new ArrayList<>();
@@ -60,7 +96,6 @@ private void filterOutOtherModulesChanges(String modulePath, List childM
}
}
- TreeFilter treeFilter = treeFilters.size() == 1 ? treeFilters.get(0) : AndTreeFilter.create(treeFilters);
- walk.setTreeFilter(treeFilter);
+ return treeFilters;
}
}
diff --git a/src/site/markdown/changelog.md b/src/site/markdown/changelog.md
index 2cb828ec..4c11c852 100644
--- a/src/site/markdown/changelog.md
+++ b/src/site/markdown/changelog.md
@@ -1,6 +1,10 @@
Changelog
---------
+### 3.7.0
+
+* Allow to define paths which should be ignored when detecting changes (`.gitignore` et.al.), #77
+
### 3.6.0
* Latest tagged version from current branch is used for unchanged modules [issue #118 for details](https://github.com/danielflower/multi-module-maven-release-plugin/issues/118)
@@ -9,7 +13,7 @@ Changelog
* Added support for processing version properties e.g. `${foo.version}`
* Added support for processing dependencies in `` sections e.g. for BOMs
-* Fix bugs with custom delimiters (joins build number to version)
+* Fix bugs with custom delimiters (joins build number to version)
### 3.1.2
@@ -91,7 +95,7 @@ Warning: inadvertently requires java 8 (rather than java 7). Use 2.0.11 for java
#### 1.3.4
* Fixed bug where a partial build failure where a single commit has multiple tags could result in subsequent releases
-failing due to the plugin picking the older tag to use when it is detected that the module hadn't changed.
+failing due to the plugin picking the older tag to use when it is detected that the module hadn't changed.
### 1.3.0
@@ -112,9 +116,9 @@ should force all children to be updated.
### 1.1.0
-* Bug fix: tags are now pushed before building so that in the event of failure, the next build will use an incremented build number.
-This is needed for cases where part of the build succeeded and some module(s) were uploaded to Nexus - re-uploading would cause an
-error if the build number is not incremented.
+* Bug fix: tags are now pushed before building so that in the event of failure, the next build will use an incremented build number.
+This is needed for cases where part of the build succeeded and some module(s) were uploaded to Nexus - re-uploading would cause an
+error if the build number is not incremented.
#### 1.0.2
diff --git a/src/test/java/com/github/danielflower/mavenplugins/release/IgnorePathsTest.java b/src/test/java/com/github/danielflower/mavenplugins/release/IgnorePathsTest.java
new file mode 100644
index 00000000..5793c556
--- /dev/null
+++ b/src/test/java/com/github/danielflower/mavenplugins/release/IgnorePathsTest.java
@@ -0,0 +1,85 @@
+package com.github.danielflower.mavenplugins.release;
+
+import static com.github.danielflower.mavenplugins.release.AnnotatedTagFinderTest.saveFileInModuleAndTag;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import com.google.common.collect.Sets;
+
+import scaffolding.TestProject;
+
+@RunWith(Parameterized.class)
+public class IgnorePathsTest {
+ private static TestProject project;
+ private static AnnotatedTag tag;
+
+ @Parameter
+ public String ignoredPathsPattern;
+
+ @Parameter(1)
+ public boolean changeExpected;
+
+ @BeforeClass
+ public static void setup() throws IOException, GitAPIException {
+ project = TestProject.nestedProject();
+ // create & commit a random file, then create a tag at that revision
+ tag = saveFileInModuleAndTag(project, "server-modules", "1.0", 0);
+ // create & commit three more files but do NOT create a tag at that revision
+ // -> changes in the project (-> signal for the plugin to release those)
+ project.commitFile("server-modules", "paul.txt");
+ project.commitFile("core-utils", "muller.txt");
+ project.commitFile("core-utils", "dave.txt");
+ project.commitFile("core-utils", "john.txt");
+ project.commitFile("parent-module", "muller.txt");
+ project.commitFile("parent-module", "dave.txt");
+ project.commitFile("parent-module", "john.txt");
+ project.commitFile(".", "4711.txt");
+ }
+
+ @Parameters(name = "{0} -> has changes: {1}")
+ public static Collection