diff --git a/Cargo.lock b/Cargo.lock index b34b3bd04..8f0befdfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -989,6 +989,8 @@ dependencies = [ "libc", "libgit2-sys", "log", + "openssl-probe", + "openssl-sys", "url", ] @@ -1377,11 +1379,27 @@ name = "libgit2-sys" version = "0.16.2+1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" dependencies = [ "cc", "libc", "libz-sys", + "openssl-sys", "pkg-config", + "vcpkg", ] [[package]] @@ -1502,6 +1520,7 @@ dependencies = [ "filetime", "flate2", "fslock", + "git2", "globset", "globwalk 0.9.1", "home", diff --git a/Cargo.toml b/Cargo.toml index bea9dbc1d..a8061e259 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ include = [ "/build.rs", "/zipsign.pub", ] -rust-version = "1.74.0" +rust-version = "1.76.0" build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -62,6 +62,7 @@ eyre = "0.6.12" filetime = "0.2.23" flate2 = "1.0.30" fslock = "0.2.1" +git2 = "0.18.3" globset = "0.4.14" globwalk = "0.9.1" home = "0.5.9" @@ -127,7 +128,7 @@ zip = { version = "1.1.2", default-features = false, features = ["deflate"] } exec = "0.3.1" [build-dependencies] -built = { version = "0.7.2", features = ["chrono"] } +built = { version = "0.7.2", features = ["chrono", "git2"] } [dev-dependencies] assert_cmd = "2.0.14" @@ -139,7 +140,7 @@ test-case = "3.3.1" [features] default = ["native-tls"] -git2 = ["built/git2"] +git2 = [] native-tls = ["reqwest/native-tls"] rustls = ["reqwest/rustls-tls", "self_update/rustls"] rustls-native-roots = ["reqwest/rustls-tls-native-roots", "self_update/rustls"] diff --git a/src/cli/doctor.rs b/src/cli/doctor.rs index e2657cc6f..f95789ea4 100644 --- a/src/cli/doctor.rs +++ b/src/cli/doctor.rs @@ -4,6 +4,7 @@ use std::process::exit; use console::{pad_str, style, Alignment}; use indenter::indented; use itertools::Itertools; +use rayon::prelude::*; use crate::build_time::built_info; use crate::cli::version; @@ -115,7 +116,7 @@ impl Doctor { if !env::is_activated() && !shims_on_path() { let cmd = style::nyellow("mise help activate"); let url = style::nunderline("https://mise.jdx.dev"); - let shims = style::ncyan(dirs::SHIMS.display()); + let shims = style::ncyan(display_path(*dirs::SHIMS)); self.errors.push(formatdoc!( r#"mise is not activated, run {cmd} or read documentation at {url} for activation instructions. @@ -161,6 +162,7 @@ impl Doctor { } fn analyze_shims(&mut self, toolset: &Toolset) { + let start_ms = std::time::Instant::now(); let mise_bin = file::which("mise").unwrap_or(env::MISE_BIN.clone()); if let Ok((missing, extra)) = shims::get_shim_diffs(mise_bin, toolset) { @@ -182,6 +184,7 @@ impl Doctor { )); } } + trace!("Shim analysis took {:?}", start_ms.elapsed()); } fn analyze_plugins(&mut self) { @@ -253,7 +256,6 @@ fn render_backends() -> String { } fn render_plugins() -> String { - let mut s = vec![]; let plugins = forge::list() .into_iter() .filter(|p| p.is_installed() && p.get_type() == ForgeType::Asdf) @@ -264,26 +266,29 @@ fn render_plugins() -> String { .max() .unwrap_or(0) .min(40); - for p in plugins { - let padded_name = pad_str(p.id(), max_plugin_name_len, Alignment::Left, None); - let extra = match p.get_plugin_type() { - PluginType::External => { - let git = Git::new(dirs::PLUGINS.join(p.id())); - match git.get_remote_url() { - Some(url) => { - let sha = git - .current_sha_short() - .unwrap_or_else(|_| "(unknown)".to_string()); - format!("{url}#{sha}") + plugins + .into_par_iter() + .map(|p| { + let padded_name = pad_str(p.id(), max_plugin_name_len, Alignment::Left, None); + let extra = match p.get_plugin_type() { + PluginType::External => { + let git = Git::new(dirs::PLUGINS.join(p.id())); + match git.get_remote_url() { + Some(url) => { + let sha = git + .current_sha_short() + .unwrap_or_else(|_| "(unknown)".to_string()); + format!("{url}#{sha}") + } + None => "".to_string(), } - None => "".to_string(), } - } - PluginType::Core => "(core)".to_string(), - }; - s.push(format!("{padded_name} {}", style::ndim(extra))); - } - s.join("\n") + PluginType::Core => "(core)".to_string(), + }; + format!("{padded_name} {}", style::ndim(extra)) + }) + .collect::>() + .join("\n") } fn build_info() -> String { diff --git a/src/config/mod.rs b/src/config/mod.rs index 723301ca4..dbd816f5f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -310,7 +310,7 @@ impl Config { fn load_tasks_includes(&self, root: &Path) -> Result> { file::recursive_ls(root)? - .into_iter() + .into_par_iter() .filter(|p| file::is_executable(p)) .map(|path| Task::from_path(&path)) .collect() diff --git a/src/file.rs b/src/file.rs index f3b1a3a6f..7f8bb684d 100644 --- a/src/file.rs +++ b/src/file.rs @@ -9,6 +9,7 @@ use color_eyre::eyre::{Context, Result}; use filetime::{set_file_times, FileTime}; use flate2::read::GzDecoder; use itertools::Itertools; +use rayon::prelude::*; use tar::Archive; use walkdir::WalkDir; use zip::ZipArchive; @@ -335,14 +336,16 @@ pub fn which_non_pristine>(name: P) -> Option { _which(name, &env::PATH_NON_PRISTINE) } -fn _which>(name: P, paths: &Vec) -> Option { - for path in paths { - let bin = path.join(name.as_ref()); +fn _which>(name: P, paths: &[PathBuf]) -> Option { + let name = name.as_ref(); + paths.par_iter().find_map_first(|path| { + let bin = path.join(name); if is_executable(&bin) { - return Some(bin); + Some(bin) + } else { + None } - } - None + }) } pub fn untar(archive: &Path, dest: &Path) -> Result<()> { diff --git a/src/git.rs b/src/git.rs index a1a520b73..09b44e805 100644 --- a/src/git.rs +++ b/src/git.rs @@ -3,12 +3,14 @@ use std::path::PathBuf; use duct::Expression; use eyre::{eyre, Result, WrapErr}; +use once_cell::sync::OnceCell; use crate::cmd; use crate::file::touch_dir; pub struct Git { pub dir: PathBuf, + pub repo: OnceCell, } macro_rules! git_cmd { @@ -33,7 +35,18 @@ macro_rules! git_cmd_read { impl Git { pub fn new(dir: PathBuf) -> Self { - Self { dir } + Self { + dir, + repo: OnceCell::new(), + } + } + + pub fn repo(&self) -> Result<&git2::Repository> { + self.repo.get_or_try_init(|| { + git2::Repository::open(&self.dir) + .wrap_err_with(|| format!("failed to open git repository at {:?}", self.dir)) + .inspect_err(|err| warn!("{err:#}")) + }) } pub fn is_repo(&self) -> bool { @@ -99,44 +112,76 @@ impl Git { } pub fn current_branch(&self) -> Result { + let dir = &self.dir; + if let Ok(repo) = self.repo() { + let branch = repo.head()?.shorthand().unwrap().to_string(); + debug!("current branch for {dir:?}: {branch}"); + return Ok(branch); + } let branch = git_cmd_read!(&self.dir, "branch", "--show-current")?; debug!("current branch for {}: {}", self.dir.display(), &branch); Ok(branch) } pub fn current_sha(&self) -> Result { + let dir = &self.dir; + if let Ok(repo) = self.repo() { + let head = repo.head()?; + let head = head.peel_to_commit()?; + let sha = head.id().to_string(); + debug!("current sha for {dir:?}: {sha}"); + return Ok(sha); + } let sha = git_cmd_read!(&self.dir, "rev-parse", "HEAD")?; debug!("current sha for {}: {}", self.dir.display(), &sha); Ok(sha) } pub fn current_sha_short(&self) -> Result { + let dir = &self.dir; + if let Ok(repo) = self.repo() { + let head = repo.head()?; + let head = head.peel_to_commit()?; + let sha = head.as_object().short_id()?.as_str().unwrap().to_string(); + debug!("current sha for {dir:?}: {sha}"); + return Ok(sha); + } let sha = git_cmd_read!(&self.dir, "rev-parse", "--short", "HEAD")?; - debug!("current sha for {}: {}", self.dir.display(), &sha); + debug!("current sha for {dir:?}: {sha}"); Ok(sha) } pub fn current_abbrev_ref(&self) -> Result { + let dir = &self.dir; + if let Ok(repo) = self.repo() { + let head = repo.head()?; + let head = head.shorthand().unwrap().to_string(); + debug!("current abbrev ref for {dir:?}: {head}"); + return Ok(head); + } let aref = git_cmd_read!(&self.dir, "rev-parse", "--abbrev-ref", "HEAD")?; debug!("current abbrev ref for {}: {}", self.dir.display(), &aref); Ok(aref) } pub fn get_remote_url(&self) -> Option { - if !self.dir.exists() { + let dir = &self.dir; + if !dir.exists() { return None; } + if let Ok(repo) = self.repo() { + let remote = repo.find_remote("origin").ok()?; + let url = remote.url()?; + trace!("remote url for {dir:?}: {url}"); + return Some(url.to_string()); + } let res = git_cmd_read!(&self.dir, "config", "--get", "remote.origin.url"); match res { Ok(url) => { - debug!("remote url for {}: {}", self.dir.display(), &url); + debug!("remote url for {dir:?}: {url}"); Some(url) } Err(err) => { - warn!( - "failed to get remote url for {}: {:#}", - self.dir.display(), - err - ); + warn!("failed to get remote url for {dir:?}: {err:#}"); None } } diff --git a/src/shims.rs b/src/shims.rs index ad1f2e2ce..5b0b25a0b 100644 --- a/src/shims.rs +++ b/src/shims.rs @@ -138,13 +138,22 @@ pub fn get_shim_diffs( mise_bin: impl AsRef, toolset: &Toolset, ) -> Result<(BTreeSet, BTreeSet)> { - let actual_shims = get_actual_shims(&mise_bin)?; - let desired_shims = get_desired_shims(toolset)?; - - Ok(( + let start_ms = std::time::Instant::now(); + let mise_bin = mise_bin.as_ref(); + let (actual_shims, desired_shims) = + rayon::join(|| get_actual_shims(mise_bin), || get_desired_shims(toolset)); + let (actual_shims, desired_shims) = (actual_shims?, desired_shims?); + let out: (BTreeSet, BTreeSet) = ( desired_shims.difference(&actual_shims).cloned().collect(), actual_shims.difference(&desired_shims).cloned().collect(), - )) + ); + trace!( + "get_shim_diffs({:?}): sizes: ({},{})", + start_ms.elapsed(), + out.0.len(), + out.1.len() + ); + Ok(out) } fn get_actual_shims(mise_bin: impl AsRef) -> Result> { @@ -161,18 +170,24 @@ fn get_actual_shims(mise_bin: impl AsRef) -> Result> { } fn list_executables_in_dir(dir: &Path) -> Result> { - let mut out = HashSet::new(); - for bin in dir.read_dir()? { - let bin = bin?; - // skip non-files and non-symlinks or non-executable files - if (!bin.file_type()?.is_file() && !bin.file_type()?.is_symlink()) - || !file::is_executable(&bin.path()) - { - continue; - } - out.insert(bin.file_name().into_string().unwrap()); - } - Ok(out) + Ok(dir + .read_dir()? + .par_bridge() + .map(|bin| { + let bin = bin?; + // files and symlinks which are executable + if file::is_executable(&bin.path()) + && (bin.file_type()?.is_file() || bin.file_type()?.is_symlink()) + { + Ok(Some(bin.file_name().into_string().unwrap())) + } else { + Ok(None) + } + }) + .collect::>>()? + .into_iter() + .flatten() + .collect()) } fn get_desired_shims(toolset: &Toolset) -> Result> { diff --git a/src/toolset/builder.rs b/src/toolset/builder.rs index 7fd7098cc..29018d8ee 100644 --- a/src/toolset/builder.rs +++ b/src/toolset/builder.rs @@ -48,6 +48,7 @@ impl ToolsetBuilder { } pub fn build(self, config: &Config) -> Result { + let start_ms = std::time::Instant::now(); let settings = Settings::try_get()?; let mut toolset = Toolset { disable_tools: settings.disable_tools.iter().map(|s| s.into()).collect(), @@ -60,7 +61,7 @@ impl ToolsetBuilder { self.load_runtime_args(&mut toolset); toolset.resolve(); - debug!("Toolset: {}", toolset); + debug!("Toolset ({:?}): {toolset}", start_ms.elapsed()); Ok(toolset) }