This is an automated email from the ASF dual-hosted git repository.

royteeuwen pushed a commit to branch feature/SLING-13253-update-dist-same-major
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 6ae0e2ddeb1a98ba6f3d38966cbafd474d3a4987
Author: Roy Teeuwen <[email protected]>
AuthorDate: Mon Aug 3 18:18:57 2026 +0200

    SLING-13253 - update-dist: only remove the previous release of the same 
major version
    
    Auto-deducing the release to remove from dist/release picked the closest
    older version regardless of major, so releasing the first version of a new
    major line would delete the still-maintained previous line. Restrict the
    candidates to the major version being released.
---
 .../sling/cli/impl/release/FinalizeCommand.java    |  3 +-
 .../sling/cli/impl/release/UpdateDistCommand.java  | 24 ++++++----
 .../cli/impl/release/UpdateDistCommandTest.java    | 55 ++++++++++++++++++++++
 3 files changed, 71 insertions(+), 11 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/cli/impl/release/FinalizeCommand.java 
b/src/main/java/org/apache/sling/cli/impl/release/FinalizeCommand.java
index 44d4aaf..69f15fd 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/FinalizeCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/FinalizeCommand.java
@@ -63,7 +63,8 @@ import picocli.CommandLine;
  * when the current user (resolved from the ASF credentials) is a PMC member 
and skipped otherwise.
  * When skipped, a PMC member must complete it separately (the {@code 
tally-votes} result email asks
  * for this when run by a non-PMC member). The previous version to remove from 
dist/release is
- * deduced from the current contents of the release directory.
+ * deduced from the current contents of the release directory, restricted to 
the major version being
+ * released so parallel major version streams stay published.
  */
 @Component(
         service = Command.class,
diff --git 
a/src/main/java/org/apache/sling/cli/impl/release/UpdateDistCommand.java 
b/src/main/java/org/apache/sling/cli/impl/release/UpdateDistCommand.java
index dcdba3c..76d45dc 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateDistCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateDistCommand.java
@@ -97,7 +97,8 @@ public class UpdateDistCommand implements Command {
     @CommandLine.Option(
             names = {"--previous-version"},
             description = "Previous release version to remove from 
dist/release (e.g. 1.0.0)."
-                    + " Optional: if omitted, the closest older version 
currently in dist/release is removed.")
+                    + " Optional: if omitted, the closest older version with 
the same major version currently in"
+                    + " dist/release is removed; other major version streams 
are left untouched.")
     private String previousVersion;
 
     @CommandLine.Mixin
@@ -290,10 +291,11 @@ public class UpdateDistCommand implements Command {
     /**
      * Determines which files to remove from {@code dist/release} when 
publishing {@code newVersion}
      * of {@code artifactId}. When {@code explicitPreviousVersion} is given, 
only that version's files
-     * are returned. Otherwise only the <em>closest older</em> version's files 
are returned — the highest
-     * version strictly lower than {@code newVersion} currently present for 
this artifact. This keeps
-     * parallel maintenance streams intact (publishing {@code 2.0.4} removes 
{@code 2.0.2} but keeps
-     * {@code 1.2.4}) and never removes a newer version.
+     * are returned. Otherwise only the <em>closest older version with the 
same major version</em> is
+     * returned — the highest version strictly lower than {@code newVersion} 
sharing its major. This
+     * keeps parallel maintenance streams intact (publishing {@code 1.2.16} 
removes {@code 1.2.14} but
+     * keeps {@code 2.1.0}, and publishing {@code 2.1.2} leaves {@code 1.2.14} 
alone) and never removes
+     * a newer version.
      */
     static List<String> listPreviousReleaseFiles(String artifactId, String 
newVersion, String explicitPreviousVersion)
             throws IOException {
@@ -305,7 +307,7 @@ public class UpdateDistCommand implements Command {
                 // right after the prefix excludes sibling artifacts such as 
artifactId-extra-...)
                 .filter(f -> isVersionedArtifactFile(f, artifactId))
                 .toList();
-        String previousVersion = closestOlderVersion(artifactFiles, 
artifactId, newVersion);
+        String previousVersion = closestOlderVersionInSameMajor(artifactFiles, 
artifactId, newVersion);
         if (previousVersion == null) {
             return List.of();
         }
@@ -315,10 +317,12 @@ public class UpdateDistCommand implements Command {
     }
 
     /**
-     * Returns the highest version among {@code artifactFiles} that is 
strictly lower than
-     * {@code newVersion}, or {@code null} if there is none.
+     * Returns the highest version among {@code artifactFiles} that shares 
{@code newVersion}'s major
+     * version and is strictly lower than it, or {@code null} if there is 
none. Versions from other
+     * major streams are never candidates, so an older major line stays 
published.
      */
-    private static String closestOlderVersion(List<String> artifactFiles, 
String artifactId, String newVersion) {
+    private static String closestOlderVersionInSameMajor(
+            List<String> artifactFiles, String artifactId, String newVersion) {
         org.osgi.framework.Version target = parseOsgiVersion(newVersion);
         if (target == null) {
             return null;
@@ -328,7 +332,7 @@ public class UpdateDistCommand implements Command {
         for (String file : artifactFiles) {
             String candidateName = extractVersion(file, artifactId);
             org.osgi.framework.Version candidate = 
parseOsgiVersion(candidateName);
-            if (candidate == null || candidate.compareTo(target) >= 0) {
+            if (candidate == null || candidate.getMajor() != target.getMajor() 
|| candidate.compareTo(target) >= 0) {
                 continue;
             }
             if (closestVersion == null || candidate.compareTo(closestVersion) 
> 0) {
diff --git 
a/src/test/java/org/apache/sling/cli/impl/release/UpdateDistCommandTest.java 
b/src/test/java/org/apache/sling/cli/impl/release/UpdateDistCommandTest.java
index 07d4e0a..05fd408 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/UpdateDistCommandTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/UpdateDistCommandTest.java
@@ -150,6 +150,61 @@ public class UpdateDistCommandTest {
         }
     }
 
+    @Test
+    public void testAutoDeduceRemovesOnlySameMajorVersion() throws Exception {
+        // publishing 1.2.16 must remove 1.2.14 and leave the 2.x stream 
untouched
+        List<String> releaseDir = List.of(
+                ARTIFACT + "-1.2.14.pom",
+                ARTIFACT + "-1.2.14-source-release.zip",
+                ARTIFACT + "-2.1.0.pom",
+                ARTIFACT + "-2.1.0-source-release.zip");
+        try (MockedStatic<UpdateDistCommand> dist = 
mockStatic(UpdateDistCommand.class, CALLS_REAL_METHODS)) {
+            dist.when(() -> 
UpdateDistCommand.listDistFiles(eq(UpdateDistCommand.DIST_RELEASE_URL), 
anyString()))
+                    .thenReturn(releaseDir);
+
+            List<String> old = 
UpdateDistCommand.listPreviousReleaseFiles(ARTIFACT, "1.2.16", null);
+
+            assertEquals(2, old.size());
+            assertTrue(old.contains(ARTIFACT + "-1.2.14.pom"));
+            assertTrue(old.contains(ARTIFACT + "-1.2.14-source-release.zip"));
+            assertFalse(old.contains(ARTIFACT + "-2.1.0.pom"));
+            assertFalse(old.contains(ARTIFACT + "-2.1.0-source-release.zip"));
+        }
+    }
+
+    @Test
+    public void testAutoDeduceIgnoresOlderMajorVersions() throws Exception {
+        // publishing 2.1.2 must remove 2.1.0 and never touch the older 1.x 
stream
+        List<String> releaseDir =
+                List.of(ARTIFACT + "-1.2.14.pom", ARTIFACT + "-2.1.0.pom", 
ARTIFACT + "-2.1.0-source-release.zip");
+        try (MockedStatic<UpdateDistCommand> dist = 
mockStatic(UpdateDistCommand.class, CALLS_REAL_METHODS)) {
+            dist.when(() -> 
UpdateDistCommand.listDistFiles(eq(UpdateDistCommand.DIST_RELEASE_URL), 
anyString()))
+                    .thenReturn(releaseDir);
+
+            List<String> old = 
UpdateDistCommand.listPreviousReleaseFiles(ARTIFACT, "2.1.2", null);
+
+            assertEquals(2, old.size());
+            assertTrue(old.contains(ARTIFACT + "-2.1.0.pom"));
+            assertTrue(old.contains(ARTIFACT + "-2.1.0-source-release.zip"));
+            assertFalse(old.contains(ARTIFACT + "-1.2.14.pom"));
+        }
+    }
+
+    @Test
+    public void 
testAutoDeduceKeepsOlderMajorWhenNoSameMajorPredecessorExists() throws 
Exception {
+        // the first release of a new major stream: nothing in dist/release 
shares its major, so the
+        // previous major line stays published (it is still maintained 
separately)
+        List<String> releaseDir = List.of(ARTIFACT + "-1.2.14.pom", ARTIFACT + 
"-1.2.14-source-release.zip");
+        try (MockedStatic<UpdateDistCommand> dist = 
mockStatic(UpdateDistCommand.class, CALLS_REAL_METHODS)) {
+            dist.when(() -> 
UpdateDistCommand.listDistFiles(eq(UpdateDistCommand.DIST_RELEASE_URL), 
anyString()))
+                    .thenReturn(releaseDir);
+
+            List<String> old = 
UpdateDistCommand.listPreviousReleaseFiles(ARTIFACT, "2.0.0", null);
+
+            assertTrue("a different major version must never be removed", 
old.isEmpty());
+        }
+    }
+
     @Test
     public void testExplicitPreviousVersionWins() throws Exception {
         try (MockedStatic<UpdateDistCommand> dist = 
mockStatic(UpdateDistCommand.class, CALLS_REAL_METHODS)) {

Reply via email to