Skip to content

Fix version sorting by using SemVer instead of publishedDate#85

Open
Moustachauve wants to merge 5 commits into
mainfrom
hotfix/version-sorting
Open

Fix version sorting by using SemVer instead of publishedDate#85
Moustachauve wants to merge 5 commits into
mainfrom
hotfix/version-sorting

Conversation

@Moustachauve
Copy link
Copy Markdown
Owner

Fixes #84

This PR updates the version retrieval logic to use Semantic Versioning (SemVer) comparison instead of GitHub's publishedAt date.

Currently, the app sorts versions by their publication date, which can be incorrect if an older version branch receives a patch after a newer major/minor version has been released (e.g., v0.15.5 having a newer publishedAt date than v16.0.0).

Changes:

  • Updated ReleaseService.getLatestVersion(branch:) to fetch all relevant versions and identify the latest one using SemanticVersion comparison.
  • Added comprehensive unit tests in ReleaseServiceTests.swift to verify the fix and ensure stable/beta filtering and nightly exclusion still work as expected.

This mirrors the fix applied to the Android app in Moustachauve/WLED-Android#168.

Fixes #84

The app previously sorted versions by their GitHub publishedAt date to
determine the latest available version. This could lead to incorrect
results when older version branches receive patches with a newer
publishedAt date (e.g. v0.15.5 appearing newer than v16.0.0).

The fix replaces the Core Data query's publishedDate sort with an
in-memory comparison using the existing SemanticVersion struct. Versions
with invalid semver tags fall back to publishedDate comparison.

Also adds comprehensive unit tests for ReleaseService covering the exact
bug scenario, stable/beta filtering, nightly exclusion, and the
getNewerReleaseTag helper.
@github-actions github-actions Bot added the bug Something isn't working label May 8, 2026
@Moustachauve Moustachauve added the patch For fixes. label May 8, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the ReleaseService to determine the latest version using SemanticVersion logic rather than relying on Core Data's date-based sorting. It also introduces a comprehensive test suite for version fetching and comparison. Review feedback suggests extending the use of SemanticVersion to the getNewerReleaseTag method to avoid comparison errors, particularly when moving from beta to stable releases. Additionally, there is a recommendation to optimize memory usage by limiting the properties fetched from Core Data, as removing the fetch limit may lead to loading large release descriptions into memory. Finally, a specific test case for the beta-to-stable transition was proposed to ensure robust update detection.

I am having trouble creating individual review comments. Click here to see my feedback.

wled/Service/ReleaseService.swift (56)

high

While getLatestVersion now correctly identifies the latest version using SemanticVersion, the getNewerReleaseTag method (lines 22-38) still relies on .numeric string comparison to determine if an update is available. This creates an inconsistency and will fail to detect updates in specific scenarios. For example, if a user is on a beta version (e.g., "0.16.0-b1") and the stable "0.16.0" is released, .numeric comparison considers "0.16.0" to be less than "0.16.0-b1" (because the former is a prefix of the latter), resulting in no update being detected. You should update getNewerReleaseTag to use SemanticVersion for its comparison logic as well.

wled/Service/ReleaseService.swift (55)

medium

Removing the fetchLimit and sortDescriptors means that every Version entity in the database is now fully instantiated into memory to find the maximum. Since each Version object contains a versionDescription (the release body) which can be quite large, this could lead to unnecessary memory pressure as the release history grows. Consider using propertiesToFetch to only load the tagName and publishedDate fields, or fetching the objects as faults to minimize the initial memory impact.

wledTests/ReleaseServiceTests.swift (177-193)

medium

The current test suite for getNewerReleaseTag is missing a critical edge case: transitioning from a pre-release to a stable release of the same version number (e.g., current "0.16.0-b1" to latest "0.16.0") when the branch is set to .beta. Adding this test case will likely reveal the comparison bug in getNewerReleaseTag where .numeric string comparison fails to recognize the stable release as newer.

    @Test func newerReleaseTagRespectsIgnoreVersion() throws {
        let context = makeInMemoryContext()
        try deleteAllVersions(context: context)

        insertVersion(context: context, tagName: "0.16.0")

        try context.save()

        let service = ReleaseService(context: context)
        let result = service.getNewerReleaseTag(
            versionName: "0.15.0",
            branch: .stable,
            ignoreVersion: "0.16.0"
        )

        #expect(result == "")
    }

    @Test func newerReleaseTagDetectsUpdateFromBetaToStable() throws {
        let context = makeInMemoryContext()
        try deleteAllVersions(context: context)

        insertVersion(context: context, tagName: "0.16.0")

        try context.save()

        let service = ReleaseService(context: context)
        let result = service.getNewerReleaseTag(
            versionName: "0.16.0-b1",
            branch: .beta,
            ignoreVersion: ""
        )

        #expect(result == "0.16.0")
    }

@Moustachauve
Copy link
Copy Markdown
Owner Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the ReleaseService to prioritize semantic versioning over publication date when identifying the latest release. It updates the getLatestVersion method to perform in-memory sorting using SemanticVersion and adds a comprehensive test suite to verify versioning logic. Feedback was provided regarding an ineffective Core Data optimization where propertiesToFetch was used with a managed object result type, which is ignored by the framework.

Comment on lines 57 to +58
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
fetchRequest.propertiesToFetch = ["tagName", "publishedDate"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The propertiesToFetch property is only effective when the resultType of the NSFetchRequest is set to .dictionaryResultType. Since this request uses the default .managedObjectResultType, this line is ignored by Core Data. For a small dataset like the one typically returned by the GitHub releases API, this optimization is unnecessary and should be removed to keep the code clean and avoid potential confusion.

Suggested change
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
fetchRequest.propertiesToFetch = ["tagName", "publishedDate"]
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working patch For fixes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Versions ordered based on Github publishedAt can be wrong

1 participant