feat(version): add semantic version tag selection #135

Merged
fuzzy merged 1 commit from feat/semver-tag-selection into main 2026-08-02 16:45:14 +00:00
Owner

What

Implement semantic version tag selection in pkg/version (stdlib only, no new dependencies):

  • Parse(tag) (#38) — parses strict v<major>.<minor>.<patch> tags into a SemVer. Rejects tags without the v prefix, non-numeric components, pre-release (-rc1) and build (+meta) suffixes, and malformed versions.
  • Highest(tags) (#40) — returns the tag with the greatest version by numeric comparison (major → minor → patch), so v10.0.0 beats v9.9.9. Ignores non-matching tags; reports false when nothing matches.
  • Comparison (#39) — numeric ordering in SemVer.greater; no lexicographic string sort.
  • SemVer.String() returns the canonical vX.Y.Z form.

Tests: table-driven coverage for parse validity (valid, missing prefix, pre-release/build suffixes, non-numeric, empty), highest selection (numeric ordering, tag filtering, empty/no-match, single element, pre-release ignored), and String.

Why

Phase 3 task #41 — the tag-selection logic the webhook receiver (#72) uses to pick the production version after a push, and rollback (#90) uses to reselect after a tag deletion.

Testing

  • go build ./...
  • go vet ./...
  • go test -race ./pkg/version/ — 3 test functions / 24 subtests pass
  • gofmt -l ./cmd ./internal ./pkg clean
  • golangci-lint run — 0 issues
  • pre-commit hooks all pass

Breaking Changes

None.

Notes

  • Pre-release suffixes are intentionally not treated as deployable for now (strict vX.Y.Z only); can be added later if preview/release-candidate tags are wanted for production.
  • Issues #38–#41 were closed via the Forgejo API as part of this task per the workflow.

Closes #38
Closes #39
Closes #40
Closes #41

## What Implement semantic version tag selection in `pkg/version` (stdlib only, no new dependencies): - **`Parse(tag)` (#38)** — parses strict `v<major>.<minor>.<patch>` tags into a `SemVer`. Rejects tags without the `v` prefix, non-numeric components, pre-release (`-rc1`) and build (`+meta`) suffixes, and malformed versions. - **`Highest(tags)` (#40)** — returns the tag with the greatest version by **numeric** comparison (major → minor → patch), so `v10.0.0` beats `v9.9.9`. Ignores non-matching tags; reports `false` when nothing matches. - **Comparison (#39)** — numeric ordering in `SemVer.greater`; no lexicographic string sort. - `SemVer.String()` returns the canonical `vX.Y.Z` form. **Tests**: table-driven coverage for parse validity (valid, missing prefix, pre-release/build suffixes, non-numeric, empty), highest selection (numeric ordering, tag filtering, empty/no-match, single element, pre-release ignored), and `String`. ## Why Phase 3 task #41 — the tag-selection logic the webhook receiver (#72) uses to pick the production version after a push, and rollback (#90) uses to reselect after a tag deletion. ## Testing - [x] `go build ./...` - [x] `go vet ./...` - [x] `go test -race ./pkg/version/` — 3 test functions / 24 subtests pass - [x] `gofmt -l ./cmd ./internal ./pkg` clean - [x] `golangci-lint run` — 0 issues - [x] pre-commit hooks all pass ## Breaking Changes None. ## Notes - Pre-release suffixes are intentionally not treated as deployable for now (strict `vX.Y.Z` only); can be added later if preview/release-candidate tags are wanted for production. - Issues #38–#41 were closed via the Forgejo API as part of this task per the workflow. Closes #38 Closes #39 Closes #40 Closes #41
feat(version): add semantic version tag selection
All checks were successful
Test and Release / lint (pull_request) Successful in 3m50s
Test and Release / test (pull_request) Successful in 19m8s
953854291d
Implement tag parsing and highest-version selection in pkg/version:

- Parse matches strict v<major>.<minor>.<patch> tags, rejecting tags
  without the v prefix, non-numeric components, and pre-release or
  build suffixes
- Highest returns the greatest version by numeric comparison (so
  v10.0.0 beats v9.9.9), ignoring non-matching tags
- Add table-driven tests covering parse validity, numeric ordering,
  filtering, and empty input

Mark task done in ROADMAP.md.

closes #38
closes #39
closes #40
closes #41
fuzzy scheduled this pull request to auto merge when all checks succeed 2026-08-02 16:32:39 +00:00
the.auditor left a comment

Review Summary

Verified locally on the PR head (9538542): go build, go vet, gofmt (incl. ./pkg), golangci-lint run all clean; all 3 test functions / 24 subtests pass with -race.

Clean, dependency-free implementation. Parse correctly rejects missing prefix, wrong component counts (including v1.2.3.4 via SplitN + Atoi failure), pre-release/build suffixes, non-numeric and negative components, and overflow. Highest uses numeric (not lexicographic) ordering, filters non-matching tags, and reports false on no match. Table-driven tests are thorough.

Extra edge cases I probed locally: v01.2.3 accepted, v1.2.3./v1.2/V1.2.3 rejected, integer overflow rejected — all sensible.

No blocking issues.

Non-blocking observation:

  1. Leading zeros are tolerated (v01.2.3 parses to 1.2.3), so two distinct tags could compare equal — Highest returns whichever comes first, and SemVer.String() normalizes to v1.2.3 while Tag preserves the original. Harmless for deployment selection, but strict semver disallows leading zeros if that ever matters.

Approving.

## Review Summary Verified locally on the PR head (`9538542`): `go build`, `go vet`, `gofmt` (incl. `./pkg`), `golangci-lint run` all clean; all 3 test functions / 24 subtests pass with `-race`. Clean, dependency-free implementation. `Parse` correctly rejects missing prefix, wrong component counts (including `v1.2.3.4` via `SplitN` + `Atoi` failure), pre-release/build suffixes, non-numeric and negative components, and overflow. `Highest` uses numeric (not lexicographic) ordering, filters non-matching tags, and reports `false` on no match. Table-driven tests are thorough. Extra edge cases I probed locally: `v01.2.3` accepted, `v1.2.3.`/`v1.2`/`V1.2.3` rejected, integer overflow rejected — all sensible. **No blocking issues.** Non-blocking observation: 1. Leading zeros are tolerated (`v01.2.3` parses to 1.2.3), so two distinct tags could compare equal — `Highest` returns whichever comes first, and `SemVer.String()` normalizes to `v1.2.3` while `Tag` preserves the original. Harmless for deployment selection, but strict semver disallows leading zeros if that ever matters. Approving.
fuzzy merged commit 953854291d into main 2026-08-02 16:45:14 +00:00
fuzzy deleted branch feat/semver-tag-selection 2026-08-02 16:45:14 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
thwap/thwap-pagesd!135
No description provided.