Fix version-tag matching and bundle filtering

This commit is contained in:
Julian Chang 2026-09-16 18:02:21 -04:00
parent 09509b1b3d
commit 41ce177483
No known key found for this signature in database
3 changed files with 87 additions and 4 deletions

View file

@ -7,10 +7,12 @@ use std::collections::{HashMap, HashSet};
use oneclient_common::domain::{ContentType, ProviderId}; use oneclient_common::domain::{ContentType, ProviderId};
use oneclient_db::dao::applied_migration as migration_dao; use oneclient_db::dao::applied_migration as migration_dao;
use oneclient_db::dao::artifact as artifact_dao; use oneclient_db::dao::artifact as artifact_dao;
use oneclient_db::dao::bundle as bundle_catalog_dao;
use oneclient_db::dao::cluster_bundle as bundle_dao; use oneclient_db::dao::cluster_bundle as bundle_dao;
use crate::ctx::ContentCtx; use crate::ctx::ContentCtx;
use crate::error::ContentResult; use crate::error::ContentResult;
use crate::packages::dependencies::base_game_version;
use crate::packages::store::PackageStore; use crate::packages::store::PackageStore;
use crate::packages::types::LinkedArtifactInfo; use crate::packages::types::LinkedArtifactInfo;
@ -96,6 +98,7 @@ fn group_duplicates(
pub async fn disable_foreign_game_versions( pub async fn disable_foreign_game_versions(
cluster_id: i64, cluster_id: i64,
mc_version: &str, mc_version: &str,
mc_loader: i64,
ctx: &ContentCtx, ctx: &ContentCtx,
) -> ContentResult<Vec<String>> { ) -> ContentResult<Vec<String>> {
if mc_version.is_empty() { if mc_version.is_empty() {
@ -105,9 +108,21 @@ pub async fn disable_foreign_game_versions(
let linked = PackageStore::list_linked_artifacts(cluster_id, ctx).await?; let linked = PackageStore::list_linked_artifacts(cluster_id, ctx).await?;
// bundle installs skip the compatibility check so launch must trust them too // bundle installs skip the compatibility check so launch must trust them too
let live_bundles: HashSet<String> =
bundle_catalog_dao::list_visible_for_version_loader(&ctx.db, mc_version, mc_loader)
.await?
.into_iter()
.filter_map(|row| row.name)
.collect();
let from_bundle: HashSet<String> = bundle_dao::list_bundle_tracked(&ctx.db, cluster_id) let from_bundle: HashSet<String> = bundle_dao::list_bundle_tracked(&ctx.db, cluster_id)
.await? .await?
.into_iter() .into_iter()
.filter(|row| {
row.bundle_name
.as_deref()
.is_some_and(|name| live_bundles.contains(name))
})
.map(|row| row.hash) .map(|row| row.hash)
.collect(); .collect();
@ -175,11 +190,13 @@ fn built_for_another_game_version(stated: &[String], mc_version: &str) -> bool {
} }
fn covers_game_version(stated: &[String], mc_version: &str) -> bool { fn covers_game_version(stated: &[String], mc_version: &str) -> bool {
let wanted = base_game_version(mc_version);
stated.is_empty() stated.is_empty()
|| stated.iter().any(|version| { || stated.iter().any(|version| {
version == mc_version let built_for = base_game_version(version);
|| mc_version built_for == wanted
.strip_prefix(version.as_str()) || wanted
.strip_prefix(built_for)
.is_some_and(|rest| rest.starts_with('.')) .is_some_and(|rest| rest.starts_with('.'))
}) })
} }
@ -295,6 +312,34 @@ mod tests {
); );
} }
#[test]
fn a_pre_release_tag_is_the_same_game_version() {
let foreign = |rows: &[&str], mc_version: &str| {
built_for_another_game_version(
&rows.iter().map(|r| (*r).to_string()).collect::<Vec<_>>(),
mc_version,
)
};
assert!(
!foreign(&["[\"26.3-rc-1\"]"], "26.3"),
"a jar built against the release candidate runs on the release"
);
assert!(!foreign(&["[\"26.3-snapshot-7\",\"26.3-pre-2\"]"], "26.3"));
assert!(
!foreign(&["[\"26.3\"]"], "26.3-snapshot-10"),
"and a snapshot cluster keeps the release build"
);
assert!(
!foreign(&["[\"26.3-rc-1\"]"], "26.3.1"),
"the component prefix still applies once the tag is off"
);
assert!(
foreign(&["[\"26.2-rc-1\"]"], "26.3"),
"stripping the tag must not blur two game versions together"
);
}
#[test] #[test]
fn only_projects_with_several_copies_are_reconciled() { fn only_projects_with_several_copies_are_reconciled() {
let groups = group_duplicates(&[ let groups = group_duplicates(&[

View file

@ -193,9 +193,18 @@ fn choose_version(
.cloned() .cloned()
} }
#[must_use]
pub(crate) fn base_game_version(version: &str) -> &str {
version
.split_once('-')
.filter(|(base, _)| base.contains('.'))
.map_or(version, |(base, _)| base)
}
#[must_use] #[must_use]
pub(crate) fn supports_game_version(game_versions: &[String], mc_version: &str) -> bool { pub(crate) fn supports_game_version(game_versions: &[String], mc_version: &str) -> bool {
game_versions.is_empty() || game_versions.iter().any(|v| v == mc_version) let wanted = base_game_version(mc_version);
game_versions.is_empty() || game_versions.iter().any(|v| base_game_version(v) == wanted)
} }
fn fits_cluster(version: &VersionSummary, cluster: &ClusterRow, loader: GameLoader) -> bool { fn fits_cluster(version: &VersionSummary, cluster: &ClusterRow, loader: GameLoader) -> bool {
@ -230,6 +239,34 @@ mod tests {
use super::*; use super::*;
#[test]
fn a_version_is_offered_only_for_the_game_version_it_names() {
let offered = |stated: &[&str], mc_version: &str| {
supports_game_version(
&stated.iter().map(|v| (*v).to_string()).collect::<Vec<_>>(),
mc_version,
)
};
assert!(offered(&[], "26.3"), "an undescribed version is offered");
assert!(offered(&["26.3"], "26.3"));
assert!(
offered(&["26.3-rc-1"], "26.3"),
"a pre-release tag names the same game version"
);
assert!(offered(&["26.3"], "26.3-snapshot-10"));
assert!(
!offered(&["26.1"], "26.1.2"),
"strict where the launch-time check is lenient: #830 stopped 26.1 \
builds being offered to a 26.1.2 cluster"
);
assert!(!offered(&["1.21.11"], "26.1.2"));
assert!(
!offered(&["rd-132211"], "rd-160052"),
"pre-classic names the build after the dash, so there is no tag to strip"
);
}
fn summary( fn summary(
version_id: &str, version_id: &str,
release_type: ReleaseType, release_type: ReleaseType,

View file

@ -139,6 +139,7 @@ pub async fn materialize_content(
match oneclient_content::packages::disable_foreign_game_versions( match oneclient_content::packages::disable_foreign_game_versions(
cluster.id, cluster.id,
&cluster.mc_version, &cluster.mc_version,
cluster.mc_loader as i64,
&services.content(), &services.content(),
) )
.await .await