From e6a9db4456bca3dc9c0b30985026275bfe7b4f03 Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Thu, 11 Jun 2026 15:40:08 -0500 Subject: [PATCH 1/3] IVY-1665: verify version is greater before reporting on available update --- .../ivy/ant/IvyDependencyUpdateChecker.java | 44 +++++++++++++++++-- .../ant/IvyDependencyUpdateCheckerTest.java | 20 +++++++++ .../repositories/IVY-1665/bar/foo/foo-1.0.jar | 1 + .../IVY-1665/bar/foo/foo-2.0-M1.jar | 1 + .../repositories/IVY-1665/bar/foo/ivy-1.0.xml | 22 ++++++++++ .../IVY-1665/bar/foo/ivy-2.0-M1.xml | 22 ++++++++++ test/repositories/IVY-1665/ivysettings.xml | 28 ++++++++++++ 7 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 test/repositories/IVY-1665/bar/foo/foo-1.0.jar create mode 100644 test/repositories/IVY-1665/bar/foo/foo-2.0-M1.jar create mode 100644 test/repositories/IVY-1665/bar/foo/ivy-1.0.xml create mode 100644 test/repositories/IVY-1665/bar/foo/ivy-2.0-M1.xml create mode 100644 test/repositories/IVY-1665/ivysettings.xml diff --git a/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java b/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java index d467d23c..76e9d090 100644 --- a/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java +++ b/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java @@ -127,9 +127,9 @@ public class IvyDependencyUpdateChecker extends IvyPostResolveTask { boolean dependencyUpdateDetected = false; for (IvyNode latest : latestReport.getDependencies()) { for (IvyNode originalDependency : originalReport.getDependencies()) { - if (originalDependency.getModuleId().equals(latest.getModuleId())) { - if (!originalDependency.getResolvedId().getRevision() - .equals(latest.getResolvedId().getRevision())) { + if (latest.getModuleId().equals(originalDependency.getModuleId())) { + if (isGreater(latest.getResolvedId().getRevision(), + originalDependency.getResolvedId().getRevision())) { // is this dependency a transitive or a direct dependency? // (unfortunately .isTransitive() methods do not have the same meaning) boolean isTransitiveDependency = latest.getDependencyDescriptor(latest @@ -195,4 +195,42 @@ public class IvyDependencyUpdateChecker extends IvyPostResolveTask { } } } + + //-------------------------------------------------------------------------- + + private static boolean isGreater(String string1, String string2) { + String[] tokens1 = string1.split("[\\._\\-\\+]"); + String[] tokens2 = string2.split("[\\._\\-\\+]"); + int i = 0; + for (final int n = Math.min(tokens1.length, tokens2.length); i < n; i += 1) { + if (!tokens1[i].equals(tokens2[i])) { + boolean is1Number = isNumeric(tokens1[i]); + boolean is2Number = isNumeric(tokens2[i]); + if (is1Number && is2Number) { + return Long.valueOf(tokens1[i]).compareTo(Long.valueOf(tokens2[i])) > 0; + } else if (is1Number && !is2Number) { + return true; + } else if (!is1Number && is2Number) { + return false; + } + return true; // special meanings accounted for by resolve + } + } + if (i < tokens1.length) { + return isNumeric(tokens1[i]); + } + if (i < tokens2.length) { + return !isNumeric(tokens2[i]); + } + return false; + } + + private static boolean isNumeric(String string) { + for (int i = 0, n = string.length(); i < n; ++i) { + if (!Character.isDigit(string.charAt(i))) { + return false; + } + } + return true; + } } diff --git a/test/java/org/apache/ivy/ant/IvyDependencyUpdateCheckerTest.java b/test/java/org/apache/ivy/ant/IvyDependencyUpdateCheckerTest.java index 26f14e84..48565de1 100644 --- a/test/java/org/apache/ivy/ant/IvyDependencyUpdateCheckerTest.java +++ b/test/java/org/apache/ivy/ant/IvyDependencyUpdateCheckerTest.java @@ -220,6 +220,26 @@ public class IvyDependencyUpdateCheckerTest extends AntTaskTestCase { assertLogContaining("org1#mod1.2\t2.0 -> 2.2"); } + /** + * Test case for IVY-1665. + * + * @see IVY-1665 + */ + @Test + public void testLatestRelease() { + dependencyUpdateChecker.getProject().setProperty("ivy.settings.file", "test/repositories/IVY-1665/ivysettings.xml"); + dependencyUpdateChecker.setRevisionToCheck("latest.release"); // exclude milestone (2.0-M1) + dependencyUpdateChecker.setOrganisation("bar"); + dependencyUpdateChecker.setModule("foo"); + dependencyUpdateChecker.setRevision("2.0-M1"); + dependencyUpdateChecker.setInline(true); + dependencyUpdateChecker.execute(); + + assertLogNotContaining("bar#foo\t2.0-M1 -> 1.0"); + // expect listing for 2.0 when it comes available + assertLogContaining("All dependencies are up to date"); + } + @Test public void testSimpleExtends() { dependencyUpdateChecker.setFile(new File("test/java/org/apache/ivy/ant/ivy-extends-multiconf.xml")); diff --git a/test/repositories/IVY-1665/bar/foo/foo-1.0.jar b/test/repositories/IVY-1665/bar/foo/foo-1.0.jar new file mode 100644 index 00000000..945c9b46 --- /dev/null +++ b/test/repositories/IVY-1665/bar/foo/foo-1.0.jar @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/test/repositories/IVY-1665/bar/foo/foo-2.0-M1.jar b/test/repositories/IVY-1665/bar/foo/foo-2.0-M1.jar new file mode 100644 index 00000000..945c9b46 --- /dev/null +++ b/test/repositories/IVY-1665/bar/foo/foo-2.0-M1.jar @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/test/repositories/IVY-1665/bar/foo/ivy-1.0.xml b/test/repositories/IVY-1665/bar/foo/ivy-1.0.xml new file mode 100644 index 00000000..75071478 --- /dev/null +++ b/test/repositories/IVY-1665/bar/foo/ivy-1.0.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/test/repositories/IVY-1665/bar/foo/ivy-2.0-M1.xml b/test/repositories/IVY-1665/bar/foo/ivy-2.0-M1.xml new file mode 100644 index 00000000..341093d7 --- /dev/null +++ b/test/repositories/IVY-1665/bar/foo/ivy-2.0-M1.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/test/repositories/IVY-1665/ivysettings.xml b/test/repositories/IVY-1665/ivysettings.xml new file mode 100644 index 00000000..908e5d48 --- /dev/null +++ b/test/repositories/IVY-1665/ivysettings.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + From fd67cacf14fb2fcb5605fb7b22ebceb71b43f20d Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Fri, 12 Jun 2026 10:19:54 -0500 Subject: [PATCH 2/3] IVY-1665: use default LatestStrategy to compare revisions --- .../ivy/ant/IvyDependencyUpdateChecker.java | 49 ++++++------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java b/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java index 76e9d090..509a02d4 100644 --- a/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java +++ b/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java @@ -28,6 +28,7 @@ import org.apache.ivy.core.module.id.ModuleRevisionId; import org.apache.ivy.core.report.ResolveReport; import org.apache.ivy.core.resolve.IvyNode; import org.apache.ivy.core.resolve.ResolveOptions; +import org.apache.ivy.plugins.latest.ArtifactInfo; import org.apache.tools.ant.BuildException; @@ -128,8 +129,10 @@ public class IvyDependencyUpdateChecker extends IvyPostResolveTask { for (IvyNode latest : latestReport.getDependencies()) { for (IvyNode originalDependency : originalReport.getDependencies()) { if (latest.getModuleId().equals(originalDependency.getModuleId())) { - if (isGreater(latest.getResolvedId().getRevision(), - originalDependency.getResolvedId().getRevision())) { + ArtifactInfo in1 = toArtifactInfo(latest); + ArtifactInfo in2 = toArtifactInfo(originalDependency); + ArtifactInfo out = getSettings().getDefaultLatestStrategy().findLatest(new ArtifactInfo[]{in1, in2}, null); + if (out == in1) { // is this dependency a transitive or a direct dependency? // (unfortunately .isTransitive() methods do not have the same meaning) boolean isTransitiveDependency = latest.getDependencyDescriptor(latest @@ -144,7 +147,6 @@ public class IvyDependencyUpdateChecker extends IvyPostResolveTask { dependencyUpdateDetected = true; } } - } } } @@ -198,39 +200,16 @@ public class IvyDependencyUpdateChecker extends IvyPostResolveTask { //-------------------------------------------------------------------------- - private static boolean isGreater(String string1, String string2) { - String[] tokens1 = string1.split("[\\._\\-\\+]"); - String[] tokens2 = string2.split("[\\._\\-\\+]"); - int i = 0; - for (final int n = Math.min(tokens1.length, tokens2.length); i < n; i += 1) { - if (!tokens1[i].equals(tokens2[i])) { - boolean is1Number = isNumeric(tokens1[i]); - boolean is2Number = isNumeric(tokens2[i]); - if (is1Number && is2Number) { - return Long.valueOf(tokens1[i]).compareTo(Long.valueOf(tokens2[i])) > 0; - } else if (is1Number && !is2Number) { - return true; - } else if (!is1Number && is2Number) { - return false; - } - return true; // special meanings accounted for by resolve + private static ArtifactInfo toArtifactInfo(IvyNode node) { + return new ArtifactInfo() { + @Override + public String getRevision() { + return node.getResolvedId().getRevision(); } - } - if (i < tokens1.length) { - return isNumeric(tokens1[i]); - } - if (i < tokens2.length) { - return !isNumeric(tokens2[i]); - } - return false; - } - - private static boolean isNumeric(String string) { - for (int i = 0, n = string.length(); i < n; ++i) { - if (!Character.isDigit(string.charAt(i))) { - return false; + @Override + public long getLastModified() { + return node.getLastModified(); } - } - return true; + }; } } From aebe2c1f0fe8b2b1677ee62efed8978d05bef406 Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Fri, 12 Jun 2026 13:06:56 -0500 Subject: [PATCH 3/3] IVY-1665: use configured LatestStrategy to compare revisions --- .../apache/ivy/ant/IvyDependencyUpdateChecker.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java b/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java index 509a02d4..4b5a7138 100644 --- a/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java +++ b/src/java/org/apache/ivy/ant/IvyDependencyUpdateChecker.java @@ -29,6 +29,9 @@ import org.apache.ivy.core.report.ResolveReport; import org.apache.ivy.core.resolve.IvyNode; import org.apache.ivy.core.resolve.ResolveOptions; import org.apache.ivy.plugins.latest.ArtifactInfo; +import org.apache.ivy.plugins.latest.LatestStrategy; +import org.apache.ivy.plugins.resolver.DependencyResolver; +import org.apache.ivy.plugins.resolver.util.HasLatestStrategy; import org.apache.tools.ant.BuildException; @@ -131,7 +134,7 @@ public class IvyDependencyUpdateChecker extends IvyPostResolveTask { if (latest.getModuleId().equals(originalDependency.getModuleId())) { ArtifactInfo in1 = toArtifactInfo(latest); ArtifactInfo in2 = toArtifactInfo(originalDependency); - ArtifactInfo out = getSettings().getDefaultLatestStrategy().findLatest(new ArtifactInfo[]{in1, in2}, null); + ArtifactInfo out = getLatestStrategy(originalDependency).findLatest(new ArtifactInfo[]{in1, in2}, null); if (out == in1) { // is this dependency a transitive or a direct dependency? // (unfortunately .isTransitive() methods do not have the same meaning) @@ -200,6 +203,14 @@ public class IvyDependencyUpdateChecker extends IvyPostResolveTask { //-------------------------------------------------------------------------- + private LatestStrategy getLatestStrategy(IvyNode node) { + DependencyResolver resolver = getSettings().getResolver(node.getResolvedId()); + if (resolver instanceof HasLatestStrategy) { + return ((HasLatestStrategy) resolver).getLatestStrategy(); + } + return getSettings().getDefaultLatestStrategy(); + } + private static ArtifactInfo toArtifactInfo(IvyNode node) { return new ArtifactInfo() { @Override