-
Notifications
You must be signed in to change notification settings - Fork 18
Clean Cache commands #285
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?
Clean Cache commands #285
Changes from 7 commits
df8708b
744811b
ee9fa2f
f64bb9a
5879aff
3094833
cd453e5
096a821
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,284 @@ | ||
| use core::fmt; | ||
| use std::{collections::HashMap, path::PathBuf}; | ||
|
|
||
| use crate::{ | ||
| Resolution, UnresolvedDependency, Version, | ||
| cli::{CliContext, context::load_databases}, | ||
| hash_string, | ||
| package::PackageType, | ||
| }; | ||
|
|
||
| use anyhow::Result; | ||
| use fs_err as fs; | ||
| use serde::Serialize; | ||
|
|
||
| /// Remove repositories and/or dependencies from the cache. | ||
| /// Dependencies only remove the package version from the resolved source | ||
| /// Repositories are aliases corresponding to repos in the config | ||
| pub fn purge_cache<'a>( | ||
| context: &'a CliContext, | ||
| resolution: &'a Resolution<'a>, | ||
|
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. Why do we need a resolution to purge things? |
||
| repositories: &'a [String], | ||
| dependencies: &'a [String], | ||
| ) -> std::io::Result<PurgeResults<'a>> { | ||
| let mut repo_res = Vec::new(); | ||
| for r in repositories { | ||
| let res = if let Some(repo) = context | ||
| .config | ||
| .repositories() | ||
| .iter() | ||
| .find(|repo| &repo.alias == r) | ||
| { | ||
| let path = context.cache.root.join(hash_string(repo.url())); | ||
| if path.exists() { | ||
| fs::remove_dir_all(&path)?; | ||
| PurgeRepoResult::Removed { | ||
| alias: &repo.alias, | ||
| url: repo.url(), | ||
| path, | ||
| } | ||
| } else { | ||
| PurgeRepoResult::NotInCache { | ||
| alias: &repo.alias, | ||
| url: repo.url(), | ||
| } | ||
| } | ||
| } else { | ||
| PurgeRepoResult::NotInProject(&r) | ||
| }; | ||
| repo_res.push(res); | ||
| } | ||
|
|
||
| let mut dep_res = Vec::new(); | ||
| for d in dependencies { | ||
| let res = if let Some(dep) = resolution.found.iter().find(|r| &r.name == d) { | ||
|
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. if people pass a dep name, don't they want to purge all the versions of that dep? |
||
| let (binary_path, source_path) = context.cache.remove_dependency(&dep.name, &dep.version, &dep.source)?; | ||
|
|
||
| let mut paths = HashMap::new(); | ||
| if let Some(bin_path) = binary_path { | ||
| paths.insert(PackageType::Binary, bin_path); | ||
| } | ||
| if let Some(src_path) = source_path { | ||
| paths.insert(PackageType::Source, src_path); | ||
| } | ||
|
|
||
| if paths.is_empty() { | ||
| PurgeDepResult::NotInCache { | ||
| name: &dep.name, | ||
| version: &dep.version, | ||
| } | ||
| } else { | ||
| PurgeDepResult::Removed { | ||
| name: &dep.name, | ||
| version: &dep.version, | ||
| paths, | ||
| } | ||
| } | ||
| } else if let Some(dep) = resolution.failed.iter().find(|r| &r.name == d) { | ||
| PurgeDepResult::Unresolved(dep) | ||
| } else { | ||
| PurgeDepResult::NotInProject(d) | ||
| }; | ||
| dep_res.push(res); | ||
| } | ||
|
|
||
| Ok(PurgeResults { | ||
| repositories: repo_res, | ||
| dependencies: dep_res, | ||
| }) | ||
| } | ||
|
|
||
| #[derive(Debug, Serialize)] | ||
| pub struct PurgeResults<'a> { | ||
| repositories: Vec<PurgeRepoResult<'a>>, | ||
| dependencies: Vec<PurgeDepResult<'a>>, | ||
| } | ||
|
|
||
| impl PurgeResults<'_> { | ||
| pub fn all_deps_found(&self) -> bool { | ||
| self.dependencies | ||
| .iter() | ||
| .all(|dep| !matches!(dep, PurgeDepResult::Unresolved(_))) | ||
| } | ||
| } | ||
|
|
||
| impl fmt::Display for PurgeResults<'_> { | ||
|
Member
Author
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. Decided to leave paths out of the displayed results as it is not what a user would typically want, but recorded them and included as part of the json to leave them accessible if someone needs them. |
||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| if !self.repositories.is_empty() { | ||
| write!(f, "== Repositories ==\n")?; | ||
| let mut not_removed = Vec::new(); | ||
| let mut removed = Vec::new(); | ||
| for repo in &self.repositories { | ||
| match repo { | ||
| PurgeRepoResult::NotInProject(alias) => not_removed.push(format!( | ||
| "{alias} - Repository alias not found in config file" | ||
| )), | ||
| PurgeRepoResult::Removed { alias, url, .. } => { | ||
| removed.push(format!("{alias} ({url})")); | ||
| } | ||
| PurgeRepoResult::NotInCache { alias, url } => { | ||
| not_removed.push(format!("{alias} ({url}) - Repository not found in cache")) | ||
| } | ||
| } | ||
| } | ||
| if !removed.is_empty() { | ||
| write!( | ||
| f, | ||
| "Removed Successfully:\n {}\n\n", | ||
| removed.join("\n ") | ||
| )?; | ||
| } | ||
| if !not_removed.is_empty() { | ||
| write!(f, "Not Removed:\n {}\n\n", not_removed.join("\n "))?; | ||
| } | ||
| } | ||
|
|
||
| if !self.dependencies.is_empty() { | ||
| write!(f, "== Dependencies ==\n")?; | ||
| let mut not_removed = Vec::new(); | ||
| let mut removed = Vec::new(); | ||
| let mut unresolved = Vec::new(); | ||
| for dep in &self.dependencies { | ||
| match dep { | ||
| PurgeDepResult::Unresolved(dep) => { | ||
| unresolved.push(dep.to_string()); | ||
| not_removed.push(format!("{} - Package could not be resolved", dep.name,)); | ||
| } | ||
| PurgeDepResult::Removed { | ||
| name, | ||
| version, | ||
| paths, | ||
| } => { | ||
| let mut types = paths.keys().map(ToString::to_string).collect::<Vec<_>>(); | ||
| types.sort(); | ||
|
|
||
| removed.push(format!("{name} ({version}) - {}", types.join(" and "))) | ||
| } | ||
| PurgeDepResult::NotInCache { name, version } => not_removed.push(format!( | ||
|
Member
Author
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. Deps that are determined to be "NotInCache" are packages that would be in cache, but haven't been synced yet. I'm a bit unsure if I should list it as "removed" or "not removed". The pros/cons I see:
Ultimately, this scenario likely won't appear that often because if you're having issues with a package, its likely in the cache and you're going to target it specifically, but wanted to call it out. |
||
| "{name} ({version}) - Dependency not found in cache" | ||
| )), | ||
| PurgeDepResult::NotInProject(name) => not_removed | ||
| .push(format!("{name} - Package not part of project dependencies")), | ||
| } | ||
| } | ||
|
|
||
| if !removed.is_empty() { | ||
| write!( | ||
| f, | ||
| "Removed Successfully:\n {}\n\n", | ||
| removed.join("\n ") | ||
| )?; | ||
| } | ||
| if !not_removed.is_empty() { | ||
| write!(f, "Not Removed:\n {}\n\n", not_removed.join("\n "))?; | ||
| } | ||
| if !unresolved.is_empty() { | ||
| write!( | ||
| f, | ||
| "Failed to resolve all dependencies. Packages may not have been purged due to the following resolution issues:\n {}\n\n", | ||
| unresolved.join("\n ") | ||
| )?; | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug, Serialize)] | ||
| enum PurgeRepoResult<'a> { | ||
| Removed { | ||
| alias: &'a str, | ||
| url: &'a str, | ||
| path: PathBuf, | ||
| }, | ||
| /// Alias is not in config file | ||
| NotInProject(&'a str), | ||
| /// Repository not in cache (nothing to remove) | ||
| NotInCache { alias: &'a str, url: &'a str }, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Serialize, PartialEq)] | ||
| enum PurgeDepResult<'a> { | ||
| /// Package is part of the unresolved dependencies | ||
| Unresolved(&'a UnresolvedDependency<'a>), | ||
| Removed { | ||
| name: &'a str, | ||
| version: &'a Version, | ||
| paths: HashMap<PackageType, PathBuf>, | ||
| }, | ||
| /// Dependency not part of the resolved dependency burden | ||
| NotInProject(&'a str), | ||
| /// Dependency resolved, but not in cache (nothing to remove) | ||
| NotInCache { name: &'a str, version: &'a Version }, | ||
| } | ||
|
|
||
| /// refresh the repository database by invalidating the packages.bin and re-loading it | ||
| /// returns a list of repositories that were refreshed and a list of repos that could not be found in the config | ||
| pub fn refresh_cache<'a>( | ||
| context: &'a CliContext, | ||
| repositories: &'a [String], | ||
| ) -> Result<(Vec<RefreshedRepo<'a>>, Vec<&'a str>)> { | ||
| let mut cache = context.cache.clone(); | ||
| // need to set cache timeout to refresh the databases for the found repositories | ||
| cache.packages_timeout = 0; | ||
|
|
||
| // if no repositories supplied, we'll refresh all repos listed in the config | ||
| let res = if repositories.is_empty() { | ||
| let res = context | ||
| .config | ||
| .repositories() | ||
| .iter() | ||
| .map(|repo| RefreshedRepo { | ||
| alias: &repo.alias, | ||
| url: repo.url(), | ||
| path: cache.get_package_db_entry(repo.url()).0, | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
|
|
||
| load_databases(context.config.repositories(), &cache)?; | ||
| (res, Vec::new()) | ||
| } else { | ||
| let mut repos = Vec::new(); | ||
| let mut refreshed = Vec::new(); | ||
| // unresolved meaning that we can't find a corresponding entry in the config | ||
| let mut unresolved = Vec::new(); | ||
|
|
||
| for r in repositories { | ||
| if let Some(repo) = context | ||
| .config | ||
| .repositories() | ||
| .iter() | ||
| .find(|repo| &repo.alias == r) | ||
| { | ||
| repos.push(repo.clone()); | ||
| let (path, _) = cache.get_package_db_entry(repo.url()); | ||
| refreshed.push(RefreshedRepo { | ||
| alias: &repo.alias, | ||
| url: repo.url(), | ||
| path, | ||
| }); | ||
| } else { | ||
| unresolved.push(r.as_str()); | ||
| } | ||
| } | ||
|
|
||
| load_databases(&repos, &cache)?; | ||
| (refreshed, unresolved) | ||
| }; | ||
|
|
||
| Ok(res) | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Serialize)] | ||
| pub struct RefreshedRepo<'a> { | ||
| alias: &'a str, | ||
| url: &'a str, | ||
| path: PathBuf, | ||
| } | ||
|
|
||
| impl fmt::Display for RefreshedRepo<'_> { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| write!(f, "{} ({})", self.alias, self.url) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| mod clean_cache; | ||
| mod init; | ||
| mod migrate; | ||
| mod tree; | ||
|
|
||
| pub use clean_cache::{purge_cache, refresh_cache}; | ||
| pub use init::{find_r_repositories, init, init_structure}; | ||
| pub use migrate::migrate_renv; | ||
| pub use tree::tree; |
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.
maybe return a struct so we don't guess which path is the source and which one is the binary?