diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e9785..3d870cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,34 @@ adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Changed — behaviour flip, read this + +- **`includeUncommitted` and `includeStaged` now default to `false` + (committed-only).** Previously both defaulted to `true`, meaning a + local `./gradlew affectedTest` would expand the diff boundary with + whatever happened to be sitting in the dev's working tree. That made + local and CI runs of the same HEAD pick different test sets, and the + inclusion was invisible in the summary log. + + The new default mirrors CI reality (where the tree is clean after + checkout anyway) and makes two runs on the same commit deterministic + regardless of workstation state. Adopters who iterate on tests + locally and want WIP to seed the diff flip the two knobs back on in + `build.gradle`: + + ```groovy + affectedTests { + includeUncommitted = true // opt back in for local WIP runs + includeStaged = true + } + ``` + + No migration is required for anyone who was already setting these + explicitly — the config resolver always preferred the explicit value + over the convention. Adopters who relied on the old `true` default + without setting anything will see a smaller local-run test set until + they commit or opt back in; CI selection is unchanged. + ### Added - `outOfScopeTestDirs` and `outOfScopeSourceDirs` now accept Ant-style diff --git a/README.md b/README.md index 0ae8468..c037374 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,15 @@ affectedTests { // Git base ref to diff against (default: "origin/master") baseRef = "origin/main" - // Include uncommitted/staged changes (default: true) - includeUncommitted = true - includeStaged = true + // Include uncommitted/staged changes (default: false — committed-only). + // The plugin ships COMMITTED-ONLY so a local run matches the MR diff + // CI will pick up on the same HEAD, and running the task twice in a + // row produces the same test selection regardless of workstation state. + // Flip to `true` locally when you are iterating on tests and want + // WIP to seed the diff. Never enable these in CI — the tree is + // already clean after checkout there, so they are pure noise. + includeUncommitted = false + includeStaged = false // v2 profile. "auto" is the recommended migration target. // See the "Mode profiles" table above. diff --git a/affected-tests-core/src/main/java/io/affectedtests/core/config/AffectedTestsConfig.java b/affected-tests-core/src/main/java/io/affectedtests/core/config/AffectedTestsConfig.java index c9f4cdd..707bf67 100644 --- a/affected-tests-core/src/main/java/io/affectedtests/core/config/AffectedTestsConfig.java +++ b/affected-tests-core/src/main/java/io/affectedtests/core/config/AffectedTestsConfig.java @@ -464,8 +464,16 @@ public static final class Builder { ); private String baseRef = "origin/master"; - private boolean includeUncommitted = true; - private boolean includeStaged = true; + // Committed-only by default: the plugin's question is "what + // tests does *this commit* touch?", not "what tests does this + // commit plus whatever is rattling around in your working + // tree touch?". Matching the default to that framing means a + // programmatic or Gradle invocation on the same HEAD picks the + // same tests every time, independent of dev workstation state, + // and lines up with how CI checks the tree out. Callers who + // want WIP to expand the diff opt in via {@code includeUncommitted(true)}. + private boolean includeUncommitted = false; + private boolean includeStaged = false; private Boolean runAllIfNoMatches; private Boolean runAllOnNonJavaChange; private Set strategies = Set.of(STRATEGY_NAMING, STRATEGY_USAGE, STRATEGY_IMPL, STRATEGY_TRANSITIVE); diff --git a/affected-tests-core/src/test/java/io/affectedtests/core/config/AffectedTestsConfigTest.java b/affected-tests-core/src/test/java/io/affectedtests/core/config/AffectedTestsConfigTest.java index 528300b..08b5da9 100644 --- a/affected-tests-core/src/test/java/io/affectedtests/core/config/AffectedTestsConfigTest.java +++ b/affected-tests-core/src/test/java/io/affectedtests/core/config/AffectedTestsConfigTest.java @@ -14,8 +14,15 @@ void defaultsAreApplied() { AffectedTestsConfig config = AffectedTestsConfig.builder().build(); assertEquals("origin/master", config.baseRef()); - assertTrue(config.includeUncommitted()); - assertTrue(config.includeStaged()); + // v1.10.x flip: the core builder now defaults to COMMITTED-ONLY + // (both flags false). Rationale lives in the release notes and + // AffectedTestsPlugin#apply — picking CI semantics as the + // default, with WIP inclusion as an explicit opt-in, keeps a + // programmatic run on the same HEAD deterministic. + assertFalse(config.includeUncommitted(), + "Core builder default must be COMMITTED-ONLY as of the v1.9.14 → next-release flip"); + assertFalse(config.includeStaged(), + "Core builder default must be COMMITTED-ONLY as of the v1.9.14 → next-release flip"); // Pre-v2 legacy defaults preserved 1:1 for zero-config callers — // the getters below read the raw configured value (or the // hardcoded pre-v2 default when unset), not the resolved diff --git a/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestTask.java b/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestTask.java index f941b63..4b4e59d 100644 --- a/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestTask.java +++ b/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestTask.java @@ -71,7 +71,11 @@ public AffectedTestTask() { /** * Whether to include uncommitted (unstaged) changes in the diff. - * Default: {@code true}. + * Default: {@code false} — committed-only, so a local run picks + * the same tests CI would pick on the same HEAD, and two runs on + * the same commit are deterministic. Flip to {@code true} in + * {@code build.gradle} if you iterate on tests locally and want + * WIP to seed the diff. * * @return the include uncommitted property */ @@ -80,7 +84,8 @@ public AffectedTestTask() { /** * Whether to include staged (added to index) changes in the diff. - * Default: {@code true}. + * Default: {@code false} — see {@link #getIncludeUncommitted()} for + * the rationale; both knobs move together on the CI-first defaults. * * @return the include staged property */ diff --git a/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestsExtension.java b/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestsExtension.java index c226f6a..0153bfc 100644 --- a/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestsExtension.java +++ b/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestsExtension.java @@ -10,7 +10,11 @@ *
{@code
  * affectedTests {
  *     baseRef = "origin/master"
- *     includeUncommitted = true
+ *     // Defaults are COMMITTED-ONLY (both flags default to false) so
+ *     // local runs match CI. Flip to true if you want WIP to seed the
+ *     // diff while iterating on tests locally.
+ *     includeUncommitted = false
+ *     includeStaged = false
  *     // v2: per-situation actions (replaces runAllIfNoMatches / runAllOnNonJavaChange).
  *     // See README.md "Migrating from v1 config" for the full table.
  *     mode = "ci"
@@ -42,7 +46,9 @@ public abstract class AffectedTestsExtension {
 
     /**
      * Include uncommitted (unstaged) changes.
-     * Default: {@code true}.
+     * Default: {@code false} — committed-only semantics match CI. Set
+     * to {@code true} if you iterate on WIP locally and want the
+     * unstaged edits to seed the diff boundary.
      *
      * @return the include uncommitted property
      */
@@ -50,7 +56,8 @@ public abstract class AffectedTestsExtension {
 
     /**
      * Include staged changes.
-     * Default: {@code true}.
+     * Default: {@code false}. See {@link #getIncludeUncommitted()} for
+     * the rationale.
      *
      * @return the include staged property
      */
diff --git a/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestsPlugin.java b/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestsPlugin.java
index ffb242e..ff68238 100644
--- a/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestsPlugin.java
+++ b/affected-tests-gradle/src/main/java/io/affectedtests/gradle/AffectedTestsPlugin.java
@@ -26,8 +26,16 @@ public void apply(Project project) {
                 project.getProviders().gradleProperty("affectedTestsBaseRef")
                         .orElse("origin/master")
         );
-        extension.getIncludeUncommitted().convention(true);
-        extension.getIncludeStaged().convention(true);
+        // COMMITTED-ONLY by default: the plugin's whole job is "what
+        // tests does this MR touch?", and the MR is the committed diff
+        // — not the dev's WIP. Matching this default to the CI reality
+        // means a local `./gradlew affectedTest` run picks the same
+        // tests CI will run on the same HEAD, and two runs on the same
+        // commit are deterministic. Adopters who iterate on WIP tests
+        // flip these back to `true` in their build.gradle; the plugin
+        // never silently expands the diff boundary.
+        extension.getIncludeUncommitted().convention(false);
+        extension.getIncludeStaged().convention(false);
         // No conventions for the two legacy booleans — leaving them
         // unset is the v2 signal that the caller has not overridden the
         // defaults, which lets the core config resolver apply
diff --git a/affected-tests-gradle/src/test/java/io/affectedtests/gradle/AffectedTestsPluginTest.java b/affected-tests-gradle/src/test/java/io/affectedtests/gradle/AffectedTestsPluginTest.java
index f538ccc..8610c65 100644
--- a/affected-tests-gradle/src/test/java/io/affectedtests/gradle/AffectedTestsPluginTest.java
+++ b/affected-tests-gradle/src/test/java/io/affectedtests/gradle/AffectedTestsPluginTest.java
@@ -35,8 +35,16 @@ void extensionHasDefaults() {
                 .getByType(AffectedTestsExtension.class);
 
         assertEquals("origin/master", ext.getBaseRef().get());
-        assertTrue(ext.getIncludeUncommitted().get());
-        assertTrue(ext.getIncludeStaged().get());
+        // Default is now COMMITTED-ONLY. Matches CI semantics exactly
+        // (where the tree is already clean after checkout anyway) and
+        // gives local `./gradlew affectedTest` runs the same test
+        // selection the operator's MR will get in CI. Devs who want WIP
+        // to seed the diff opt in explicitly — see CHANGELOG for the
+        // v1.9.14 → next-release behaviour flip.
+        assertFalse(ext.getIncludeUncommitted().get(),
+                "Default must be COMMITTED-ONLY so local runs match CI — WIP inclusion is an explicit opt-in");
+        assertFalse(ext.getIncludeStaged().get(),
+                "Default must be COMMITTED-ONLY so local runs match CI — staged-index inclusion is an explicit opt-in");
         // v2: no convention for the legacy booleans — leaving them unset
         // is the signal the core builder uses to fall through to
         // mode-based defaults. Zero-config users still observe pre-v2