Skip to content

VersionHelper: prefer semver compare over list-index (refs #538)#561

Open
rainxchzed wants to merge 1 commit intomainfrom
fix/downgrade-detection-prefers-semver
Open

VersionHelper: prefer semver compare over list-index (refs #538)#561
rainxchzed wants to merge 1 commit intomainfrom
fix/downgrade-detection-prefers-semver

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 9, 2026

What this PR fixes

Refs #538.

A fresh install of the latest release was being flagged as a downgrade in the install flow on the details screen.

Root cause

The old VersionHelper.isDowngradeVersion algorithm used the position of each tag in allReleases as the primary heuristic, with semver compare as the fallback. Comment said "releases are newest-first" — but GitHub orders the release feed by published_at, not by version. As soon as a maintainer:

  • republishes an old release,
  • backdates a release to fix a typo,
  • or pushes a hotfix that gets a fresher published_at than the supposed-latest,

the list ordering and the version ordering diverge. candidateIndex > currentIndex flips even when candidate is genuinely newer, and the user sees a phantom downgrade warning over a perfectly normal upgrade.

Fix

  • Use scheme-aware comparison: when both sides parse as SemVer or CalVer, trust VersionMath.compareVersions (which already handles prefix strip, build-metadata, pre-release ordering, and the build-variant suffix added in Strip build-variant suffix during version normalization (closes #557) #560). The sign of the comparator is authoritative.
  • Keep list-index lookup as a secondary fallback for non-parseable inputs (commit-hash tags, ad-hoc strings).
  • As a last-resort tie-breaker fall through to VersionMath.compareVersions (which itself lex-compares when nothing else parses).

Side cleanup

  • VersionHelper was carrying its own duplicate normalizeVersion / compareSemanticVersions implementations that drifted from the canonical VersionMath. Both now delegate. Public API surface is unchanged so the single call site (DetailsViewModel.install) needs no updates.

Out of scope (mentioned in issue, tracked separately)

  • Root-based silent install — would be a new InstallSource alongside Shizuku / Sui / Dhizuku. Separate feature work.
  • UI glitch in the second screenshot — needs reproduction steps; tracked as a separate bug.

Test plan

  • Manual: simulate a maintainer republishing an old release on a watched repo. Then attempt to install the latest version → no downgrade warning.
  • Manual: a genuine downgrade (install older release over newer installed) → still warns.
  • Manual: a clean upgrade (install newer over older, normal release ordering) → no warning, proceeds to install.
  • App that uses commit-hash tags → list-index fallback still kicks in.

Summary by CodeRabbit

  • Improvements

    • Enhanced version comparison and downgrade detection logic for improved reliability across different version scheme formats.
  • Documentation

    • Updated documentation to clarify version handling behavior and strategies.

Review Change Stack

…eck.

Issue #538: a fresh install of the latest release was incorrectly
flagged as a downgrade.

Root cause: the previous algorithm used the position of each tag in
the release list as the primary heuristic, on the assumption that the
list is newest-first by version. GitHub orders releases by
`published_at` though, and that ordering diverges from semver
ordering as soon as a maintainer republishes an old release, fixes a
typo on a tag, or backdates a release. In that case
`candidateIndex > currentIndex` flips and the install flow shows a
phantom downgrade warning over a perfectly normal upgrade.

Switch to scheme-aware compare: when both sides parse as SemVer or
CalVer, trust VersionMath.compareVersions (which already handles
prefix strip, build-metadata, pre-release ordering, and the
build-variant suffix added in #560). List-index is now a secondary
fallback used only when at least one input has no parseable scheme
(commit-hash tags, ad-hoc strings).

Also collapses the duplicate normaliser/comparator implementations
into the single source of truth in
`core.domain.util.VersionMath`. The public surface of VersionHelper
is unchanged, so the only call site (DetailsViewModel.install)
needs no updates.

Out of scope for this PR (mentioned in issue but separate concerns):
- Root-based silent install (would be a new InstallSource alongside
  Shizuku/Sui/Dhizuku).
- UI glitch shown in the second screenshot — needs reproduction
  steps; tracked separately.

Refs #538.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 527bc6f2-ef6e-40f3-af54-c604f58b4af6

📥 Commits

Reviewing files that changed from the base of the PR and between f20a4b4 and bc5c02d.

📒 Files selected for processing (1)
  • feature/details/domain/src/commonMain/kotlin/zed/rainxch/details/domain/util/VersionHelper.kt

Walkthrough

VersionHelper refactors to delegate version normalization and comparison operations to centralized VersionMath utilities. The downgrade detection logic shifts from index-based heuristics to a multi-step strategy: scheme detection first, scheme-aware comparison when both schemes are recognized, release-list ordering when detection is incomplete, and VersionMath comparison as final fallback.

Changes

VersionHelper Delegation to VersionMath

Layer / File(s) Summary
Imports & Delegation Strategy
feature/details/domain/src/commonMain/kotlin/zed/rainxch/details/domain/util/VersionHelper.kt
VersionHelper imports VersionMath and documents revised downgrade strategy combining scheme-aware comparison with release-list fallback.
Normalization Delegation
feature/details/domain/src/commonMain/kotlin/zed/rainxch/details/domain/util/VersionHelper.kt
normalizeVersion delegates to VersionMath.normalizeVersion, removing local trim and prefix-removal logic.
Downgrade Detection Rewrite
feature/details/domain/src/commonMain/kotlin/zed/rainxch/details/domain/util/VersionHelper.kt
isDowngradeVersion uses scheme detection, scheme-aware comparison when both schemes recognized, release-list ordering on incomplete detection, and VersionMath fallback.
Semantic Comparison Delegation
feature/details/domain/src/commonMain/kotlin/zed/rainxch/details/domain/util/VersionHelper.kt
compareSemanticVersions forwards to VersionMath.compareVersions, removing local semantic parsing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • OpenHub-Store/GitHub-Store#560: Modifies VersionMath.normalizeVersion (stripping build-variant suffixes), directly affecting the normalization behavior now delegated by VersionHelper.
  • OpenHub-Store/GitHub-Store#555: Introduces VersionMath utilities like normalizeVersion enhancements and detectScheme that VersionHelper now depends on for scheme-aware comparison.
  • OpenHub-Store/GitHub-Store#466: Adds VersionMath centralization logic including isSameVersion that shares the same comparison infrastructure now used by VersionHelper.

Poem

🐰 From local rules to shared commands,
VersionHelper now extends its hands
To VersionMath, a central guide—
Schemes and fallbacks side by side,
Downgrades dance with logic refined! 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: shifting the version comparison strategy to prefer semantic version comparison over list-index ordering. It directly references the PR objective (fixing downgrade detection) and the related issue #538.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/downgrade-detection-prefers-semver

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant