Repository: vackosar/gitflow-incremental-builder
Pull request #73 (2018-11-13 21:43:26)

Issue #71: Leverage Maven-provided JSR330 dependency injection instead of custom Guice setup
- Switched annotations as described here: https://maven.apache.org/maven-jsr330.html
- Removed Guice dependency because Maven-built-in Guice is now utilized.
- To avoid pre-mature creation of Configuration by Maven (*before* properties are set),
  Configuration had to be turned into a plain old non-injectable class.
  The resulting "manual passing around" is the main drawback of this approach.
  One positive side-effect of this is the removal of System.setProperty(...).
- Similar to Configuration, Git is not injectable anymore and is now private
  to DifferentFiles, which actually fits better (no other class is using Git).
- Setup of DifferentFilesTest cleaned up a bit, now re-using more of its base class.
- Removed some unused code.

See also commit messages.

famod: >
> > Configuration had to be turned into a plain old non-injectable class
>
>This is one of the reasons I implemented what I described the post: http://vaclavkosar.com/2018/01/24/Modern-Config-Injection-In-Maven-Plugins.html

Well, that would help if GIB was a plugin, not an extension like it is at the moment. Or am I missing something?
famod: I took a stab at the `Provider` approach for `Configuration` and I am quite happy with it: 812e892e47ac111a8030e174d8af9eb64ea385f2

Travis build passed: https://travis-ci.org/vackosar/gitflow-incremental-builder/builds/455260136

@vackosar in case you agree/approve I would rebase that last commit into the first one and merge the PR.
vackosar: > > This is one of the reasons I implemented what I described the post: http://vaclavkosar.com/2018/01/24/Modern-Config-Injection-In-Maven-Plugins.html
> 
> Well, that would help if GIB was a plugin, not an extension like it is at the moment. Or am I missing something?

The approach described would allow you to inject individual POM properties, plugin configs and potentially other things into constructor. Constructor injection has several benefits and it is recommended injection strategy for Spring container.

But yeah, most important is that this works as well.


Patch:
diff --git a/pom.xml b/pom.xml
index 5c094eb..32aa3c6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,11 @@
             <url>https://github.com/vackosar</url>
             <id>vackosar</id>
         </developer>
+        <developer>
+            <name>Falko Modler</name>
+            <url>https://github.com/famod</url>
+            <id>famod</id>
+        </developer>
     </developers>
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -38,8 +43,9 @@
         <!-- Dependency versions -->
         <version.jgit>5.1.3.201810200350-r</version.jgit>
         <version.maven-core>3.3.9</version.maven-core>
-        <version.guice>4.2.2</version.guice>
+        <version.javax.inject>1</version.javax.inject>
         <version.junit>4.12</version.junit>
+        <version.powermock-reflect>1.7.4</version.powermock-reflect>
         <version.mockito-all>1.10.19</version.mockito-all>
 
         <!-- Plugin versions -->
@@ -60,6 +66,13 @@
         <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 to skip UnitTests (UTs) via surefire-plugin and/or IntegrationTests (UTs) via failsafe-plugin
+             See also: https://stackoverflow.com/a/37880379 -->
+        <skipTests>false</skipTests>
+        <skipITs>${skipTests}</skipITs>
+        <skipUTs>${skipTests}</skipUTs>
+
     </properties>
     <dependencies>
         <dependency>
@@ -67,16 +80,24 @@
             <artifactId>org.eclipse.jgit</artifactId>
             <version>${version.jgit}</version>
         </dependency>
-        <dependency>
-            <groupId>com.google.inject</groupId>
-            <artifactId>guice</artifactId>
-            <version>${version.guice}</version>
-        </dependency>
         <dependency>
             <groupId>org.apache.maven</groupId>
             <artifactId>maven-core</artifactId>
             <version>${version.maven-core}</version>
             <scope>provided</scope>
+            <exclusions>
+                <!-- avoid direct usage of Guice annotations -->
+                <exclusion>
+                    <groupId>com.google.inject</groupId>
+                    <artifactId>guice</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <version>${version.javax.inject}</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
@@ -84,6 +105,12 @@
             <version>${version.junit}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-reflect</artifactId>
+            <version>${version.powermock-reflect}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-all</artifactId>
@@ -183,6 +210,7 @@
                     <artifactId>maven-surefire-plugin</artifactId>
                     <version>${version.maven-surefire-plugin}</version>
                     <configuration>
+                        <skipTests>${skipUTs}</skipTests>
                         <trimStackTrace>false</trimStackTrace> <!-- https://issues.apache.org/jira/browse/SUREFIRE-1457 -->
                         <excludes>
                             <exclude>**/*IntegrationTest.java</exclude>
@@ -222,6 +250,8 @@
                     <artifactId>maven-failsafe-plugin</artifactId>
                     <version>${version.maven-failsafe-plugin}</version>
                     <configuration>
+                        <skipTests>${skipTests}</skipTests>
+                        <skipITs>${skipITs}</skipITs>
                         <trimStackTrace>false</trimStackTrace> <!-- https://issues.apache.org/jira/browse/SUREFIRE-1457 -->
                         <includes>
                             <include>**/*IntegrationTest.java</include>
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java
index 5083c89..b533f09 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/Configuration.java
@@ -5,9 +5,6 @@ import com.vackosar.gitflowincrementalbuild.control.Property;
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenSession;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 import static java.util.stream.Collectors.collectingAndThen;
 import static java.util.stream.Collectors.toList;
 
@@ -20,6 +17,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
+import java.util.Properties;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.regex.Pattern;
@@ -28,17 +26,19 @@ import java.util.stream.Collector;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-@Singleton
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
 public class Configuration {
 
     private static final List<String> alsoMakeBehaviours = Arrays.asList(
             MavenExecutionRequest.REACTOR_MAKE_UPSTREAM, MavenExecutionRequest.REACTOR_MAKE_BOTH);
 
-    public final boolean enabled;
     public final Optional<Path> key;
     public final boolean disableBranchComparison;
     public final String referenceBranch;
-    public String baseBranch;
+    public final String baseBranch;
     public final boolean uncommited;
     public final boolean untracked;
     public final boolean makeUpstream;
@@ -52,46 +52,58 @@ public class Configuration {
     public final boolean fetchReferenceBranch;
     public final Predicate<String> excludePathRegex;
     public final boolean failOnMissingGitDir;
-
-    @Inject
-    public Configuration(MavenSession session) {
-        checkProperties();
-
-        enabled = Boolean.valueOf(Property.enabled.getValue());
-        key = parseKey(session);
-        disableBranchComparison = Boolean.valueOf(Property.disableBranchComparison.getValue());
-        referenceBranch = Property.referenceBranch.getValue();
-        baseBranch = Property.baseBranch.getValue();
-        uncommited = Boolean.valueOf(Property.uncommited.getValue());
-        untracked = Boolean.valueOf(Property.untracked.getValue());
+    public final boolean failOnError;
+
+    private Configuration(MavenSession session) {
+        Properties projectProperties = session.getTopLevelProject().getProperties();
+        checkProperties(projectProperties);
+
+        key = parseKey(session, projectProperties);
+        disableBranchComparison = Boolean.valueOf(Property.disableBranchComparison.getValue(projectProperties));
+        referenceBranch = Property.referenceBranch.getValue(projectProperties);
+        baseBranch = Property.baseBranch.getValue(projectProperties);
+        uncommited = Boolean.valueOf(Property.uncommited.getValue(projectProperties));
+        untracked = Boolean.valueOf(Property.untracked.getValue(projectProperties));
         makeUpstream = alsoMakeBehaviours.contains(session.getRequest().getMakeBehavior());
-        skipTestsForNotImpactedModules = Boolean.valueOf(Property.skipTestsForNotImpactedModules.getValue());
+        skipTestsForNotImpactedModules = Boolean.valueOf(Property.skipTestsForNotImpactedModules.getValue(projectProperties));
 
-        argsForNotImpactedModules = parseDelimited(Property.argsForNotImpactedModules.getValue(), " ")
+        argsForNotImpactedModules = parseDelimited(Property.argsForNotImpactedModules.getValue(projectProperties), " ")
                 .map(Configuration::keyValueStringToEntry)
                 .collect(collectingAndThen(toLinkedMap(), Collections::unmodifiableMap));
 
-        buildAll = Boolean.valueOf(Property.buildAll.getValue());
+        buildAll = Boolean.valueOf(Property.buildAll.getValue(projectProperties));
 
-        forceBuildModules = parseDelimited(Property.forceBuildModules.getValue(), ",")
+        forceBuildModules = parseDelimited(Property.forceBuildModules.getValue(projectProperties), ",")
                 .map(str -> compilePattern(str, Property.forceBuildModules))
                 .collect(collectingAndThen(toList(), Collections::unmodifiableList));
 
-        excludeTransitiveModulesPackagedAs = parseDelimited(Property.excludeTransitiveModulesPackagedAs.getValue(), ",")
+        excludeTransitiveModulesPackagedAs = parseDelimited(Property.excludeTransitiveModulesPackagedAs.getValue(projectProperties), ",")
                 .collect(collectingAndThen(toList(), Collections::unmodifiableList));
 
-        compareToMergeBase = Boolean.valueOf(Property.compareToMergeBase.getValue());
-        fetchReferenceBranch = Boolean.valueOf(Property.fetchReferenceBranch.getValue());
-        fetchBaseBranch = Boolean.valueOf(Property.fetchBaseBranch.getValue());
-        excludePathRegex = compilePattern(Property.excludePathRegex).asPredicate();
-        failOnMissingGitDir = Boolean.valueOf(Property.failOnMissingGitDir.getValue());
+        compareToMergeBase = Boolean.valueOf(Property.compareToMergeBase.getValue(projectProperties));
+        fetchReferenceBranch = Boolean.valueOf(Property.fetchReferenceBranch.getValue(projectProperties));
+        fetchBaseBranch = Boolean.valueOf(Property.fetchBaseBranch.getValue(projectProperties));
+        excludePathRegex = compilePattern(Property.excludePathRegex, projectProperties).asPredicate();
+        failOnMissingGitDir = Boolean.valueOf(Property.failOnMissingGitDir.getValue(projectProperties));
+        failOnError = Boolean.valueOf(Property.failOnError.getValue(projectProperties));
+    }
+
+    /**
+     * Returns the value for {@link Property#enabled} without initializing all the other configuration fields to abort quickly without any additional overhead.
+     *
+     * @param session the current session
+     * @return whether or not GIB is enabled or not
+     */
+    public static boolean isEnabled(MavenSession session) {
+        return Boolean.valueOf(Property.enabled.getValue(session.getTopLevelProject().getProperties()));
     }
 
-    private static void checkProperties() {
+    private static void checkProperties(Properties projectProperties) {
         Set<String> availablePropertyNames = Arrays.stream(Property.values())
                 .map(Property::fullName)
                 .collect(Collectors.toSet());
-        String invalidPropertyNames = System.getProperties().keySet().stream()
+        String invalidPropertyNames = Stream.concat(System.getProperties().keySet().stream(), projectProperties.keySet().stream())
+                .distinct()
                 .map(k -> (String) k)
                 .filter(k -> k.startsWith(Property.PREFIX) && !availablePropertyNames.contains(k))
                 .collect(Collectors.joining("\n\t"));
@@ -101,8 +113,8 @@ public class Configuration {
         }
     }
 
-    private static Optional<Path> parseKey(MavenSession session) {
-        String keyOptionValue = Property.repositorySshKey.getValue();
+    private static Optional<Path> parseKey(MavenSession session, Properties projectProperties) {
+        String keyOptionValue = Property.repositorySshKey.getValue(projectProperties);
         if (keyOptionValue != null && ! keyOptionValue.isEmpty()) {
             Path pomDir = session.getCurrentProject().getBasedir().toPath();
             return Optional.of(pomDir.resolve(keyOptionValue).toAbsolutePath().normalize());
@@ -138,7 +150,34 @@ public class Configuration {
         }
     }
 
-    private static Pattern compilePattern(Property property) {
-        return compilePattern(property.getValue(), property);
+    private static Pattern compilePattern(Property property, Properties projectProperties) {
+        return compilePattern(property.getValue(projectProperties), property);
+    }
+
+    @Singleton
+    @Named("gib.configurationProvider")
+    public static class Provider implements javax.inject.Provider<Configuration> {
+
+        private final MavenSession mavenSession;
+
+        private Configuration configuration;
+
+        @Inject
+        public Provider(MavenSession mavenSession) {
+            this.mavenSession = mavenSession;
+        }
+
+        /**
+         * Returns a {@link Configuration} instance which is constructed when first called. Subsequent calls will return the same instance.
+         * 
+         * @return a {@link Configuration} instance
+         */
+        @Override
+        public Configuration get() {
+            if (configuration == null) {
+                configuration = new Configuration(mavenSession);
+            }
+            return configuration;
+        }
     }
 }
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/GuiceModule.java b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/GuiceModule.java
deleted file mode 100644
index da04414..0000000
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/GuiceModule.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.vackosar.gitflowincrementalbuild.boundary;
-
-import com.google.inject.AbstractModule;
-import com.google.inject.Provides;
-import com.vackosar.gitflowincrementalbuild.entity.SkipExecutionException;
-import org.apache.maven.execution.MavenSession;
-import org.codehaus.plexus.logging.Logger;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
-import org.slf4j.impl.StaticLoggerBinder;
-
-import javax.inject.Singleton;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-public class GuiceModule extends AbstractModule {
-
-    public static final String UNSUPPORTED_WORKTREE = "JGit unsupported separate worktree checkout detected from current git dir path: ";
-    private final Logger logger;
-    private final MavenSession mavenSession;
-
-    public GuiceModule(Logger logger, MavenSession mavenSession) {
-        this.logger = logger;
-        this.mavenSession = mavenSession;
-    }
-
-    @Provides
-    @Singleton
-    public Git provideGit(final StaticLoggerBinder staticLoggerBinder, final Configuration configuration) throws IOException, GitAPIException {
-        final FileRepositoryBuilder builder = new FileRepositoryBuilder();
-        File pomDir = mavenSession.getCurrentProject().getBasedir().toPath().toFile();
-        builder.findGitDir(pomDir);
-        if (builder.getGitDir() == null) {
-            String gitDirNotFoundMessage = "Git repository root directory not found ascending from current working directory:'" + pomDir + "'.";
-            logger.warn(gitDirNotFoundMessage + " Next step is determined by failOnMissingGitDir property.");
-            if (configuration.failOnMissingGitDir) {
-                throw new IllegalArgumentException(gitDirNotFoundMessage);
-            } else {
-                throw new SkipExecutionException(gitDirNotFoundMessage);
-            }
-        }
-        if (isWorktree(builder)) {
-            throw new SkipExecutionException(UNSUPPORTED_WORKTREE + builder.getGitDir());
-        }
-        logger.info("Git dir is: " + String.valueOf(builder.getGitDir().getAbsolutePath()));
-        return Git.wrap(builder.build());
-    }
-
-    private void reconfigureForWorktree(Configuration configuration, FileRepositoryBuilder builder) throws IOException {
-
-        Path worktreeGitDir = builder.getGitDir().toPath().normalize().toAbsolutePath();
-        logger.info("Separate worktree checkout detected from current git dir: " + worktreeGitDir);
-        builder.setWorkTree(worktreeGitDir.resolve(Files.lines(worktreeGitDir.resolve("gitdir")).findAny().get()).normalize().toAbsolutePath().toFile());
-        logger.info("Git worktree dir is: " + builder.getWorkTree());
-        builder.setGitDir(worktreeGitDir.resolve(Files.lines(worktreeGitDir.resolve("commondir")).findAny().get()).normalize().toAbsolutePath().toFile());
-        if (configuration.baseBranch.equals("HEAD")) {
-            String fixedHeadRef = "worktrees/" + worktreeGitDir.getFileName().toString() + "/HEAD";
-            logger.info("Replacing HEAD with " + fixedHeadRef + " to compensate for worktree usage.");
-            configuration.baseBranch = fixedHeadRef;
-        }
-    }
-
-    private boolean isWorktree(FileRepositoryBuilder builder) {
-        return builder.getGitDir().toPath().getParent().getFileName().toString().equals("worktrees")
-                && builder.getGitDir().toPath().getParent().getParent().getFileName().toString().equals(".git");
-    }
-
-    @Provides @Singleton public MavenSession provideMavenSession() { return mavenSession; }
-
-    @Provides @Singleton public Logger provideLogger() { return logger; }
-
-    @Override
-    protected void configure() {}
-
-}
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/MavenLifecycleParticipant.java b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/MavenLifecycleParticipant.java
index 77ac85f..937b2af 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/MavenLifecycleParticipant.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/MavenLifecycleParticipant.java
@@ -1,34 +1,35 @@
 package com.vackosar.gitflowincrementalbuild.boundary;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
 import com.vackosar.gitflowincrementalbuild.control.Property;
 import com.vackosar.gitflowincrementalbuild.entity.SkipExecutionException;
 import org.apache.maven.AbstractMavenLifecycleParticipant;
 import org.apache.maven.MavenExecutionException;
 import org.apache.maven.execution.MavenSession;
-import org.codehaus.plexus.component.annotations.Component;
-import org.codehaus.plexus.component.annotations.Requirement;
 import org.codehaus.plexus.logging.Logger;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
 
-import java.io.IOException;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
 
-@Component(role = AbstractMavenLifecycleParticipant.class)
+@Singleton
+@Named("gib")
 public class MavenLifecycleParticipant extends AbstractMavenLifecycleParticipant {
 
-    @Requirement private Logger logger;
+    @Inject private Logger logger;
+
+    @Inject private UnchangedProjectsRemover unchangedProjectsRemover;
+
+    @Inject private Configuration.Provider configProvider;
 
     @Override
     public void afterProjectsRead(MavenSession session) throws MavenExecutionException {
-        mergeCurrentProjectProperties(session);
 
-        if (!Boolean.valueOf(Property.enabled.getValue())) {
+        if (!Configuration.isEnabled(session)) {
             logger.info("gitflow-incremental-builder is disabled.");
             return;
         }
 
+
         // check prerequisites
         if (session.getProjectDependencyGraph() == null) {
             logger.warn("Execution of gitflow-incremental-builder is not supported in this environment: "
@@ -39,11 +40,11 @@ public class MavenLifecycleParticipant extends AbstractMavenLifecycleParticipant
 
         logger.info("gitflow-incremental-builder starting..."); //TODO Print version.
         try {
-            execute(session);
+            unchangedProjectsRemover.act();
         } catch (Exception e) {
-            Boolean failOnError = Boolean.valueOf(Property.failOnError.getValue());
-            if (! failOnError || (e.getMessage() != null && e.getMessage().contains(SkipExecutionException.class.getCanonicalName()))) {
-                logger.info("gitflow-incremental-builder execution skipped: " + e.toString());
+            boolean isSkipExecException = e instanceof SkipExecutionException;
+            if (!configProvider.get().failOnError || isSkipExecException) {
+                logger.info("gitflow-incremental-builder execution skipped: " + (isSkipExecException ? e.getMessage() : e.toString()));
                 logger.debug("Full exception:", e);
             } else {
                 throw new MavenExecutionException("Exception during gitflow-incremental-builder execution occurred.", e);
@@ -51,23 +52,4 @@ public class MavenLifecycleParticipant extends AbstractMavenLifecycleParticipant
         }
         logger.info("gitflow-incremental-builder exiting...");
     }
-
-    private void execute(MavenSession session) throws GitAPIException, IOException {
-        Injector injector = Guice.createInjector(new GuiceModule(logger, session));
-        try {
-            injector.getInstance(UnchangedProjectsRemover.class).act();
-        } finally {
-            Git git = injector.getInstance(Git.class);
-            git.getRepository().close();
-            git.close();
-        }
-    }
-
-    private void mergeCurrentProjectProperties(MavenSession mavenSession) {
-        mavenSession.getTopLevelProject().getProperties().entrySet().stream()
-                .filter(e->e.getKey().toString().startsWith(Property.PREFIX))
-                .filter(e->System.getProperty(e.getKey().toString()) == null)
-                .forEach(e->System.setProperty(e.getKey().toString(), e.getValue().toString()));
-    }
-
 }
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/PhasesComparator.java b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/PhasesComparator.java
deleted file mode 100644
index 6880537..0000000
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/PhasesComparator.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.vackosar.gitflowincrementalbuild.boundary;
-
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Based on https://maven.apache.org/ref/3.3.9/maven-core/lifecycles.html
- */
-class PhasesComparator implements Comparator<String> {
-
-    private static final List<String> list = Arrays.asList(
-            "validate",
-            "initialize",
-            "generate-sources",
-            "process-sources",
-            "generate-resources",
-            "process-resources",
-            "compile",
-            "process-classes",
-            "generate-test-sources",
-            "process-test-sources",
-            "generate-test-resources",
-            "process-test-resources",
-            "test-compile",
-            "process-test-classes",
-            "test",
-            "prepare-package",
-            "package",
-            "pre-integration-test",
-            "integration-test",
-            "post-integration-test",
-            "verify",
-            "install");
-
-    public @Override int compare(String o1, String o2) {
-        return list.indexOf(o1) - list.indexOf(o2);
-    }
-
-}
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java
index e89b179..b57863c 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemover.java
@@ -1,6 +1,5 @@
 package com.vackosar.gitflowincrementalbuild.boundary;
 
-import com.google.inject.Singleton;
 import com.vackosar.gitflowincrementalbuild.control.ChangedProjects;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.project.MavenProject;
@@ -8,6 +7,9 @@ import org.codehaus.plexus.logging.Logger;
 import org.eclipse.jgit.api.errors.GitAPIException;
 
 import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
 import java.io.IOException;
 import java.util.*;
 import java.util.regex.Pattern;
@@ -15,6 +17,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 @Singleton
+@Named("gib.unchangedProjectsRemover")
 class UnchangedProjectsRemover {
 
     private static final String MAVEN_TEST_SKIP = "maven.test.skip";
@@ -22,19 +25,10 @@ class UnchangedProjectsRemover {
     static final String TEST_JAR_DETECTED = "Dependency with test-jar goal detected. Will compile test sources.";
     private static final String GOAL_TEST_JAR = "test-jar";
 
-    private Configuration configuration;
-    private Logger logger;
-    private ChangedProjects changedProjects;
-    private MavenSession mavenSession;
-
-    @Inject
-    UnchangedProjectsRemover(Configuration configuration, Logger logger, ChangedProjects changedProjects,
-            MavenSession mavenSession) {
-        this.configuration = configuration;
-        this.logger = logger;
-        this.changedProjects = changedProjects;
-        this.mavenSession = mavenSession;
-    }
+    @Inject private Logger logger;
+    @Inject private ChangedProjects changedProjects;
+    @Inject private MavenSession mavenSession;
+    @Inject private Configuration.Provider configProvider;
 
     void act() throws GitAPIException, IOException {
         Set<MavenProject> changed = changedProjects.get();
@@ -43,16 +37,16 @@ class UnchangedProjectsRemover {
         Set<MavenProject> impacted = changed.stream()
             .flatMap(this::streamProjectWithDependents)
             .collect(Collectors.toCollection(LinkedHashSet::new));
-        if (!configuration.buildAll) {
+        if (!configProvider.get().buildAll) {
             Set<MavenProject> rebuild = getRebuildProjects(impacted);
             if (rebuild.isEmpty()) {
                 logger.info("No changed artifacts to build. Executing validate goal on current project only.");
                 mavenSession.setProjects(Collections.singletonList(mavenSession.getCurrentProject()));
                 mavenSession.getGoals().clear();
                 mavenSession.getGoals().add("validate");
-            } else if (!configuration.forceBuildModules.isEmpty()) {
+            } else if (!configProvider.get().forceBuildModules.isEmpty()) {
                 Stream<MavenProject> forceBuildModules = mavenSession.getProjects().stream()
-                        .filter(p -> matchesAny(p.getArtifactId(), configuration.forceBuildModules))
+                        .filter(p -> matchesAny(p.getArtifactId(), configProvider.get().forceBuildModules))
                         .filter(p -> !rebuild.contains(p))
                         .map(this::applyNotImpactedModuleArgs);
                 mavenSession.setProjects(
@@ -68,7 +62,7 @@ class UnchangedProjectsRemover {
     }
 
     private Set<MavenProject> getRebuildProjects(Set<MavenProject> changedProjects) {
-        if (configuration.makeUpstream) {
+        if (configProvider.get().makeUpstream) {
             return Stream.concat(changedProjects.stream(), collectDependencies(changedProjects))
                 .collect(Collectors.toCollection(LinkedHashSet::new));
         } else {
@@ -85,7 +79,7 @@ class UnchangedProjectsRemover {
 
     private MavenProject applyNotImpactedModuleArgs(MavenProject mavenProject) {
         final Properties projectProperties = mavenProject.getProperties();
-        if (configuration.skipTestsForNotImpactedModules) {
+        if (configProvider.get().skipTestsForNotImpactedModules) {
             if (projectDeclaresTestJarGoal(mavenProject)) {
                 logger.debug(mavenProject.getArtifactId() + ": " + TEST_JAR_DETECTED);
                 projectProperties.setProperty(MAVEN_TEST_SKIP_EXEC, Boolean.TRUE.toString());
@@ -93,7 +87,7 @@ class UnchangedProjectsRemover {
                 projectProperties.setProperty(MAVEN_TEST_SKIP, Boolean.TRUE.toString());
             }
         }
-        configuration.argsForNotImpactedModules.forEach(projectProperties::setProperty);
+        configProvider.get().argsForNotImpactedModules.forEach(projectProperties::setProperty);
         return mavenProject;
     }
 
@@ -119,7 +113,7 @@ class UnchangedProjectsRemover {
         return Stream.concat(
             Stream.of(project),
             mavenSession.getProjectDependencyGraph().getDownstreamProjects(project, true).stream()
-                .filter(p -> !configuration.excludeTransitiveModulesPackagedAs.contains(p.getPackaging())));
+                .filter(p -> !configProvider.get().excludeTransitiveModulesPackagedAs.contains(p.getPackaging())));
     }
 
     private Stream<MavenProject> streamProjectWithDependencies(MavenProject project) {
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/control/ChangedProjects.java b/src/main/java/com/vackosar/gitflowincrementalbuild/control/ChangedProjects.java
index 236ec44..675f799 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/control/ChangedProjects.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/control/ChangedProjects.java
@@ -6,14 +6,17 @@ import org.codehaus.plexus.logging.Logger;
 import org.eclipse.jgit.api.errors.GitAPIException;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 import javax.inject.Singleton;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 @Singleton
+@Named("gib.changedProjects")
 public class ChangedProjects {
 
     @Inject private Logger logger;
@@ -23,12 +26,12 @@ public class ChangedProjects {
 
     public Set<MavenProject> get() throws GitAPIException, IOException {
         return differentFiles.get().stream()
-                .map(path -> findProject(path, mavenSession))
-                .filter(project -> project != null)
+                .map(path -> findProject(path))
+                .filter(Objects::nonNull)
                 .collect(Collectors.toSet());
     }
 
-    private MavenProject findProject(Path diffPath, MavenSession mavenSession) {
+    private MavenProject findProject(Path diffPath) {
         Map<Path, MavenProject> map = modules.createPathMap(mavenSession);
         Path path = diffPath;
         while (path != null && ! map.containsKey(path)) {
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/control/DifferentFiles.java b/src/main/java/com/vackosar/gitflowincrementalbuild/control/DifferentFiles.java
index 34de37e..a7feaaf 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/control/DifferentFiles.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/control/DifferentFiles.java
@@ -1,6 +1,9 @@
 package com.vackosar.gitflowincrementalbuild.control;
 
 import com.vackosar.gitflowincrementalbuild.boundary.Configuration;
+import com.vackosar.gitflowincrementalbuild.entity.SkipExecutionException;
+
+import org.apache.maven.execution.MavenSession;
 import org.codehaus.plexus.logging.Logger;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.Status;
@@ -9,12 +12,16 @@ import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 import javax.inject.Singleton;
+
+import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.HashSet;
@@ -22,131 +29,181 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 @Singleton
+@Named("gib.differentFiles")
 public class DifferentFiles {
 
+    public static final String UNSUPPORTED_WORKTREE = "JGit unsupported separate worktree checkout detected from current git dir path: ";
+
     private static final String HEAD = "HEAD";
     private static final String REFS_REMOTES = "refs/remotes/";
     private static final String REFS_HEADS = "refs/heads/";
-    @Inject private Git git;
-    @Inject private Configuration configuration;
+
     @Inject private Logger logger;
+    @Inject private MavenSession mavenSession;
+    @Inject private Configuration.Provider configProvider;
 
     public Set<Path> get() throws GitAPIException, IOException {
         Set<Path> paths = new HashSet<>();
-        fetch();
-        checkout();
-        final Path workTree = git.getRepository().getWorkTree().toPath().normalize().toAbsolutePath();
-        if (!configuration.disableBranchComparison) {
-            paths.addAll(getBranchDiff(workTree));
-        }
-        if (configuration.uncommited || configuration.untracked) {
-            paths.addAll(getChangesFromStatus(workTree));
-        }
-        return paths;
-    }
 
-    private Set<Path> getBranchDiff(Path workTree) throws IOException {
-        RevCommit base = getBranchCommit(configuration.baseBranch);
-        final TreeWalk treeWalk = new TreeWalk(git.getRepository());
+        Configuration configuration = configProvider.get();
+        Git git = setupGit(configuration);
         try {
-            treeWalk.addTree(base.getTree());
-            treeWalk.addTree(resolveReference(base).getTree());
-            treeWalk.setFilter(TreeFilter.ANY_DIFF);
-            treeWalk.setRecursive(true);
-            return getDiff(treeWalk, workTree);
+            Worker worker = new Worker(git, configuration);
+
+            worker.fetch();
+            worker.checkout();
+            if (!configuration.disableBranchComparison) {
+                paths.addAll(worker.getBranchDiff());
+            }
+            if (configuration.uncommited || configuration.untracked) {
+                paths.addAll(worker.getChangesFromStatus());
+            }
         } finally {
-            treeWalk.close();
+            git.getRepository().close();
+            git.close();
         }
+        return paths;
     }
 
-    private void checkout() throws IOException, GitAPIException {
-        if (! (HEAD.equals(configuration.baseBranch) || configuration.baseBranch.startsWith("worktrees/")) && ! git.getRepository().getFullBranch().equals(configuration.baseBranch)) {
-            logger.info("Checking out base branch " + configuration.baseBranch + "...");
-            git.checkout().setName(configuration.baseBranch).call();
+    private Git setupGit(Configuration configuration) throws IOException {
+        final FileRepositoryBuilder builder = new FileRepositoryBuilder();
+        File pomDir = mavenSession.getCurrentProject().getBasedir().toPath().toFile();
+        builder.findGitDir(pomDir);
+        if (builder.getGitDir() == null) {
+            String gitDirNotFoundMessage = "Git repository root directory not found ascending from current working directory:'" + pomDir + "'.";
+            logger.warn(gitDirNotFoundMessage + " Next step is determined by failOnMissingGitDir property.");
+            if (configuration.failOnMissingGitDir) {
+                throw new IllegalArgumentException(gitDirNotFoundMessage);
+            } else {
+                throw new SkipExecutionException(gitDirNotFoundMessage);
+            }
+        }
+        if (isWorktree(builder)) {
+            throw new SkipExecutionException(UNSUPPORTED_WORKTREE + builder.getGitDir());
         }
+        return Git.wrap(builder.build());
     }
 
-    private void fetch() throws GitAPIException {
-        if (!configuration.disableBranchComparison && configuration.fetchReferenceBranch) {
-            fetch(configuration.referenceBranch);
-        }
-        if (configuration.fetchBaseBranch) {
-            fetch(configuration.baseBranch);
-        }
+    private boolean isWorktree(FileRepositoryBuilder builder) {
+        Path gitDirParent = builder.getGitDir().toPath().getParent();
+        return gitDirParent.getFileName().toString().equals("worktrees")
+                && gitDirParent.getParent().getFileName().toString().equals(".git");
     }
 
-    private void fetch(String branchName) throws GitAPIException {
-        logger.info("Fetching branch " + branchName);
-        if (!branchName.startsWith(REFS_REMOTES)) {
-            throw new IllegalArgumentException("Branch name '" + branchName + "' is not tracking branch name since it does not start " + REFS_REMOTES);
+    private class Worker {
+
+        private final Git git;
+        private final Path workTree;
+        private final Configuration configuration;
+
+        public Worker(Git git, Configuration configuration) {
+            this.git = git;
+            this.workTree = git.getRepository().getWorkTree().toPath().normalize().toAbsolutePath();
+            this.configuration = configuration;
         }
-        String remoteName = extractRemoteName(branchName);
-        String shortName = extractShortName(remoteName, branchName);
-        git.fetch().setRemote(remoteName).setRefSpecs(new RefSpec(REFS_HEADS + shortName + ":" + branchName)).call();
-    }
 
-    private String extractRemoteName(String branchName) {
-        return branchName.split("/")[2];
-    }
+        private Set<Path> getBranchDiff() throws IOException {
+            RevCommit base = getBranchCommit(configuration.baseBranch);
+            final TreeWalk treeWalk = new TreeWalk(git.getRepository());
+            try {
+                treeWalk.addTree(base.getTree());
+                treeWalk.addTree(resolveReference(base).getTree());
+                treeWalk.setFilter(TreeFilter.ANY_DIFF);
+                treeWalk.setRecursive(true);
+                return getDiff(treeWalk, workTree);
+            } finally {
+                treeWalk.close();
+            }
+        }
 
-    private String extractShortName(String remoteName, String branchName) {
-        return branchName.replaceFirst(REFS_REMOTES + remoteName + "/", "");
-    }
+        private void checkout() throws IOException, GitAPIException {
+            if (! (HEAD.equals(configuration.baseBranch) || configuration.baseBranch.startsWith("worktrees/")) && ! git.getRepository().getFullBranch().equals(configuration.baseBranch)) {
+                logger.info("Checking out base branch " + configuration.baseBranch + "...");
+                git.checkout().setName(configuration.baseBranch).call();
+            }
+        }
 
-    private RevCommit getMergeBase(RevCommit baseCommit, RevCommit referenceHeadCommit) throws IOException {
-        RevWalk walk = new RevWalk(git.getRepository());
-        walk.setRevFilter(RevFilter.MERGE_BASE);
-        walk.markStart(walk.lookupCommit(baseCommit));
-        walk.markStart(walk.lookupCommit(referenceHeadCommit));
-        RevCommit commit = walk.next();
-        walk.close();
-        logger.info("Using merge base of id: " + commit.getId());
-        return commit;
-    }
+        private void fetch() throws GitAPIException {
+            if (!configuration.disableBranchComparison && configuration.fetchReferenceBranch) {
+                fetch(configuration.referenceBranch);
+            }
+            if (configuration.fetchBaseBranch) {
+                fetch(configuration.baseBranch);
+            }
+        }
 
-    private Set<Path> getDiff(TreeWalk treeWalk, Path gitDir) throws IOException {
-        final Set<Path> paths = new HashSet<>();
-        while (treeWalk.next()) {
-            Path path = gitDir.resolve(treeWalk.getPathString()).normalize();
-            if (! configuration.excludePathRegex.test(path.toString())) {
-                paths.add(path);
+        private void fetch(String branchName) throws GitAPIException {
+            logger.info("Fetching branch " + branchName);
+            if (!branchName.startsWith(REFS_REMOTES)) {
+                throw new IllegalArgumentException("Branch name '" + branchName + "' is not tracking branch name since it does not start " + REFS_REMOTES);
             }
+            String remoteName = extractRemoteName(branchName);
+            String shortName = extractShortName(remoteName, branchName);
+            git.fetch().setRemote(remoteName).setRefSpecs(new RefSpec(REFS_HEADS + shortName + ":" + branchName)).call();
         }
-        return paths;
-    }
 
-    private RevCommit getBranchCommit(String branchName) throws IOException {
-        ObjectId objectId = git.getRepository().resolve(branchName);
+        private String extractRemoteName(String branchName) {
+            return branchName.split("/")[2];
+        }
 
-        if (objectId == null) {
-            throw new IllegalArgumentException("Git branch of name '" + branchName + "' not found.");
+        private String extractShortName(String remoteName, String branchName) {
+            return branchName.replaceFirst(REFS_REMOTES + remoteName + "/", "");
         }
-        final RevWalk walk = new RevWalk(git.getRepository());
-        RevCommit commit = walk.parseCommit(objectId);
-        walk.close();
-        logger.info("Reference commit of branch " + branchName + " is commit of id: " + commit.getId());
-        return commit;
-    }
 
-    private Set<Path> getChangesFromStatus(Path gitDir) throws GitAPIException {
-        Set<String> changes = new HashSet<>();
-        Status status = git.status().call();
-        if (configuration.uncommited) {
-            changes.addAll(status.getUncommittedChanges());
+        private RevCommit getMergeBase(RevCommit baseCommit, RevCommit referenceHeadCommit) throws IOException {
+            RevWalk walk = new RevWalk(git.getRepository());
+            walk.setRevFilter(RevFilter.MERGE_BASE);
+            walk.markStart(walk.lookupCommit(baseCommit));
+            walk.markStart(walk.lookupCommit(referenceHeadCommit));
+            RevCommit commit = walk.next();
+            walk.close();
+            logger.info("Using merge base of id: " + commit.getId());
+            return commit;
         }
-        if (configuration.untracked) {
-            changes.addAll(status.getUntracked());
+
+        private Set<Path> getDiff(TreeWalk treeWalk, Path gitDir) throws IOException {
+            final Set<Path> paths = new HashSet<>();
+            while (treeWalk.next()) {
+                Path path = gitDir.resolve(treeWalk.getPathString()).normalize();
+                if (! configuration.excludePathRegex.test(path.toString())) {
+                    paths.add(path);
+                }
+            }
+            return paths;
         }
-        return changes.stream().map(gitDir::resolve).map(Path::normalize).collect(Collectors.toSet());
-    }
 
-    private RevCommit resolveReference(RevCommit base) throws IOException {
-        RevCommit refHead = getBranchCommit(configuration.referenceBranch);
-        if (configuration.compareToMergeBase) {
-            return getMergeBase(base, refHead);
-        } else {
-            return refHead;
+        private RevCommit getBranchCommit(String branchName) throws IOException {
+            ObjectId objectId = git.getRepository().resolve(branchName);
+
+            if (objectId == null) {
+                throw new IllegalArgumentException("Git branch of name '" + branchName + "' not found.");
+            }
+            final RevWalk walk = new RevWalk(git.getRepository());
+            RevCommit commit = walk.parseCommit(objectId);
+            walk.close();
+            logger.info("Reference commit of branch " + branchName + " is commit of id: " + commit.getId());
+            return commit;
         }
-    }
 
+        private Set<Path> getChangesFromStatus() throws GitAPIException {
+            Set<String> changes = new HashSet<>();
+            Status status = git.status().call();
+            if (configuration.uncommited) {
+                changes.addAll(status.getUncommittedChanges());
+            }
+            if (configuration.untracked) {
+                changes.addAll(status.getUntracked());
+            }
+            return changes.stream().map(workTree::resolve).map(Path::normalize).collect(Collectors.toSet());
+        }
+
+        private RevCommit resolveReference(RevCommit base) throws IOException {
+            RevCommit refHead = getBranchCommit(configuration.referenceBranch);
+            if (configuration.compareToMergeBase) {
+                return getMergeBase(base, refHead);
+            } else {
+                return refHead;
+            }
+        }
+    }
 }
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/control/Modules.java b/src/main/java/com/vackosar/gitflowincrementalbuild/control/Modules.java
index bca11d4..ae262a9 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/control/Modules.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/control/Modules.java
@@ -1,6 +1,5 @@
 package com.vackosar.gitflowincrementalbuild.control;
 
-import com.google.inject.Singleton;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.project.MavenProject;
 
@@ -8,7 +7,11 @@ import java.nio.file.Path;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import javax.inject.Named;
+import javax.inject.Singleton;
+
 @Singleton
+@Named("gib.modules")
 public class Modules {
 
     public Map<Path, MavenProject> createPathMap(MavenSession session) {
diff --git a/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java b/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java
index cc67fb8..773fe36 100644
--- a/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java
+++ b/src/main/java/com/vackosar/gitflowincrementalbuild/control/Property.java
@@ -1,5 +1,8 @@
 package com.vackosar.gitflowincrementalbuild.control;
 
+import java.util.Optional;
+import java.util.Properties;
+
 public enum Property {
     enabled("true"),
     repositorySshKey(""),
@@ -18,14 +21,15 @@ public enum Property {
     fetchReferenceBranch("false"),
     excludePathRegex(Constants.NEVER_MATCH_REGEX),
     failOnMissingGitDir("true"),
-    failOnError("true")
-    ;
+    failOnError("true");
 
     public static final String PREFIX = "gib.";
 
-    public final String defaultValue;
+    private final String fullName;
+    private final String defaultValue;
 
     Property(String defaultValue) {
+        this.fullName = PREFIX + name();
         this.defaultValue = defaultValue;
     }
 
@@ -34,19 +38,12 @@ public enum Property {
     }
 
     public String fullName() {
-        return PREFIX + this.name();
-    }
-
-    public String getValue() {
-        return System.getProperty(fullName(), defaultValue);
+        return fullName;
     }
 
-    public void setValue(String value) {
-        if (value ==null) {
-            System.clearProperty(fullName());
-        } else {
-            System.setProperty(fullName(), value);
-        }
+    public String getValue(Properties projectProperties) {
+        return Optional.ofNullable(System.getProperty(fullName))
+                .orElseGet(() -> projectProperties.getProperty(fullName, defaultValue));
     }
 
     public static String exemplifyAll() {
diff --git a/src/main/java/org/slf4j/impl/StaticLoggerBinder.java b/src/test/java/org/slf4j/impl/StaticLoggerBinder.java
similarity index 90%
rename from src/main/java/org/slf4j/impl/StaticLoggerBinder.java
rename to src/test/java/org/slf4j/impl/StaticLoggerBinder.java
index a160345..1aeac21 100644
--- a/src/main/java/org/slf4j/impl/StaticLoggerBinder.java
+++ b/src/test/java/org/slf4j/impl/StaticLoggerBinder.java
@@ -1,26 +1,24 @@
 package org.slf4j.impl;
 
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
+import org.codehaus.plexus.logging.console.ConsoleLoggerManager;
 import org.slf4j.ILoggerFactory;
 import org.slf4j.Logger;
 import org.slf4j.Marker;
+import org.slf4j.spi.LoggerFactoryBinder;
 
 import java.util.Arrays;
 
-@Singleton
-public class StaticLoggerBinder {
+public class StaticLoggerBinder implements LoggerFactoryBinder {
 
     private final org.codehaus.plexus.logging.Logger logger;
-    private static StaticLoggerBinder staticLoggerBinder;
 
-    @Inject
-    public StaticLoggerBinder(org.codehaus.plexus.logging.Logger logger) {
+    private StaticLoggerBinder(org.codehaus.plexus.logging.Logger logger) {
         this.logger = logger;
-        StaticLoggerBinder.staticLoggerBinder = this;
     }
 
-    public static final StaticLoggerBinder getSingleton() {return staticLoggerBinder;}
+    public static final StaticLoggerBinder getSingleton() {
+        return LazyHolder.INSTANCE;
+    }
 
     public String getLoggerFactoryClassStr() {return null;}
 
@@ -95,4 +93,9 @@ public class StaticLoggerBinder {
         };
     }
 
+    // https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
+    private static class LazyHolder {
+
+        public static final StaticLoggerBinder INSTANCE = new StaticLoggerBinder(new ConsoleLoggerManager().getLoggerForComponent("Test"));
+    }
 }


Test patch:
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/BaseRepoTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/BaseRepoTest.java
index 6b0cd5e..cc14fff 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/BaseRepoTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/BaseRepoTest.java
@@ -6,25 +6,29 @@ import com.vackosar.gitflowincrementalbuild.mocks.MavenSessionMock;
 
 import org.apache.commons.lang3.SystemUtils;
 import org.apache.maven.execution.MavenSession;
-import org.codehaus.plexus.logging.console.ConsoleLoggerManager;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.rules.TemporaryFolder;
-import org.slf4j.impl.StaticLoggerBinder;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.PrintStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Properties;
 
 public abstract class BaseRepoTest {
 
+    /** The project properties for the top-level project of {@link #getMavenSessionMock() MavenSessionMock} for config init. */
+    protected final Properties projectProperties = new Properties();
     private final boolean useSymLinkedFolder;
+    private final boolean withRemote;
 
     protected LocalRepoMock localRepoMock;
-    private StaticLoggerBinder staticLoggerBinder;
+    /** {@link LocalRepoMock#getBaseCanonicalBaseFolder()} of {@link #localRepoMock}. */
+    protected Path repoPath;
+
     protected ByteArrayOutputStream consoleOut;
     private final PrintStream normalOut;
 
@@ -32,12 +36,13 @@ public abstract class BaseRepoTest {
     public TemporaryFolder temporaryFolder = new TemporaryFolder();
 
     public BaseRepoTest() {
-        this(false);
+        this(false, false);
     }
 
-    public BaseRepoTest(boolean useSymLinkedFolder) {
+    public BaseRepoTest(boolean useSymLinkedFolder, boolean withRemote) {
         this.normalOut = System.out;
         this.useSymLinkedFolder = useSymLinkedFolder;
+        this.withRemote = withRemote;
     }
 
     @Before
@@ -65,13 +70,17 @@ public abstract class BaseRepoTest {
 
             repoBaseFolder = link.toFile();
         }
-        localRepoMock = new LocalRepoMock(repoBaseFolder, false);
+        localRepoMock = new LocalRepoMock(repoBaseFolder, withRemote);
+        repoPath = localRepoMock.getBaseCanonicalBaseFolder().toPath();
     }
 
-    protected void init() {
-        staticLoggerBinder = new StaticLoggerBinder(new ConsoleLoggerManager().getLoggerForComponent("Test"));
+    private void init() {
         resetConsoleOut();
-        resetProperties();
+
+        projectProperties.setProperty(Property.uncommited.fullName(), "false");
+        projectProperties.setProperty(Property.untracked.fullName(), "false");
+        projectProperties.setProperty(Property.referenceBranch.fullName(), "refs/heads/develop");
+        projectProperties.setProperty(Property.compareToMergeBase.fullName(), "false");
     }
 
     private void resetConsoleOut() {
@@ -79,15 +88,6 @@ public abstract class BaseRepoTest {
         System.setOut(new PrintStream(consoleOut));
     }
 
-    private void resetProperties() {
-        for (Property property: Property.values()) {
-            property.setValue(property.defaultValue);
-        }
-        Property.uncommited.setValue("false");
-        Property.referenceBranch.setValue("refs/heads/develop");
-        Property.compareToMergeBase.setValue("false");
-    }
-
     @After
     public void after() throws Exception {
         if (localRepoMock != null) {
@@ -97,12 +97,7 @@ public abstract class BaseRepoTest {
         normalOut.print(consoleOut.toString());
     }
 
-    protected LocalRepoMock getLocalRepoMock() {
-        return localRepoMock;
-    }
-
     protected MavenSession getMavenSessionMock() throws Exception {
-        return MavenSessionMock.get(localRepoMock.getBaseCanonicalBaseFolder().toPath());
+        return MavenSessionMock.get(repoPath, projectProperties);
     }
-
 }
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/ConfigurationTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/ConfigurationTest.java
index 0f841db..d2df5b9 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/ConfigurationTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/ConfigurationTest.java
@@ -3,6 +3,8 @@ package com.vackosar.gitflowincrementalbuild.boundary;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.Arrays;
 import java.util.Properties;
@@ -11,6 +13,7 @@ import java.util.regex.PatternSyntaxException;
 
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
 import org.hamcrest.core.IsInstanceOf;
 import org.junit.Before;
 import org.junit.Rule;
@@ -20,7 +23,6 @@ import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
 import com.google.common.collect.ImmutableMap;
@@ -57,11 +59,17 @@ public class ConfigurationTest {
     private MavenExecutionRequest mavenExecutionRequestMock;
 
     @Mock
-    private MavenSession mavenSessioMock;
+    private MavenSession mavenSessionMock;
+
+    private final Properties projectProperties = new Properties();
 
     @Before
     public void setup() {
-        Mockito.when(mavenSessioMock.getRequest()).thenReturn(mavenExecutionRequestMock);
+        when(mavenSessionMock.getRequest()).thenReturn(mavenExecutionRequestMock);
+
+        MavenProject mockTLProject = mock(MavenProject.class);
+        when(mockTLProject.getProperties()).thenReturn(projectProperties);
+        when(mavenSessionMock.getTopLevelProject()).thenReturn(mockTLProject);
     }
 
     @Test
@@ -72,23 +80,36 @@ public class ConfigurationTest {
         thrown.expectMessage(invalidProperty);
         thrown.expectMessage(Property.disableBranchComparison.fullName());  // just one of those valid ones
 
-        new Configuration(mavenSessioMock);
+        new Configuration.Provider(mavenSessionMock).get();
     }
 
     @Test
     public void enabled() {
         System.setProperty(Property.enabled.fullName(), "false");
 
-        Configuration configuration = new Configuration(mavenSessioMock);
+        assertFalse(Configuration.isEnabled(mavenSessionMock));
+    }
+
+    @Test
+    public void enabled_projectProperties() {
+        projectProperties.put(Property.enabled.fullName(), "false");
+
+        assertFalse(Configuration.isEnabled(mavenSessionMock));
+    }
+
+    @Test
+    public void enabled_projectProperties_overriddenBySystemProperty() {
+        projectProperties.put(Property.enabled.fullName(), "true");
+        System.setProperty(Property.enabled.fullName(), "false");
 
-        assertFalse(configuration.enabled);
+        assertFalse(Configuration.isEnabled(mavenSessionMock));
     }
 
     @Test
     public void argsForNotImpactedModules() {
         System.setProperty(Property.argsForNotImpactedModules.fullName(), "x=true a=false");
 
-        Configuration configuration = new Configuration(mavenSessioMock);
+        Configuration configuration = new Configuration.Provider(mavenSessionMock).get();
 
         assertEquals(ImmutableMap.of("x", "true", "a", "false"), configuration.argsForNotImpactedModules);
     }
@@ -97,7 +118,7 @@ public class ConfigurationTest {
     public void excludeTransitiveModulesPackagedAs() {
         System.setProperty(Property.excludeTransitiveModulesPackagedAs.fullName(), "ear,war");
 
-        Configuration configuration = new Configuration(mavenSessioMock);
+        Configuration configuration = new Configuration.Provider(mavenSessionMock).get();
 
         assertEquals(Arrays.asList("ear", "war"), configuration.excludeTransitiveModulesPackagedAs);
     }
@@ -107,7 +128,7 @@ public class ConfigurationTest {
         String expectedPatternString = ".*-some-artifact";
         System.setProperty(Property.forceBuildModules.fullName(), expectedPatternString);
 
-        Configuration configuration = new Configuration(mavenSessioMock);
+        Configuration configuration = new Configuration.Provider(mavenSessionMock).get();
 
         assertNotNull("Field forceBuildModules is null", configuration.forceBuildModules);
         assertEquals("Unexpected number of Patterns in forceBuildModules", 1, configuration.forceBuildModules.size());
@@ -123,6 +144,6 @@ public class ConfigurationTest {
         thrown.expectMessage(Property.forceBuildModules.fullName());
         thrown.expectCause(IsInstanceOf.instanceOf(PatternSyntaxException.class));
 
-        new Configuration(mavenSessioMock);
+        new Configuration.Provider(mavenSessionMock).get();
     }
 }
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenIntegrationTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenIntegrationTest.java
index c242301..3cb3824 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenIntegrationTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenIntegrationTest.java
@@ -2,6 +2,7 @@ package com.vackosar.gitflowincrementalbuild.boundary;
 
 import com.vackosar.gitflowincrementalbuild.BaseRepoTest;
 import com.vackosar.gitflowincrementalbuild.ProcessUtils;
+import com.vackosar.gitflowincrementalbuild.control.DifferentFiles;
 import com.vackosar.gitflowincrementalbuild.control.Property;
 
 import org.eclipse.jgit.api.Git;
@@ -64,7 +65,7 @@ public class MavenIntegrationTest extends BaseRepoTest {
         ProcessUtils.awaitProcess(new ProcessBuilder(
                 ProcessUtils.cmdArgs(
                         "mvn", "install", localRepoArg, gibVersionArg, DEFAULT_POMFILE_ARG, "-Dgib." + Property.enabled + "=false"))
-                .directory(getLocalRepoMock().getBaseCanonicalBaseFolder())
+                .directory(localRepoMock.getBaseCanonicalBaseFolder())
                 .start());
         initialInstallDone = true;
     }
@@ -73,7 +74,7 @@ public class MavenIntegrationTest extends BaseRepoTest {
     public void worktreeFails() throws Exception {
         final String output = executeBuild(Collections.singletonList("--file=wrkf2/parent/pom.xml"));
         System.out.println(output);
-        Assert.assertTrue(output.contains(GuiceModule.UNSUPPORTED_WORKTREE));
+        Assert.assertTrue(output.contains(DifferentFiles.UNSUPPORTED_WORKTREE));
     }
 
     @Test
@@ -178,7 +179,7 @@ public class MavenIntegrationTest extends BaseRepoTest {
     }
 
     private String executeBuild(List<String> args) throws IOException, InterruptedException {
-        final List<String> commandBase = ProcessUtils.cmdArgs("mvn", "package", localRepoArg, gibVersionArg);
+        final List<String> commandBase = ProcessUtils.cmdArgs("mvn", "-e", "package", localRepoArg, 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());
@@ -188,7 +189,7 @@ public class MavenIntegrationTest extends BaseRepoTest {
         List<String> command = Stream.concat(commandBaseWithFile.stream(), args.stream()).collect(Collectors.toList());
         final Process process =
                 new ProcessBuilder(command)
-                        .directory(getLocalRepoMock().getBaseCanonicalBaseFolder())
+                        .directory(localRepoMock.getBaseCanonicalBaseFolder())
                         .start();
         return ProcessUtils.awaitProcess(process);
     }
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenLifecycleParticipantTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenLifecycleParticipantTest.java
index 0fbbba3..fd0c77b 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenLifecycleParticipantTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/MavenLifecycleParticipantTest.java
@@ -1,126 +1,130 @@
 package com.vackosar.gitflowincrementalbuild.boundary;
 
 import com.vackosar.gitflowincrementalbuild.control.Property;
-import com.vackosar.gitflowincrementalbuild.BaseRepoTest;
 import com.vackosar.gitflowincrementalbuild.entity.SkipExecutionException;
+
+import org.apache.maven.MavenExecutionException;
+import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenSession;
+import org.apache.maven.execution.ProjectDependencyGraph;
+import org.apache.maven.project.MavenProject;
 import org.codehaus.plexus.logging.Logger;
-import org.junit.Assert;
+import org.hamcrest.core.IsSame;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.mockito.stubbing.Answer;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.powermock.reflect.Whitebox;
 
-import java.lang.reflect.Field;
+import java.util.Properties;
 
 import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Matchers.contains;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
-public class MavenLifecycleParticipantTest extends BaseRepoTest {
+@RunWith(MockitoJUnitRunner.class)
+public class MavenLifecycleParticipantTest {
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    @Mock
+    private Logger loggerMock;
+
+    @Mock
+    private MavenSession mavenSessionMock;
 
-    private MavenLifecycleParticipant participant;
-    private StringBuilder builder;
+    @Mock
+    private UnchangedProjectsRemover unchangedProjectsRemoverMock;
+
+    @InjectMocks
+    private MavenLifecycleParticipant underTest;
+
+    private final Properties projectProperties = new Properties();
 
     @Before
-    public void before() throws Exception {
-        super.before();
-        Logger logger = mock(Logger.class);
-        participant = new MavenLifecycleParticipant();
-        Field loggerField = participant.getClass().getDeclaredField("logger");
-        loggerField.setAccessible(true);
-        loggerField.set(participant, logger);
-        builder = new StringBuilder();
-        Answer<?> loggingAnswer = invocation -> builder.append(invocation.getArguments()[0]).append("\n");
-        doAnswer(loggingAnswer).when(logger).warn(anyString());
-        doAnswer(loggingAnswer).when(logger).info(anyString());
-        doAnswer(invocation -> builder).when(logger).debug(anyString());
-    }
+    public void before() {
+        MavenProject mockTLProject = mock(MavenProject.class);
+        when(mockTLProject.getProperties()).thenReturn(projectProperties);
+        when(mavenSessionMock.getTopLevelProject()).thenReturn(mockTLProject);
 
-    @Test
-    public void disabled() throws Exception {
-        Property.enabled.setValue("false");
-        MavenSession session = getMavenSessionMock();
+        when(mavenSessionMock.getRequest()).thenReturn(mock(MavenExecutionRequest.class));
 
-        participant.afterProjectsRead(session);
+        when(mavenSessionMock.getProjectDependencyGraph()).thenReturn(mock(ProjectDependencyGraph.class));
 
-        Assert.assertTrue(builder.toString().contains("gitflow-incremental-builder is disabled."));
-        verify(session).getTopLevelProject();
-        verifyNoMoreInteractions(session);
+        Whitebox.setInternalState(underTest, new Configuration.Provider(mavenSessionMock));
     }
 
     @Test
-    public void disabledOverridingPom() throws Exception {
-        Property.enabled.setValue("false");
-        MavenSession session = getMavenSessionMock();
-        session.getTopLevelProject().getProperties().setProperty(Property.enabled.fullName(), "true");
+    public void disabled() throws Exception {
+        projectProperties.setProperty(Property.enabled.fullName(), "false");
 
-        participant.afterProjectsRead(session);
+        underTest.afterProjectsRead(mavenSessionMock);
 
-        Assert.assertTrue(builder.toString().contains("gitflow-incremental-builder is disabled."));
-        verify(session, times(2)).getTopLevelProject();
-        verifyNoMoreInteractions(session);
+        verify(loggerMock).info(contains("disabled"));
+        verifyZeroInteractions(unchangedProjectsRemoverMock);
+        verify(mavenSessionMock, never()).getProjectDependencyGraph();
     }
 
     @Test
-    public void disabledInPom() throws Exception {
-        Property.enabled.setValue(null);
-        MavenSession session = getMavenSessionMock();
-        session.getTopLevelProject().getProperties().setProperty(Property.enabled.fullName(), "false");
+    public void defaultlyEnabled() throws Exception {
 
-        participant.afterProjectsRead(session);
+        underTest.afterProjectsRead(mavenSessionMock);
 
-        Assert.assertTrue(builder.toString().contains("gitflow-incremental-builder is disabled."));
-        verify(session, times(2)).getTopLevelProject();
-        verifyNoMoreInteractions(session);
+        verify(loggerMock).info(contains("starting..."));
+        verify(unchangedProjectsRemoverMock).act();
     }
 
     @Test
-    public void defaultlyEnabled() throws Exception {
-        MavenSession session = getMavenSessionMock();
-
-        participant.afterProjectsRead(session);
+    public void onRuntimeException() throws Exception {
+        RuntimeException runtimeException = new RuntimeException("FAIL !!!");
+        doThrow(runtimeException).when(unchangedProjectsRemoverMock).act();
+        thrown.expect(MavenExecutionException.class);
+        thrown.expectCause(IsSame.sameInstance(runtimeException));
 
-        Assert.assertTrue(builder.toString().contains("gitflow-incremental-builder starting..."));
+        underTest.afterProjectsRead(mavenSessionMock);
     }
 
     @Test
-    public void failOnErrorFalse() throws Exception {
-        Property.failOnError.setValue("false");
-        MavenSession session = getMavenSessionMock();
-        when(session.getCurrentProject()).thenThrow(new RuntimeException("FAIL !!!"));
+    public void onRuntimeException_failOnErrorFalse() throws Exception {
+        projectProperties.setProperty(Property.failOnError.fullName(), "false");
+        RuntimeException runtimeException = new RuntimeException("FAIL !!!");
+        doThrow(runtimeException).when(unchangedProjectsRemoverMock).act();
 
-        participant.afterProjectsRead(session);
+        underTest.afterProjectsRead(mavenSessionMock);
 
-        Assert.assertTrue(builder.toString().contains("gitflow-incremental-builder execution skipped:"));
-        Assert.assertTrue(builder.toString().contains("FAIL !!!"));
+        verify(loggerMock).info(contains(" skipped:"));
+        verify(loggerMock).debug(anyString(), eq(runtimeException));
     }
 
     @Test
-    public void skipOnSkippable() throws Exception {
-        MavenSession session = getMavenSessionMock();
-        when(session.getCurrentProject()).thenThrow(new SkipExecutionException("FAIL !!!"));
+    public void onSkipExecutionException() throws Exception {
+        SkipExecutionException skipExecutionException = new SkipExecutionException("FAIL !!!");
+        doThrow(skipExecutionException).when(unchangedProjectsRemoverMock).act();
 
-        participant.afterProjectsRead(session);
+        underTest.afterProjectsRead(mavenSessionMock);
 
-        Assert.assertTrue(builder.toString().contains("gitflow-incremental-builder execution skipped:"));
-        Assert.assertTrue(builder.toString().contains("SkipExecutionException"));
-        Assert.assertTrue(builder.toString().contains("FAIL !!!"));
+        verify(loggerMock).info(contains(" skipped:"));
+        verify(loggerMock).debug(anyString(), eq(skipExecutionException));
     }
 
     @Test
     public void projectDependencyGraphMissing() throws Exception {
-        MavenSession session = getMavenSessionMock();
-        when(session.getProjectDependencyGraph()).thenReturn(null);
+        when(mavenSessionMock.getProjectDependencyGraph()).thenReturn(null);
 
-        participant.afterProjectsRead(session);
+        underTest.afterProjectsRead(mavenSessionMock);
 
-        Assert.assertTrue(builder.toString().contains("ProjectDependencyGraph"));
-        verify(session).getTopLevelProject();
-        verify(session).getProjectDependencyGraph();
-        verifyNoMoreInteractions(session);
+        verify(loggerMock).warn(contains("ProjectDependencyGraph"));
+        verifyZeroInteractions(unchangedProjectsRemoverMock);
     }
 }
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java
index 3b9c93d..fbccc07 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/boundary/UnchangedProjectsRemoverTest.java
@@ -28,11 +28,12 @@ import org.eclipse.jgit.api.errors.GitAPIException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
+import org.powermock.reflect.Whitebox;
 
-import com.google.common.collect.ImmutableMap;
 import com.vackosar.gitflowincrementalbuild.control.ChangedProjects;
 import com.vackosar.gitflowincrementalbuild.control.Property;
 
@@ -66,27 +67,36 @@ public class UnchangedProjectsRemoverTest {
     @Mock
     private ChangedProjects changedProjectsMock;
 
+    @InjectMocks
+    private UnchangedProjectsRemover underTest;
+
     private final List<MavenProject> projects = new ArrayList<>(); 
     private final Set<MavenProject> changedProjects = new LinkedHashSet<>();
+    private final Properties projectProperties = new Properties();
     
     @Before
     public void setup() throws GitAPIException, IOException {
-        addPropertiesToMock(mavenProjectMock);
+        when(mavenProjectMock.getProperties()).thenReturn(projectProperties);
         when(mavenProjectMock.getBasedir()).thenReturn(new File("."));
         when(mavenProjectMock.getArtifactId()).thenReturn(ARTIFACT_ID_1);
+
         when(mavenSessionMock.getCurrentProject()).thenReturn(mavenProjectMock);
+        when(mavenSessionMock.getTopLevelProject()).thenReturn(mavenProjectMock);
+
         when(mavenSessionMock.getRequest()).thenReturn(mavenExecutionRequestMock);
         projects.add(mavenProjectMock);
         when(mavenSessionMock.getProjects()).thenReturn(projects);
         when(mavenSessionMock.getProjectDependencyGraph()).thenReturn(projectDependencyGraphMock);
         when(changedProjectsMock.get()).thenReturn(changedProjects);
+
+        Whitebox.setInternalState(underTest, new Configuration.Provider(mavenSessionMock));
     }
     
     @Test
     public void singleChanged() throws GitAPIException, IOException {
         MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
 
-        buildUnderTest().act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(Collections.singletonList(changedModuleMock));
     }
@@ -97,10 +107,9 @@ public class UnchangedProjectsRemoverTest {
 
         when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.argsForNotImpactedModules, "enforcer.skip=true argWithNoValue");
+        projectProperties.put(Property.argsForNotImpactedModules.fullName(), "enforcer.skip=true argWithNoValue");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(Arrays.asList(changedModuleMock, mavenProjectMock));
         
@@ -116,11 +125,10 @@ public class UnchangedProjectsRemoverTest {
     public void singleChanged_buildAll_argsForNotImpactedModules() throws GitAPIException, IOException {
         MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.argsForNotImpactedModules, "enforcer.skip=true argWithNoValue",
-                Property.buildAll, "true");
+        projectProperties.put(Property.argsForNotImpactedModules.fullName(), "enforcer.skip=true argWithNoValue");
+        projectProperties.put(Property.buildAll.fullName(), "true");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock, never()).setProjects(anyListOf(MavenProject.class));
         
@@ -136,10 +144,9 @@ public class UnchangedProjectsRemoverTest {
     public void singleChanged_forceBuildModules() throws GitAPIException, IOException {
         MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.forceBuildModules, mavenProjectMock.getArtifactId());
+        projectProperties.put(Property.forceBuildModules.fullName(), mavenProjectMock.getArtifactId());
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(Arrays.asList(mavenProjectMock, changedModuleMock));
     }
@@ -149,11 +156,10 @@ public class UnchangedProjectsRemoverTest {
         MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
         MavenProject unchangedModuleMock = addModuleMock("unchanged-module", false);
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.forceBuildModules,
+        projectProperties.put(Property.forceBuildModules.fullName(),
                 mavenProjectMock.getArtifactId() + "," + unchangedModuleMock.getArtifactId());
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(
                 Arrays.asList(mavenProjectMock, unchangedModuleMock, changedModuleMock));
@@ -164,11 +170,9 @@ public class UnchangedProjectsRemoverTest {
         MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
         MavenProject unchangedModuleMock = addModuleMock("unchanged-module", false);
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.forceBuildModules,
-                ".*unchanged-module");
+        projectProperties.put(Property.forceBuildModules.fullName(), ".*unchanged-module");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(
                 Arrays.asList(mavenProjectMock, unchangedModuleMock, changedModuleMock));
@@ -179,11 +183,9 @@ public class UnchangedProjectsRemoverTest {
         MavenProject changedModuleMock = addModuleMock(ARTIFACT_ID_2, true);
         MavenProject unchangedModuleMock = addModuleMock("unchanged-module", false);
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.forceBuildModules,
-                "first-.*-module,unchanged-.*");
+        projectProperties.put(Property.forceBuildModules.fullName(), "first-.*-module,unchanged-.*");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(
                 Arrays.asList(mavenProjectMock, unchangedModuleMock, changedModuleMock));
@@ -197,10 +199,9 @@ public class UnchangedProjectsRemoverTest {
         when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
                 .thenReturn(Arrays.asList(dependentWar));
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.excludeTransitiveModulesPackagedAs,"war");
+        projectProperties.put(Property.excludeTransitiveModulesPackagedAs.fullName(), "war");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(
                 Arrays.asList(changedProjectMock));
@@ -215,10 +216,9 @@ public class UnchangedProjectsRemoverTest {
         when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
                 .thenReturn(Arrays.asList(dependentWar, dependentJar));
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.excludeTransitiveModulesPackagedAs,"war");
+        projectProperties.put(Property.excludeTransitiveModulesPackagedAs.fullName(), "war");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(
                 Arrays.asList(changedProjectMock, dependentJar));
@@ -233,10 +233,9 @@ public class UnchangedProjectsRemoverTest {
         when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
                 .thenReturn(Arrays.asList(dependentWar, dependentEar));
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.excludeTransitiveModulesPackagedAs,"war,ear");
+        projectProperties.put(Property.excludeTransitiveModulesPackagedAs.fullName(), "war,ear");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(
                 Arrays.asList(changedProjectMock));
@@ -250,11 +249,10 @@ public class UnchangedProjectsRemoverTest {
         when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
                 .thenReturn(Arrays.asList(dependentWar));
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.excludeTransitiveModulesPackagedAs,"war",
-                Property.buildAll,"true");
+        projectProperties.put(Property.excludeTransitiveModulesPackagedAs.fullName(), "war");
+        projectProperties.put(Property.buildAll.fullName(), "true");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock, never())
                 .setProjects(anyListOf(MavenProject.class));
@@ -268,11 +266,10 @@ public class UnchangedProjectsRemoverTest {
         when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
                 .thenReturn(Arrays.asList(dependentWar));
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.excludeTransitiveModulesPackagedAs,"war",
-                Property.forceBuildModules,dependentWar.getArtifactId());
+        projectProperties.put(Property.excludeTransitiveModulesPackagedAs.fullName(), "war");
+        projectProperties.put(Property.forceBuildModules.fullName(), dependentWar.getArtifactId());
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(
                 Arrays.asList(dependentWar, changedProjectMock));
@@ -285,10 +282,9 @@ public class UnchangedProjectsRemoverTest {
 
         // war module is changed, must be retained!
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.excludeTransitiveModulesPackagedAs,"war");
+        projectProperties.put(Property.excludeTransitiveModulesPackagedAs.fullName(), "war");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(
                 Arrays.asList(changedProjectMock, dependentWar));
@@ -303,21 +299,14 @@ public class UnchangedProjectsRemoverTest {
         when(projectDependencyGraphMock.getDownstreamProjects(changedProjectMock, true))
                 .thenReturn(Arrays.asList(dependentWar));
 
-        ImmutableMap<Property, String> propertyMap = ImmutableMap.of(
-                Property.excludeTransitiveModulesPackagedAs,"war");
+        projectProperties.put(Property.excludeTransitiveModulesPackagedAs.fullName(), "war");
 
-        buildUnderTest(propertyMap).act();
+        underTest.act();
 
         Mockito.verify(mavenSessionMock).setProjects(
                 Arrays.asList(changedProjectMock, dependentWar));
     }
 
-    private Properties addPropertiesToMock(MavenProject mavenProjectMock) {
-        Properties properties = new Properties();
-        when(mavenProjectMock.getProperties()).thenReturn(properties);
-        return properties;
-    }
-
     private MavenProject addModuleMock(String moduleArtifactId, boolean addToChanged) {
         return addModuleMock(moduleArtifactId, addToChanged, "jar");
     }
@@ -330,26 +319,11 @@ public class UnchangedProjectsRemoverTest {
             changedProjects.add(changedModuleMock);
         }
         projects.add(changedModuleMock);
-        addPropertiesToMock(changedModuleMock);
+
+        when(changedModuleMock.getProperties()).thenReturn(new Properties());
 
         when(projectDependencyGraphMock.getUpstreamProjects(changedModuleMock, true))
                 .thenReturn(Collections.singletonList(mavenProjectMock));
         return changedModuleMock;
     }
-
-    private UnchangedProjectsRemover buildUnderTest() throws IOException {
-        return buildUnderTest(ImmutableMap.of());
-    }
-
-    private UnchangedProjectsRemover buildUnderTest(ImmutableMap<Property, String> propertyMap) {
-        Configuration configuration;
-        Properties origProps = (Properties) System.getProperties().clone();
-        try {
-            propertyMap.forEach((k, v) -> System.setProperty(k.fullName(), v));
-            configuration = new Configuration(mavenSessionMock);
-        } finally {
-            System.setProperties(origProps);
-        }
-        return new UnchangedProjectsRemover(configuration, loggerMock, changedProjectsMock, mavenSessionMock);
-    }
 }
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/control/BaseChangedProjectsTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/control/BaseChangedProjectsTest.java
index 7c8f083..e3e7570 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/control/BaseChangedProjectsTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/control/BaseChangedProjectsTest.java
@@ -1,12 +1,20 @@
 package com.vackosar.gitflowincrementalbuild.control;
 
-import com.google.inject.Guice;
-import com.vackosar.gitflowincrementalbuild.boundary.GuiceModule;
 import com.vackosar.gitflowincrementalbuild.BaseRepoTest;
+import com.vackosar.gitflowincrementalbuild.boundary.Configuration;
+
+import org.apache.maven.execution.MavenSession;
 import org.apache.maven.project.MavenProject;
-import org.codehaus.plexus.logging.console.ConsoleLogger;
+import org.codehaus.plexus.logging.Logger;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.powermock.reflect.Whitebox;
 
 import java.io.File;
 import java.nio.file.Path;
@@ -16,10 +24,41 @@ import java.util.HashSet;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+/**
+ * Base class for testing {@link ChangedProjects}. This class does <em>not</em> isolate {@link ChangedProjects}, it rather combines
+ * {@link ChangedProjects}, {@link DifferentFiles} and {@link Modules} which is actually too much for a focused unit test and should be
+ * cleaned up. This test should thus be considered a "small scale integration test".
+ *
+ * @author famod
+ *
+ */
+@RunWith(MockitoJUnitRunner.class)
 public abstract class BaseChangedProjectsTest extends BaseRepoTest {
 
+    @Mock
+    private Logger loggerMock;
+
+    @Spy
+    @InjectMocks    // note: this won't populate mavenSession because the mock for this is created later via getMavenSessionMock()
+    protected DifferentFiles differentFilesSpy;
+
+    @Spy
+    protected Modules modulesSpy;
+
+    @InjectMocks
+    protected ChangedProjects underTest;
+
+    private MavenSession mavenSessionMock;
+
     public BaseChangedProjectsTest(boolean useSymLinkedFolder) {
-        super(useSymLinkedFolder);
+        super(useSymLinkedFolder, /* withRemote */ false);
+    }
+
+    @Before
+    public void injectMavenSessionMock() throws Exception {
+        mavenSessionMock = getMavenSessionMock();
+        Whitebox.setInternalState(differentFilesSpy, mavenSessionMock, new Configuration.Provider(mavenSessionMock));
+        Whitebox.setInternalState(underTest, mavenSessionMock);
     }
 
     @Test
@@ -30,12 +69,13 @@ public abstract class BaseChangedProjectsTest extends BaseRepoTest {
                 Paths.get("child4"),
                 Paths.get("testJarDependent")
         ));
-        final Set<Path> actual = Guice.createInjector(new GuiceModule(new ConsoleLogger(), getMavenSessionMock()))
-                .getInstance(ChangedProjects.class).get().stream()
+
+        final Set<Path> actual = underTest.get().stream()
                 .map(MavenProject::getBasedir)
                     .map(File::toPath)
                     .map(localRepoMock.getBaseCanonicalBaseFolder().toPath().resolve("parent")::relativize)
                 .collect(Collectors.toSet());
+
         Assert.assertEquals(expected, actual);
     }
 
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/control/DifferentFilesTest.java b/src/test/java/com/vackosar/gitflowincrementalbuild/control/DifferentFilesTest.java
index 98664c0..b0c0455 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/control/DifferentFilesTest.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/control/DifferentFilesTest.java
@@ -1,34 +1,5 @@
 package com.vackosar.gitflowincrementalbuild.control;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Provides;
-import com.google.inject.ProvisionException;
-import com.vackosar.gitflowincrementalbuild.boundary.Configuration;
-import com.vackosar.gitflowincrementalbuild.boundary.GuiceModule;
-import com.vackosar.gitflowincrementalbuild.mocks.LocalRepoMock;
-import com.vackosar.gitflowincrementalbuild.mocks.MavenSessionMock;
-import com.vackosar.gitflowincrementalbuild.BaseRepoTest;
-import org.apache.maven.execution.MavenSession;
-import org.codehaus.plexus.logging.Logger;
-import org.codehaus.plexus.logging.console.ConsoleLogger;
-import org.codehaus.plexus.logging.console.ConsoleLoggerManager;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.ResetCommand;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.slf4j.impl.StaticLoggerBinder;
-
-import javax.inject.Singleton;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -38,165 +9,140 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.stream.Collectors;
 
-@RunWith(MockitoJUnitRunner.class)
+import org.apache.maven.execution.MavenSession;
+import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.logging.console.ConsoleLoggerManager;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand;
+import org.junit.Assert;
+import org.junit.Test;
+import org.powermock.reflect.Whitebox;
+
+import com.vackosar.gitflowincrementalbuild.BaseRepoTest;
+import com.vackosar.gitflowincrementalbuild.boundary.Configuration;
+import com.vackosar.gitflowincrementalbuild.entity.SkipExecutionException;
+import com.vackosar.gitflowincrementalbuild.mocks.MavenSessionMock;
+
 public class DifferentFilesTest extends BaseRepoTest {
 
+    private static final Logger CONSOLE_LOGGER = new ConsoleLoggerManager().getLoggerForComponent("Test");
+
     private static final String REFS_HEADS_FEATURE_2 = "refs/heads/feature/2";
     private static final String HEAD = "HEAD";
     private static final String FETCH_FILE = "fetch-file";
     private static final String DEVELOP = "refs/heads/develop";
     private static final String REMOTE_DEVELOP = "refs/remotes/origin/develop";
-    private Path workDir;
-    private ModuleFacade moduleFacade;
-
-    @Rule
-    public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
-    /**
-     * Resets the user.dir system property that is manipulated by {@link #setWorkDir(Path)} to keep test classes isolated.
-     */
-    @Rule
-    public TestRule userDirResettingRule = (base, description) -> new Statement() {
-        @Override
-        public void evaluate() throws Throwable {
-            final String origUserDir = System.getProperty("user.dir");
-            try {
-                base.evaluate();
-            } finally {
-                System.setProperty("user.dir", origUserDir);
-            }
-        }
-    };
-
-    @Before
-    public void before() throws Exception {
-        super.init();
-        workDir = temporaryFolder.getRoot().getCanonicalFile().toPath().resolve("tmp/repo/");
-        setWorkDir(workDir);
-        localRepoMock = new LocalRepoMock(temporaryFolder.getRoot(), true);
-
-        Property.uncommited.setValue(Boolean.FALSE.toString());
-        Property.untracked.setValue(Boolean.FALSE.toString());
-    }
 
-    @After
-    public void after() throws Exception {
-        if (moduleFacade != null) {
-            moduleFacade.close();
-        }
-        super.after();
+    public DifferentFilesTest() {
+        super(/* useSymLinkedFolder */ false, /* withRemote */ true);
     }
 
-    @Test(expected = ProvisionException.class)
+    @Test(expected = SkipExecutionException.class)
     public void worktree() throws Exception {
         Path workDir = temporaryFolder.getRoot().toPath().resolve("tmp/repo/wrkf2");
-        setWorkDir(workDir);
-        getInstance(workDir).get();
+
+        invokeUnderTest(MavenSessionMock.get(workDir, projectProperties));
     }
 
     @Test
     public void listIncludingUncommitted() throws Exception {
-        Path repoPath = localRepoMock.getBaseCanonicalBaseFolder().toPath();
         Path modifiedFilePath = modifyTrackedFile(repoPath);
-        Property.uncommited.setValue(Boolean.TRUE.toString());
+        projectProperties.setProperty(Property.uncommited.fullName(), "true");
 
-        Assert.assertTrue(getInstance(repoPath).get().contains(modifiedFilePath));
+        Assert.assertTrue(invokeUnderTest().contains(modifiedFilePath));
     }
 
     @Test
     public void listIncludingUncommitted_disabled() throws Exception {
-        Path repoPath = localRepoMock.getBaseCanonicalBaseFolder().toPath();
         Path modifiedFilePath = modifyTrackedFile(repoPath);
-        Property.uncommited.setValue(Boolean.FALSE.toString());
+        projectProperties.setProperty(Property.uncommited.fullName(), "false");
 
-        Assert.assertFalse(getInstance(repoPath).get().contains(modifiedFilePath));
+        Assert.assertFalse(invokeUnderTest().contains(modifiedFilePath));
     }
 
     @Test
     public void listIncludingUntracked() throws Exception {
-        Path repoPath = localRepoMock.getBaseCanonicalBaseFolder().toPath();
         Path newFilePath = createNewUntrackedFile(repoPath);
-        Property.untracked.setValue(Boolean.TRUE.toString());
+        projectProperties.setProperty(Property.untracked.fullName(), "true");
 
-        Assert.assertTrue(getInstance(repoPath).get().contains(newFilePath));
+        Assert.assertTrue(invokeUnderTest().contains(newFilePath));
     }
 
     @Test
     public void listIncludingUntracked_disabled() throws Exception {
-        Path repoPath = localRepoMock.getBaseCanonicalBaseFolder().toPath();
         Path newFilePath = createNewUntrackedFile(repoPath);
-        Property.untracked.setValue(Boolean.FALSE.toString());
+        projectProperties.setProperty(Property.untracked.fullName(), "false");
 
-        Assert.assertFalse(getInstance(repoPath).get().contains(newFilePath));
+        Assert.assertFalse(invokeUnderTest().contains(newFilePath));
     }
 
     @Test
     public void listWithCheckout() throws Exception {
-        getLocalRepoMock().getGit().reset().setRef(HEAD).setMode(ResetCommand.ResetType.HARD).call();
-        Property.baseBranch.setValue("refs/heads/feature/2");
-        getInstance(localRepoMock.getBaseCanonicalBaseFolder().toPath()).get();
+        localRepoMock.getGit().reset().setRef(HEAD).setMode(ResetCommand.ResetType.HARD).call();
+        projectProperties.setProperty(Property.baseBranch.fullName(), "refs/heads/feature/2");
+        projectProperties.setProperty(Property.baseBranch.fullName(), "refs/heads/feature/2");
+
+        invokeUnderTest();
+
         Assert.assertTrue(consoleOut.toString().contains("Checking out base branch refs/heads/feature/2"));
     }
 
     @Test
     public void list() throws Exception {
-        final DifferentFiles differentFiles = getInstance(localRepoMock.getBaseCanonicalBaseFolder().toPath());
         final Set<Path> expected = new HashSet<>(Arrays.asList(
-                Paths.get(workDir + "/parent/child2/subchild2/src/resources/file2"),
-                Paths.get(workDir + "/parent/child2/subchild2/src/resources/file22"),
-                Paths.get(workDir + "/parent/child3/src/resources/file1"),
-                Paths.get(workDir + "/parent/child4/pom.xml"),
-                Paths.get(workDir + "/parent/testJarDependent/src/resources/file5")
+                Paths.get(repoPath + "/parent/child2/subchild2/src/resources/file2"),
+                Paths.get(repoPath + "/parent/child2/subchild2/src/resources/file22"),
+                Paths.get(repoPath + "/parent/child3/src/resources/file1"),
+                Paths.get(repoPath + "/parent/child4/pom.xml"),
+                Paths.get(repoPath + "/parent/testJarDependent/src/resources/file5")
                 ));
-        Assert.assertEquals(expected, differentFiles.get());
+
+        Assert.assertEquals(expected, invokeUnderTest());
     }
 
     @Test
     public void listExcluding() throws Exception {
-        Property.excludePathRegex.setValue(".*file2.*");
-        final DifferentFiles differentFiles = getInstance(localRepoMock.getBaseCanonicalBaseFolder().toPath());
+        projectProperties.setProperty(Property.excludePathRegex.fullName(), ".*file2.*");
         final Set<Path> expected = new HashSet<>(Arrays.asList(
-                Paths.get(workDir + "/parent/child3/src/resources/file1"),
-                Paths.get(workDir + "/parent/child4/pom.xml"),
-                Paths.get(workDir + "/parent/testJarDependent/src/resources/file5")
+                Paths.get(repoPath + "/parent/child3/src/resources/file1"),
+                Paths.get(repoPath + "/parent/child4/pom.xml"),
+                Paths.get(repoPath + "/parent/testJarDependent/src/resources/file5")
         ));
-        Assert.assertEquals(expected, differentFiles.get());
+
+        Assert.assertEquals(expected, invokeUnderTest());
     }
 
     @Test
     public void listWithDisabledBranchComparison() throws Exception {
-        Property.disableBranchComparison.setValue(Boolean.TRUE.toString());
-
-        final DifferentFiles differentFiles = getInstance(localRepoMock.getBaseCanonicalBaseFolder().toPath());
+        projectProperties.setProperty(Property.disableBranchComparison.fullName(), "true");
 
-        Assert.assertEquals(Collections.emptySet(), differentFiles.get());
+        Assert.assertEquals(Collections.emptySet(), invokeUnderTest());
     }
 
     @Test
     public void listInSubdir() throws Exception {
-        Path workDir = localRepoMock.getBaseCanonicalBaseFolder().toPath().resolve("parent/child2");
-        setWorkDir(workDir);
-        final DifferentFiles differentFiles = getInstance(localRepoMock.getBaseCanonicalBaseFolder().toPath());
+        Path dir = repoPath.resolve("parent/child2");
         final Set<Path> expected = new HashSet<>(Arrays.asList(
-                workDir.resolve("subchild2/src/resources/file2"),
-                workDir.resolve("subchild2/src/resources/file22"),
-                workDir.resolve("../child3/src/resources/file1").normalize(),
-                workDir.resolve("../child4/pom.xml").normalize(),
-                workDir.resolve("../testJarDependent/src/resources/file5").normalize()
+                dir.resolve("subchild2/src/resources/file2"),
+                dir.resolve("subchild2/src/resources/file22"),
+                dir.resolve("../child3/src/resources/file1").normalize(),
+                dir.resolve("../child4/pom.xml").normalize(),
+                dir.resolve("../testJarDependent/src/resources/file5").normalize()
         ));
-        Assert.assertEquals(expected, differentFiles.get());
+
+        Assert.assertEquals(expected, invokeUnderTest());
     }
 
     @Test
     public void listComparedToMergeBase() throws Exception {
-        getLocalRepoMock().getGit().reset().setRef(HEAD).setMode(ResetCommand.ResetType.HARD).call();
-        getLocalRepoMock().getGit().checkout().setName(REFS_HEADS_FEATURE_2).call();
-        getLocalRepoMock().getGit().reset().setRef(HEAD).setMode(ResetCommand.ResetType.HARD).call();
-        Property.baseBranch.setValue(REFS_HEADS_FEATURE_2);
-        Property.compareToMergeBase.setValue("true");
-        Assert.assertTrue(getInstance(localRepoMock.getBaseCanonicalBaseFolder().toPath()).get().stream().collect(Collectors.toSet()).contains(workDir.resolve("parent/feature2-only-file.txt")));
+        localRepoMock.getGit().reset().setRef(HEAD).setMode(ResetCommand.ResetType.HARD).call();
+        localRepoMock.getGit().checkout().setName(REFS_HEADS_FEATURE_2).call();
+        localRepoMock.getGit().reset().setRef(HEAD).setMode(ResetCommand.ResetType.HARD).call();
+        projectProperties.setProperty(Property.baseBranch.fullName(), REFS_HEADS_FEATURE_2);
+        projectProperties.setProperty(Property.compareToMergeBase.fullName(), "true");
+
+        Assert.assertTrue(invokeUnderTest().stream().anyMatch(repoPath.resolve("parent/feature2-only-file.txt")::equals));
         Assert.assertTrue(consoleOut.toString().contains("59dc82fa887d9ca82a0d3d1790c6d767e738e71a"));
     }
 
@@ -209,9 +155,11 @@ public class DifferentFilesTest extends BaseRepoTest {
         remoteGit.add().addFilepattern(".").call();
         remoteGit.commit().setMessage(FETCH_FILE).call();
         Assert.assertEquals(FETCH_FILE, remoteGit.log().setMaxCount(1).call().iterator().next().getFullMessage());
-        Property.fetchReferenceBranch.setValue(Boolean.TRUE.toString());
-        Property.referenceBranch.setValue(REMOTE_DEVELOP);
-        getInstance(localRepoMock.getBaseCanonicalBaseFolder().toPath()).get();
+        projectProperties.setProperty(Property.fetchReferenceBranch.fullName(), "true");
+        projectProperties.setProperty(Property.referenceBranch.fullName(), REMOTE_DEVELOP);
+
+        invokeUnderTest();
+
         Git localGit = localRepoMock.getGit();
         localGit.reset().setMode(ResetCommand.ResetType.HARD).call();
         localGit.checkout().setName(REMOTE_DEVELOP).call();
@@ -230,59 +178,30 @@ public class DifferentFilesTest extends BaseRepoTest {
         localGit.branchDelete().setBranchNames(DEVELOP).call();
         localGit.branchDelete().setBranchNames(REMOTE_DEVELOP).call();
         Assert.assertEquals(FETCH_FILE, remoteGit.log().setMaxCount(1).call().iterator().next().getFullMessage());
-        Property.fetchReferenceBranch.setValue(Boolean.TRUE.toString());
-        Property.referenceBranch.setValue(REMOTE_DEVELOP);
-        getInstance(localRepoMock.getBaseCanonicalBaseFolder().toPath()).get();
+        projectProperties.setProperty(Property.fetchReferenceBranch.fullName(), "true");
+        projectProperties.setProperty(Property.referenceBranch.fullName(), REMOTE_DEVELOP);
+
+        invokeUnderTest();
+
         localGit.reset().setMode(ResetCommand.ResetType.HARD).call();
         localGit.checkout().setName(REMOTE_DEVELOP).call();
         Assert.assertEquals(FETCH_FILE, localGit.log().setMaxCount(1).call().iterator().next().getFullMessage());
     }
 
-    private static class ModuleFacade extends AbstractModule {
-        private final GuiceModule guiceModule;
-        private Git git;
-        private Path workDir;
-
-        public ModuleFacade(Path dir) throws Exception {
-            this.guiceModule = new GuiceModule(new ConsoleLogger(), MavenSessionMock.get(dir));
-            this.workDir = dir;
-        }
-
-        @Singleton @Provides public Logger provideLogger() {
-            return new ConsoleLoggerManager().getLoggerForComponent("Test");
-        }
-
-        @Singleton @Provides public Git provideGit(Configuration configuration) throws IOException, GitAPIException {
-            git = guiceModule.provideGit(new StaticLoggerBinder(new ConsoleLoggerManager().getLoggerForComponent("Test")), configuration);
-            return git;
-        }
-
-        @Singleton @Provides public Configuration configuration() throws Exception {
-            MavenSession mavenSession = MavenSessionMock.get(workDir);
-            return new Configuration(mavenSession);
-        }
-
-        @Override
-        protected void configure() {}
-
-        public void close() {
-            if (git != null) {
-                if (git.getRepository() != null) {
-                    git.getRepository().close();
-                }
-                git.close();
-            }
-        }
-
+    private Set<Path> invokeUnderTest() throws Exception {
+        return invokeUnderTest(getMavenSessionMock());
     }
 
-    private DifferentFiles getInstance(Path dir) throws Exception {
-        moduleFacade = new ModuleFacade(dir);
-        return Guice.createInjector(moduleFacade).getInstance(DifferentFiles.class);
-    }
+    private Set<Path> invokeUnderTest(final MavenSession mavenSessionMock) throws Exception {
+        mavenSessionMock.getTopLevelProject().getProperties().putAll(projectProperties);
+
+        DifferentFiles underTest = new DifferentFiles();
+        Whitebox.setInternalState(underTest, mavenSessionMock, CONSOLE_LOGGER, new Configuration.Provider(mavenSessionMock));
+
+        Set<Path> result = underTest.get();
 
-    private void setWorkDir(final Path path) {
-        System.setProperty("user.dir", path.toString());
+        Assert.assertNotNull("Resulting set is unexpectedly null", result);
+        return result;
     }
 
     private Path modifyTrackedFile(Path repoPath) throws IOException {
diff --git a/src/test/java/com/vackosar/gitflowincrementalbuild/mocks/MavenSessionMock.java b/src/test/java/com/vackosar/gitflowincrementalbuild/mocks/MavenSessionMock.java
index 7620ef3..8de2744 100644
--- a/src/test/java/com/vackosar/gitflowincrementalbuild/mocks/MavenSessionMock.java
+++ b/src/test/java/com/vackosar/gitflowincrementalbuild/mocks/MavenSessionMock.java
@@ -22,7 +22,7 @@ import static org.mockito.Mockito.when;
 
 public class MavenSessionMock {
 
-    public static MavenSession get(Path workDir) throws Exception {
+    public static MavenSession get(Path workDir, Properties topLevelProjectProperties) throws Exception {
         PomFinder finder = new PomFinder();
         Files.walkFileTree(workDir, finder);
         List<MavenProject> projects = finder.projects.stream()
@@ -30,12 +30,14 @@ public class MavenSessionMock {
                 .map(MavenSessionMock::createProject)
                 .collect(Collectors.toList());
 
+        MavenProject topLevelProject = projects.get(0);
+        topLevelProject.getModel().setProperties(topLevelProjectProperties);
         MavenSession mavenSession = mock(MavenSession.class);
-        when(mavenSession.getCurrentProject()).thenReturn(projects.get(0));
+        when(mavenSession.getCurrentProject()).thenReturn(topLevelProject);
         MavenExecutionRequest request = mock(MavenExecutionRequest.class);
         when(mavenSession.getRequest()).thenReturn(request);
         when(mavenSession.getProjects()).thenReturn(projects);
-        when(mavenSession.getTopLevelProject()).thenReturn(projects.get(0));
+        when(mavenSession.getTopLevelProject()).thenReturn(topLevelProject);
         ProjectDependencyGraph dependencyGraphMock = mock(ProjectDependencyGraph.class);
         when(mavenSession.getProjectDependencyGraph()).thenReturn(dependencyGraphMock);
         return mavenSession;