Repository: vackosar/gitflow-incremental-builder
Pull request #62 (2018-10-22 22:19:45)

Fix issue #51: Introduce gib.excludeTransitiveModulesPackagedAs


Patch:
diff --git a/README.md b/README.md
index da67c51..dd76edc 100644
--- a/README.md
+++ b/README.md
@@ -6,19 +6,6 @@
 A maven extension for incremental building of multi-module projects when using [feature branches (Git Flow)](http://nvie.com/posts/a-successful-git-branching-model/).
 Builds or tests only changed maven modules compared to reference branch in Git (e.g. origin/develop) and all their dependents.
 
-## Table of Contents
-
-- [Usage](#usage)
-  * [Disable in IDE](#disable-in-ide)
-- [Example](#example)
-- [Configuration](#configuration)
-  * [gib.enabled](#gibenabled)
-  * [gib.disableBranchComparison](#gibdisablebranchcomparison)
-  * [gib.uncommited](#gibuncommited)
-  * [gib.untracked](#gibuntracked)
-  * [gib.skipTestsForNotImpactedModules](#gibskiptestsfornotimpactedmodules)
-  * [gib.argsForNotImpactedModules](#gibargsfornotimpactedmodules)
-  * [gib.forceBuildModules](#gibforcebuildmodules)
 
 ## Usage
 
@@ -36,13 +23,6 @@ Builds or tests only changed maven modules compared to reference branch in Git (
 </build>
 ```
 
-### Disable in IDE
-
-As IDEs like IntelliJ IDEA or Eclipse usually apply their own custom strategy to building changed modules,
-this extension should be disabled in such environments to avoid inconsistencies or errors (e.g. see issue #49).
-
-See [gib.enabled](#gibenabled) in the configuration section.
-
 ## Example
 
 Maven project `parent` has two submodules `child1` and `child2`:
@@ -240,6 +220,7 @@ Maven pom properties configuration with default values is below:
 	<gib.argsForNotImpactedModules></gib.argsForNotImpactedModules>
 	<gib.buildAll>false</gib.buildAll>
 	<gib.forceBuildModules></gib.forceBuildModules>
+	<gib.excludeTransitiveModulesPackagedAs></gib.excludeTransitiveModulesPackagedAs>
 	<gib.compareToMergeBase>true</gib.compareToMergeBase>
 	<gib.fetchBaseBranch>false</gib.fetchBaseBranch>
 	<gib.fetchReferenceBranch>false</gib.fetchReferenceBranch>
@@ -249,10 +230,6 @@ Maven pom properties configuration with default values is below:
 </properties>
 ```
 
-### gib.enabled
-
-Can be used to disable this extension temporarily or permanently (e.g. to avoid clashes with IDE building strategy).
-
 ### gib.disableBranchComparison
 
 Disables the comparison between `baseBranch` and `referenceBranch`. This property should be enabled if _only_ uncommitted and/or untracked files shall be detected to only build projects that have been changed since the last commit in the current branch (see `gib.uncommited` and `gib.untracked`).
@@ -296,6 +273,16 @@ Each of these modules is subject to `argsForNotImpactedModules` and `skipTestsFo
 
 This property has no effect in case `buildAll` is enabled.
 
+### gib.excludeTransitiveModulesPackagedAs
+
+Defines the packaging (e.g. `jar`) of modules that depend on changed modules but shall not be built.
+
+One possible use case for this is mainly working in an IDE, fixing all compile errors etc. and then just quickly building the least possible amount of modules
+which are needed to (hot-)deploy the changes via `mvn` on the command line.
+In this scenario, by defining `-Dgib.excludeTransitiveModulesPackagedAs=jar,pom`, only the directly changed `jar` modules and the dependent `war` and/or `ear` deployment modules will be built. 
+
+This property has no effect in case `buildAll` is enabled and an exclusion might be overriden by `gib.forceBuildModules`.
+
 ## Requirements
 
 - Maven version 3+.
diff --git a/pom.xml b/pom.xml
index 43f79bd..55a6a75 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,18 +48,15 @@
         <version.plexus-component-metadata>1.7.1</version.plexus-component-metadata>
         <version.maven-resources-plugin>3.1.0</version.maven-resources-plugin>
         <version.maven-compiler-plugin>3.7.0</version.maven-compiler-plugin>
-        <version.maven-surefire-plugin>2.22.1</version.maven-surefire-plugin>
+        <version.maven-surefire-plugin>2.22.0</version.maven-surefire-plugin>
         <version.maven-source-plugin>3.0.1</version.maven-source-plugin>
         <version.maven-javadoc-plugin>3.0.1</version.maven-javadoc-plugin>
         <version.maven-jar-plugin>3.1.0</version.maven-jar-plugin>
-        <version.maven-failsafe-plugin>2.22.1</version.maven-failsafe-plugin>
         <version.maven-site-plugin>3.7.1</version.maven-site-plugin>
         <version.maven-install-plugin>2.5.2</version.maven-install-plugin>
         <version.maven-deploy-plugin>2.8.2</version.maven-deploy-plugin>
         <version.maven-gpg-plugin>1.6</version.maven-gpg-plugin>
         <version.nexus-staging-maven-plugin>1.6.3</version.nexus-staging-maven-plugin>
-
-        <gib.it.repo>${project.build.directory}${file.separator}it${file.separator}repo</gib.it.repo>
     </properties>
     <dependencies>
         <dependency>
@@ -173,10 +170,6 @@
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-compiler-plugin</artifactId>
                     <version>${version.maven-compiler-plugin}</version>
-                    <configuration>
-                        <source>1.8</source>
-                        <target>1.8</target>
-                    </configuration>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -184,18 +177,7 @@
                     <version>${version.maven-surefire-plugin}</version>
                     <configuration>
                         <trimStackTrace>false</trimStackTrace> <!-- https://issues.apache.org/jira/browse/SUREFIRE-1457 -->
-                        <excludes>
-                            <exclude>**/*IntegrationTest.java</exclude>
-                            <exclude>**/*$*.java</exclude>
-                        </excludes>
                     </configuration>
-                    <dependencies>
-                        <dependency>
-                            <groupId>org.apache.maven.surefire</groupId>
-                            <artifactId>surefire-junit47</artifactId>
-                            <version>${version.maven-surefire-plugin}</version>
-                        </dependency>
-                    </dependencies>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -217,28 +199,6 @@
                     <artifactId>maven-jar-plugin</artifactId>
                     <version>${version.maven-jar-plugin}</version>
                 </plugin>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-failsafe-plugin</artifactId>
-                    <version>${version.maven-failsafe-plugin}</version>
-                    <configuration>
-                        <trimStackTrace>false</trimStackTrace> <!-- https://issues.apache.org/jira/browse/SUREFIRE-1457 -->
-                        <includes>
-                            <include>**/*IntegrationTest.java</include>
-                        </includes>
-                        <systemPropertyVariables>
-                            <gib.it.repo>${gib.it.repo}</gib.it.repo>
-                            <gib.it.version>${project.version}</gib.it.version>
-                        </systemPropertyVariables>
-                    </configuration>
-                    <dependencies>
-                        <dependency>
-                            <groupId>org.apache.maven.surefire</groupId>
-                            <artifactId>surefire-junit47</artifactId>
-                            <version>${version.maven-failsafe-plugin}</version>
-                        </dependency>
-                    </dependencies>
-                </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-install-plugin</artifactId>
@@ -281,6 +241,13 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
             <plugin>
                 <groupId>org.codehaus.plexus</groupId>
                 <artifactId>plexus-component-metadata</artifactId>
@@ -292,34 +259,6 @@
                     </execution>
                 </executions>
             </plugin>
-            <plugin>
-                <artifactId>maven-install-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>pre-it-install-gib</id>
-                        <phase>pre-integration-test</phase>
-                        <goals>
-                            <goal>install-file</goal>
-                        </goals>
-                        <configuration>
-                            <file>${project.build.directory}/${project.build.finalName}.${project.packaging}</file>
-                            <localRepositoryPath>${gib.it.repo}</localRepositoryPath>
-                            <pomFile>${project.basedir}/pom.xml</pomFile>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <artifactId>maven-failsafe-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>integration-test</goal>
-                            <goal>verify</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java
index ea6a7e2..bfe88f4 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java
@@ -39,6 +39,7 @@ public class Configuration {
     public final Map<String, String> argsForNotImpactedModules;
     public final boolean buildAll;
     public final List<String> forceBuildModules;
+    public final List<String> excludeTransitiveModulesPackagedAs;
     public final boolean compareToMergeBase;
     public final boolean fetchBaseBranch;
     public final boolean fetchReferenceBranch;
@@ -63,6 +64,8 @@ public class Configuration {
             buildAll = Boolean.valueOf(Property.buildAll.getValue());
             forceBuildModules = Collections.unmodifiableList(
                     parseDelimited(Property.forceBuildModules.getValue(), ","));
+            excludeTransitiveModulesPackagedAs = Collections.unmodifiableList(
+                    parseDelimited(Property.excludeTransitiveModulesPackagedAs.getValue(), ","));
             compareToMergeBase = Boolean.valueOf(Property.compareToMergeBase.getValue());
             fetchReferenceBranch = Boolean.valueOf(Property.fetchReferenceBranch.getValue());
             fetchBaseBranch = Boolean.valueOf(Property.fetchBaseBranch.getValue());
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java
index 0e682ab..1a1edd6 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java
@@ -117,7 +117,8 @@ class UnchangedProjectsRemover {
     private Stream<MavenProject> streamProjectWithDependents(MavenProject project) {
         return Stream.concat(
             Stream.of(project),
-            mavenSession.getProjectDependencyGraph().getDownstreamProjects(project, true).stream());
+            mavenSession.getProjectDependencyGraph().getDownstreamProjects(project, true).stream()
+                .filter(p -> !configuration.excludeTransitiveModulesPackagedAs.contains(p.getPackaging())));
     }
 
     private Stream<MavenProject> streamProjectWithDependencies(MavenProject project) {
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java b/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java
index 11fa35a..cc67fb8 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java
@@ -12,6 +12,7 @@ public enum Property {
     argsForNotImpactedModules(""),
     buildAll("false"),
     forceBuildModules(""),
+    excludeTransitiveModulesPackagedAs(""),
     compareToMergeBase("true"),
     fetchBaseBranch("false"),
     fetchReferenceBranch("false"),


Test patch:
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenIntegrationTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenInstallRunningIntegrationTest.java
similarity index 79%
rename from src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenIntegrationTest.java
rename to src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenInstallRunningIntegrationTest.java
index 6c6a3bf..575bad1 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenIntegrationTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenInstallRunningIntegrationTest.java
@@ -6,12 +6,15 @@ import com.vackosar.gitflowincrementalbuild.control.Property;
 import org.apache.commons.lang3.SystemUtils;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.ResetCommand;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -19,54 +22,51 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
- * Integration test running the {@code mvn} command on a test project with active {@code gitflow-incremental-builder}.
- * <p/>
- * This test is expected to be called via {@code maven-failsafe-plugin} and requires two system properties:
- * <ul>
- * <li>{@code gib.it.repo} defining the path of the maven repo containing the {@code gitflow-incremental-builder} jarfile to test</li>
- * <li>{@code gib.it.version} defining the version of the {@code gitflow-incremental-builder} jarfile to test</li>
- * </ul>
+ * Integration test which installs current version into your Maven Repository! See {@link MavenInstallRunningIntegrationTest#installCurrentVersion()}
  */
-public class MavenIntegrationTest extends BaseRepoTest {
+public class MavenInstallRunningIntegrationTest extends BaseRepoTest {
 
     private static final String DEFAULT_POMFILE_ARG = "--file=parent/pom.xml";
 
-    private static String localRepoArg;
     private static String gibVersionArg;
-    private static boolean initialInstallDone;
 
     @BeforeClass
-    public static void evaluateSystemProperties() throws IOException, InterruptedException {
-        localRepoArg = "-Dmaven.repo.local=" + System.getProperty("gib.it.repo");
-        gibVersionArg = "-DgibVersion=" + System.getProperty("gib.it.version");
-
-        System.out.println(
-                "The first test method will execute an initial 'mvn install ...' on the test project to populate the test repo."
-                + " This might take a while.");
+    public static void installCurrentVersion() throws IOException, InterruptedException {
+        awaitProcess(new ProcessBuilder(cmdArgs("mvn", "install", "-Dmaven.test.skip=true")).start());
+        // Get current version from pom.xml
+        String version = Files.readAllLines(Paths.get("pom.xml")).stream().filter(s -> s.contains("<version>")).findFirst().get()
+            .replaceAll("</*version>", "").replaceAll("^[ \t]*", "");
+        gibVersionArg = "-DgibVersion=" + version;
     }
 
     /**
-     * Installs all test artifacts/modules to avoid dependency problems when only building a subset incrementally.
-     * <p/>
-     * This also downloads all required maven core and plugin dependencies into the test repo.
-     * <p/>
-     * This is performed only once for the entire class but cannot be moved to {@link BeforeClass} as {@link BaseRepoTest} (re-)creates the
-     * test project for each test in {@link Before}.
+     * Installs all test artifacts/modules to avoid dependency problems when only building a subset incrementally. 
      *
      * @throws IOException on process execution errors
      * @throws InterruptedException on process execution errors
      */
     @Before
-    public void initialInstall() throws IOException, InterruptedException {
-        if (initialInstallDone) {
-            return;
-        }
-        awaitProcess(new ProcessBuilder(
-                cmdArgs(
-                        "mvn", "install", localRepoArg, gibVersionArg, DEFAULT_POMFILE_ARG, "-Dgib." + Property.enabled + "=false"))
-                .directory(getLocalRepoMock().getBaseCanonicalBaseFolder())
-                .start());
-        initialInstallDone = true;
+    public void installTestArtifacts() throws IOException, InterruptedException {
+        awaitProcess(
+            new ProcessBuilder(cmdArgs("mvn", "install",
+                DEFAULT_POMFILE_ARG, gibVersionArg, "-Dgib." + Property.enabled + "=false"))
+                    .directory(getLocalRepoMock().getBaseCanonicalBaseFolder())
+                    .start());
+    }
+
+    /**
+     * Removes the test artifacts/modules which have been installed by {@link #installTestArtifacts()}.
+     *
+     * @throws IOException on process execution errors
+     * @throws InterruptedException on process execution errors
+     */
+    @After
+    public void purgeInstalledTestArtifacts() throws IOException, InterruptedException {
+        awaitProcess(
+            new ProcessBuilder(cmdArgs("mvn", "org.codehaus.mojo:build-helper-maven-plugin:3.0.0:remove-project-artifact",
+                DEFAULT_POMFILE_ARG, gibVersionArg,"-Dgib." + Property.enabled + "=false"))
+                    .directory(getLocalRepoMock().getBaseCanonicalBaseFolder())
+                    .start());
     }
     
     @Test
@@ -178,7 +178,7 @@ public class MavenIntegrationTest extends BaseRepoTest {
     }
 
     private String executeBuild(List<String> args) throws IOException, InterruptedException {
-        final List<String> commandBase = cmdArgs("mvn", "package", localRepoArg, gibVersionArg);
+        final List<String> commandBase = cmdArgs("mvn", "package", gibVersionArg);
         final List<String> commandBaseWithFile;
         if (args.stream().noneMatch(s->s.startsWith("--file"))) {
             commandBaseWithFile = Stream.concat(commandBase.stream(), Stream.of(DEFAULT_POMFILE_ARG)).collect(Collectors.toList());
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java
index d122ecf..de2128d 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java
@@ -46,6 +46,7 @@ public class UnchangedProjectsRemoverTest {
 
     private static final String ARTIFACT_ID_1 = "first-module";
     private static final String ARTIFACT_ID_2 = "changed-module";
+    private static final String ARTIFACT_ID_2_DEP_WAR = ARTIFACT_ID_2 + "-dependent-war";
 
     @Mock(name = ARTIFACT_ID_1)
     private MavenProject mavenProjectMock;
@@ -83,7 +84,7 @@ public class UnchangedProjectsRemoverTest {
     
     @Test
     public void singleChanged() throws GitAPIException, IOException {
-        MavenProject changedModuleMock = addChangedModuleMock(ARTIFACT_ID_2);
+        MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
 
         buildUnderTest().act();
 
@@ -92,7 +93,7 @@ public class UnchangedProjectsRemoverTest {
 
     @Test
     public void singleChanged_alsoMake_argsForNotImpactedModules() throws GitAPIException, IOException {
-        MavenProject changedModuleMock = addChangedModuleMock(ARTIFACT_ID_2);
+        MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
 
         when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);
 
@@ -113,7 +114,7 @@ public class UnchangedProjectsRemoverTest {
 
     @Test
     public void singleChanged_buildAll_argsForNotImpactedModules() throws GitAPIException, IOException {
-        MavenProject changedModuleMock = addChangedModuleMock(ARTIFACT_ID_2);
+        MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
 
         ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
                 Property.argsForNotImpactedModules, "enforcer.skip=true argWithNoValue",
@@ -133,7 +134,7 @@ public class UnchangedProjectsRemoverTest {
 
     @Test
     public void singleChanged_forceBuildModules() throws GitAPIException, IOException {
-        MavenProject changedModuleMock = addChangedModuleMock(ARTIFACT_ID_2);
+        MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
 
         ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
                 Property.forceBuildModules, mavenProjectMock.getArtifactId());
@@ -145,18 +146,140 @@ public class UnchangedProjectsRemoverTest {
 
     @Test
     public void singleChanged_forceBuildModules_two() throws GitAPIException, IOException {
-        MavenProject changedProjectMock = addChangedModuleMock(ARTIFACT_ID_2);
-        MavenProject unchangedProjectMock = addChangedModuleMock("unchanged-module");
-        changedProjects.remove(unchangedProjectMock);
+        MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
+        MavenProject unchangedModuleMock = addModuleMock("unchanged-module", false);
 
         ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
                 Property.forceBuildModules,
-                mavenProjectMock.getArtifactId() + "," + unchangedProjectMock.getArtifactId());
+                mavenProjectMock.getArtifactId() + "," + unchangedModuleMock.getArtifactId());
 
         buildUnderTest(propertyMap).act();
 
         Mockito.verify(mavenSessionMock).setProjects(
-                Arrays.asList(mavenProjectMock, unchangedProjectMock, changedProjectMock));
+                Arrays.asList(mavenProjectMock, unchangedModuleMock, changedModuleMock));
+    }
+
+    @Test
+    public void singleChanged_excludeTransitiveModulesPackagedAs_oneTransitive_oneExcluded() throws GitAPIException, IOException {
+        MavenProject changedProjectMock = addModuleMock(ARTIFACT_ID_2, true);
+        MavenProject dependentWar = addModuleMock(ARTIFACT_ID_2_DEP_WAR, false, "war");
+
+        when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
+                .thenReturn(Arrays.asList(dependentWar));
+
+        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
+                Property.excludeTransitiveModulesPackagedAs,"war");
+
+        buildUnderTest(propertyMap).act();
+
+        Mockito.verify(mavenSessionMock).setProjects(
+                Arrays.asList(changedProjectMock));
+    }
+
+    @Test
+    public void singleChanged_excludeTransitiveModulesPackagedAs_twoTransitive_oneExcluded() throws GitAPIException, IOException {
+        MavenProject changedProjectMock = addModuleMock(ARTIFACT_ID_2, true);
+        MavenProject dependentWar = addModuleMock(ARTIFACT_ID_2_DEP_WAR, false, "war");
+        MavenProject dependentJar = addModuleMock(ARTIFACT_ID_2 + "-dependent-jar", false);
+
+        when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
+                .thenReturn(Arrays.asList(dependentWar, dependentJar));
+
+        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
+                Property.excludeTransitiveModulesPackagedAs,"war");
+
+        buildUnderTest(propertyMap).act();
+
+        Mockito.verify(mavenSessionMock).setProjects(
+                Arrays.asList(changedProjectMock, dependentJar));
+    }
+
+    @Test
+    public void singleChanged_excludeTransitiveModulesPackagedAs_twoTransitive_twoExcluded() throws GitAPIException, IOException {
+        MavenProject changedProjectMock = addModuleMock(ARTIFACT_ID_2, true);
+        MavenProject dependentWar = addModuleMock(ARTIFACT_ID_2_DEP_WAR, false, "war");
+        MavenProject dependentEar = addModuleMock(ARTIFACT_ID_2 + "-dependent-ear", false, "ear");
+
+        when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
+                .thenReturn(Arrays.asList(dependentWar, dependentEar));
+
+        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
+                Property.excludeTransitiveModulesPackagedAs,"war,ear");
+
+        buildUnderTest(propertyMap).act();
+
+        Mockito.verify(mavenSessionMock).setProjects(
+                Arrays.asList(changedProjectMock));
+    }
+
+    @Test
+    public void singleChanged_excludeTransitiveModulesPackagedAs_oneTransitive_buildAll() throws GitAPIException, IOException {
+        MavenProject changedProjectMock = addModuleMock(ARTIFACT_ID_2, true);
+        MavenProject dependentWar = addModuleMock(ARTIFACT_ID_2_DEP_WAR, false, "war");
+
+        when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
+                .thenReturn(Arrays.asList(dependentWar));
+
+        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
+                Property.excludeTransitiveModulesPackagedAs,"war",
+                Property.buildAll,"true");
+
+        buildUnderTest(propertyMap).act();
+
+        Mockito.verify(mavenSessionMock, never())
+                .setProjects(anyListOf(MavenProject.class));
+    }
+
+    @Test
+    public void singleChanged_excludeTransitiveModulesPackagedAs_oneTransitive_forceBuildModules() throws GitAPIException, IOException {
+        MavenProject changedProjectMock = addModuleMock(ARTIFACT_ID_2, true);
+        MavenProject dependentWar = addModuleMock(ARTIFACT_ID_2_DEP_WAR, false, "war");
+
+        when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
+                .thenReturn(Arrays.asList(dependentWar));
+
+        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
+                Property.excludeTransitiveModulesPackagedAs,"war",
+                Property.forceBuildModules,dependentWar.getArtifactId());
+
+        buildUnderTest(propertyMap).act();
+
+        Mockito.verify(mavenSessionMock).setProjects(
+                Arrays.asList(dependentWar, changedProjectMock));
+    }
+
+    @Test
+    public void twoChanged_excludeTransitiveModulesPackagedAs_changedNotExcluded() throws GitAPIException, IOException {
+        MavenProject changedProjectMock = addModuleMock(ARTIFACT_ID_2, true);
+        MavenProject dependentWar = addModuleMock(ARTIFACT_ID_2_DEP_WAR, true, "war");
+
+        // war module is changed, must be retained!
+
+        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
+                Property.excludeTransitiveModulesPackagedAs,"war");
+
+        buildUnderTest(propertyMap).act();
+
+        Mockito.verify(mavenSessionMock).setProjects(
+                Arrays.asList(changedProjectMock, dependentWar));
+    }
+
+    @Test
+    public void twoChanged_excludeTransitiveModulesPackagedAs_changedNotExcluded_transitive() throws GitAPIException, IOException {
+        MavenProject changedProjectMock = addModuleMock(ARTIFACT_ID_2, true);
+        MavenProject dependentWar = addModuleMock(ARTIFACT_ID_2_DEP_WAR, true, "war");
+
+        // war module is changed, must be retained - even if depending on changedProjectMock!
+        when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
+                .thenReturn(Arrays.asList(dependentWar));
+
+        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
+                Property.excludeTransitiveModulesPackagedAs,"war");
+
+        buildUnderTest(propertyMap).act();
+
+        Mockito.verify(mavenSessionMock).setProjects(
+                Arrays.asList(changedProjectMock, dependentWar));
     }
 
     private Properties addPropertiesToMock(MavenProject mavenProjectMock) {
@@ -165,10 +288,17 @@ public class UnchangedProjectsRemoverTest {
         return properties;
     }
 
-    private MavenProject addChangedModuleMock(String moduleArtifactId) {
+    private MavenProject addModuleMock(String moduleArtifactId, boolean addToChanged) {
+        return addModuleMock(moduleArtifactId, addToChanged, "jar");
+    }
+
+    private MavenProject addModuleMock(String moduleArtifactId, boolean addToChanged, final String packaging) {
         MavenProject changedModuleMock = mock(MavenProject.class, moduleArtifactId);
         when(changedModuleMock.getArtifactId()).thenReturn(moduleArtifactId);
-        changedProjects.add(changedModuleMock);
+        when(changedModuleMock.getPackaging()).thenReturn(packaging);
+        if (addToChanged) {
+            changedProjects.add(changedModuleMock);
+        }
         projects.add(changedModuleMock);
         addPropertiesToMock(changedModuleMock);