Support prerelease versions in language_version - #2397
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2397 +/- ##
==========================================
+ Coverage 93.38% 93.43% +0.04%
==========================================
Files 129 129
Lines 27637 27902 +265
==========================================
+ Hits 25810 26070 +260
- Misses 1827 1832 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5d7611b6fa
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds first-class prerelease parsing support for language_version so Go and Python prerelease requests can be resolved/installed correctly using a shared prerelease parsing helper.
Changes:
- Introduces
parse_prerelease_versionto normalize compact prerelease strings into semver for ordering/comparison. - Extends Go version/request parsing to recognize prereleases and ensures go.dev downloads use Go-native version strings (e.g.
1.24rc1). - Extends Python request parsing to recognize PEP 440 prereleases and forwards them to
uv.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/prek/src/languages/version.rs | Adds shared prerelease parsing helper + unit tests for ordering/validation. |
| crates/prek/src/languages/python/version.rs | Adds PythonRequest::Prerelease and parsing/matching for PEP 440 prerelease inputs. |
| crates/prek/src/languages/python/python.rs | Forwards prerelease requests to uv via --python argument generation. |
| crates/prek/src/languages/golang/version.rs | Adds Go prerelease request variant, parsing, matching, and Go-native version string formatting. |
| crates/prek/src/languages/golang/installer.rs | Uses Go-native version strings for download filename/checksum lookup to support prerelease artifacts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📦 Cargo Bloat ComparisonBinary size change: +0.34% (29.2 MiB → 29.3 MiB) Expand for cargo-bloat outputHead Branch ResultsBase Branch Results |
⚡️ Hyperfine BenchmarksSummary: 6 regressions, 4 improvements above the 10% threshold. Environment
CLI CommandsBenchmarking basic commands in the main repo:
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base --version |
1.8 ± 0.1 | 1.6 | 1.9 | 1.00 |
prek-head --version |
1.9 ± 0.1 | 1.7 | 2.2 | 1.05 ± 0.07 |
prek list
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base list |
11.3 ± 15.2 | 7.2 | 93.5 | 1.00 |
prek-head list |
18.4 ± 33.7 | 7.3 | 170.1 | 1.62 ± 3.70 |
prek list: 62.4700% slower
prek validate-config .pre-commit-config.yaml
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base validate-config .pre-commit-config.yaml |
7.7 ± 25.7 | 2.5 | 163.2 | 1.00 |
prek-head validate-config .pre-commit-config.yaml |
8.9 ± 29.7 | 2.6 | 199.9 | 1.16 ± 5.50 |
prek validate-config .pre-commit-config.yaml: 16.0900% slower
prek sample-config
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base sample-config |
9.2 ± 28.8 | 1.9 | 170.8 | 1.21 ± 5.11 |
prek-head sample-config |
7.7 ± 22.1 | 2.0 | 104.2 | 1.00 |
✅ Performance improvement for prek sample-config: 17.0300% faster
Cold vs Warm Runs
Comparing first run (cold) vs subsequent runs (warm cache):
prek run --all-files (cold - no cache)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --all-files |
43.4 ± 1.6 | 40.8 | 45.9 | 1.00 |
prek-head run --all-files |
44.6 ± 1.8 | 41.8 | 47.3 | 1.03 ± 0.06 |
prek run --all-files (warm - with cache)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --all-files |
43.8 ± 1.3 | 41.6 | 46.1 | 1.00 |
prek-head run --all-files |
46.4 ± 10.6 | 41.2 | 89.5 | 1.06 ± 0.24 |
Full Hook Suite
Running the builtin hook suite on the benchmark workspace:
prek run --all-files (full builtin hook suite)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --all-files |
45.3 ± 12.2 | 39.7 | 128.6 | 1.05 ± 0.29 |
prek-head run --all-files |
43.0 ± 1.4 | 40.1 | 49.7 | 1.00 |
Individual Hook Performance
Benchmarking each hook individually on the test repo:
prek run trailing-whitespace --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run trailing-whitespace --all-files |
13.0 ± 0.3 | 12.4 | 13.8 | 1.00 |
prek-head run trailing-whitespace --all-files |
14.6 ± 6.7 | 12.5 | 49.7 | 1.13 ± 0.52 |
prek run trailing-whitespace --all-files: 12.5000% slower
prek run end-of-file-fixer --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run end-of-file-fixer --all-files |
19.1 ± 31.2 | 11.8 | 182.1 | 1.49 ± 2.44 |
prek-head run end-of-file-fixer --all-files |
12.8 ± 1.4 | 11.8 | 19.5 | 1.00 |
✅ Performance improvement for prek run end-of-file-fixer --all-files: 32.7500% faster
prek run check-json --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-json --all-files |
6.4 ± 3.9 | 5.4 | 27.2 | 1.11 ± 0.68 |
prek-head run check-json --all-files |
5.8 ± 0.3 | 5.4 | 6.5 | 1.00 |
prek run check-yaml --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-yaml --all-files |
7.8 ± 12.2 | 5.3 | 72.4 | 1.21 ± 2.04 |
prek-head run check-yaml --all-files |
6.4 ± 4.1 | 5.3 | 27.9 | 1.00 |
✅ Performance improvement for prek run check-yaml --all-files: 17.4700% faster
prek run check-toml --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-toml --all-files |
5.8 ± 0.9 | 5.2 | 10.4 | 1.00 |
prek-head run check-toml --all-files |
5.9 ± 0.4 | 5.2 | 7.4 | 1.02 ± 0.17 |
prek run check-xml --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-xml --all-files |
8.7 ± 10.1 | 5.2 | 50.7 | 1.52 ± 1.77 |
prek-head run check-xml --all-files |
5.8 ± 0.8 | 5.4 | 10.1 | 1.00 |
✅ Performance improvement for prek run check-xml --all-files: 34.0100% faster
prek run detect-private-key --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run detect-private-key --all-files |
8.0 ± 0.4 | 7.5 | 8.8 | 1.01 ± 0.08 |
prek-head run detect-private-key --all-files |
7.9 ± 0.5 | 7.2 | 9.1 | 1.00 |
prek run fix-byte-order-marker --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run fix-byte-order-marker --all-files |
13.6 ± 0.4 | 12.9 | 14.5 | 1.01 ± 0.04 |
prek-head run fix-byte-order-marker --all-files |
13.4 ± 0.3 | 12.9 | 14.1 | 1.00 |
Installation Performance
Benchmarking hook installation (fast path hooks skip Python setup):
prek install-hooks (cold - no cache)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base install-hooks |
3.5 ± 0.1 | 3.4 | 3.6 | 1.01 ± 0.04 |
prek-head install-hooks |
3.5 ± 0.1 | 3.3 | 3.6 | 1.00 |
prek install-hooks (warm - with cache)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base install-hooks |
3.6 ± 0.1 | 3.4 | 3.8 | 1.00 |
prek-head install-hooks |
3.7 ± 0.1 | 3.6 | 3.8 | 1.02 ± 0.05 |
File Filtering/Scoping Performance
Testing different file selection modes:
prek run (staged files only)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run |
24.2 ± 0.4 | 23.6 | 25.1 | 1.00 |
prek-head run |
24.6 ± 0.5 | 24.0 | 25.9 | 1.02 ± 0.03 |
prek run --files '*.json' (specific file type)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --files '*.json' |
6.0 ± 1.4 | 5.3 | 10.3 | 1.00 |
prek-head run --files '*.json' |
6.1 ± 2.6 | 5.4 | 17.0 | 1.02 ± 0.49 |
Workspace Discovery & Initialization
Benchmarking hook discovery and initialization overhead:
prek run --dry-run --all-files (measures init overhead)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --dry-run --all-files |
5.0 ± 0.1 | 4.8 | 5.3 | 1.00 |
prek-head run --dry-run --all-files |
5.1 ± 0.2 | 4.8 | 5.4 | 1.01 ± 0.04 |
Meta Hooks Performance
Benchmarking meta hooks separately:
prek run check-hooks-apply --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-hooks-apply --all-files |
8.2 ± 0.1 | 8.1 | 8.6 | 1.00 |
prek-head run check-hooks-apply --all-files |
11.1 ± 6.9 | 8.1 | 30.9 | 1.34 ± 0.83 |
prek run check-hooks-apply --all-files: 34.3200% slower
prek run check-useless-excludes --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-useless-excludes --all-files |
7.6 ± 0.2 | 7.4 | 8.2 | 1.00 |
prek-head run check-useless-excludes --all-files |
11.4 ± 14.3 | 7.4 | 63.2 | 1.50 ± 1.88 |
prek run check-useless-excludes --all-files: 49.7200% slower
prek run identity --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run identity --all-files |
7.4 ± 1.7 | 6.8 | 13.6 | 1.00 |
prek-head run identity --all-files |
10.5 ± 12.2 | 7.0 | 54.7 | 1.41 ± 1.68 |
prek run identity --all-files: 40.9500% slower
5d7611b to
596ba25
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 596ba25b48
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
596ba25 to
c84a856
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c84a856995
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
c84a856 to
9496e05
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9496e0534d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
9496e05 to
5fa6a75
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
crates/prek/src/languages/python/version.rs:56
parse_prerelease_versionaccepts multiple prerelease label spellings (e.g.beta,alpha,c,pre,preview). For Python,query_python_infonormalizes prerelease labels toa/b/rc, so a user request like3.13.0beta1or3.13.0c1would parse successfully here but never match an installed interpreter (3.13.0-b.1/3.13.0-rc.1) becausePrereleaserequests are matched by exact semver equality. Normalizing the parsed prerelease label to the same canonicala/b/rcset before constructingPythonRequest::Prereleaseavoids unnecessary reinstall/recreation of environments and makes equivalent PEP 440 spellings behave consistently.
if let Some(version) = parse_prerelease_version(version_part) {
if !version.pre.is_empty() {
return Ok(PythonRequest::Prerelease(version, version_part.to_string()));
}
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
crates/prek/src/languages/python/version.rs:56
PythonRequest::Prereleasestores the parsedsemver::Versionand later requires exact equality with the interpreter version recorded byquery_python_info. Right nowparse_prerelease_versionwill accept PEP 440 synonym labels likealpha,beta,c,pre,previewand preserve them in the semver prerelease (e.g.3.13.0beta1->3.13.0-beta.1), butquery_python_infoencodes prereleases using the short canonical labels (a/b/rc). That means requests like3.13.0beta1/3.13.0c1will never match an existing env for the same interpreter (and may cause unnecessary re-installs).
if let Some(version) = parse_prerelease_version(version_part) {
if !version.pre.is_empty() {
return Ok(PythonRequest::Prerelease(version, version_part.to_string()));
}
}
5fa6a75 to
de563cf
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de563cf873
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
de563cf to
b184205
Compare
| pub(crate) fn matches(&self, version: &GoVersion) -> bool { | ||
| match self { | ||
| GoRequest::Any => true, | ||
| GoRequest::Major(major) => version.0.major == *major, | ||
| GoRequest::Any => version.0.pre.is_empty(), | ||
| GoRequest::Major(major) => version.0.pre.is_empty() && version.0.major == *major, | ||
| GoRequest::MajorMinor(major, minor) => { | ||
| version.0.major == *major && version.0.minor == *minor | ||
| version.0.pre.is_empty() && version.0.major == *major && version.0.minor == *minor | ||
| } |
| // A default/omitted `language_version` means a normal, stable interpreter for | ||
| // Python/Go, so it must not silently reuse a prerelease env installed for another | ||
| // hook's explicit request. Other languages (e.g. Rust, where a nightly/beta toolchain | ||
| // can legitimately be "the default") keep their existing permissive behavior. | ||
| LanguageRequest::Any { .. } => match install_info.language { | ||
| Language::Python | Language::Golang => install_info.language_version.pre.is_empty(), | ||
| _ => true, | ||
| }, |
Parse prerelease versions in language_version and match/resolve them, via a shared parse_prerelease_version helper: - Go: Go-style prereleases (go1.24rc1, go1.18beta1); resolve and download using the Go-native version string (not semver), and keep stable/Any requests from selecting a prerelease. - Python: PEP 440 prereleases (3.13.0rc1, 3.14.0a1), forwarded to uv. Rust already supports prereleases via rustup channels (beta/nightly). Node, Deno, .NET, and Ruby also ship prereleases but need installer-specific download handling and are left as follow-ups. Closes j178#1582.
b184205 to
d468b2f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d468b2fe04
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| match self { | ||
| GoRequest::Any => true, | ||
| GoRequest::Major(major) => version.0.major == *major, | ||
| GoRequest::Any => version.0.pre.is_empty(), |
There was a problem hiding this comment.
Allow system Go prereleases through the Any request
When language_version: system is used for Go, the current Go installer still maps LanguageRequest::Any { system_only: true } to GoRequest::Any with downloads disabled, so this new prerelease exclusion rejects a prerelease go found on PATH and installation bails with “downloads are disabled” instead of honoring the requested system toolchain. The earlier outer LanguageRequest exemption doesn't help this install path; pass the system-only context through or use a separate matcher for default-vs-system Go requests.
Useful? React with 👍 / 👎.
| // Fall back to plain semver parsing so exotic inputs still yield a real error. This | ||
| // also rejects shapes `parse_prerelease_version` accepts but Go never publishes (a | ||
| // patch alongside a prerelease, or a non-Go label), since they aren't valid semver. | ||
| semver::Version::parse(s).map(GoVersion) | ||
| } | ||
| } | ||
|
|
||
| /// Go only ever publishes patchless `beta`/`rc` prereleases (`go1.24rc1`, `go1.18beta1`); other | ||
| /// shapes `parse_prerelease_version` would otherwise accept can't be mapped to a real download. | ||
| fn is_valid_go_prerelease(version: &semver::Version) -> bool { | ||
| if version.pre.is_empty() { | ||
| return true; | ||
| } | ||
| version.patch == 0 && matches!(version.pre.as_str().split('.').next(), Some("beta" | "rc")) | ||
| } |
Parse prerelease versions in language_version and match/resolve them via a shared
parse_prerelease_versionhelper:Rust already supports prereleases via rustup channels (beta/nightly). Node, Deno, .NET, and Ruby also ship prereleases but need installer-specific download handling and are left as follow-ups.
Closes #1582.