Fix changelog generator missing directly removed properties

The changelog generator did not detect properties that were added to a new
version with error-level deprecation (indicating immediate removal). This
commonly occurs when upgrading dependencies like Flyway 10, where properties
are removed without prior deprecation.

Modified the computeDifferences method to detect properties that only exist
in the new metadata with error-level deprecation and properly mark them as
DELETED in the changelog.

See gh-45267

Signed-off-by: yybmion <yunyubin54@gmail.com>
This commit is contained in:
yybmion 2025-04-23 21:31:41 +09:00 committed by Andy Wilkinson
parent c8c7632c1b
commit 30a679872c
4 changed files with 41 additions and 11 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,6 +21,7 @@ import java.util.List;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository;
import org.springframework.boot.configurationmetadata.Deprecation;
/**
* A changelog containing differences computed from two repositories of configuration
@ -32,6 +33,7 @@ import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepos
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Phillip Webb
* @author Yoobin Yoon
*/
record Changelog(String oldVersionNumber, String newVersionNumber, List<Difference> differences) {
@ -48,16 +50,26 @@ record Changelog(String oldVersionNumber, String newVersionNumber, List<Differen
String id = oldProperty.getId();
seenIds.add(id);
ConfigurationMetadataProperty newProperty = newMetadata.getAllProperties().get(id);
if (newProperty == null) {
differences.add(new Difference(DifferenceType.DELETED, oldProperty, null));
}
else {
Difference difference = Difference.compute(oldProperty, newProperty);
if (difference != null) {
differences.add(difference);
}
}
}
for (ConfigurationMetadataProperty newProperty : newMetadata.getAllProperties().values()) {
if ((!seenIds.contains(newProperty.getId())) && (!newProperty.isDeprecated())) {
if (!seenIds.contains(newProperty.getId())) {
if (newProperty.isDeprecated() && newProperty.getDeprecation().getLevel() == Deprecation.Level.ERROR) {
differences.add(new Difference(DifferenceType.DELETED, null, newProperty));
}
else if (!newProperty.isDeprecated()) {
differences.add(new Difference(DifferenceType.ADDED, null, newProperty));
}
}
}
return List.copyOf(differences);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Yoobin Yoon
*/
class ChangelogTests {
@ -38,7 +39,7 @@ class ChangelogTests {
assertThat(differences).isNotNull();
assertThat(differences.oldVersionNumber()).isEqualTo("1.0");
assertThat(differences.newVersionNumber()).isEqualTo("2.0");
assertThat(differences.differences()).hasSize(4);
assertThat(differences.differences()).hasSize(5);
List<Difference> added = differences.differences()
.stream()
.filter((difference) -> difference.type() == DifferenceType.ADDED)
@ -49,10 +50,12 @@ class ChangelogTests {
.stream()
.filter((difference) -> difference.type() == DifferenceType.DELETED)
.toList();
assertThat(deleted).hasSize(2)
assertThat(deleted).hasSize(3)
.anySatisfy((entry) -> assertProperty(entry.oldProperty(), "test.delete", String.class, "delete"))
.anySatisfy(
(entry) -> assertProperty(entry.newProperty(), "test.delete.deprecated", String.class, "delete"));
(entry) -> assertProperty(entry.newProperty(), "test.delete.deprecated", String.class, "delete"))
.anySatisfy((entry) -> assertProperty(entry.newProperty(), "test.removed.directly", String.class,
"directlyRemoved"));
List<Difference> deprecated = differences.differences()
.stream()
.filter((difference) -> difference.type() == DifferenceType.DEPRECATED)

View File

@ -31,6 +31,17 @@
"replacement": "test.add",
"reason": "it was just bad"
}
},
{
"name": "test.removed.directly",
"type": "java.lang.String",
"description": "Test property removed without prior deprecation.",
"defaultValue": "directlyRemoved",
"deprecation": {
"level": "error",
"replacement": "test.new.property",
"reason": "Removed in Upgrade 10"
}
}
]
}

View File

@ -32,4 +32,8 @@ _None_.
| `test.delete.deprecated`
| `test.add`
| it was just bad
| `test.removed.directly`
| `test.new.property`
| Removed in Upgrade 10
|======================