-
Notifications
You must be signed in to change notification settings - Fork 10
feat: detect upstream toolchain updates #239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
2dbad1e
b2aa4f7
4de43c7
0d8d6a3
e0963c8
395c8ad
c2cc7f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ use crate::{ | |
| manifest::Component, | ||
| options::{InstallationOptions, IntentUpdate, PathUpdate, UpdateOptions}, | ||
| state::{Installation, LocalState}, | ||
| version::Authority, | ||
| version::{Authority, GitTarget}, | ||
| }; | ||
|
|
||
| /// Updates installed toolchains. | ||
|
|
@@ -409,6 +409,56 @@ fn work_for( | |
| Ok(Work::Nothing) | ||
| } | ||
|
|
||
| /// Whether the manifest's content changed for this installation. | ||
| pub fn needs_update(installation: &Installation, upstream: &Channel) -> bool { | ||
| match crate::resolve::resolve(upstream, &installation.intent) { | ||
| Ok(resolved) => { | ||
| let installed_names: std::collections::BTreeSet<&str> = installation | ||
| .components | ||
| .iter() | ||
| .map(|component| component.name.as_ref()) | ||
| .collect(); | ||
| let resolved_names: std::collections::BTreeSet<&str> = | ||
| resolved.iter().map(|component| component.name.as_ref()).collect(); | ||
| if installed_names != resolved_names { | ||
| return true; | ||
| } | ||
| }, | ||
| Err(_) => return true, | ||
| } | ||
|
|
||
| installation.components.iter().any(|installed| { | ||
| upstream | ||
| .get_component(&installed.name) | ||
| .is_some_and(|new| definition_changed(installed, new)) | ||
| }) | ||
| } | ||
|
|
||
| /// Whether two definitions of one component differ as manifest content. | ||
| /// | ||
| /// The pins install records on an authority -- a branch's commit, a path's modification time -- | ||
| /// exist only locally and move on their own, so they are normalized away: drift behind them is | ||
| /// reconciled by the update itself, which is what pins for ([`classify`]); a listing must not. | ||
| fn definition_changed(installed: &Component, upstream: &Component) -> bool { | ||
| let normalized = |component: &Component| { | ||
| let mut component = component.clone(); | ||
| match &mut component.version { | ||
| Authority::Path { last_modification, .. } => *last_modification = None, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Normalize relative path authorities before comparing definitions Installation rewrites a relative |
||
| Authority::Git { | ||
| target: GitTarget::Branch { latest_revision, .. }, | ||
| .. | ||
| } => *latest_revision = None, | ||
| Authority::Git { .. } | Authority::Registry { .. } => (), | ||
| } | ||
| serde_json::to_value(component).ok() | ||
| }; | ||
|
|
||
| match (normalized(installed), normalized(upstream)) { | ||
| (Some(old), Some(new)) => old != new, | ||
| _ => true, | ||
| } | ||
| } | ||
|
|
||
| /// Commits selection and metadata changes that no installed file reflects. | ||
| /// | ||
| /// Each component's recorded *authority* is preserved rather than taken from upstream. Reaching | ||
|
|
@@ -672,6 +722,57 @@ mod tests { | |
| assert_eq!(classify_pair(base(), base()), ChangeClass::None); | ||
| } | ||
|
|
||
| /// The pins install records -- a branch's commit, a path's modification time -- exist only | ||
| /// locally and move on their own, so a listing must not read them as a manifest change. | ||
| #[test] | ||
| fn an_authority_pin_is_not_a_definition_change() { | ||
| let on_branch = |latest_revision: Option<&str>| { | ||
| let mut component = base(); | ||
| component.version = Authority::Git { | ||
| repository_url: "https://example.invalid/repo".to_string(), | ||
| subpath: None, | ||
| target: GitTarget::Branch { | ||
| name: "main".to_string(), | ||
| latest_revision: latest_revision.map(str::to_string), | ||
| }, | ||
| }; | ||
| component | ||
| }; | ||
| assert!(!definition_changed(&on_branch(Some("abc123")), &on_branch(None))); | ||
|
|
||
| let at_path = |last_modification: Option<std::time::SystemTime>| { | ||
| let mut component = base(); | ||
| component.version = Authority::Path { path: "vm".into(), last_modification }; | ||
| component | ||
| }; | ||
| let pinned = at_path(Some(std::time::SystemTime::UNIX_EPOCH)); | ||
| assert!(!definition_changed(&pinned, &at_path(None))); | ||
| } | ||
|
|
||
| /// Normalizing the pin must not mask a real change riding alongside it. | ||
| #[test] | ||
| fn a_change_next_to_a_pin_is_still_a_definition_change() { | ||
| let mut installed = base(); | ||
| installed.version = Authority::Git { | ||
| repository_url: "https://example.invalid/repo".to_string(), | ||
| subpath: None, | ||
| target: GitTarget::Branch { | ||
| name: "main".to_string(), | ||
| latest_revision: Some("abc123".to_string()), | ||
| }, | ||
| }; | ||
| let mut upstream = base(); | ||
| upstream.version = Authority::Git { | ||
| repository_url: "https://example.invalid/repo".to_string(), | ||
| subpath: None, | ||
| target: GitTarget::Branch { | ||
| name: "next".to_string(), | ||
| latest_revision: None, | ||
| }, | ||
| }; | ||
| assert!(definition_changed(&installed, &upstream)); | ||
| } | ||
|
|
||
| /// An artifact is a file in the publication, so a component whose artifact URI moves to a new | ||
| /// release must be reinstalled even though nothing else about it changed. | ||
| #[test] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Honor same-version precedence when displaying migration updates
Checking only whether the predecessor is installed does not match the updater's counterpart-selection rule. If upstream contains both installed
0.15.0and an uninstalled0.16.0declaringmigrates_from: "0.15.0", this branch prints0.16.0 (update available). However,upstream_counterpart_rawprefers the still-published0.15.0, soupdate 0.15.0reportsToolchain 0.15.0 is up to dateand performs no migration;show listalso reports no update. This was reproduced against this PR's head, and the false marker remains after updating. Please use the same counterpart-selection rule as the updater when deciding whether a successor represents an available update.