Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions e2e/core/test_python_precompiled_minimum_release_age
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

export MISE_PYTHON_COMPILE=0

latest=$(mise latest python)
filtered=$(mise latest python --minimum-release-age 2025-01-01)

[[ -n $filtered ]] || fail "expected filtered python latest to be non-empty"
[[ $filtered =~ ^[0-9]+(\.[0-9]+)*$ ]] || fail "expected filtered python latest to be a version, got $filtered"
[[ $filtered != "$latest" ]] || fail "expected minimum_release_age to hide precompiled python latest $latest"

json=$(mise ls-remote python --json --minimum-release-age 2025-01-01)
if ! jq -e 'length > 0 and all(.[]; (.created_at != null and .created_at < "2025-01-02T00:00:00Z"))' <<<"$json" >/dev/null; then
fail "expected precompiled python versions to include created_at before the cutoff: $json"
Comment thread
cursor[bot] marked this conversation as resolved.
fi
104 changes: 103 additions & 1 deletion src/plugins/core/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,23 @@ impl PythonPlugin {
}
}
}
fn python_build_definition_created_at(&self) -> eyre::Result<BTreeMap<String, String>> {
let output = crate::cmd!(
"git",
"-C",
self.python_build_path(),
"-c",
format!("safe.directory={}", self.python_build_path().display()),
"log",
"--format=%cI",
"--diff-filter=A",
"--name-only",
"--",
"plugins/python-build/share/python-build",
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.read()?;
Comment on lines +193 to +206

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.

P1 Adding --diff-filter=A restricts the log to commits that first Added each definition file, ensuring the map records the original release date rather than the date of any subsequent patch or maintenance commit.

Suggested change
let output = crate::cmd!(
"git",
"-C",
self.python_build_path(),
"-c",
format!("safe.directory={}", self.python_build_path().display()),
"log",
"--format=%cI",
"--name-only",
"--",
"plugins/python-build/share/python-build",
)
.read()?;
let output = crate::cmd!(
"git",
"-C",
self.python_build_path(),
"-c",
format!("safe.directory={}", self.python_build_path().display()),
"log",
"--format=%cI",
"--name-only",
"--diff-filter=A",
"--",
"plugins/python-build/share/python-build",
)
.read()?;

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Ok(parse_python_build_definition_created_at(&output))
}

async fn fetch_precompiled_remote_versions(
&self,
Expand Down Expand Up @@ -764,15 +781,22 @@ impl Backend for PythonPlugin {
.fetch_precompiled_remote_versions()
.await?
.iter()
.map(|(v, _, _)| VersionInfo {
.map(|(v, date, _)| VersionInfo {
version: v.clone(),
created_at: python_precompiled_created_at(date),
..Default::default()
})
.collect())
} else {
self.install_or_update_python_build(None)?;
let python_build_bin = self.python_build_bin();
let python_build_str = python_build_bin.to_string_lossy().to_string();
let definition_created_at = self
.python_build_definition_created_at()
.inspect_err(|err| {
debug!("failed to get python-build definition timestamps: {err:#}")
})
.unwrap_or_default();
plugins::core::run_fetch_task_with_timeout_async(async move || {
let output = crate::cmd::cmd_read_async_inherited_env(
&python_build_str,
Expand All @@ -786,6 +810,7 @@ impl Backend for PythonPlugin {
.filter(|s| !regex!(r"\dt(-dev)?$").is_match(s))
.map(|s| VersionInfo {
version: s.to_string(),
created_at: definition_created_at.get(s).cloned(),
..Default::default()
})
.sorted_by_cached_key(|v| python_version_sort_key(&v.version))
Expand Down Expand Up @@ -974,6 +999,42 @@ impl Backend for PythonPlugin {
}
}

fn parse_python_build_definition_created_at(output: &str) -> BTreeMap<String, String> {
let mut created_at = BTreeMap::new();
let mut current_timestamp = None;
for line in output.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
if !line.starts_with("plugins/") && crate::duration::parse_into_timestamp(line).is_ok() {
current_timestamp = Some(line.to_string());
continue;
}
if let Some(version) = line.strip_prefix("plugins/python-build/share/python-build/")
&& !version.contains('/')
&& let Some(timestamp) = &current_timestamp
{
created_at
.entry(version.to_string())
.or_insert_with(|| timestamp.clone());
Comment thread
cursor[bot] marked this conversation as resolved.
Comment on lines +1018 to +1020

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.

P1 Newest-first git log + or_insert_with stores modification date, not creation date

git log outputs commits newest-first. or_insert_with keeps the first value inserted, which is the most-recent commit touching each definition file. If a definition (e.g. 3.12.0) was patched or fixed in a later commit, the map stores that modification timestamp rather than the original creation timestamp. A user running minimum_release_age on the source-build path would see Python 3.12.0 (released 2023-10-03) silently hidden because its definition was touched more recently.

Adding --diff-filter=A to the git log call restricts output to commits that first Added each file, so the map always records the true release date.

Fix in Claude Code

}
}
created_at
}

fn python_precompiled_created_at(date: &str) -> Option<String> {
if date.len() != 8 || !date.chars().all(|c| c.is_ascii_digit()) {
return None;
}
Some(format!(
"{}-{}-{}T00:00:00Z",
&date[..4],
&date[4..6],
&date[6..]
))
}

fn python_precompiled_url_path(settings: &Settings) -> String {
if cfg!(windows) || cfg!(linux) || cfg!(macos) {
format!(
Expand Down Expand Up @@ -1130,6 +1191,47 @@ mod tests {
assert!(PythonOptions::new(&opts).lockfile_options().is_empty());
}

#[test]
fn parses_python_build_definition_created_at() {
let output = "\
2026-06-11T12:34:56+00:00
plugins/python-build/share/python-build/3.14.6
plugins/python-build/share/python-build/3.13.8

2026-06-01T01:02:03+00:00
plugins/python-build/share/python-build/3.14.5
plugins/python-build/share/python-build/patches/3.14.5/foo.patch
";

assert_eq!(
parse_python_build_definition_created_at(output),
BTreeMap::from([
(
"3.13.8".to_string(),
"2026-06-11T12:34:56+00:00".to_string()
),
(
"3.14.5".to_string(),
"2026-06-01T01:02:03+00:00".to_string()
),
(
"3.14.6".to_string(),
"2026-06-11T12:34:56+00:00".to_string()
),
])
);
}

#[test]
fn parses_python_precompiled_created_at() {
assert_eq!(
python_precompiled_created_at("20260611").as_deref(),
Some("2026-06-11T00:00:00Z")
);
assert_eq!(python_precompiled_created_at("2026-06-11"), None);
assert_eq!(python_precompiled_created_at("notadate"), None);
}

#[test]
fn test_resolve_python_arch_windows_x64() {
assert_eq!(resolve_python_arch("windows", "x64"), "x86_64");
Expand Down
Loading